mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Merge remote-tracking branch 'origin/ni/i4-deferred' into HEAD
# Conflicts: # packages/compiler/src/backend/llvm/lib-shared.ts # packages/compiler/src/ir/builtin-effects.ts # packages/compiler/src/ir/builtin-signatures.ts # packages/libnode-common/bridge/scr_node_bridge.cc
This commit is contained in:
@@ -969,6 +969,8 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"nodeIsland.share": "scr_node_island_share",
|
||||
"nodeIsland.sharedTdz": "scr_node_island_shared_tdz",
|
||||
"nodeIsland.classMethodsOf": "scr_node_island_class_methods_of",
|
||||
"nodeIsland.copyArrays": "scr_node_island_copy_arrays",
|
||||
"nodeIsland.awaitCopy": "scr_node_island_await_copy",
|
||||
"dyn.keySet": "scr_dyn_key_set",
|
||||
"dyn.keySetComputed": "scr_dyn_key_set_computed",
|
||||
"dyn.keyDelete": "scr_dyn_key_delete",
|
||||
@@ -1824,6 +1826,7 @@ export const USES_TIMERS_LIB_FNS = new Set<string>([
|
||||
"nodeIsland.require",
|
||||
"nodeIsland.defer",
|
||||
"nodeIsland.import",
|
||||
"nodeIsland.awaitCopy",
|
||||
"nodeIsland.get",
|
||||
"nodeIsland.getKey",
|
||||
"nodeIsland.set",
|
||||
|
||||
@@ -492,6 +492,30 @@ function fromIsland(lowerer: Lowerer, raw: IrExpr, node: ts.Expression): IrExpr
|
||||
};
|
||||
return mapped.inner.kind === "dyn" ? promise : lowerer.coerceToExpected(promise, mapped);
|
||||
}
|
||||
// Arrays of copied values (or arrays of those) cross as copies of the
|
||||
// island array, directly or as an awaited promise's value; the checked
|
||||
// cast then meets the copy. A native array that crossed in comes back as
|
||||
// itself.
|
||||
if (mapped.kind === "promise" && copiedArrayType(lowerer, mapped.inner)) {
|
||||
const promise: IrExpr = {
|
||||
kind: "libCall",
|
||||
fn: "nodeIsland.awaitCopy",
|
||||
args: [raw],
|
||||
type: { kind: "promise", inner: DYN },
|
||||
loc: raw.loc,
|
||||
};
|
||||
return lowerer.coerceToExpected(promise, mapped);
|
||||
}
|
||||
if (copiedArrayType(lowerer, mapped)) {
|
||||
const copy: IrExpr = {
|
||||
kind: "libCall",
|
||||
fn: "nodeIsland.copyArrays",
|
||||
args: [raw],
|
||||
type: DYN,
|
||||
loc: raw.loc,
|
||||
};
|
||||
return lowerer.coerceToExpected(copy, mapped);
|
||||
}
|
||||
// NI-5: a native value the program passed in comes back as itself (an
|
||||
// export); the checked cast unboxes it, and anything else fails the cast
|
||||
// (types don't lie).
|
||||
@@ -506,6 +530,12 @@ function fromIsland(lowerer: Lowerer, raw: IrExpr, node: ts.Expression): IrExpr
|
||||
return lowerer.coerceToExpected(raw, mapped);
|
||||
}
|
||||
|
||||
/** An array whose elements are copied values or such arrays. */
|
||||
function copiedArrayType(lowerer: Lowerer, t: IrType): boolean {
|
||||
if (t.kind !== "array") return false;
|
||||
return copiedType(lowerer, t.elem) || copiedArrayType(lowerer, t.elem);
|
||||
}
|
||||
|
||||
/** Whether a value of IR type `t` is copied at the boundary: primitives,
|
||||
* strings, null/undefined, and unions of those. */
|
||||
function copiedType(lowerer: Lowerer, t: IrType): boolean {
|
||||
|
||||
@@ -23,6 +23,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"nodeIsland.instanceof",
|
||||
"nodeIsland.classMethods",
|
||||
"nodeIsland.classMethodsOf",
|
||||
"nodeIsland.copyArrays",
|
||||
// Node's argument, cipher-name, IV, tag-length and key-length errors.
|
||||
"crypto.cipherNew",
|
||||
// Node's curve, key, point, encoding and format errors.
|
||||
|
||||
@@ -212,6 +212,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
"nodeIsland.share": { argTypes: [STRING, STRING, DYN], result: VOID },
|
||||
"nodeIsland.sharedTdz": { argTypes: [], result: DYN },
|
||||
"nodeIsland.classMethodsOf": { argTypes: [DYN, DYN, DYN], result: DYN },
|
||||
/** Island arrays of copied values as copies (a result, or an awaited
|
||||
* promise's value). */
|
||||
"nodeIsland.copyArrays": { argTypes: [DYN], result: DYN },
|
||||
"nodeIsland.awaitCopy": { argTypes: [DYN], result: { kind: "promise", inner: DYN } },
|
||||
/** Keyed WRITE on a dyn value — `h.onDone = cb` / `h["k"] = v` on a
|
||||
* checked-dynamic object (args: receiver, key string, value — all
|
||||
* borrowed; the runtime copies the key and retains the value in). An
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6837-node-island-clone-and-arrays/main.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -183,6 +183,16 @@ bool TakeFunction(Isolate *isolate, Local<Context> context, Local<Object> from,
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t ExportIdOf(Isolate *isolate, Local<Context> context, Local<Value> v);
|
||||
|
||||
// isExport(value): whether a value is the representative of a native value
|
||||
// (NI-5), for the bootstrap's structuredClone of native data.
|
||||
void IsExport(const FunctionCallbackInfo<Value> &info) {
|
||||
Isolate *isolate = info.GetIsolate();
|
||||
info.GetReturnValue().Set(info.Length() > 0 &&
|
||||
ExportIdOf(isolate, isolate->GetCurrentContext(), info[0]) != 0);
|
||||
}
|
||||
|
||||
char *Start(const ScrNodeBridgeHost *host, const char *bootstrap, const char *argv0,
|
||||
uintptr_t stack_limit) {
|
||||
if (g_setup) return nullptr;
|
||||
@@ -244,6 +254,7 @@ char *Start(const ScrNodeBridgeHost *host, const char *bootstrap, const char *ar
|
||||
SetMethod(context, native, "sharedTable", SharedTable);
|
||||
SetMethod(context, native, "sharedGet", SharedGet);
|
||||
SetMethod(context, native, "sharedTdz", SharedTdz);
|
||||
SetMethod(context, native, "isExport", IsExport);
|
||||
global->Set(context, Utf8(isolate, "__scriptcNodeIslandHost"), native).Check();
|
||||
TryCatch try_catch(isolate);
|
||||
if (node::LoadEnvironment(g_setup->env(), bootstrap).IsEmpty()) {
|
||||
|
||||
@@ -81,6 +81,9 @@ bool scr_ni_is_function(const ScrDyn *value); /* an island handle that is callab
|
||||
ScrStr *scr_ni_inspect(const ScrDyn *value, double recurse, double depth);
|
||||
ScrDyn *scr_ni_own_keys(ScrDyn *obj, int mode); /* 0 Object.keys, 1 Reflect.ownKeys */
|
||||
ScrPromise *scr_ni_await(ScrDyn *value);
|
||||
/* Island arrays as copies for results typed as arrays of copied values. */
|
||||
ScrDyn *scr_node_island_copy_arrays(ScrDyn *value);
|
||||
ScrPromise *scr_node_island_await_copy(ScrDyn *value);
|
||||
uint32_t scr_ni_live_handles(void); /* live import-table entries (leak tests) */
|
||||
|
||||
/* Between the two island units. */
|
||||
|
||||
@@ -1159,6 +1159,58 @@ ScrDyn *scr_ni_value_in(const ScrNodeValue *v) {
|
||||
return d != NULL ? d : scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
|
||||
/* ── island arrays as copies (results typed as arrays of copied values) ─
|
||||
* An island array whose declared native type is an array of primitives or
|
||||
* strings (or arrays of those) crosses as a copy of its elements, the
|
||||
* values the checked cast then meets; nested island arrays copy too, any
|
||||
* other value (a native export coming back, an island object) is itself. */
|
||||
ScrDyn *scr_node_island_copy_arrays(ScrDyn *value) {
|
||||
ScrNodeRef *ref = nh_ref_of(value);
|
||||
if (ref == NULL || (ref->flags & SCR_NVF_ARRAY) == 0) return scr_dyn_retain(value);
|
||||
ScrDyn *len = scr_ni_get_str(value, "length", 6);
|
||||
if (len == NULL) return NULL;
|
||||
double n = len->kind == SCR_DYN_NUM ? len->v.num : 0;
|
||||
scr_dyn_release(len);
|
||||
ScrDyn *out = scr_dyn_new_arr();
|
||||
for (double i = 0; i < n; i++) {
|
||||
ScrDyn *key = scr_dyn_new_num(i);
|
||||
ScrDyn *element = scr_ni_get(value, key);
|
||||
scr_dyn_release(key);
|
||||
ScrDyn *copy = element != NULL ? scr_node_island_copy_arrays(element) : NULL;
|
||||
scr_dyn_release(element);
|
||||
if (copy == NULL) {
|
||||
scr_dyn_release(out);
|
||||
return NULL;
|
||||
}
|
||||
scr_dyn_arr_push(out, copy);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static void nh_copy_fulfilled(ScrPromise *dst, ScrPromise *src) {
|
||||
ScrDyn *payload = scr_promise_payload_is_dyn(src) ? (ScrDyn *)scr_promise_payload_ref(src)
|
||||
: scr_dyn_retain(scr_dyn_undefined());
|
||||
ScrDyn *copy = scr_node_island_copy_arrays(payload);
|
||||
scr_dyn_release(payload);
|
||||
if (copy == NULL) {
|
||||
scr_promise_reject_pending(dst);
|
||||
return;
|
||||
}
|
||||
scr_promise_fulfill_ref(dst, copy, scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v);
|
||||
}
|
||||
|
||||
/* `await` of an island promise whose declared result holds arrays: the
|
||||
* fulfillment value's island arrays copied (inside the settlement, so no
|
||||
* reaction turn is added), rejections as they are. +1 promise<dyn>. */
|
||||
ScrPromise *scr_node_island_await_copy(ScrDyn *value) {
|
||||
ScrPromise *src = scr_ni_await(value);
|
||||
ScrPromise *dst = scr_promise_new();
|
||||
scr_promise_mark_handled(src);
|
||||
scr_promise_race_add(dst, src, nh_copy_fulfilled);
|
||||
scr_promise_release(src);
|
||||
return dst;
|
||||
}
|
||||
|
||||
uint32_t scr_ni_live_handles(void) {
|
||||
const ScrNodeBridge *b = scr_ni_bridge_loaded();
|
||||
if (b == NULL) return 0;
|
||||
|
||||
@@ -30,6 +30,63 @@ const ERROR_KINDS = [
|
||||
[URIError, 7],
|
||||
];
|
||||
|
||||
// structuredClone of native values (NI-5). A native record, class
|
||||
// instance or object is an object with interceptors in V8, which V8's
|
||||
// serializer cannot clone (DataCloneError). Node clones such values as the
|
||||
// plain data they are, so when a clone fails on a value holding native
|
||||
// values, it clones a plain copy instead: native values and the plain
|
||||
// objects and arrays around them are copied (own enumerable string keys,
|
||||
// shared references and cycles kept), everything else is cloned as is.
|
||||
const nodeStructuredClone = globalThis.structuredClone;
|
||||
function plainCopy(value, memo) {
|
||||
if (value === null || typeof value !== "object") return value;
|
||||
if (memo.has(value)) return memo.get(value);
|
||||
const native = nativeHost.isExport(value);
|
||||
if (Array.isArray(value)) {
|
||||
const out = [];
|
||||
memo.set(value, out);
|
||||
for (let i = 0; i < value.length; i++) out[i] = plainCopy(value[i], memo);
|
||||
return out;
|
||||
}
|
||||
const proto = Object.getPrototypeOf(value);
|
||||
if (!native && proto !== Object.prototype && proto !== null) return value;
|
||||
const out = {};
|
||||
memo.set(value, out);
|
||||
for (const key of Object.keys(value)) out[key] = plainCopy(value[key], memo);
|
||||
return out;
|
||||
}
|
||||
function holdsNative(value, seen) {
|
||||
if (value === null || typeof value !== "object" || seen.has(value)) return false;
|
||||
seen.add(value);
|
||||
if (nativeHost.isExport(value)) return true;
|
||||
const proto = Object.getPrototypeOf(value);
|
||||
if (!Array.isArray(value) && proto !== Object.prototype && proto !== null) return false;
|
||||
for (const key of Object.keys(value)) if (holdsNative(value[key], seen)) return true;
|
||||
return false;
|
||||
}
|
||||
if (typeof nodeStructuredClone === "function") {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(globalThis, "structuredClone");
|
||||
const structuredClone = {
|
||||
structuredClone(value, options = undefined) {
|
||||
try {
|
||||
return Reflect.apply(nodeStructuredClone, globalThis, arguments);
|
||||
} catch (error) {
|
||||
if (!holdsNative(value, new Set())) throw error;
|
||||
return Reflect.apply(nodeStructuredClone, globalThis, [
|
||||
plainCopy(value, new Map()),
|
||||
options,
|
||||
]);
|
||||
}
|
||||
},
|
||||
}.structuredClone;
|
||||
Object.defineProperty(globalThis, "structuredClone", {
|
||||
configurable: descriptor === undefined || descriptor.configurable,
|
||||
enumerable: descriptor !== undefined && descriptor.enumerable,
|
||||
writable: true,
|
||||
value: structuredClone,
|
||||
});
|
||||
}
|
||||
|
||||
const requires = new Map();
|
||||
|
||||
function exportedFunction(file, specifier, name) {
|
||||
|
||||
@@ -37,6 +37,63 @@ static const char scr_node_island_host_js[] =
|
||||
" [URIError, 7],\012"
|
||||
"];\012"
|
||||
"\012"
|
||||
"// structuredClone of native values (NI-5). A native record, class\012"
|
||||
"// instance or object is an object with interceptors in V8, which V8's\012"
|
||||
"// serializer cannot clone (DataCloneError). Node clones such values as the\012"
|
||||
"// plain data they are, so when a clone fails on a value holding native\012"
|
||||
"// values, it clones a plain copy instead: native values and the plain\012"
|
||||
"// objects and arrays around them are copied (own enumerable string keys,\012"
|
||||
"// shared references and cycles kept), everything else is cloned as is.\012"
|
||||
"const nodeStructuredClone = globalThis.structuredClone;\012"
|
||||
"function plainCopy(value, memo) {\012"
|
||||
" if (value === null || typeof value !== \"object\") return value;\012"
|
||||
" if (memo.has(value)) return memo.get(value);\012"
|
||||
" const native = nativeHost.isExport(value);\012"
|
||||
" if (Array.isArray(value)) {\012"
|
||||
" const out = [];\012"
|
||||
" memo.set(value, out);\012"
|
||||
" for (let i = 0; i < value.length; i++) out[i] = plainCopy(value[i], memo);\012"
|
||||
" return out;\012"
|
||||
" }\012"
|
||||
" const proto = Object.getPrototypeOf(value);\012"
|
||||
" if (!native && proto !== Object.prototype && proto !== null) return value;\012"
|
||||
" const out = {};\012"
|
||||
" memo.set(value, out);\012"
|
||||
" for (const key of Object.keys(value)) out[key] = plainCopy(value[key], memo);\012"
|
||||
" return out;\012"
|
||||
"}\012"
|
||||
"function holdsNative(value, seen) {\012"
|
||||
" if (value === null || typeof value !== \"object\" || seen.has(value)) return false;\012"
|
||||
" seen.add(value);\012"
|
||||
" if (nativeHost.isExport(value)) return true;\012"
|
||||
" const proto = Object.getPrototypeOf(value);\012"
|
||||
" if (!Array.isArray(value) && proto !== Object.prototype && proto !== null) return false;\012"
|
||||
" for (const key of Object.keys(value)) if (holdsNative(value[key], seen)) return true;\012"
|
||||
" return false;\012"
|
||||
"}\012"
|
||||
"if (typeof nodeStructuredClone === \"function\") {\012"
|
||||
" const descriptor = Object.getOwnPropertyDescriptor(globalThis, \"structuredClone\");\012"
|
||||
" const structuredClone = {\012"
|
||||
" structuredClone(value, options = undefined) {\012"
|
||||
" try {\012"
|
||||
" return Reflect.apply(nodeStructuredClone, globalThis, arguments);\012"
|
||||
" } catch (error) {\012"
|
||||
" if (!holdsNative(value, new Set())) throw error;\012"
|
||||
" return Reflect.apply(nodeStructuredClone, globalThis, [\012"
|
||||
" plainCopy(value, new Map()),\012"
|
||||
" options,\012"
|
||||
" ]);\012"
|
||||
" }\012"
|
||||
" },\012"
|
||||
" }.structuredClone;\012"
|
||||
" Object.defineProperty(globalThis, \"structuredClone\", {\012"
|
||||
" configurable: descriptor === undefined || descriptor.configurable,\012"
|
||||
" enumerable: descriptor !== undefined && descriptor.enumerable,\012"
|
||||
" writable: true,\012"
|
||||
" value: structuredClone,\012"
|
||||
" });\012"
|
||||
"}\012"
|
||||
"\012"
|
||||
"const requires = new Map();\012"
|
||||
"\012"
|
||||
"function exportedFunction(file, specifier, name) {\012"
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// @requires-libnode
|
||||
// structuredClone in a Node island of native objects passed in clones
|
||||
// their data, as under Node; arrays of plain values the island returns,
|
||||
// directly or through a promise, arrive as arrays.
|
||||
import { cloneClaims, cloneFails, cloneNested, listFiles, matrix, names } from "data-pkg";
|
||||
|
||||
interface Claims {
|
||||
sub: string;
|
||||
aud: string;
|
||||
exp: number;
|
||||
}
|
||||
|
||||
class Session {
|
||||
user = "ada";
|
||||
level = 2;
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const claims: Claims = { sub: "user-1", aud: "api", exp: 1700000000 };
|
||||
console.log(cloneClaims(claims));
|
||||
console.log("native unchanged", claims.sub);
|
||||
console.log(cloneNested({ list: [1, 2], session: new Session() }));
|
||||
console.log(cloneNested([claims, claims]));
|
||||
console.log(cloneFails());
|
||||
const files = await listFiles("/tmp");
|
||||
for (const f of files) console.log("file", f);
|
||||
console.log("count", files.length, files.join(","));
|
||||
console.log("names", names().join("|"), names().length);
|
||||
const m = await matrix();
|
||||
console.log("matrix", m.length, m[0].join("+"), m[1][0]);
|
||||
}
|
||||
|
||||
main();
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
export declare function cloneClaims(payload: object): string;
|
||||
export declare function cloneNested(value: unknown): string;
|
||||
export declare function cloneFails(): string;
|
||||
export declare function listFiles(dir: string): Promise<string[]>;
|
||||
export declare function names(): string[];
|
||||
export declare function matrix(): Promise<number[][]>;
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
"use strict";
|
||||
function cloneClaims(payload) {
|
||||
const copy = structuredClone(payload);
|
||||
copy.sub = "changed";
|
||||
return `${JSON.stringify(copy)} same=${copy === payload} proto=${Object.getPrototypeOf(copy) === Object.prototype}`;
|
||||
}
|
||||
function cloneNested(value) {
|
||||
const copy = structuredClone({ value, when: new Date(0) });
|
||||
return `${JSON.stringify(copy.value)} date=${copy.when instanceof Date}`;
|
||||
}
|
||||
function cloneFails() {
|
||||
try {
|
||||
structuredClone({ f() {} });
|
||||
return "cloned";
|
||||
} catch (e) {
|
||||
return `${e.name}`;
|
||||
}
|
||||
}
|
||||
async function listFiles(dir) {
|
||||
await null;
|
||||
return [`${dir}/a.txt`, `${dir}/b.txt`];
|
||||
}
|
||||
function names() {
|
||||
return ["x", "y", "z"];
|
||||
}
|
||||
async function matrix() {
|
||||
return [[1, 2], [3]];
|
||||
}
|
||||
module.exports = { cloneClaims, cloneNested, cloneFails, listFiles, names, matrix };
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{ "name": "data-pkg", "version": "1.0.0", "main": "index.js", "types": "index.d.ts" }
|
||||
@@ -0,0 +1 @@
|
||||
{ "name": "island-clone-and-arrays", "private": true, "type": "module", "scriptc": { "packages": { "data-pkg": "node" } } }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2000, "san": 30000 }
|
||||
Reference in New Issue
Block a user