mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-23 06:10:02 +08:00
* feat: add host-callback channels to library mode - the profile's `callbacks` array declares named channels and `abi.callback_register_symbol` names the one registration entry point: `int32_t <sym>(const char *name, void (*fn)(void), void *ctx)` — 0 on success, -1 for an unknown or NULL name, latest registration wins, a NULL fn clears, registrations persist across init/reset. The stored pointer's typed shape is the channel's with the opaque context first, the panic sink's layout - channel signatures ride the existing marshalling classes: params are f64/bool/string/bytes plus the u8/u32/i32 plumbing classes (outbound plumbing keeps JS's ToUint32/ToInt32, the executable FFI rule); returns are scalar only (f64/bool/u8/u32/i32/void — a buffer return needs an ownership contract the mode does not define). string/bytes parameters arrive as (ptr, len) pairs borrowed for the call's duration - compiled code reaches a channel as a signature-only ambient function declaration whose direct calls deliver synchronously on the calling thread; the recognition rides the FFI import machinery under a library flavor: SC4024 refuses a call the channels cannot serve (undeclared name or off-class signature), unused channels are legal capacity, and callback-free profiles keep the ambient ReferenceError semantics - calling an unregistered channel is the SC4025 runtime trap: a detected trap through the library funnel, so the structured sink message names the channel in its text and the entry the host called in its symbol field, and profile teachings/remediations overlay it like the rest of the runtime family - registration slots are per copy of the runtime state, the sink's story: per-archive under abi.localize_runtime (the register symbol joins the localization keep-list), per-thread instance under abi.instance_per_thread. The host callback must not reenter any library entry or unwind across library frames - both emissions produce identical behavior by construction: the slot store/fetch lives in scr_library.c (scr_library_cb_set/_require/_ctx), the generated TU emits the registration dispatch and typed indirect calls, and the trap text is assembled once at export resolution - callback-free profiles emit byte-identical program TUs and serialized IR, and executable builds are byte-identical end to end - conformance: the CB suite (tests/harness/library-callbacks.test.ts) covers the acceptance stream, unregistered and pre-registration traps, symbol exactness, teaching overlays, SC4024 refusals, capacity and callback-free postures, the localized+thread-instanced composition probe, and ASan reruns; profile-shape refusals join library-profile.test.ts * fix: preserve callback binding identity * fix: harden library callback contracts * fix: recognize project callback declarations * fix: enforce host callback declarations
41 lines
1.4 KiB
TypeScript
41 lines
1.4 KiB
TypeScript
// Host-callback conformance fixture: profile-declared channels a
|
|
// service-shaped export streams through — synchronously, on the calling
|
|
// thread. `stream` emits N chunks interleaved with computation (the return
|
|
// value depends on work done between emits, so ordering is observable);
|
|
// `askHost` round-trips scalars through host RETURNS (i32/u32 inbound);
|
|
// `pokeOrphan` calls the one channel the probe deliberately never
|
|
// registers (the SC4025 unregistered-call trap path).
|
|
declare function emitChunk(chunk: Uint8Array, seq: number): void;
|
|
declare function progress(done: number, total: number): number;
|
|
declare function note(text: string, last: boolean): void;
|
|
declare function mix(a: number, b: number): number;
|
|
declare function orphan(x: number): void;
|
|
|
|
let sessions = 0;
|
|
|
|
export function stream(n: number, base: number): number {
|
|
sessions++;
|
|
let acc = 0;
|
|
for (let i = 0; i < n; i++) {
|
|
const chunk = new Uint8Array(3);
|
|
chunk[0] = 65 + i; // 'A' + i
|
|
chunk[1] = 48 + ((base + i) % 10); // a digit tied to the arguments
|
|
chunk[2] = 33; // '!'
|
|
emitChunk(chunk, i);
|
|
acc += (i + 1) * base; // computation between emits
|
|
note(`chunk ${i} away`, i === n - 1);
|
|
}
|
|
return acc + sessions;
|
|
}
|
|
|
|
export function askHost(x: number): number {
|
|
return progress(x, 10) * 2 + mix(x + 300, 0 - x);
|
|
}
|
|
|
|
export function pokeOrphan(): number {
|
|
orphan(7);
|
|
return -1;
|
|
}
|
|
|
|
console.log("callbacks ready");
|