From e19fa7a6b14def2126b9df415dd7fa72fd0ecada Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Thu, 29 Oct 2026 02:32:54 -0700 Subject: [PATCH] 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(, "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 --- .../src/backend/executable-features.ts | 2 + .../compiler/src/backend/llvm/lib-shared.ts | 2 + .../src/backend/native-link-info-core.ts | 7 + .../compiler/src/backend/native/contracts.ts | 7 + .../compiler/src/backend/native/executable.ts | 8 + .../compiler/src/backend/runtime-pack-core.ts | 8 + .../src/executable/executable-cache.ts | 5 + packages/compiler/src/index.ts | 2 + packages/compiler/src/ir/builtin-effects.ts | 1 + .../compiler/src/ir/builtin-signatures.ts | 7 + packages/compiler/src/ir/runtime-features.ts | 11 + .../runtime-pack-matrix.mjs | 5 + packages/runtime/src/scr_dyn_array_like.c | 691 ++++++++++++++++++ .../runtime/src/scr_dyn_builtin_prototype.c | 170 +++++ scripts/zero-cost-compare.mjs | 2 + 15 files changed, 928 insertions(+) create mode 100644 packages/runtime/src/scr_dyn_array_like.c create mode 100644 packages/runtime/src/scr_dyn_builtin_prototype.c diff --git a/packages/compiler/src/backend/executable-features.ts b/packages/compiler/src/backend/executable-features.ts index 15cab8778..017255c8f 100644 --- a/packages/compiler/src/backend/executable-features.ts +++ b/packages/compiler/src/backend/executable-features.ts @@ -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). diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 73fd0b799..a8454b2a1 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -1146,6 +1146,8 @@ export const LIB_FN_SYMS: Record = { "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", diff --git a/packages/compiler/src/backend/native-link-info-core.ts b/packages/compiler/src/backend/native-link-info-core.ts index ded050bce..bb6ab9982 100644 --- a/packages/compiler/src/backend/native-link-info-core.ts +++ b/packages/compiler/src/backend/native-link-info-core.ts @@ -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; diff --git a/packages/compiler/src/backend/native/contracts.ts b/packages/compiler/src/backend/native/contracts.ts index 5026fd5f2..9ca6a2df1 100644 --- a/packages/compiler/src/backend/native/contracts.ts +++ b/packages/compiler/src/backend/native/contracts.ts @@ -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 diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index a650955d6..ef29a6378 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -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"), diff --git a/packages/compiler/src/backend/runtime-pack-core.ts b/packages/compiler/src/backend/runtime-pack-core.ts index 2ce4f21fe..29e9ec7e1 100644 --- a/packages/compiler/src/backend/runtime-pack-core.ts +++ b/packages/compiler/src/backend/runtime-pack-core.ts @@ -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, diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index 91fa7d791..589421d27 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -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; diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index b837b74fd..c460e08e9 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -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, diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 60f46e7ee..cd17940a2 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -765,6 +765,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "child.propsDescriptor", "child.propsOnDyn", "dyn.arrayProtoCall", + "dyn.arrayLikeProtoCall", "process.chdir", "os.getPriority", "os.setPriority", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 3287ac54a..4747009aa 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -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). diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 28677f3ec..2b0e05cd2 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -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 = 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, diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs index 87d42ac5f..e4f3f80a7 100644 --- a/packages/runtime-pack-common/runtime-pack-matrix.mjs +++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs @@ -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. diff --git a/packages/runtime/src/scr_dyn_array_like.c b/packages/runtime/src/scr_dyn_array_like.c new file mode 100644 index 000000000..3ac546084 --- /dev/null +++ b/packages/runtime/src/scr_dyn_array_like.c @@ -0,0 +1,691 @@ +/* Array.prototype's generic algorithms over array-like receivers. + * + * `Array.prototype..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 +#include +#include + +/* 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 " 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); +} diff --git a/packages/runtime/src/scr_dyn_builtin_prototype.c b/packages/runtime/src/scr_dyn_builtin_prototype.c new file mode 100644 index 000000000..1bb87746f --- /dev/null +++ b/packages/runtime/src/scr_dyn_builtin_prototype.c @@ -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 +#include + +/* 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); +} diff --git a/scripts/zero-cost-compare.mjs b/scripts/zero-cost-compare.mjs index 40f7287fb..da3e2eae3 100644 --- a/scripts/zero-cost-compare.mjs +++ b/scripts/zero-cost-compare.mjs @@ -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",