Broken site based impl
This commit is contained in:
+299
@@ -0,0 +1,299 @@
|
||||
// any_t: a value of any kind, and JS's operators on it
|
||||
#pragma once
|
||||
|
||||
#include "number.h"
|
||||
|
||||
// A value of any kind. An object in it is the pointer to its cell, so it keeps
|
||||
// its identity. A field can also hold absent
|
||||
|
||||
typedef enum {
|
||||
JSRT_KIND_ABSENT,
|
||||
JSRT_KIND_UNDEFINED,
|
||||
JSRT_KIND_NULL,
|
||||
JSRT_KIND_BOOL,
|
||||
JSRT_KIND_INT,
|
||||
JSRT_KIND_FLOAT,
|
||||
JSRT_KIND_STRING,
|
||||
JSRT_KIND_OBJECT,
|
||||
} jsrt_kind;
|
||||
|
||||
typedef struct {
|
||||
jsrt_kind kind;
|
||||
union {
|
||||
bool b;
|
||||
int32_t i;
|
||||
double f;
|
||||
string_t s;
|
||||
void *o;
|
||||
} as;
|
||||
} any_t;
|
||||
|
||||
// Box a value. A pointer that is a sentinel (NULL, JSRT_UNDEFINED,
|
||||
// JSRT_ABSENT) boxes as null, undefined or absent
|
||||
any_t __any_undefined(void);
|
||||
any_t __any_null(void);
|
||||
any_t __any_absent(void);
|
||||
any_t __any_from_bool(bool b);
|
||||
any_t __any_from_int(int32_t n);
|
||||
any_t __any_from_float(double x);
|
||||
any_t __any_from_string(string_t s);
|
||||
any_t __any_from_object(void *o);
|
||||
|
||||
// Unbox a value. A value of another kind throws. A string or an object unboxes
|
||||
// null, undefined and absent to their sentinels
|
||||
bool __any_as_bool(any_t a);
|
||||
int32_t __any_as_int(any_t a);
|
||||
double __any_as_float(any_t a); // an int too
|
||||
string_t __any_as_string(any_t a);
|
||||
void *__any_as_object(any_t a);
|
||||
|
||||
// The kind of a value
|
||||
bool __any_is_absent(any_t a);
|
||||
bool __any_is_undefined(any_t a);
|
||||
bool __any_is_null(any_t a);
|
||||
bool __any_is_nullish(any_t a); // undefined or null
|
||||
bool __any_is_boolean(any_t a);
|
||||
bool __any_is_number(any_t a);
|
||||
bool __any_is_string(any_t a);
|
||||
bool __any_is_object(any_t a); // a fn too
|
||||
bool __any_is_function(any_t a);
|
||||
|
||||
// JS's operators, with JS's conversions
|
||||
any_t __any_add(any_t a, any_t b);
|
||||
any_t __any_sub(any_t a, any_t b);
|
||||
any_t __any_mul(any_t a, any_t b);
|
||||
any_t __any_div(any_t a, any_t b);
|
||||
any_t __any_mod(any_t a, any_t b);
|
||||
any_t __any_pow(any_t a, any_t b);
|
||||
any_t __any_neg(any_t a);
|
||||
any_t __any_plus(any_t a); // ToNumber
|
||||
any_t __any_inc(any_t a);
|
||||
any_t __any_dec(any_t a);
|
||||
|
||||
bool __any_lt(any_t a, any_t b);
|
||||
bool __any_lte(any_t a, any_t b);
|
||||
bool __any_gt(any_t a, any_t b);
|
||||
bool __any_gte(any_t a, any_t b);
|
||||
bool __any_eq(any_t a, any_t b);
|
||||
bool __any_neq(any_t a, any_t b);
|
||||
bool __any_strict_eq(any_t a, any_t b);
|
||||
bool __any_strict_neq(any_t a, any_t b);
|
||||
|
||||
// JS's abstract operations
|
||||
bool __any_truthy(any_t a); // ToBoolean
|
||||
int32_t __any_to_int32(any_t a); // ToInt32
|
||||
string_t __any_to_string(any_t a); // ToString
|
||||
string_t __any_typeof(any_t a);
|
||||
|
||||
#ifdef JSRT_IMPLEMENTATION
|
||||
#include <math.h>
|
||||
|
||||
// ── any: boxing ──
|
||||
|
||||
any_t __any_undefined(void) { return (any_t){ .kind = JSRT_KIND_UNDEFINED }; }
|
||||
any_t __any_null(void) { return (any_t){ .kind = JSRT_KIND_NULL }; }
|
||||
any_t __any_absent(void) { return (any_t){ .kind = JSRT_KIND_ABSENT }; }
|
||||
any_t __any_from_bool(bool b) { return (any_t){ .kind = JSRT_KIND_BOOL, .as.b = b }; }
|
||||
any_t __any_from_int(int32_t n) { return (any_t){ .kind = JSRT_KIND_INT, .as.i = n }; }
|
||||
any_t __any_from_float(double x) { return (any_t){ .kind = JSRT_KIND_FLOAT, .as.f = x }; }
|
||||
|
||||
// A pointer that can be a sentinel, as the kind it stands for, else as `kind`
|
||||
static any_t jsrt_from_ptr(void *p, jsrt_kind kind) {
|
||||
if (p == NULL) return __any_null();
|
||||
if (p == JSRT_UNDEFINED) return __any_undefined();
|
||||
if (p == JSRT_ABSENT) return __any_absent();
|
||||
return (any_t){ .kind = kind, .as.o = p };
|
||||
}
|
||||
|
||||
any_t __any_from_string(string_t s) { return jsrt_from_ptr(s, JSRT_KIND_STRING); }
|
||||
any_t __any_from_object(void *o) { return jsrt_from_ptr(o, JSRT_KIND_OBJECT); }
|
||||
|
||||
bool __any_as_bool(any_t a) {
|
||||
if (a.kind != JSRT_KIND_BOOL) jsrt_type_error();
|
||||
return a.as.b;
|
||||
}
|
||||
|
||||
double __any_as_float(any_t a) {
|
||||
if (a.kind == JSRT_KIND_INT) return a.as.i;
|
||||
if (a.kind != JSRT_KIND_FLOAT) jsrt_type_error();
|
||||
return a.as.f;
|
||||
}
|
||||
|
||||
int32_t __any_as_int(any_t a) {
|
||||
return a.kind == JSRT_KIND_INT ? a.as.i : (int32_t)__any_as_float(a);
|
||||
}
|
||||
|
||||
// A pointer of kind `kind`, or the sentinel of a nullish or absent value
|
||||
static void *jsrt_as_ptr(any_t a, jsrt_kind kind) {
|
||||
switch (a.kind) {
|
||||
case JSRT_KIND_NULL: return NULL;
|
||||
case JSRT_KIND_UNDEFINED: return JSRT_UNDEFINED;
|
||||
case JSRT_KIND_ABSENT: return JSRT_ABSENT;
|
||||
default:
|
||||
if (a.kind != kind) jsrt_type_error();
|
||||
return a.as.o;
|
||||
}
|
||||
}
|
||||
|
||||
string_t __any_as_string(any_t a) { return jsrt_as_ptr(a, JSRT_KIND_STRING); }
|
||||
void *__any_as_object(any_t a) { return jsrt_as_ptr(a, JSRT_KIND_OBJECT); }
|
||||
|
||||
// ── any: kinds ──
|
||||
|
||||
bool __any_is_absent(any_t a) { return a.kind == JSRT_KIND_ABSENT; }
|
||||
bool __any_is_undefined(any_t a) { return a.kind == JSRT_KIND_UNDEFINED || a.kind == JSRT_KIND_ABSENT; }
|
||||
bool __any_is_null(any_t a) { return a.kind == JSRT_KIND_NULL; }
|
||||
bool __any_is_nullish(any_t a) { return __any_is_undefined(a) || __any_is_null(a); }
|
||||
bool __any_is_boolean(any_t a) { return a.kind == JSRT_KIND_BOOL; }
|
||||
bool __any_is_number(any_t a) { return a.kind == JSRT_KIND_INT || a.kind == JSRT_KIND_FLOAT; }
|
||||
bool __any_is_string(any_t a) { return a.kind == JSRT_KIND_STRING; }
|
||||
bool __any_is_object(any_t a) { return a.kind == JSRT_KIND_OBJECT; }
|
||||
|
||||
bool __any_is_function(any_t a) {
|
||||
return a.kind == JSRT_KIND_OBJECT && jsrt_fn_sites[((jsrt_header *)a.as.o)->site];
|
||||
}
|
||||
|
||||
// ── any: abstract operations ──
|
||||
|
||||
// ToNumber. An object would call its `valueOf` or `toString` first, which the
|
||||
// runtime cannot do, so it gives NaN
|
||||
static double jsrt_to_number(any_t a) {
|
||||
switch (a.kind) {
|
||||
case JSRT_KIND_NULL: return 0;
|
||||
case JSRT_KIND_BOOL: return a.as.b;
|
||||
case JSRT_KIND_INT: return a.as.i;
|
||||
case JSRT_KIND_FLOAT: return a.as.f;
|
||||
case JSRT_KIND_STRING: return __native_string_to_number(a.as.s);
|
||||
default: return NAN;
|
||||
}
|
||||
}
|
||||
|
||||
bool __any_truthy(any_t a) {
|
||||
switch (a.kind) {
|
||||
case JSRT_KIND_BOOL: return a.as.b;
|
||||
case JSRT_KIND_INT: return a.as.i != 0;
|
||||
case JSRT_KIND_FLOAT: return a.as.f != 0 && !isnan(a.as.f);
|
||||
case JSRT_KIND_STRING: return a.as.s->length != 0;
|
||||
case JSRT_KIND_OBJECT: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t __any_to_int32(any_t a) {
|
||||
double x = jsrt_to_number(a);
|
||||
|
||||
if (isnan(x) || isinf(x)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
x = fmod(trunc(x), 4294967296.0);
|
||||
return (int32_t)(uint32_t)(int64_t)(x < 0 ? x + 4294967296.0 : x);
|
||||
}
|
||||
|
||||
// ToString. An object would call its `toString`, which the runtime cannot do
|
||||
string_t __any_to_string(any_t a) {
|
||||
switch (a.kind) {
|
||||
case JSRT_KIND_UNDEFINED: case JSRT_KIND_ABSENT: return jsrt_string_lit("undefined");
|
||||
case JSRT_KIND_NULL: return jsrt_string_lit("null");
|
||||
case JSRT_KIND_BOOL: return jsrt_string_lit(a.as.b ? "true" : "false");
|
||||
case JSRT_KIND_INT: return __native_number_to_string(a.as.i);
|
||||
case JSRT_KIND_FLOAT: return __native_number_to_string(a.as.f);
|
||||
case JSRT_KIND_STRING: return a.as.s;
|
||||
default: return jsrt_string_lit(__any_is_function(a) ? "function" : "[object Object]");
|
||||
}
|
||||
}
|
||||
|
||||
string_t __any_typeof(any_t a) {
|
||||
switch (a.kind) {
|
||||
case JSRT_KIND_UNDEFINED: case JSRT_KIND_ABSENT: return jsrt_string_lit("undefined");
|
||||
case JSRT_KIND_BOOL: return jsrt_string_lit("boolean");
|
||||
case JSRT_KIND_INT: case JSRT_KIND_FLOAT: return jsrt_string_lit("number");
|
||||
case JSRT_KIND_STRING: return jsrt_string_lit("string");
|
||||
case JSRT_KIND_OBJECT: return jsrt_string_lit(__any_is_function(a) ? "function" : "object");
|
||||
default: return jsrt_string_lit("object");
|
||||
}
|
||||
}
|
||||
|
||||
// ── any: operators ──
|
||||
|
||||
any_t __any_add(any_t a, any_t b) {
|
||||
// An object becomes a string (see `__any_to_string`), so it concatenates
|
||||
bool strings = a.kind == JSRT_KIND_STRING || b.kind == JSRT_KIND_STRING
|
||||
|| a.kind == JSRT_KIND_OBJECT || b.kind == JSRT_KIND_OBJECT;
|
||||
|
||||
if (strings) {
|
||||
return __any_from_string(__native_string_concat(__any_to_string(a), __any_to_string(b)));
|
||||
}
|
||||
|
||||
return __any_from_float(jsrt_to_number(a) + jsrt_to_number(b));
|
||||
}
|
||||
|
||||
any_t __any_sub(any_t a, any_t b) { return __any_from_float(jsrt_to_number(a) - jsrt_to_number(b)); }
|
||||
any_t __any_mul(any_t a, any_t b) { return __any_from_float(jsrt_to_number(a) * jsrt_to_number(b)); }
|
||||
any_t __any_div(any_t a, any_t b) { return __any_from_float(jsrt_to_number(a) / jsrt_to_number(b)); }
|
||||
any_t __any_mod(any_t a, any_t b) { return __any_from_float(fmod(jsrt_to_number(a), jsrt_to_number(b))); }
|
||||
any_t __any_pow(any_t a, any_t b) { return __any_from_float(pow(jsrt_to_number(a), jsrt_to_number(b))); }
|
||||
any_t __any_neg(any_t a) { return __any_from_float(-jsrt_to_number(a)); }
|
||||
any_t __any_plus(any_t a) { return __any_from_float(jsrt_to_number(a)); }
|
||||
any_t __any_inc(any_t a) { return __any_from_float(jsrt_to_number(a) + 1); }
|
||||
any_t __any_dec(any_t a) { return __any_from_float(jsrt_to_number(a) - 1); }
|
||||
|
||||
// ── any: comparisons ──
|
||||
|
||||
// Two strings compare by code units. Anything else compares as numbers, where
|
||||
// NaN makes every comparison false
|
||||
bool __any_lt(any_t a, any_t b) {
|
||||
bool strings = a.kind == JSRT_KIND_STRING && b.kind == JSRT_KIND_STRING;
|
||||
return strings ? jsrt_string_cmp(a.as.s, b.as.s) < 0 : jsrt_to_number(a) < jsrt_to_number(b);
|
||||
}
|
||||
|
||||
bool __any_lte(any_t a, any_t b) {
|
||||
bool strings = a.kind == JSRT_KIND_STRING && b.kind == JSRT_KIND_STRING;
|
||||
return strings ? jsrt_string_cmp(a.as.s, b.as.s) <= 0 : jsrt_to_number(a) <= jsrt_to_number(b);
|
||||
}
|
||||
|
||||
bool __any_gt(any_t a, any_t b) { return __any_lt(b, a); }
|
||||
bool __any_gte(any_t a, any_t b) { return __any_lte(b, a); }
|
||||
|
||||
bool __any_strict_eq(any_t a, any_t b) {
|
||||
if (__any_is_number(a) && __any_is_number(b)) {
|
||||
return jsrt_to_number(a) == jsrt_to_number(b);
|
||||
}
|
||||
|
||||
if (__any_is_undefined(a) || __any_is_undefined(b)) {
|
||||
return __any_is_undefined(a) && __any_is_undefined(b);
|
||||
}
|
||||
|
||||
if (a.kind != b.kind) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (a.kind) {
|
||||
case JSRT_KIND_NULL: return true;
|
||||
case JSRT_KIND_BOOL: return a.as.b == b.as.b;
|
||||
case JSRT_KIND_STRING: return jsrt_string_eq(a.as.s, b.as.s);
|
||||
default: return a.as.o == b.as.o;
|
||||
}
|
||||
}
|
||||
|
||||
bool __any_strict_neq(any_t a, any_t b) { return !__any_strict_eq(a, b); }
|
||||
|
||||
// Undefined and null equal each other and nothing else. Values of one kind,
|
||||
// and objects, compare as `===` does. Other primitives compare as numbers
|
||||
bool __any_eq(any_t a, any_t b) {
|
||||
if (__any_is_nullish(a) || __any_is_nullish(b)) {
|
||||
return __any_is_nullish(a) && __any_is_nullish(b);
|
||||
}
|
||||
|
||||
bool same = a.kind == b.kind || (__any_is_number(a) && __any_is_number(b));
|
||||
|
||||
if (same || a.kind == JSRT_KIND_OBJECT || b.kind == JSRT_KIND_OBJECT) {
|
||||
return __any_strict_eq(a, b);
|
||||
}
|
||||
|
||||
return jsrt_to_number(a) == jsrt_to_number(b);
|
||||
}
|
||||
|
||||
bool __any_neq(any_t a, any_t b) { return !__any_eq(a, b); }
|
||||
|
||||
#endif // JSRT_IMPLEMENTATION
|
||||
@@ -0,0 +1,95 @@
|
||||
// Values and objects: the types that every other part uses, the object
|
||||
// header and its tags, the allocator and errors. See docs/model.md
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct { char _; } undefined_t;
|
||||
typedef struct { char _; } null_t;
|
||||
|
||||
// A string: UTF-16 code units, as JS counts them
|
||||
typedef struct jsrt_string *string_t;
|
||||
|
||||
// The start of every object: the site that created it, which tells its shape
|
||||
// and, for a fn, its code
|
||||
typedef struct { uint32_t site; } jsrt_header;
|
||||
|
||||
// The sites below 3 are tags. An object of a value shape is a struct, not a
|
||||
// pointer, so its header tells if it is undefined, null or absent instead
|
||||
#define JSRT_SITE_UNDEFINED 0
|
||||
#define JSRT_SITE_NULL 1
|
||||
#define JSRT_SITE_ABSENT 2
|
||||
|
||||
// A pointer to an object or a string can also be one of these. NULL is null
|
||||
extern jsrt_header jsrt_undefined_cell, jsrt_absent_cell;
|
||||
#define JSRT_UNDEFINED ((void *)&jsrt_undefined_cell)
|
||||
#define JSRT_ABSENT ((void *)&jsrt_absent_cell) // a field that an object lacks
|
||||
|
||||
// Whether the objects of each site are fns, by site. The emitted C defines it
|
||||
extern const bool jsrt_fn_sites[];
|
||||
|
||||
// A new heap cell of `size` bytes, all zero
|
||||
void *jsrt_alloc(size_t size);
|
||||
|
||||
// `items`, an array's elements of `size` bytes each, with room for at least
|
||||
// `need` of them. `*cap` is the room that it has, and is set to the new room
|
||||
void *jsrt_grow(void *items, int32_t *cap, int32_t need, size_t size);
|
||||
|
||||
// Throws a TypeError. Exceptions are not supported yet, so it stops the program
|
||||
_Noreturn void jsrt_type_error(void);
|
||||
|
||||
// `p`, a pointer to an object or a string. A sentinel for null, undefined or
|
||||
// absent throws a TypeError
|
||||
void *jsrt_some(void *p);
|
||||
|
||||
#ifdef JSRT_IMPLEMENTATION
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
jsrt_header jsrt_undefined_cell = { JSRT_SITE_UNDEFINED };
|
||||
jsrt_header jsrt_absent_cell = { JSRT_SITE_ABSENT };
|
||||
|
||||
void *jsrt_alloc(size_t size) {
|
||||
void *p = calloc(1, size);
|
||||
|
||||
if (!p) {
|
||||
fputs("jsrt: out of memory\n", stderr);
|
||||
abort();
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void *jsrt_grow(void *items, int32_t *cap, int32_t need, size_t size) {
|
||||
if (need <= *cap) {
|
||||
return items;
|
||||
}
|
||||
|
||||
int32_t room = *cap * 2 > need ? *cap * 2 : need < 4 ? 4 : need;
|
||||
items = realloc(items, (size_t)room * size);
|
||||
|
||||
if (!items) {
|
||||
fputs("jsrt: out of memory\n", stderr);
|
||||
abort();
|
||||
}
|
||||
|
||||
*cap = room;
|
||||
return items;
|
||||
}
|
||||
|
||||
_Noreturn void jsrt_type_error(void) {
|
||||
fputs("TypeError\n", stderr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void *jsrt_some(void *p) {
|
||||
if (p == NULL || p == JSRT_UNDEFINED || p == JSRT_ABSENT) {
|
||||
jsrt_type_error();
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
#endif // JSRT_IMPLEMENTATION
|
||||
+455
@@ -0,0 +1,455 @@
|
||||
// IO natives. `__native_print_string` writes at once. The rest are async, on
|
||||
// io_uring (see uring.h)
|
||||
//
|
||||
// A `__native_io_queue_<op>` queues an op and gives its handle. It makes no
|
||||
// syscall. `__native_io_wait` puts queued ops into the SQ, submits them,
|
||||
// waits until one op is done, and gives its handle. The getters then read
|
||||
// that op's result, until the next wait
|
||||
//
|
||||
// At most `cq_entries` ops are in flight, so the CQ cannot overflow. The rest
|
||||
// stay queued until a later wait. So a queue never fails, and the program
|
||||
// never sees a limit
|
||||
//
|
||||
// The ring is made at the first wait that has ops, so a program that does no
|
||||
// async IO never makes it
|
||||
#pragma once
|
||||
|
||||
#include "str.h"
|
||||
|
||||
#ifdef JSRT_IMPLEMENTATION
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/stat.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "uring.h"
|
||||
|
||||
// ── sync ──
|
||||
|
||||
undefined_t __native_print_string(string_t s) {
|
||||
char *utf8 = jsrt_string_utf8(s);
|
||||
puts(utf8);
|
||||
free(utf8);
|
||||
return (undefined_t){0};
|
||||
}
|
||||
|
||||
// ── ops ──
|
||||
|
||||
typedef enum {
|
||||
JSRT_IO_TIMEOUT,
|
||||
JSRT_IO_OPEN,
|
||||
JSRT_IO_READ,
|
||||
JSRT_IO_WRITE,
|
||||
JSRT_IO_CLOSE,
|
||||
JSRT_IO_STAT,
|
||||
JSRT_IO_CANCEL,
|
||||
} jsrt_io_kind;
|
||||
|
||||
typedef enum {
|
||||
JSRT_IO_FREE, // in the free list
|
||||
JSRT_IO_QUEUED, // in the backlog, not in the SQ yet
|
||||
JSRT_IO_FLIGHT, // in the SQ or in the kernel
|
||||
JSRT_IO_DONE, // in the done list, or the last op a wait gave
|
||||
} jsrt_io_state;
|
||||
|
||||
// An op. Its handle is its index in the table, and its SQE's user_data
|
||||
//
|
||||
// The table moves when it grows, which only a queue does. So the kernel
|
||||
// must not hold a pointer into it after a submit returns: `ts` is read
|
||||
// before (SUBMIT_STABLE), and everything that the kernel reads or writes
|
||||
// later is in `buf`, which does not move
|
||||
typedef struct {
|
||||
uint8_t kind, state;
|
||||
bool cancelled; // cancelled while queued: never goes to the SQ
|
||||
uint32_t gen; // counts reuses of this slot (see CANCEL)
|
||||
int32_t next; // the next op in its list, or -1
|
||||
int32_t res; // the CQE's res: >= 0 on success, -errno on failure
|
||||
|
||||
int32_t fd; // READ, WRITE, CLOSE
|
||||
int32_t flags, mode; // OPEN
|
||||
uint32_t len; // READ, WRITE: the size of `buf`
|
||||
uint64_t off; // READ, WRITE: -1 is the file's position
|
||||
int32_t target; // CANCEL: the op to cancel...
|
||||
uint32_t target_gen; // ...while it is still this use of its slot
|
||||
struct __kernel_timespec ts; // TIMEOUT
|
||||
void *buf; // OPEN: path. READ, WRITE: data. STAT: statx, then path
|
||||
} jsrt_io_op;
|
||||
|
||||
// A FIFO of ops, linked through `next`
|
||||
typedef struct { int32_t head, tail; } jsrt_io_list;
|
||||
|
||||
static struct {
|
||||
jsrt_uring ring;
|
||||
bool ring_ready;
|
||||
unsigned in_flight;
|
||||
|
||||
jsrt_io_op *ops;
|
||||
int32_t cap;
|
||||
int32_t free; // a stack, linked through `next`
|
||||
jsrt_io_list backlog, done;
|
||||
int32_t given; // the op the last wait gave, or -1
|
||||
} jsrt_io = {
|
||||
.free = -1,
|
||||
.backlog = { -1, -1 },
|
||||
.done = { -1, -1 },
|
||||
.given = -1,
|
||||
};
|
||||
|
||||
static void jsrt_io_push(jsrt_io_list *l, int32_t h) {
|
||||
jsrt_io.ops[h].next = -1;
|
||||
|
||||
if (l->tail < 0) {
|
||||
l->head = h;
|
||||
} else {
|
||||
jsrt_io.ops[l->tail].next = h;
|
||||
}
|
||||
|
||||
l->tail = h;
|
||||
}
|
||||
|
||||
static int32_t jsrt_io_pop(jsrt_io_list *l) {
|
||||
int32_t h = l->head;
|
||||
|
||||
if (h >= 0) {
|
||||
l->head = jsrt_io.ops[h].next;
|
||||
|
||||
if (l->head < 0) {
|
||||
l->tail = -1;
|
||||
}
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
// A new op of `kind`, not in any list yet
|
||||
static int32_t jsrt_io_new(jsrt_io_kind kind) {
|
||||
if (jsrt_io.free < 0) {
|
||||
int32_t cap = jsrt_io.cap ? jsrt_io.cap * 2 : 64;
|
||||
jsrt_io_op *ops = realloc(jsrt_io.ops, (size_t)cap * sizeof *ops);
|
||||
|
||||
if (!ops) {
|
||||
fputs("jsrt: out of memory\n", stderr);
|
||||
abort();
|
||||
}
|
||||
|
||||
// The new slots go on the free stack, the lowest on top
|
||||
for (int32_t h = cap - 1; h >= jsrt_io.cap; h--) {
|
||||
ops[h] = (jsrt_io_op){ .state = JSRT_IO_FREE, .next = jsrt_io.free };
|
||||
jsrt_io.free = h;
|
||||
}
|
||||
|
||||
jsrt_io.ops = ops;
|
||||
jsrt_io.cap = cap;
|
||||
}
|
||||
|
||||
int32_t h = jsrt_io.free;
|
||||
jsrt_io_op *op = &jsrt_io.ops[h];
|
||||
jsrt_io.free = op->next;
|
||||
*op = (jsrt_io_op){ .kind = kind, .state = JSRT_IO_QUEUED, .gen = op->gen + 1, .next = -1 };
|
||||
return h;
|
||||
}
|
||||
|
||||
static int32_t jsrt_io_queue(int32_t h) {
|
||||
jsrt_io_push(&jsrt_io.backlog, h);
|
||||
return h;
|
||||
}
|
||||
|
||||
// Ends op `h` with `res`, without the kernel
|
||||
static int32_t jsrt_io_finish(int32_t h, int32_t res) {
|
||||
jsrt_io.ops[h].state = JSRT_IO_DONE;
|
||||
jsrt_io.ops[h].res = res;
|
||||
jsrt_io_push(&jsrt_io.done, h);
|
||||
return h;
|
||||
}
|
||||
|
||||
static void jsrt_io_release(int32_t h) {
|
||||
jsrt_io_op *op = &jsrt_io.ops[h];
|
||||
free(op->buf);
|
||||
op->buf = NULL;
|
||||
op->state = JSRT_IO_FREE;
|
||||
op->next = jsrt_io.free;
|
||||
jsrt_io.free = h;
|
||||
}
|
||||
|
||||
// The op that the last wait gave. A handle of any other op is an error
|
||||
static jsrt_io_op *jsrt_io_given(int32_t h) {
|
||||
if (h < 0 || h != jsrt_io.given) {
|
||||
jsrt_type_error();
|
||||
}
|
||||
|
||||
return &jsrt_io.ops[h];
|
||||
}
|
||||
|
||||
// 0 if op `h` still has work for the kernel, else the -errno it ends with
|
||||
//
|
||||
// An op that was cancelled while queued has none. Nor has a cancel whose
|
||||
// target ended, because then the target's slot can hold a new op
|
||||
static int32_t jsrt_io_dead(int32_t h) {
|
||||
jsrt_io_op *op = &jsrt_io.ops[h];
|
||||
|
||||
if (op->cancelled) {
|
||||
return -ECANCELED;
|
||||
}
|
||||
|
||||
if (op->kind == JSRT_IO_CANCEL) {
|
||||
jsrt_io_op *t = &jsrt_io.ops[op->target];
|
||||
|
||||
if (t->gen != op->target_gen || t->state != JSRT_IO_FLIGHT) {
|
||||
return -ENOENT;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void jsrt_io_prep(struct io_uring_sqe *sqe, int32_t h) {
|
||||
jsrt_io_op *op = &jsrt_io.ops[h];
|
||||
sqe->user_data = (uint64_t)h;
|
||||
|
||||
switch (op->kind) {
|
||||
case JSRT_IO_TIMEOUT:
|
||||
// A plain timer: `off` 0 means that no count of CQEs ends it
|
||||
sqe->opcode = IORING_OP_TIMEOUT;
|
||||
sqe->fd = -1;
|
||||
sqe->addr = (uint64_t)(uintptr_t)&op->ts;
|
||||
sqe->len = 1;
|
||||
break;
|
||||
|
||||
case JSRT_IO_OPEN:
|
||||
sqe->opcode = IORING_OP_OPENAT;
|
||||
sqe->fd = AT_FDCWD;
|
||||
sqe->addr = (uint64_t)(uintptr_t)op->buf;
|
||||
sqe->len = (uint32_t)op->mode;
|
||||
sqe->open_flags = (uint32_t)op->flags;
|
||||
break;
|
||||
|
||||
case JSRT_IO_READ:
|
||||
case JSRT_IO_WRITE:
|
||||
sqe->opcode = op->kind == JSRT_IO_READ ? IORING_OP_READ : IORING_OP_WRITE;
|
||||
sqe->fd = op->fd;
|
||||
sqe->addr = (uint64_t)(uintptr_t)op->buf;
|
||||
sqe->len = op->len;
|
||||
sqe->off = op->off;
|
||||
break;
|
||||
|
||||
case JSRT_IO_CLOSE:
|
||||
sqe->opcode = IORING_OP_CLOSE;
|
||||
sqe->fd = op->fd;
|
||||
break;
|
||||
|
||||
case JSRT_IO_STAT:
|
||||
sqe->opcode = IORING_OP_STATX;
|
||||
sqe->fd = AT_FDCWD;
|
||||
sqe->addr = (uint64_t)(uintptr_t)((char *)op->buf + sizeof(struct statx));
|
||||
sqe->len = STATX_SIZE;
|
||||
sqe->addr2 = (uint64_t)(uintptr_t)op->buf;
|
||||
break;
|
||||
|
||||
case JSRT_IO_CANCEL:
|
||||
// A timeout is removed with its own op
|
||||
sqe->opcode = jsrt_io.ops[op->target].kind == JSRT_IO_TIMEOUT
|
||||
? IORING_OP_TIMEOUT_REMOVE
|
||||
: IORING_OP_ASYNC_CANCEL;
|
||||
sqe->fd = -1;
|
||||
sqe->addr = (uint64_t)op->target;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Moves queued ops into the SQ while fewer than `cq_entries` are in flight,
|
||||
// and submits them
|
||||
static void jsrt_io_drain(void) {
|
||||
if (jsrt_io.backlog.head < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
jsrt_uring *r = &jsrt_io.ring;
|
||||
|
||||
if (!jsrt_io.ring_ready) {
|
||||
jsrt_uring_init(r, 128);
|
||||
jsrt_io.ring_ready = true;
|
||||
}
|
||||
|
||||
while (jsrt_io.backlog.head >= 0 && jsrt_io.in_flight < r->cq_entries) {
|
||||
int32_t h = jsrt_io.backlog.head;
|
||||
int32_t dead = jsrt_io_dead(h);
|
||||
|
||||
if (dead) {
|
||||
jsrt_io_pop(&jsrt_io.backlog);
|
||||
jsrt_io_finish(h, dead);
|
||||
continue;
|
||||
}
|
||||
|
||||
struct io_uring_sqe *sqe = jsrt_uring_sqe(r);
|
||||
|
||||
// The SQ is full: a submit empties it
|
||||
if (!sqe) {
|
||||
jsrt_uring_submit(r, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
jsrt_io_pop(&jsrt_io.backlog);
|
||||
jsrt_io_prep(sqe, h);
|
||||
jsrt_io.ops[h].state = JSRT_IO_FLIGHT;
|
||||
jsrt_io.in_flight++;
|
||||
}
|
||||
|
||||
// No SQE may stay unsubmitted when the wait returns, because a queue can
|
||||
// then move the table (see jsrt_io_op)
|
||||
jsrt_uring_submit(r, false);
|
||||
}
|
||||
|
||||
// ── natives ──
|
||||
|
||||
int32_t __native_io_queue_timeout(double ms) {
|
||||
int32_t h = jsrt_io_new(JSRT_IO_TIMEOUT);
|
||||
|
||||
if (!(ms > 0)) {
|
||||
ms = 0;
|
||||
}
|
||||
|
||||
jsrt_io.ops[h].ts.tv_sec = (int64_t)(ms / 1000);
|
||||
jsrt_io.ops[h].ts.tv_nsec = (int64_t)(fmod(ms, 1000) * 1e6);
|
||||
return jsrt_io_queue(h);
|
||||
}
|
||||
|
||||
// `flags` and `mode` are open(2)'s, as numbers
|
||||
int32_t __native_io_queue_open(string_t path, int32_t flags, int32_t mode) {
|
||||
int32_t h = jsrt_io_new(JSRT_IO_OPEN);
|
||||
jsrt_io.ops[h].buf = jsrt_string_utf8(path);
|
||||
jsrt_io.ops[h].flags = flags | O_CLOEXEC;
|
||||
jsrt_io.ops[h].mode = mode;
|
||||
return jsrt_io_queue(h);
|
||||
}
|
||||
|
||||
// Reads at most `len` bytes at `offset`, or at the file's position if
|
||||
// `offset` is -1. The status is the number of bytes read
|
||||
int32_t __native_io_queue_read(int32_t fd, int32_t len, double offset) {
|
||||
int32_t h = jsrt_io_new(JSRT_IO_READ);
|
||||
jsrt_io_op *op = &jsrt_io.ops[h];
|
||||
op->fd = fd;
|
||||
op->len = len > 0 ? (uint32_t)len : 0;
|
||||
op->off = offset < 0 ? (uint64_t)-1 : (uint64_t)offset;
|
||||
op->buf = jsrt_alloc(op->len ? op->len : 1);
|
||||
return jsrt_io_queue(h);
|
||||
}
|
||||
|
||||
// Writes `data` as UTF-8, at `offset` as for a read. The status is the number
|
||||
// of bytes written, which can be fewer than all
|
||||
int32_t __native_io_queue_write(int32_t fd, string_t data, double offset) {
|
||||
int32_t h = jsrt_io_new(JSRT_IO_WRITE);
|
||||
jsrt_io_op *op = &jsrt_io.ops[h];
|
||||
op->fd = fd;
|
||||
op->buf = jsrt_string_utf8(data);
|
||||
op->len = (uint32_t)strlen(op->buf);
|
||||
op->off = offset < 0 ? (uint64_t)-1 : (uint64_t)offset;
|
||||
return jsrt_io_queue(h);
|
||||
}
|
||||
|
||||
int32_t __native_io_queue_close(int32_t fd) {
|
||||
int32_t h = jsrt_io_new(JSRT_IO_CLOSE);
|
||||
jsrt_io.ops[h].fd = fd;
|
||||
return jsrt_io_queue(h);
|
||||
}
|
||||
|
||||
// Gives the file's size, as `__native_io_result_number`
|
||||
int32_t __native_io_queue_stat(string_t path) {
|
||||
int32_t h = jsrt_io_new(JSRT_IO_STAT);
|
||||
char *utf8 = jsrt_string_utf8(path);
|
||||
size_t size = strlen(utf8) + 1;
|
||||
char *buf = jsrt_alloc(sizeof(struct statx) + size);
|
||||
memcpy(buf + sizeof(struct statx), utf8, size);
|
||||
free(utf8);
|
||||
jsrt_io.ops[h].buf = buf;
|
||||
return jsrt_io_queue(h);
|
||||
}
|
||||
|
||||
// Cancels op `target`, which then ends with -ECANCELED if it did not end
|
||||
// first. The cancel's own status is 0 if it found the op, else -ENOENT or
|
||||
// -EALREADY
|
||||
int32_t __native_io_queue_cancel(int32_t target) {
|
||||
if (target < 0 || target >= jsrt_io.cap) {
|
||||
jsrt_type_error();
|
||||
}
|
||||
|
||||
int32_t h = jsrt_io_new(JSRT_IO_CANCEL);
|
||||
jsrt_io_op *t = &jsrt_io.ops[target];
|
||||
|
||||
switch (t->state) {
|
||||
case JSRT_IO_QUEUED:
|
||||
t->cancelled = true;
|
||||
return jsrt_io_finish(h, 0);
|
||||
case JSRT_IO_FLIGHT:
|
||||
jsrt_io.ops[h].target = target;
|
||||
jsrt_io.ops[h].target_gen = t->gen;
|
||||
return jsrt_io_queue(h);
|
||||
default:
|
||||
return jsrt_io_finish(h, -ENOENT);
|
||||
}
|
||||
}
|
||||
|
||||
// The handle of an op that is done, or -1 if no op is queued or in flight.
|
||||
// The op that the last wait gave is freed now
|
||||
int32_t __native_io_wait(void) {
|
||||
if (jsrt_io.given >= 0) {
|
||||
jsrt_io_release(jsrt_io.given);
|
||||
jsrt_io.given = -1;
|
||||
}
|
||||
|
||||
jsrt_io_drain();
|
||||
|
||||
int32_t h = jsrt_io_pop(&jsrt_io.done);
|
||||
|
||||
while (h < 0) {
|
||||
if (jsrt_io.in_flight == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint64_t user_data;
|
||||
int32_t res;
|
||||
|
||||
if (!jsrt_uring_cqe(&jsrt_io.ring, &user_data, &res)) {
|
||||
jsrt_uring_submit(&jsrt_io.ring, true);
|
||||
continue;
|
||||
}
|
||||
|
||||
jsrt_io.in_flight--;
|
||||
h = (int32_t)user_data;
|
||||
|
||||
// A timer that runs out ends with -ETIME, which is its success
|
||||
bool timer = jsrt_io.ops[h].kind == JSRT_IO_TIMEOUT;
|
||||
jsrt_io.ops[h].state = JSRT_IO_DONE;
|
||||
jsrt_io.ops[h].res = timer && res == -ETIME ? 0 : res;
|
||||
}
|
||||
|
||||
jsrt_io.given = h;
|
||||
return h;
|
||||
}
|
||||
|
||||
// >= 0 if the op succeeded (a count of bytes, an fd, or 0), else -errno
|
||||
int32_t __native_io_status(int32_t h) {
|
||||
return jsrt_io_given(h)->res;
|
||||
}
|
||||
|
||||
// The data of a read, decoded as UTF-8. Empty for any other op, or a failure
|
||||
string_t __native_io_result_string(int32_t h) {
|
||||
jsrt_io_op *op = jsrt_io_given(h);
|
||||
bool data = op->kind == JSRT_IO_READ && op->res > 0;
|
||||
return jsrt_string_from_utf8(op->buf, data ? (size_t)op->res : 0);
|
||||
}
|
||||
|
||||
// The size of a stat's file. The status for any other op
|
||||
double __native_io_result_number(int32_t h) {
|
||||
jsrt_io_op *op = jsrt_io_given(h);
|
||||
|
||||
if (op->kind == JSRT_IO_STAT && op->res >= 0) {
|
||||
return (double)((struct statx *)op->buf)->stx_size;
|
||||
}
|
||||
|
||||
return op->res;
|
||||
}
|
||||
|
||||
#endif // JSRT_IMPLEMENTATION
|
||||
@@ -0,0 +1,26 @@
|
||||
// The runtime that emitted C is compiled with. See docs/model.md for the
|
||||
// representation of values
|
||||
//
|
||||
// Each header declares its part, and if JSRT_IMPLEMENTATION is defined before
|
||||
// this is included, also defines it. The emitted C is one file, which does
|
||||
// that, and includes this before any other header. The natives are declared
|
||||
// by the emitted C itself, from compiler/src/native.rs, and defined here
|
||||
//
|
||||
// core.h values, the object header, the allocator, errors
|
||||
// str.h strings and the string natives
|
||||
// number.h the number natives
|
||||
// any.h any_t and JS's operators on it
|
||||
// io.h IO natives, async on io_uring (uring.h)
|
||||
#pragma once
|
||||
|
||||
// io.h needs Linux's syscall() and mmap(), which -std=c11 hides. This must
|
||||
// come before the first system header
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#include "core.h"
|
||||
#include "str.h"
|
||||
#include "number.h"
|
||||
#include "any.h"
|
||||
#include "io.h"
|
||||
@@ -0,0 +1,93 @@
|
||||
// The number natives: conversions between numbers and strings
|
||||
#pragma once
|
||||
|
||||
#include "str.h"
|
||||
|
||||
#ifdef JSRT_IMPLEMENTATION
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// JS's Number::toString: the fewest digits that give the number back. Plain
|
||||
// notation while the decimal point is within 21 places, else exponential
|
||||
string_t __native_number_to_string(double x) {
|
||||
if (isnan(x)) return jsrt_string_lit("NaN");
|
||||
if (isinf(x)) return jsrt_string_lit(x < 0 ? "-Infinity" : "Infinity");
|
||||
if (x == 0) return jsrt_string_lit("0");
|
||||
|
||||
// The fewest digits: buf is [-]d[.ddd]e±xx
|
||||
char buf[64];
|
||||
|
||||
for (int precision = 0; precision < 17; precision++) {
|
||||
snprintf(buf, sizeof buf, "%.*e", precision, x);
|
||||
|
||||
if (strtod(buf, NULL) == x) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
char digits[32], out[64];
|
||||
char *p = buf, *o = out;
|
||||
int n = 0;
|
||||
|
||||
if (*p == '-') {
|
||||
*o++ = *p++;
|
||||
}
|
||||
|
||||
for (; *p != 'e'; p++) {
|
||||
if (*p != '.') {
|
||||
digits[n++] = *p;
|
||||
}
|
||||
}
|
||||
|
||||
digits[n] = 0;
|
||||
|
||||
// The decimal point goes after the first `point` digits
|
||||
int point = atoi(p + 1) + 1;
|
||||
|
||||
if (n <= point && point <= 21) {
|
||||
o += sprintf(o, "%s", digits);
|
||||
for (int i = n; i < point; i++) *o++ = '0';
|
||||
*o = 0;
|
||||
} else if (0 < point && point <= 21) {
|
||||
sprintf(o, "%.*s.%s", point, digits, digits + point);
|
||||
} else if (-6 < point && point <= 0) {
|
||||
o += sprintf(o, "0.");
|
||||
for (int i = point; i < 0; i++) *o++ = '0';
|
||||
sprintf(o, "%s", digits);
|
||||
} else {
|
||||
int e = point - 1;
|
||||
sprintf(o, "%c%s%se%c%d", digits[0], n > 1 ? "." : "", digits + 1, e < 0 ? '-' : '+', abs(e));
|
||||
}
|
||||
|
||||
return jsrt_string_lit(out);
|
||||
}
|
||||
|
||||
string_t __native_number_to_fixed(double x, int32_t digits) {
|
||||
char buf[400];
|
||||
snprintf(buf, sizeof buf, "%.*f", digits, x);
|
||||
return jsrt_string_lit(buf);
|
||||
}
|
||||
|
||||
// JS's ToNumber on a string: a decimal number with whitespace around it, or
|
||||
// only whitespace, which is 0. Anything else is NaN
|
||||
double __native_string_to_number(string_t s) {
|
||||
char *utf8 = jsrt_string_utf8(s);
|
||||
char *start = utf8, *end;
|
||||
|
||||
while (*start == ' ' || (*start >= '\t' && *start <= '\r')) start++;
|
||||
|
||||
double x = 0;
|
||||
|
||||
if (*start) {
|
||||
x = strtod(start, &end);
|
||||
while (*end == ' ' || (*end >= '\t' && *end <= '\r')) end++;
|
||||
if (*end || start == end) x = NAN;
|
||||
}
|
||||
|
||||
free(utf8);
|
||||
return x;
|
||||
}
|
||||
|
||||
#endif // JSRT_IMPLEMENTATION
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
// Strings: UTF-16 code units, as JS counts them, and the string natives
|
||||
#pragma once
|
||||
|
||||
#include "core.h"
|
||||
|
||||
string_t jsrt_string_lit(const char *utf8);
|
||||
string_t jsrt_string_from_utf8(const char *utf8, size_t size);
|
||||
int32_t jsrt_string_length(string_t s);
|
||||
bool jsrt_string_eq(string_t a, string_t b);
|
||||
int32_t jsrt_string_cmp(string_t a, string_t b);
|
||||
|
||||
// `s[i]`: the code unit at `i`, as a string, or JSRT_UNDEFINED past the end
|
||||
string_t jsrt_string_at(string_t s, int32_t i);
|
||||
|
||||
#ifdef JSRT_IMPLEMENTATION
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct jsrt_string {
|
||||
int32_t length;
|
||||
uint16_t units[];
|
||||
};
|
||||
|
||||
static string_t jsrt_string_new(int32_t length) {
|
||||
string_t s = jsrt_alloc(sizeof *s + (size_t)length * sizeof(uint16_t));
|
||||
s->length = length;
|
||||
return s;
|
||||
}
|
||||
|
||||
string_t jsrt_string_lit(const char *utf8) {
|
||||
return jsrt_string_from_utf8(utf8, strlen(utf8));
|
||||
}
|
||||
|
||||
// The `size` bytes at `utf8`, which need not end in a 0
|
||||
string_t jsrt_string_from_utf8(const char *utf8, size_t size) {
|
||||
const unsigned char *p = (const unsigned char *)utf8, *end = p + size;
|
||||
string_t s = jsrt_string_new((int32_t)size * 2);
|
||||
int32_t n = 0;
|
||||
|
||||
while (p < end) {
|
||||
uint32_t c = *p++;
|
||||
int extra = c >= 0xF0 ? 3 : c >= 0xE0 ? 2 : c >= 0xC0 ? 1 : 0;
|
||||
c &= extra ? 0x3F >> extra : 0x7F;
|
||||
|
||||
for (; extra && p < end; extra--) {
|
||||
c = (c << 6) | (*p++ & 0x3F);
|
||||
}
|
||||
|
||||
if (c >= 0x10000) {
|
||||
c -= 0x10000;
|
||||
s->units[n++] = (uint16_t)(0xD800 + (c >> 10));
|
||||
s->units[n++] = (uint16_t)(0xDC00 + (c & 0x3FF));
|
||||
} else {
|
||||
s->units[n++] = (uint16_t)c;
|
||||
}
|
||||
}
|
||||
|
||||
s->length = n;
|
||||
return s;
|
||||
}
|
||||
|
||||
int32_t jsrt_string_length(string_t s) {
|
||||
return s->length;
|
||||
}
|
||||
|
||||
bool jsrt_string_eq(string_t a, string_t b) {
|
||||
return a->length == b->length && !memcmp(a->units, b->units, (size_t)a->length * sizeof(uint16_t));
|
||||
}
|
||||
|
||||
int32_t jsrt_string_cmp(string_t a, string_t b) {
|
||||
int32_t n = a->length < b->length ? a->length : b->length;
|
||||
|
||||
for (int32_t i = 0; i < n; i++) {
|
||||
if (a->units[i] != b->units[i]) {
|
||||
return a->units[i] < b->units[i] ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
return a->length < b->length ? -1 : a->length > b->length;
|
||||
}
|
||||
|
||||
string_t jsrt_string_at(string_t s, int32_t i) {
|
||||
if (i < 0 || i >= s->length) {
|
||||
return JSRT_UNDEFINED;
|
||||
}
|
||||
|
||||
string_t c = jsrt_string_new(1);
|
||||
c->units[0] = s->units[i];
|
||||
return c;
|
||||
}
|
||||
|
||||
// `s` as UTF-8, in a new buffer for the caller to free
|
||||
static char *jsrt_string_utf8(string_t s) {
|
||||
char *utf8 = jsrt_alloc((size_t)s->length * 3 + 1);
|
||||
char *out = utf8;
|
||||
|
||||
for (int32_t i = 0; i < s->length; i++) {
|
||||
uint32_t c = s->units[i];
|
||||
bool pair = c >= 0xD800 && c < 0xDC00 && i + 1 < s->length
|
||||
&& s->units[i + 1] >= 0xDC00 && s->units[i + 1] < 0xE000;
|
||||
|
||||
if (pair) {
|
||||
c = 0x10000 + ((c - 0xD800) << 10) + (s->units[++i] - 0xDC00);
|
||||
}
|
||||
|
||||
if (c < 0x80) {
|
||||
*out++ = (char)c;
|
||||
} else if (c < 0x800) {
|
||||
*out++ = (char)(0xC0 | c >> 6);
|
||||
*out++ = (char)(0x80 | (c & 0x3F));
|
||||
} else if (c < 0x10000) {
|
||||
*out++ = (char)(0xE0 | c >> 12);
|
||||
*out++ = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
*out++ = (char)(0x80 | (c & 0x3F));
|
||||
} else {
|
||||
*out++ = (char)(0xF0 | c >> 18);
|
||||
*out++ = (char)(0x80 | (c >> 12 & 0x3F));
|
||||
*out++ = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
*out++ = (char)(0x80 | (c & 0x3F));
|
||||
}
|
||||
}
|
||||
|
||||
*out = 0;
|
||||
return utf8;
|
||||
}
|
||||
|
||||
// ── natives ──
|
||||
|
||||
string_t __native_string_concat(string_t a, string_t b) {
|
||||
string_t s = jsrt_string_new(a->length + b->length);
|
||||
memcpy(s->units, a->units, (size_t)a->length * sizeof(uint16_t));
|
||||
memcpy(s->units + a->length, b->units, (size_t)b->length * sizeof(uint16_t));
|
||||
return s;
|
||||
}
|
||||
|
||||
string_t __native_string_slice(string_t s, int32_t start, int32_t end) {
|
||||
start = start < 0 ? 0 : start > s->length ? s->length : start;
|
||||
end = end < start ? start : end > s->length ? s->length : end;
|
||||
|
||||
string_t r = jsrt_string_new(end - start);
|
||||
memcpy(r->units, s->units + start, (size_t)(end - start) * sizeof(uint16_t));
|
||||
return r;
|
||||
}
|
||||
|
||||
int32_t __native_string_char_code_at(string_t s, int32_t i) {
|
||||
return s->units[i];
|
||||
}
|
||||
|
||||
string_t __native_string_from_char_code(int32_t code) {
|
||||
string_t s = jsrt_string_new(1);
|
||||
s->units[0] = (uint16_t)code;
|
||||
return s;
|
||||
}
|
||||
|
||||
#endif // JSRT_IMPLEMENTATION
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
// A minimal io_uring ring, on the raw syscalls. Only io.h uses it
|
||||
//
|
||||
// We write SQEs into the SQ and the kernel takes them when we submit. The
|
||||
// kernel writes CQEs into the CQ and we take them. Each side moves only its
|
||||
// own index, and reads the other's with acquire, so it sees the entries that
|
||||
// came before
|
||||
#pragma once
|
||||
|
||||
#ifdef JSRT_IMPLEMENTATION
|
||||
#include <errno.h>
|
||||
#include <linux/io_uring.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
|
||||
// The SQ: we own the tail, the kernel owns the head. `array` maps each
|
||||
// slot of the ring to an SQE, and we keep it the identity
|
||||
unsigned *sq_head, *sq_tail, *sq_array;
|
||||
unsigned sq_mask, sq_entries;
|
||||
struct io_uring_sqe *sqes;
|
||||
unsigned tail; // our copy of the SQ tail, ahead of it until submit
|
||||
unsigned to_submit; // SQEs written that the kernel did not take yet
|
||||
|
||||
// The CQ: the kernel owns the tail, we own the head
|
||||
unsigned *cq_head, *cq_tail;
|
||||
unsigned cq_mask, cq_entries;
|
||||
struct io_uring_cqe *cqes;
|
||||
} jsrt_uring;
|
||||
|
||||
_Noreturn static void jsrt_uring_fail(const char *what) {
|
||||
fprintf(stderr, "jsrt: %s: %s\n", what, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// The features that io.h depends on, all from Linux 5.5:
|
||||
// SINGLE_MMAP the SQ and the CQ rings are one mapping
|
||||
// NODROP the kernel keeps CQEs that do not fit the CQ, never drops them
|
||||
// SUBMIT_STABLE the kernel copies what an SQE points to (a timespec, a path)
|
||||
// before submit returns, so it needs to stay only until then
|
||||
static void jsrt_uring_init(jsrt_uring *r, unsigned entries) {
|
||||
struct io_uring_params p;
|
||||
memset(&p, 0, sizeof p);
|
||||
|
||||
int fd = (int)syscall(__NR_io_uring_setup, entries, &p);
|
||||
|
||||
if (fd < 0) {
|
||||
jsrt_uring_fail("io_uring_setup");
|
||||
}
|
||||
|
||||
unsigned need = IORING_FEAT_SINGLE_MMAP | IORING_FEAT_NODROP | IORING_FEAT_SUBMIT_STABLE;
|
||||
|
||||
if ((p.features & need) != need) {
|
||||
fputs("jsrt: io_uring: the kernel is older than Linux 5.5\n", stderr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
size_t sq_size = p.sq_off.array + p.sq_entries * sizeof(unsigned);
|
||||
size_t cq_size = p.cq_off.cqes + p.cq_entries * sizeof(struct io_uring_cqe);
|
||||
size_t size = sq_size > cq_size ? sq_size : cq_size;
|
||||
int prot = PROT_READ | PROT_WRITE, flags = MAP_SHARED | MAP_POPULATE;
|
||||
|
||||
char *ring = mmap(NULL, size, prot, flags, fd, IORING_OFF_SQ_RING);
|
||||
void *sqes = mmap(NULL, p.sq_entries * sizeof(struct io_uring_sqe), prot, flags, fd, IORING_OFF_SQES);
|
||||
|
||||
if (ring == MAP_FAILED || sqes == MAP_FAILED) {
|
||||
jsrt_uring_fail("io_uring mmap");
|
||||
}
|
||||
|
||||
*r = (jsrt_uring){
|
||||
.fd = fd,
|
||||
.sq_head = (unsigned *)(ring + p.sq_off.head),
|
||||
.sq_tail = (unsigned *)(ring + p.sq_off.tail),
|
||||
.sq_array = (unsigned *)(ring + p.sq_off.array),
|
||||
.sq_mask = *(unsigned *)(ring + p.sq_off.ring_mask),
|
||||
.sq_entries = p.sq_entries,
|
||||
.sqes = sqes,
|
||||
.tail = *(unsigned *)(ring + p.sq_off.tail),
|
||||
.cq_head = (unsigned *)(ring + p.cq_off.head),
|
||||
.cq_tail = (unsigned *)(ring + p.cq_off.tail),
|
||||
.cq_mask = *(unsigned *)(ring + p.cq_off.ring_mask),
|
||||
.cq_entries = p.cq_entries,
|
||||
.cqes = (struct io_uring_cqe *)(ring + p.cq_off.cqes),
|
||||
};
|
||||
}
|
||||
|
||||
// A new SQE, all zero, or NULL if the SQ is full. The kernel sees it at the
|
||||
// next submit
|
||||
static struct io_uring_sqe *jsrt_uring_sqe(jsrt_uring *r) {
|
||||
unsigned head = __atomic_load_n(r->sq_head, __ATOMIC_ACQUIRE);
|
||||
|
||||
if (r->tail - head == r->sq_entries) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unsigned i = r->tail & r->sq_mask;
|
||||
r->sq_array[i] = i;
|
||||
r->tail++;
|
||||
r->to_submit++;
|
||||
|
||||
memset(&r->sqes[i], 0, sizeof r->sqes[i]);
|
||||
return &r->sqes[i];
|
||||
}
|
||||
|
||||
static bool jsrt_uring_cq_empty(jsrt_uring *r) {
|
||||
return *r->cq_head == __atomic_load_n(r->cq_tail, __ATOMIC_ACQUIRE);
|
||||
}
|
||||
|
||||
// Submits every SQE written, and if `wait`, waits until the CQ has a CQE
|
||||
//
|
||||
// The kernel can take fewer SQEs than we give, for example when one fails
|
||||
// before it starts. That one still gets a CQE, and we give the rest again
|
||||
static void jsrt_uring_submit(jsrt_uring *r, bool wait) {
|
||||
__atomic_store_n(r->sq_tail, r->tail, __ATOMIC_RELEASE);
|
||||
|
||||
for (;;) {
|
||||
bool get = wait && jsrt_uring_cq_empty(r);
|
||||
|
||||
if (!r->to_submit && !get) {
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned flags = get ? IORING_ENTER_GETEVENTS : 0;
|
||||
int n = (int)syscall(__NR_io_uring_enter, r->fd, r->to_submit, get ? 1 : 0, flags, NULL, 0);
|
||||
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
jsrt_uring_fail("io_uring_enter");
|
||||
}
|
||||
|
||||
r->to_submit -= (unsigned)n;
|
||||
}
|
||||
}
|
||||
|
||||
// Takes the next CQE, if there is one
|
||||
static bool jsrt_uring_cqe(jsrt_uring *r, uint64_t *user_data, int32_t *res) {
|
||||
if (jsrt_uring_cq_empty(r)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned head = *r->cq_head;
|
||||
struct io_uring_cqe *cqe = &r->cqes[head & r->cq_mask];
|
||||
*user_data = cqe->user_data;
|
||||
*res = cqe->res;
|
||||
|
||||
__atomic_store_n(r->cq_head, head + 1, __ATOMIC_RELEASE);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // JSRT_IMPLEMENTATION
|
||||
Reference in New Issue
Block a user