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
55 lines
1.7 KiB
C
55 lines
1.7 KiB
C
/* CB6 (callback-free posture): the same signature-only ambient call under
|
|
* a profile with NO callbacks section keeps Node's ReferenceError
|
|
* semantics — the entry throws, and the escaped exception reaches the
|
|
* sink as the SC4013 structured message naming the entry. */
|
|
#include <setjmp.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
extern void cb_init(void);
|
|
extern void cb_set_panic_sink(void (*fn)(void *, const uint8_t *, size_t, uint64_t), void *ctx);
|
|
extern double cb_stream(double n, double base);
|
|
|
|
static jmp_buf trap_jmp;
|
|
static int sink_calls = 0;
|
|
|
|
static void sink(void *ctx, const uint8_t *msg, size_t len, uint64_t addr) {
|
|
(void)ctx; (void)addr;
|
|
sink_calls++;
|
|
printf("sink[%d]:\n", sink_calls);
|
|
int has_referror = 0;
|
|
if (len > 0 && msg[0] == 0x01) {
|
|
const uint8_t *p = msg + 1, *end = msg + len;
|
|
int field = 0;
|
|
for (;;) {
|
|
const uint8_t *sep = memchr(p, 0x1f, (size_t)(end - p));
|
|
const uint8_t *stop = sep != NULL ? sep : end;
|
|
if (field == 0) {
|
|
for (const uint8_t *q = p; q + 14 <= stop; q++) {
|
|
if (memcmp(q, "ReferenceError", 14) == 0) { has_referror = 1; break; }
|
|
}
|
|
}
|
|
if (field == 1) printf("code=[%.*s]\n", (int)(stop - p), (const char *)p);
|
|
if (field == 2) printf("symbol=[%.*s]\n", (int)(stop - p), (const char *)p);
|
|
field++;
|
|
if (sep == NULL) break;
|
|
p = sep + 1;
|
|
}
|
|
}
|
|
printf("text has ReferenceError: %d\n", has_referror);
|
|
longjmp(trap_jmp, 1);
|
|
}
|
|
|
|
int main(void) {
|
|
cb_set_panic_sink(sink, NULL);
|
|
cb_init();
|
|
if (setjmp(trap_jmp) == 0) {
|
|
cb_stream(1, 1);
|
|
printf("UNREACHABLE\n");
|
|
} else {
|
|
printf("survived, sink_calls=%d\n", sink_calls);
|
|
}
|
|
return 0;
|
|
}
|