Support two gated dynamic-tier runtime units

The checked-dynamic tier needs two more optional units: Array.prototype's
generic algorithms over array-like receivers (scr_dyn_array_like.c) and the
Number/Boolean prototype objects as values (scr_dyn_builtin_prototype.c).
Both are listed as optional runtime sources under a predicate of their own
(all(<feature>, "dynInvoke")), so they link only into a program whose IR
uses the matching libCall, and neither is an always-linked unit. Their
symbol prefixes are in DYNAMIC_TIER_PREFIXES, so a fully typed executable
may not carry them either.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Malte Ubl
2026-10-29 05:38:56 -07:00
co-authored by Claude Opus 5.5
parent 0faade48ae
commit e19fa7a6b1
15 changed files with 928 additions and 0 deletions
@@ -44,6 +44,8 @@ export function executableLinkFeatures(mod: IrModule, largePages = false): Nativ
...(features.utilTiming ? { utilTiming: true } : {}),
...(features.dynBuffer ? { dynBuffer: true } : {}),
...(features.builtinValues ? { builtinValues: true } : {}),
...(features.arrayLikeDispatch ? { arrayLikeDispatch: true } : {}),
...(features.builtinPrototypeValue ? { builtinPrototypeValue: true } : {}),
...(features.computedRequire ? { computedRequire: true } : {}),
// Island code's signal and exit listeners join the events unit's
// registries (scope-B NI-3).
@@ -1146,6 +1146,8 @@ export const LIB_FN_SYMS: Record<string, string> = {
"dyn.defineProperty": "scr_dyn_define_property",
"dyn.getOwnPropertyDescriptor": "scr_dyn_get_own_property_descriptor",
"dyn.arrayProtoCall": "scr_dyn_array_proto_call",
"dyn.arrayLikeProtoCall": "scr_dyn_array_like_call",
"dyn.builtinPrototype": "scr_dyn_builtin_prototype",
"dyn.promiseAll": "scr_dyn_promise_all",
"dyn.promiseAllSettled": "scr_dyn_promise_all_settled",
"dyn.promiseAny": "scr_dyn_promise_any",
@@ -106,6 +106,13 @@ export interface NativeLinkFeatures {
utilPromisify: boolean;
dynBuiltinClass: boolean;
dynBuiltinStream: boolean;
/** An Array.prototype method runs over an array-like receiver:
* scr_dyn_array_like.c. Absent (false) otherwise, so programs that never
* call one over an array-like keep their exact link line. */
arrayLikeDispatch?: boolean;
/** Number.prototype or Boolean.prototype read as a value:
* scr_dyn_builtin_prototype.c. Absent (false) otherwise. */
builtinPrototypeValue?: boolean;
parseArgs: boolean;
stream: boolean;
net: boolean;
@@ -150,6 +150,13 @@ export interface CcOptions {
computedRequire?: boolean;
/** Builtin functions read as values: compiles scr_builtin_values.c. */
builtinValues?: boolean;
/** An Array.prototype method runs over an array-like receiver:
* compiles scr_dyn_array_like.c, which the dyn-invoke unit's array
* dispatch calls for receivers that are not arrays. */
arrayLikeDispatch?: boolean;
/** Number.prototype or Boolean.prototype read as a value in a JavaScript
* source: compiles scr_dyn_builtin_prototype.c. */
builtinPrototypeValue?: boolean;
/** The program constructs EventTarget/Event/CustomEvent values or
* calls events.addAbortListener (the event.* libCalls), or uses the
* native fetch AbortSignal built on the same engine: compiles
@@ -199,6 +199,14 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [
unit((u) => u.opts.dynBuffer === true && u.opts.dynInvoke, "scr_bytes_dyn.c"),
// Builtin functions read as values in JavaScript dependencies.
unit((u) => u.opts.builtinValues === true, "scr_builtin_values.c"),
// Array.prototype's generic algorithms over array-like receivers, and the
// Number/Boolean prototype objects as values: the dynamic tier's gated
// units, whose work is JavaScript-only.
unit((u) => u.opts.arrayLikeDispatch === true && u.opts.dynInvoke, "scr_dyn_array_like.c"),
unit(
(u) => u.opts.builtinPrototypeValue === true && u.opts.dynInvoke,
"scr_dyn_builtin_prototype.c",
),
// createRequire's computed require and require.resolve.
unit((u) => u.opts.computedRequire === true, "scr_require.c"),
unit((u) => u.opts.dc, "scr_dc.c"),
@@ -98,6 +98,12 @@ export interface RuntimeFeatureSet extends NativeLinkFeatures {
utilTiming: boolean;
dynBuffer: boolean;
builtinValues: boolean;
/** Array.prototype's generic algorithms over array-likes
* (scr_dyn_array_like.c). */
arrayLikeDispatch?: boolean;
/** Number.prototype and Boolean.prototype as values
* (scr_dyn_builtin_prototype.c). */
builtinPrototypeValue?: boolean;
computedRequire: boolean;
warningReplace: boolean;
stdioExtended: boolean;
@@ -309,6 +315,8 @@ export function effectiveRuntimeFeatures(
utilTiming: features.utilTiming === true,
dynBuffer: features.dynBuffer === true,
builtinValues: features.builtinValues === true,
arrayLikeDispatch: features.arrayLikeDispatch === true,
builtinPrototypeValue: features.builtinPrototypeValue === true,
computedRequire: features.computedRequire === true,
warningReplace: features.warningReplace === true,
stdioExtended: features.stdioExtended === true,
@@ -101,6 +101,11 @@ export interface EarlyExecutableNativeFeatures {
utilPromisify: boolean;
dynBuiltinClass: boolean;
dynBuiltinStream: boolean;
/** Array.prototype methods over array-like receivers: scr_dyn_array_like.c. */
arrayLikeDispatch?: boolean;
/** Number.prototype and Boolean.prototype as values:
* scr_dyn_builtin_prototype.c. */
builtinPrototypeValue?: boolean;
parseArgs: boolean;
stream: boolean;
net: boolean;
+2
View File
@@ -725,6 +725,8 @@ async function compileExecutableNative(
...(features.utilTiming ? { utilTiming: true } : {}),
...(features.dynBuffer ? { dynBuffer: true } : {}),
...(features.builtinValues ? { builtinValues: true } : {}),
...(features.arrayLikeDispatch ? { arrayLikeDispatch: true } : {}),
...(features.builtinPrototypeValue ? { builtinPrototypeValue: true } : {}),
...(features.computedRequire ? { computedRequire: true } : {}),
events: features.events,
emitter: features.emitter,
@@ -765,6 +765,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
"child.propsDescriptor",
"child.propsOnDyn",
"dyn.arrayProtoCall",
"dyn.arrayLikeProtoCall",
"process.chdir",
"os.getPriority",
"os.setPriority",
@@ -402,6 +402,13 @@ function buildLibFnSignatures() {
"dyn.defineProperty": { argTypes: [DYN, DYN, DYN], result: DYN },
"dyn.getOwnPropertyDescriptor": { argTypes: [DYN, DYN], result: DYN },
"dyn.arrayProtoCall": { argTypes: [DYN, STRING, DYN], result: DYN },
/** The same call through the array-like unit (JavaScript sources):
* Array.prototype's generic algorithms over a receiver that is not a
* native array, and the invoke unit's dispatch for everything else. */
"dyn.arrayLikeProtoCall": { argTypes: [DYN, STRING, DYN], result: DYN },
/** Number.prototype / Boolean.prototype as JavaScript values: the
* prototype object (dyn.builtinPrototype unit). */
"dyn.builtinPrototype": { argTypes: [STRING], result: DYN },
/** Promise.all / Promise.allSettled / Promise.any over a spread
* checked-dynamic array: the values, the settled-record array, or the
* first fulfillment (AggregateError once every entry rejects).
@@ -330,6 +330,8 @@ const READABLE_DYN_ASYNC_PREFIXES: readonly string[] = ["readable.fromGen", "rea
/** Entry points that link the dyn-invoke unit. */
const DYN_INVOKE_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
"dyn.arrayProtoCall",
"dyn.arrayLikeProtoCall",
"dyn.builtinPrototype",
"dyn.arrayPrototype",
"dyn.builtinFunction",
"dyn.builtinMethod",
@@ -963,6 +965,13 @@ const RUNTIME_FEATURE_RULES = {
* half (scr_dyn_builtin_stream.c): native stream state for constructor
* functions, over the stream unit. */
dynBuiltinStream: rule({ fns: ["dyn.builtinStreamClass"] }),
/** Array.prototype's generic algorithms over array-like receivers
* (scr_dyn_array_like.c), which delegates every other name to the
* dyn-invoke unit's array dispatch. */
arrayLikeDispatch: rule({ fns: ["dyn.arrayLikeProtoCall"] }),
/** Number.prototype and Boolean.prototype as values
* (scr_dyn_builtin_prototype.c). */
builtinPrototypeValue: rule({ fns: ["dyn.builtinPrototype"] }),
dynInvoke: rule({
initial: (mod) => mod.workers === true,
fns: [
@@ -1283,6 +1292,8 @@ function noRuntimeFeatures(): RuntimeFeatures {
dynBuiltinClass: false,
dynBuiltinStream: false,
dynInvoke: false,
arrayLikeDispatch: false,
builtinPrototypeValue: false,
dynTier: false,
ecdh: false,
x509: false,
@@ -74,6 +74,11 @@ export const OPTIONAL_RUNTIME_SOURCES = [
// Island libCalls on native values typed by island declarations invoke
// through the checked-dynamic path (scr_node_island_handle.c).
["scr_dyn_invoke.c", any("dynInvoke", "nativeFetch", "nodeIsland")],
// Array.prototype generic algorithms over array-like receivers
// (scr_dyn_array_like.c); it calls the invoke unit's dispatch.
["scr_dyn_array_like.c", all("arrayLikeDispatch", "dynInvoke")],
// Number.prototype and Boolean.prototype read as values in JavaScript.
["scr_dyn_builtin_prototype.c", all("builtinPrototypeValue", "dynInvoke")],
// Buffer.prototype methods called on checked-dynamic values.
["scr_bytes_dyn.c", all("dynBuffer", "dynInvoke")],
// Builtin functions read as values in JavaScript dependencies.
+691
View File
@@ -0,0 +1,691 @@
/* Array.prototype's generic algorithms over array-like receivers.
*
* `Array.prototype.<method>.call(obj, ...)` (and a stored method value
* called on an object) reaches scr_dyn_array_proto_call, which runs
* at/slice/indexOf/lastIndexOf/includes and the higher-order methods over
* any receiver. Every other method answered "not supported yet", so a call
* on an array-like — an ordinary object with a length, or a primitive's
* ToObject wrapper — threw instead of running the algorithm.
*
* This unit runs those methods the way the specification writes them, as
* Get/HasProperty/Set/DeletePropertyOrThrow steps bounded by ToLength, and
* hands every other name back to scr_dyn_array_proto_call so the two units
* keep one implementation each. It links only into programs that use the
* dynamic array-like path (the frontend marks dyn.arrayLikeProtoCall for
* JavaScript sources), so typed programs keep their exact executables and
* scr_dyn_invoke.c is not touched: a program that only uses the older
* borrow methods links exactly what it linked before.
*
* The algorithms only use the checked-dynamic tree's public surface; the
* few helpers they share (an index key, Get/Has/Set at an index, the
* array-like length, a callback gate) are private to this unit. */
#include "scr_runtime.h"
#include "scr_dyn_methods.h"
#include <math.h>
#include <stdio.h>
#include <string.h>
/* One million indexed steps per call: past it the iteration reports
* SC2020 rather than running an unbounded loop over a length property the
* program chose. */
static bool al_iteration(size_t step) {
if (step < 1000000) return true;
static const char msg[] = "array-like iteration over one million entries is not supported yet";
scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
return false;
}
static bool al_is(const ScrStr *method, const char *name) {
size_t len = strlen(name);
return method->len == len && memcmp(method->data, name, len) == 0;
}
/* The canonical array index a property key spells, if it does. */
static bool al_index(const ScrStr *key, size_t *out) {
if (!key->len || (key->len > 1 && key->data[0] == '0')) return false;
size_t n = 0;
for (size_t i = 0; i < key->len; i++) {
unsigned digit = (unsigned)(key->data[i] - '0');
if (digit > 9 || n > (SIZE_MAX - digit) / 10) return false;
n = n * 10 + digit;
}
*out = n;
return true;
}
/* Get(receiver, key) through the receiver's checked-dynamic kind. A
* primitive answers an ordinary property read on its ToObject wrapper,
* which is fresh and unobservable: an absent member is undefined. The
* result is owned (+1). */
static ScrDyn *al_get(ScrDyn *recv, const ScrStr *key) {
if (recv->kind == SCR_DYN_TYPED_REF) {
ScrDyn *snapshot = scr_dyn_typed_ref_materialize(recv);
if (!snapshot) return NULL;
ScrDyn *result = al_get(snapshot, key);
scr_dyn_release(snapshot);
return result;
}
if (recv->kind == SCR_DYN_OBJ) return scr_dyn_obj_read(recv, key->data, key->len);
if (recv->kind == SCR_DYN_PROXY) return scr_dyn_proxy_get(recv, key);
if (recv->kind == SCR_DYN_FUNC) {
ScrDyn *value = scr_dyn_fn_get(recv, key->data, key->len);
return value ? value : scr_dyn_retain(scr_dyn_undefined());
}
if (recv->kind == SCR_DYN_ARR || recv->kind == SCR_DYN_STR || recv->kind == SCR_DYN_BYTES) {
if (key->len == 6 && memcmp(key->data, "length", 6) == 0) {
double length = recv->kind == SCR_DYN_ARR ? (double)recv->v.arr.len
: recv->kind == SCR_DYN_STR ? scr_str_utf16_len(recv->v.str)
: (double)recv->v.bytes->len;
return scr_dyn_new_num(length);
}
size_t index;
if (!al_index(key, &index)) {
if (recv->kind == SCR_DYN_ARR && recv->v.arr.properties)
return scr_dyn_arr_named_get(recv, key);
return scr_dyn_retain(scr_dyn_undefined());
}
if (recv->kind == SCR_DYN_ARR && index >= recv->v.arr.len && recv->v.arr.properties)
return scr_dyn_arr_named_get(recv, key);
if (recv->kind == SCR_DYN_ARR) return scr_dyn_arr_at(recv, (double)index);
if (recv->kind == SCR_DYN_BYTES) {
return index < recv->v.bytes->len
? scr_dyn_new_num(scr_bytes_get(recv->v.bytes, (double)index))
: scr_dyn_retain(scr_dyn_undefined());
}
if ((double)index >= scr_str_utf16_len(recv->v.str))
return scr_dyn_retain(scr_dyn_undefined());
ScrStr *unit = scr_str_slice(recv->v.str, (double)index, (double)index + 1);
ScrDyn *value = scr_dyn_new_str(unit);
scr_str_release(unit);
return value;
}
return scr_dyn_retain(scr_dyn_undefined());
}
/* HasProperty(receiver, key). */
static bool al_has(ScrDyn *recv, const ScrStr *key) {
if (recv->kind == SCR_DYN_STR || recv->kind == SCR_DYN_BYTES) {
size_t index;
return al_index(key, &index) &&
(recv->kind == SCR_DYN_STR
? (double)index < scr_str_utf16_len(recv->v.str)
: index < recv->v.bytes->len);
}
if (recv->kind == SCR_DYN_FUNC) {
ScrDyn *value = scr_dyn_fn_get(recv, key->data, key->len);
bool present = value != NULL;
scr_dyn_release(value);
return present;
}
return scr_dyn_has_key(recv, key);
}
static ScrStr *al_key(size_t index) {
char text[32];
int n = snprintf(text, sizeof text, "%zu", index);
return scr_str_new(text, (size_t)n);
}
/* ToLength(Get(receiver, "length")). */
static bool al_length(ScrDyn *recv, size_t *out) {
ScrStr *key = scr_str_new("length", 6);
ScrDyn *value = al_get(recv, key);
scr_str_release(key);
if (!value) return false;
double number;
bool ok = scr_dyn_number_coerce_js(value, &number);
scr_dyn_release(value);
if (!ok) return false;
if (isnan(number) || number <= 0) { *out = 0; return true; }
number = fmin(trunc(number), 9007199254740991.0);
if (number > (double)SIZE_MAX) {
static const char msg[] = "array-like length exceeds the native index range";
scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
return false;
}
*out = (size_t)number;
return true;
}
/* ToIntegerOrInfinity of an argument, truncated. */
static bool al_number(const ScrDyn *value, double *out) {
if (!scr_dyn_number_coerce_js(value, out)) return false;
if (isnan(*out)) *out = 0;
else if (isfinite(*out)) *out = trunc(*out);
return true;
}
static bool al_present(ScrDyn *recv, size_t index) {
ScrStr *key = al_key(index);
bool found = al_has(recv, key);
scr_str_release(key);
return found;
}
static ScrDyn *al_item(ScrDyn *recv, size_t index) {
ScrStr *key = al_key(index);
ScrDyn *value = al_get(recv, key);
scr_str_release(key);
return value;
}
/* A primitive receiver's ToObject wrapper is fresh and unobservable: the
* generic array algorithms read through it and discard their writes, which
* is exactly what a wrapper nobody holds observes. */
static bool al_primitive(const ScrDyn *recv) {
switch (recv->kind) {
case SCR_DYN_BOOL:
case SCR_DYN_NUM:
case SCR_DYN_STR:
case SCR_DYN_BIGINT:
case SCR_DYN_SYMBOL:
return true;
default:
return false;
}
}
/* Set(O, index, value, true): a failing write throws from scr_dyn_key_set
* and the caller checks scr_exc_pending(). */
static void al_set(ScrDyn *recv, size_t index, ScrDyn *value) {
if (al_primitive(recv)) return;
ScrStr *key = al_key(index);
scr_dyn_key_set(recv, key, value);
scr_str_release(key);
}
/* DeletePropertyOrThrow(O, index). */
static void al_delete(ScrDyn *recv, size_t index) {
if (al_primitive(recv)) return;
ScrStr *key = al_key(index);
scr_dyn_key_delete(recv, key, true);
scr_str_release(key);
}
/* Set(O, "length", length, true). */
static void al_set_length(ScrDyn *recv, size_t length) {
if (al_primitive(recv)) return;
ScrStr *key = scr_str_new("length", 6);
ScrDyn *value = scr_dyn_new_num((double)length);
scr_dyn_key_set(recv, key, value);
scr_dyn_release(value);
scr_str_release(key);
}
/* The receiver's elements in index order as a fresh dyn array. Every index
* the receiver does not carry becomes undefined, as toReversed, toSorted
* and with create their result with CreateDataProperty for every index.
* Owned (+1), NULL with the exception pending. */
static ScrDyn *al_snapshot(ScrDyn *recv, size_t len) {
ScrDyn *out = scr_dyn_new_arr();
for (size_t i = 0; i < len; i++) {
if (len > 1000000 && !al_iteration(i)) { scr_dyn_release(out); return NULL; }
if (!al_present(recv, i)) {
if (scr_exc_pending()) { scr_dyn_release(out); return NULL; }
scr_dyn_arr_push(out, scr_dyn_retain(scr_dyn_undefined()));
continue;
}
ScrDyn *item = al_item(recv, i);
if (!item) { scr_dyn_release(out); return NULL; }
scr_dyn_arr_push(out, item);
}
return out;
}
/* CreateDataProperty(out, index, Get(receiver, index)) — an ordinary
* property read, with an absent index kept as a hole. */
static bool al_copy_item(ScrDyn *out, ScrDyn *recv, size_t index) {
if (!al_present(recv, index)) {
if (scr_exc_pending()) return false;
scr_dyn_arr_push_hole(out);
return true;
}
ScrDyn *item = al_item(recv, index);
if (!item) return false;
scr_dyn_arr_push(out, item);
return true;
}
/* JS relative-index normalization (slice's rule). */
static size_t al_rel_index(double rel, size_t len) {
if (rel < 0) {
double r = (double)len + rel;
return r < 0 ? 0 : (size_t)r;
}
return rel > (double)len ? len : (size_t)rel;
}
/* The callable-callback gate: JS's "<String(cb)> is not a function". */
static bool al_cb_check(ScrDyn *const *args, size_t argc) {
ScrDyn *cb = argc > 0 ? args[0] : scr_dyn_undefined();
if (scr_dyn_is_callable(cb)) return true;
ScrJsonBuf b;
scr_jb_init(&b);
ScrStr *text = scr_dyn_string_coerce_js(cb);
if (text) {
scr_jb_write(&b, text->data, text->len);
scr_str_release(text);
}
scr_jb_puts(&b, " is not a function");
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
return false;
}
/* One packed argument list, borrowed: the array dispatch hands the
* algorithm its arguments as a dyn array. */
ScrDyn *scr_dyn_array_like_call(ScrDyn *recv, ScrStr *method, ScrDyn *args) {
if (recv->kind == SCR_DYN_ARR ||
(recv->kind == SCR_DYN_TYPED_REF && scr_dyn_typed_ref_array_ops(recv)))
return scr_dyn_array_proto_call(recv, method, args);
/* Step 1 of each of these algorithms: `? ToObject(this value)`. The
* array dispatch answers it for its own receivers; a nullish one never
* reaches a length read. */
if (recv->kind == SCR_DYN_NULL || recv->kind == SCR_DYN_UNDEF) {
static const char msg[] = "Cannot convert undefined or null to object";
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
return NULL;
}
if (!args || args->kind != SCR_DYN_ARR) scr_trap("scriptc: invalid Array.prototype arguments\n");
ScrDyn *const *argv = args->v.arr.items;
size_t argc = args->v.arr.len;
size_t len;
if (!al_length(recv, &len)) return NULL;
if (al_is(method, "pop")) {
if (len == 0) {
al_set_length(recv, 0);
return scr_exc_pending() ? NULL : scr_dyn_retain(scr_dyn_undefined());
}
ScrDyn *item = al_item(recv, len - 1);
if (!item) return NULL;
al_delete(recv, len - 1);
if (!scr_exc_pending()) al_set_length(recv, len - 1);
if (scr_exc_pending()) { scr_dyn_release(item); return NULL; }
return item;
}
if (al_is(method, "push") || al_is(method, "unshift")) {
if (al_is(method, "push")) {
for (size_t i = 0; i < argc; i++) {
if (len > 1000000 && !al_iteration(i)) return NULL;
al_set(recv, len + i, argv[i]);
if (scr_exc_pending()) return NULL;
}
al_set_length(recv, len + argc);
if (scr_exc_pending()) return NULL;
return scr_dyn_new_num((double)(len + argc));
}
if (argc > 0) {
/* Right to left, so each element lands one `argc` slot further. */
for (size_t k = len; k > 0; k--) {
size_t from = k - 1;
size_t to = from + argc;
if (len > 1000000 && !al_iteration(len - k)) return NULL;
if (scr_exc_pending()) return NULL;
if (!al_present(recv, from)) { al_delete(recv, to); }
else {
ScrDyn *item = al_item(recv, from);
if (!item) return NULL;
al_set(recv, to, item);
scr_dyn_release(item);
}
if (scr_exc_pending()) return NULL;
}
for (size_t j = 0; j < argc; j++) {
al_set(recv, j, argv[j]);
if (scr_exc_pending()) return NULL;
}
}
al_set_length(recv, len + argc);
if (scr_exc_pending()) return NULL;
return scr_dyn_new_num((double)(len + argc));
}
if (al_is(method, "shift")) {
if (len == 0) {
al_set_length(recv, 0);
return scr_exc_pending() ? NULL : scr_dyn_retain(scr_dyn_undefined());
}
ScrDyn *first = al_item(recv, 0);
if (!first) return NULL;
for (size_t k = 1; k < len; k++) {
if (len > 1000000 && !al_iteration(k - 1)) { scr_dyn_release(first); return NULL; }
size_t to = k - 1;
if (scr_exc_pending()) { scr_dyn_release(first); return NULL; }
if (!al_present(recv, k)) { al_delete(recv, to); }
else {
ScrDyn *item = al_item(recv, k);
if (!item) { scr_dyn_release(first); return NULL; }
al_set(recv, to, item);
scr_dyn_release(item);
}
if (scr_exc_pending()) { scr_dyn_release(first); return NULL; }
}
al_delete(recv, len - 1);
if (!scr_exc_pending()) al_set_length(recv, len - 1);
if (scr_exc_pending()) { scr_dyn_release(first); return NULL; }
return first;
}
if (al_is(method, "reverse")) {
size_t middle = len / 2;
for (size_t lower = 0; lower < middle; lower++) {
if (len > 1000000 && !al_iteration(lower)) return NULL;
size_t upper = len - lower - 1;
bool has_lower = al_present(recv, lower);
if (scr_exc_pending()) return NULL;
ScrDyn *lower_value = has_lower ? al_item(recv, lower) : NULL;
if (has_lower && !lower_value) return NULL;
bool has_upper = al_present(recv, upper);
ScrDyn *upper_value = has_upper && !scr_exc_pending() ? al_item(recv, upper) : NULL;
if (scr_exc_pending() || (has_upper && !upper_value)) {
scr_dyn_release(lower_value);
return NULL;
}
if (has_lower && has_upper) {
al_set(recv, lower, upper_value);
if (!scr_exc_pending()) al_set(recv, upper, lower_value);
} else if (has_upper) {
al_set(recv, lower, upper_value);
if (!scr_exc_pending()) al_delete(recv, upper);
} else if (has_lower) {
al_delete(recv, lower);
if (!scr_exc_pending()) al_set(recv, upper, lower_value);
}
scr_dyn_release(lower_value);
scr_dyn_release(upper_value);
if (scr_exc_pending()) return NULL;
}
return scr_dyn_retain(recv);
}
if (al_is(method, "join")) {
ScrStr *sep = argc > 0 && argv[0]->kind != SCR_DYN_UNDEF
? scr_dyn_string_coerce_js(argv[0])
: scr_str_new(",", 1);
if (!sep) return NULL;
ScrJsonBuf buffer;
scr_jb_init(&buffer);
for (size_t i = 0; i < len; i++) {
if (i) scr_jb_write(&buffer, sep->data, sep->len);
if (len > 1000000 && !al_iteration(i)) { scr_str_release(sep); return NULL; }
if (!al_present(recv, i)) {
if (scr_exc_pending()) { scr_str_release(sep); return NULL; }
continue;
}
ScrDyn *item = al_item(recv, i);
if (!item) { scr_str_release(sep); return NULL; }
if (item->kind != SCR_DYN_UNDEF && item->kind != SCR_DYN_NULL) {
ScrStr *text = scr_dyn_string_coerce_js(item);
if (text) {
scr_jb_write(&buffer, text->data, text->len);
scr_str_release(text);
}
}
scr_dyn_release(item);
if (scr_exc_pending()) { scr_str_release(sep); return NULL; }
}
scr_str_release(sep);
ScrStr *text = scr_jb_finish(&buffer);
ScrDyn *answer = scr_dyn_new_str(text);
scr_str_release(text);
return answer;
}
if (al_is(method, "fill")) {
ScrDyn *value = argc > 0 ? argv[0] : scr_dyn_undefined();
double start_d = 0;
double end_d = (double)len;
if (argc > 1 && !al_number(argv[1], &start_d)) return NULL;
if (argc > 2 && argv[2]->kind != SCR_DYN_UNDEF && !al_number(argv[2], &end_d)) return NULL;
size_t start = al_rel_index(trunc(start_d), len);
size_t end = al_rel_index(trunc(end_d), len);
for (size_t i = start; i < end; i++) {
if (len > 1000000 && !al_iteration(i - start)) return NULL;
al_set(recv, i, value);
if (scr_exc_pending()) return NULL;
}
return scr_dyn_retain(recv);
}
if (al_is(method, "copyWithin")) {
double target_d = 0;
double start_d = 0;
double end_d = (double)len;
if (argc > 0 && !al_number(argv[0], &target_d)) return NULL;
if (argc > 1 && !al_number(argv[1], &start_d)) return NULL;
if (argc > 2 && argv[2]->kind != SCR_DYN_UNDEF && !al_number(argv[2], &end_d)) return NULL;
size_t to = al_rel_index(trunc(target_d), len);
size_t from = al_rel_index(trunc(start_d), len);
size_t end = al_rel_index(trunc(end_d), len);
if (from >= end || to >= len) return scr_dyn_retain(recv);
size_t count = end - from < len - to ? end - from : len - to;
bool backwards = from < to && to < from + count;
for (size_t step = 0; step < count; step++) {
if (len > 1000000 && !al_iteration(step)) return NULL;
size_t i = backwards ? count - 1 - step : step;
size_t source = from + i;
size_t target = to + i;
if (!al_present(recv, source)) { al_delete(recv, target); }
else {
ScrDyn *item = al_item(recv, source);
if (!item) return NULL;
al_set(recv, target, item);
scr_dyn_release(item);
}
if (scr_exc_pending()) return NULL;
}
return scr_dyn_retain(recv);
}
if (al_is(method, "concat")) {
/* The receiver's own elements spread one level when it is a native
* array; an array-like receiver is appended as a single value, as
* Array.prototype.concat treats a non-array `this`. */
ScrDyn *out = scr_dyn_new_arr();
if (recv->kind == SCR_DYN_ARR) {
for (size_t i = 0; i < len; i++)
if (!al_copy_item(out, recv, i)) { scr_dyn_release(out); return NULL; }
} else {
scr_dyn_arr_push(out, scr_dyn_retain(recv));
}
/* An array argument spreads one level (holes included); every other
* value is appended as itself. */
for (size_t a = 0; a < argc; a++) {
ScrDyn *arg = argv[a];
if (arg->kind != SCR_DYN_ARR) {
scr_dyn_arr_push(out, scr_dyn_retain(arg));
continue;
}
for (size_t i = 0; i < arg->v.arr.len; i++)
if (!al_copy_item(out, arg, i)) { scr_dyn_release(out); return NULL; }
}
return out;
}
if (al_is(method, "splice")) {
/* No arguments is the one form whose actualStart is len, not 0: it
* deletes nothing and only rewrites the same length. Starting at 0
* would delete — and copy out — the whole receiver. */
size_t start = len;
size_t removed = 0;
if (argc > 0) {
double start_d = 0;
if (!al_number(argv[0], &start_d)) return NULL;
start = al_rel_index(trunc(start_d), len);
removed = len - start;
if (argc > 1) {
double count_d = 0;
if (!al_number(argv[1], &count_d)) return NULL;
double clamped = count_d < 0 ? 0 : count_d;
removed = clamped > (double)removed ? removed : (size_t)clamped;
}
}
size_t items = argc > 2 ? argc - 2 : 0;
/* ArraySpeciesCreate(O, actualDeleteCount) runs before any index is
* read or written, and ArrayCreate refuses a length past 2^32-1. */
if (removed > 4294967295u) {
static const char msg[] = "Invalid array length";
scr_throw_error_msg(SCR_ERR_RANGE, msg, sizeof msg - 1);
return NULL;
}
ScrDyn *out = scr_dyn_new_arr();
for (size_t i = 0; i < removed; i++) {
if (len > 1000000 && !al_iteration(i)) { scr_dyn_release(out); return NULL; }
if (!al_copy_item(out, recv, start + i)) { scr_dyn_release(out); return NULL; }
}
if (items < removed) {
for (size_t k = start; k < len - removed; k++) {
if (len > 1000000 && !al_iteration(k - start)) { scr_dyn_release(out); return NULL; }
size_t target = k + items;
if (!al_present(recv, k + removed)) { al_delete(recv, target); }
else {
ScrDyn *item = al_item(recv, k + removed);
if (!item) { scr_dyn_release(out); return NULL; }
al_set(recv, target, item);
scr_dyn_release(item);
}
if (scr_exc_pending()) { scr_dyn_release(out); return NULL; }
}
for (size_t k = len; k > len - removed + items; k--) {
al_delete(recv, k - 1);
if (scr_exc_pending()) { scr_dyn_release(out); return NULL; }
}
} else if (items > removed) {
for (size_t k = len - removed; k > start; k--) {
if (len > 1000000 && !al_iteration(len - k)) { scr_dyn_release(out); return NULL; }
size_t source = k + removed - 1;
size_t target = k + items - 1;
if (!al_present(recv, source)) { al_delete(recv, target); }
else {
ScrDyn *item = al_item(recv, source);
if (!item) { scr_dyn_release(out); return NULL; }
al_set(recv, target, item);
scr_dyn_release(item);
}
if (scr_exc_pending()) { scr_dyn_release(out); return NULL; }
}
}
for (size_t j = 0; j < items; j++) {
al_set(recv, start + j, argv[2 + j]);
if (scr_exc_pending()) { scr_dyn_release(out); return NULL; }
}
al_set_length(recv, len - removed + items);
if (scr_exc_pending()) { scr_dyn_release(out); return NULL; }
return out;
}
if (al_is(method, "with")) {
double index_d = 0;
if (!al_number(argc > 0 ? argv[0] : scr_dyn_undefined(), &index_d)) return NULL;
double index = index_d < 0 ? (double)len + index_d : index_d;
if (index < 0 || index >= (double)len) {
static const char msg[] = "Invalid index";
scr_throw_error_msg(SCR_ERR_RANGE, msg, sizeof msg - 1);
return NULL;
}
ScrDyn *out = al_snapshot(recv, len);
if (!out) return NULL;
al_set(out, (size_t)index, argc > 1 ? argv[1] : scr_dyn_undefined());
if (scr_exc_pending()) { scr_dyn_release(out); return NULL; }
return out;
}
if (al_is(method, "toReversed")) {
ScrDyn *out = al_snapshot(recv, len);
if (!out) return NULL;
ScrDyn *reversed = scr_dyn_invoke(out, "reverse", NULL, 0, "toReversed");
scr_dyn_release(out);
return reversed;
}
if (al_is(method, "toSorted")) {
ScrDyn *cmp = argc > 0 ? argv[0] : scr_dyn_undefined();
if (cmp->kind != SCR_DYN_UNDEF && !scr_dyn_is_callable(cmp)) {
static const char msg[] = "The comparison function must be either a function or undefined";
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
return NULL;
}
ScrDyn *out = al_snapshot(recv, len);
if (!out) return NULL;
if (out->v.arr.len < 2) return out;
ScrDyn *sorted = scr_dyn_invoke(out, "sort", argv, argc, "toSorted");
scr_dyn_release(out);
return sorted;
}
if (al_is(method, "toSpliced")) {
double start_d = 0;
if (argc > 0 && !al_number(argv[0], &start_d)) return NULL;
size_t start = al_rel_index(trunc(start_d), len);
size_t removed = len - start;
if (argc > 1) {
double count_d = 0;
if (!al_number(argv[1], &count_d)) return NULL;
double clamped = count_d < 0 ? 0 : count_d;
removed = clamped > (double)removed ? removed : (size_t)clamped;
}
size_t items = argc > 2 ? argc - 2 : 0;
/* The result array is created up front: a length past 2^53-1 is a
* TypeError of its own, and ArrayCreate refuses anything past
* 2^32-1 with a RangeError — both before any index is read. */
double new_len = (double)len - (double)removed + (double)items;
if (new_len > 9007199254740991.0) {
static const char msg[] = "Invalid array length";
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
return NULL;
}
if (new_len > 4294967295.0) {
static const char msg[] = "Invalid array length";
scr_throw_error_msg(SCR_ERR_RANGE, msg, sizeof msg - 1);
return NULL;
}
ScrDyn *out = scr_dyn_new_arr();
for (size_t i = 0; i < start; i++) {
if (len > 1000000 && !al_iteration(i)) { scr_dyn_release(out); return NULL; }
if (!al_copy_item(out, recv, i)) { scr_dyn_release(out); return NULL; }
}
for (size_t j = 2; j < argc; j++) scr_dyn_arr_push(out, scr_dyn_retain(argv[j]));
for (size_t i = start + removed; i < len; i++) {
if (len > 1000000 && !al_iteration(i)) { scr_dyn_release(out); return NULL; }
if (!al_copy_item(out, recv, i)) { scr_dyn_release(out); return NULL; }
}
return out;
}
if (al_is(method, "flat") || al_is(method, "flatMap")) {
/* Snapshot the receiver into a native array the array dispatch
* flattens: the flattening rules (depth counting, nested arrays and
* wrapped array views) live in one implementation. flatMap maps first,
* then flattens the mapped array at depth 1. */
bool mapping = al_is(method, "flatMap");
if (mapping && !al_cb_check(argv, argc)) return NULL;
ScrDyn *mapped = scr_dyn_new_arr();
for (size_t i = 0; i < len; i++) {
if (len > 1000000 && !al_iteration(i)) { scr_dyn_release(mapped); return NULL; }
if (!al_present(recv, i)) {
if (scr_exc_pending()) { scr_dyn_release(mapped); return NULL; }
scr_dyn_arr_push_hole(mapped);
continue;
}
ScrDyn *item = al_item(recv, i);
if (!item) { scr_dyn_release(mapped); return NULL; }
if (mapping) {
ScrDyn *index = scr_dyn_new_num((double)i);
ScrDyn *cbargs[3] = { item, index, recv };
ScrDyn *result = scr_dyn_call(argv[0], cbargs, 3, "callback");
scr_dyn_release(index);
scr_dyn_release(item);
if (!result) { scr_dyn_release(mapped); return NULL; }
item = result;
}
scr_dyn_arr_push(mapped, item);
}
ScrDyn *one = scr_dyn_new_num(1);
ScrDyn *flatargs[1] = { one };
ScrDyn *out;
if (mapping) {
out = scr_dyn_invoke(mapped, "flat", flatargs, 1, "flatMap");
} else {
/* flat's own depth argument: absent, undefined or a number. */
ScrDyn *given[1] = { argc > 0 ? argv[0] : scr_dyn_undefined() };
size_t count = argc > 0 ? 1 : 0;
out = scr_dyn_invoke(mapped, "flat", given, count, "flat");
}
scr_dyn_release(one);
scr_dyn_release(mapped);
return out;
}
/* Every other name keeps the one implementation in the invoke unit
* (at, slice, indexOf, the higher-order methods, and the "not supported
* yet" answers). */
return scr_dyn_array_proto_call(recv, method, args);
}
@@ -0,0 +1,170 @@
/* Number.prototype and Boolean.prototype as values.
*
* A JavaScript source reading `Number.prototype` or `Boolean.prototype`
* used to fall through to an ordinary member read of the constructor
* value, which answered undefined: there was no prototype object to read.
* This unit builds the two prototype objects the way the language defines
* them — an ordinary object whose prototype is Object.prototype, carrying
* the kind's methods as non-enumerable, writable, configurable data
* properties — and answers them from one box per thread, so every read is
* the same object (`Number.prototype === Number.prototype`).
*
* A method called on the prototype object itself runs on the kind's empty
* primitive (`Number.prototype.toString(2)` is "0", `Boolean.prototype
* .valueOf()` is false), which is what Node's Number and Boolean prototype
* objects are: wrappers around +0 and false. The methods are not generic,
* so any other receiver — a plain object, null, a primitive of the other
* kind — is the TypeError Node answers.
*
* The unit links only into programs that read those values in a JavaScript
* source (the frontend marks dyn.builtinPrototype there), so typed programs
* keep their exact executables and no always-linked unit changes.
*
* Wrapper objects (`new Number(1)`, the primitiveWrapper unit) build a
* prototype object of their own for their instances' chain. A program that
* does both should see ONE object: when that unit lands, this builder
* should answer its prototype for a program that links it rather than
* building a second one. */
#include "scr_runtime.h"
#include "scr_dyn_methods.h"
#include <stdio.h>
#include <string.h>
/* The methods each prototype declares, with Node's `length` values. */
static const struct { const char *name; uint32_t length; } bp_number_methods[] = {
{"toString", 1},
{"toFixed", 1},
{"toExponential", 1},
{"toPrecision", 1},
{"valueOf", 0},
};
static const struct { const char *name; uint32_t length; } bp_boolean_methods[] = {
{"toString", 0},
{"valueOf", 0},
};
static SCR_TL ScrDyn *bp_number_prototype;
static SCR_TL ScrDyn *bp_boolean_prototype;
static void bp_prototypes_cleanup(void) {
scr_dyn_release(bp_number_prototype);
scr_dyn_release(bp_boolean_prototype);
bp_number_prototype = NULL;
bp_boolean_prototype = NULL;
}
/** A prototype method of the kind: caps[0] is the kind's name ("Number" or
* "Boolean") and caps[1] the method name. The method runs on the receiver's
* primitive value as `this`: the receiver itself when the call passed a
* primitive of the kind (`Number.prototype.toString.call(255)`), else the
* empty primitive (+0 / false) the prototype object wraps, which is its
* [[PrimitiveValue]] in Node too. Boolean's two methods are answered here,
* since the checked-dynamic dispatch keeps no Boolean prototype table. */
/** The prototype methods are not generic: each one reads the receiver's
* [[NumberData]] / [[BooleanData]], so only a primitive of the kind or its
* prototype object — the wrapper around +0 / false — is a valid `this`.
* Anything else, another primitive included, is a TypeError in Node. */
static void bp_bad_this(const ScrStr *kind, const ScrStr *method) {
char message[128];
snprintf(message, sizeof message, "%s.prototype.%s requires that 'this' be a %s",
kind->data, method->data, kind->data);
scr_throw_error_msg(SCR_ERR_TYPE, message, strlen(message));
}
static ScrDyn *bp_method_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
/* Both reads are +1, so the two strings are released below. */
ScrStr *kind = scr_box_get_ref(closure->caps[0]);
ScrStr *method = scr_box_get_ref(closure->caps[1]);
ScrDyn *receiver = scr_dyn_this_get();
ScrDyn *result = NULL;
if (!strcmp(kind->data, "Boolean")) {
if (receiver->kind != SCR_DYN_BOOL && receiver != bp_boolean_prototype) {
bp_bad_this(kind, method);
scr_dyn_release(receiver);
scr_str_release(kind);
scr_str_release(method);
return NULL;
}
bool value = receiver->kind == SCR_DYN_BOOL ? receiver->v.b : false;
if (!strcmp(method->data, "valueOf")) {
result = scr_dyn_new_bool(value);
} else {
const char *text = value ? "true" : "false";
ScrStr *string = scr_str_new(text, strlen(text));
result = scr_dyn_new_str(string);
scr_str_release(string);
}
} else if (receiver->kind != SCR_DYN_NUM && receiver != bp_number_prototype) {
bp_bad_this(kind, method);
scr_dyn_release(receiver);
scr_str_release(kind);
scr_str_release(method);
return NULL;
} else {
ScrDyn *self = receiver->kind == SCR_DYN_NUM ? scr_dyn_retain(receiver) : scr_dyn_new_num(0);
if (!strcmp(method->data, "toString")) {
/* Number.prototype.toString is the radix form, which the number
* dispatch answers through its own helper rather than as a method. */
ScrStr *value = scr_dyn_to_string_argument(self, argc ? args[0] : scr_dyn_undefined(), method);
if (value) {
result = scr_dyn_new_str(value);
scr_str_release(value);
}
} else {
result = scr_dyn_invoke(self, method->data, args, argc, method->data);
}
scr_dyn_release(self);
}
scr_dyn_release(receiver);
scr_str_release(kind);
scr_str_release(method);
return result;
}
/** Defines one non-enumerable data property on `object`: the attributes
* every built-in method has ({ writable, configurable }, not enumerable). */
static void bp_define(ScrDyn *object, const char *name, size_t name_len, ScrDyn *value) {
ScrDyn *descriptor = scr_dyn_new_obj();
scr_dyn_obj_set(descriptor, "value", 5, value);
scr_dyn_obj_set(descriptor, "writable", 8, scr_dyn_new_bool(true));
scr_dyn_obj_set(descriptor, "configurable", 12, scr_dyn_new_bool(true));
ScrStr *key = scr_str_new(name, name_len);
ScrDyn *key_value = scr_dyn_new_str(key);
ScrDyn *defined = scr_dyn_define_property(object, key_value, descriptor);
scr_dyn_release(defined);
scr_dyn_release(key_value);
scr_dyn_release(descriptor);
scr_str_release(key);
}
/** The kind's prototype object, built on first use; +1, one per thread (the
* slot keeps its own reference, as `scr_dyn_array_prototype_base` does).
* `kind` is "Number" or "Boolean" (the only two the frontend marks). */
ScrDyn *scr_dyn_builtin_prototype(const ScrStr *kind) {
bool number = !strcmp(kind->data, "Number");
ScrDyn **slot = number ? &bp_number_prototype : &bp_boolean_prototype;
if (*slot) return scr_dyn_retain(*slot);
ScrDyn *prototype = scr_dyn_new_obj();
*slot = prototype;
scr_atexit(bp_prototypes_cleanup);
ScrStr *kind_str = scr_str_new(kind->data, strlen(kind->data));
const struct { const char *name; uint32_t length; } *methods =
number ? bp_number_methods : bp_boolean_methods;
size_t count = number ? sizeof bp_number_methods / sizeof bp_number_methods[0]
: sizeof bp_boolean_methods / sizeof bp_boolean_methods[0];
for (size_t i = 0; i < count; i++) {
ScrStr *name = scr_str_new(methods[i].name, strlen(methods[i].name));
ScrClosure *closure = scr_closure_new(NULL, 2);
closure->caps[0] = scr_box_new(SCR_BOX_STR);
closure->caps[1] = scr_box_new(SCR_BOX_STR);
scr_box_set_ref(closure->caps[0], scr_str_retain(kind_str));
scr_box_set_ref(closure->caps[1], scr_str_retain(name));
ScrDyn *method = scr_dyn_new_func(closure, bp_method_call, methods[i].length,
number ? "native:Number.prototype" : "native:Boolean.prototype", methods[i].name);
bp_define(prototype, methods[i].name, strlen(methods[i].name), method);
scr_str_release(name);
}
scr_str_release(kind_str);
return scr_dyn_retain(prototype);
}
+2
View File
@@ -357,6 +357,8 @@ export const DYNAMIC_TIER_PREFIXES = [
"scr_dyn_call",
"scr_dyn_apply",
"scr_dyn_invoke",
"scr_dyn_array_like",
"scr_dyn_builtin_prototype",
"scr_dyn_construct",
"scr_dyn_function_apply",
"scr_dyn_fn_get",