mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-19 05:29:46 +08:00
Co-authored-by: mmamedel <23098414+mmamedel@users.noreply.github.com>
196 lines
6.3 KiB
C
196 lines
6.3 KiB
C
/* Composition probe: ONE runtime-localized (abi.localize_runtime),
|
|
* thread-instanced (abi.instance_per_thread) archive with host-callback
|
|
* channels, driven from two embedder threads — the documented contract:
|
|
* the calling thread IS the instance selector, and channel registrations
|
|
* are per-instance state exactly like the sink's. Checks:
|
|
* - each thread registers its OWN emitChunk context and sink, inits its
|
|
* own instance, and streams concurrently (a barrier forces overlap);
|
|
* chunks route to the registering thread's log with exact contents,
|
|
* order, and thread identity — nothing crosses instances;
|
|
* - thread A then reaches the channel it never registered: the SC4025
|
|
* structured trap arrives at A's sink exactly once (A's ctx, symbol
|
|
* cbt_poke_orphan) and poisons only A's instance;
|
|
* - thread B keeps streaming through and after A's trap window with
|
|
* exact values; B's sink never fires.
|
|
* Workers record into per-thread state; main prints after both joins, so
|
|
* stdout is deterministic under any interleaving. */
|
|
#include <pthread.h>
|
|
#include <setjmp.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
extern void cbt_init(void);
|
|
extern void cbt_set_panic_sink(void (*fn)(void *, const uint8_t *, size_t, uint64_t), void *ctx);
|
|
extern int32_t cbt_set_callback(const char *name, void (*fn)(void), void *ctx);
|
|
extern double cbt_stream(double n, double base);
|
|
extern double cbt_ask_host(double x);
|
|
extern double cbt_poke_orphan(void);
|
|
|
|
typedef void (*cb_fn)(void);
|
|
|
|
/* Stages: 0 start; 1 both instances initialized (concurrent streams run);
|
|
* 2 both first streams done (A traps); 3 A's trap delivered (B streams
|
|
* once more and exits). */
|
|
static pthread_mutex_t mu = PTHREAD_MUTEX_INITIALIZER;
|
|
static pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
|
|
static int stage = 0;
|
|
static int inited = 0, streamed = 0;
|
|
|
|
static void stage_set(int s) {
|
|
pthread_mutex_lock(&mu);
|
|
if (stage < s) stage = s;
|
|
pthread_cond_broadcast(&cv);
|
|
pthread_mutex_unlock(&mu);
|
|
}
|
|
|
|
static void stage_wait(int s) {
|
|
pthread_mutex_lock(&mu);
|
|
while (stage < s) pthread_cond_wait(&cv, &mu);
|
|
pthread_mutex_unlock(&mu);
|
|
}
|
|
|
|
static void arrive(int *counter, int total, int next_stage) {
|
|
pthread_mutex_lock(&mu);
|
|
(*counter)++;
|
|
if (*counter == total && stage < next_stage) {
|
|
stage = next_stage;
|
|
pthread_cond_broadcast(&cv);
|
|
}
|
|
pthread_mutex_unlock(&mu);
|
|
}
|
|
|
|
typedef struct {
|
|
char bytes[8];
|
|
size_t len;
|
|
uint32_t seq;
|
|
} ChunkRec;
|
|
|
|
typedef struct {
|
|
pthread_t self;
|
|
ChunkRec chunks[16];
|
|
int count;
|
|
int thread_ok;
|
|
int sink_calls;
|
|
int sink_ctx_ok;
|
|
char sink_code[16];
|
|
char sink_symbol[64];
|
|
double r1, r2;
|
|
jmp_buf trap_jmp;
|
|
} Worker;
|
|
|
|
static void on_chunk(void *ctx, const uint8_t *p, size_t len, uint32_t seq) {
|
|
Worker *w = (Worker *)ctx;
|
|
ChunkRec *rec = &w->chunks[w->count];
|
|
memcpy(rec->bytes, p, len < sizeof rec->bytes ? len : sizeof rec->bytes);
|
|
rec->len = len;
|
|
rec->seq = seq;
|
|
w->count++;
|
|
if (!pthread_equal(pthread_self(), w->self)) w->thread_ok = 0;
|
|
}
|
|
|
|
static int32_t on_progress(void *ctx, double done, double total) {
|
|
(void)ctx;
|
|
return (int32_t)(total - done);
|
|
}
|
|
|
|
static void on_note(void *ctx, const uint8_t *p, size_t len, uint8_t last) {
|
|
(void)ctx; (void)p; (void)len; (void)last;
|
|
}
|
|
|
|
static uint32_t on_mix(void *ctx, uint8_t a, int32_t b) {
|
|
(void)ctx;
|
|
return (uint32_t)a + (uint32_t)(-b);
|
|
}
|
|
|
|
static Worker workers[2];
|
|
|
|
static void sink(void *ctx, const uint8_t *msg, size_t len, uint64_t addr) {
|
|
(void)addr;
|
|
Worker *w = (Worker *)ctx;
|
|
w->sink_calls++;
|
|
w->sink_ctx_ok = pthread_equal(pthread_self(), w->self) ? 1 : 0;
|
|
/* Structured parse: fields 1 (code) and 2 (symbol). */
|
|
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 == 1) snprintf(w->sink_code, sizeof w->sink_code, "%.*s", (int)(stop - p), (const char *)p);
|
|
if (field == 2) snprintf(w->sink_symbol, sizeof w->sink_symbol, "%.*s", (int)(stop - p), (const char *)p);
|
|
field++;
|
|
if (sep == NULL) break;
|
|
p = sep + 1;
|
|
}
|
|
}
|
|
longjmp(w->trap_jmp, 1);
|
|
}
|
|
|
|
static void *worker_a(void *arg) {
|
|
Worker *w = (Worker *)arg;
|
|
w->self = pthread_self();
|
|
w->thread_ok = 1;
|
|
cbt_set_panic_sink(sink, w);
|
|
cbt_set_callback("emitChunk", (cb_fn)on_chunk, w);
|
|
cbt_set_callback("progress", (cb_fn)on_progress, NULL);
|
|
cbt_set_callback("note", (cb_fn)on_note, NULL);
|
|
cbt_set_callback("mix", (cb_fn)on_mix, NULL);
|
|
cbt_init();
|
|
fflush(stdout); /* keep the two independent init logs line-separated */
|
|
arrive(&inited, 2, 1);
|
|
stage_wait(1);
|
|
w->r1 = cbt_stream(3, 2); /* concurrent with B's stream */
|
|
arrive(&streamed, 2, 2);
|
|
stage_wait(2);
|
|
if (setjmp(w->trap_jmp) == 0) {
|
|
cbt_poke_orphan(); /* A never registered 'orphan': SC4025 to A's sink */
|
|
printf("UNREACHABLE A\n");
|
|
}
|
|
stage_set(3);
|
|
return NULL;
|
|
}
|
|
|
|
static void *worker_b(void *arg) {
|
|
Worker *w = (Worker *)arg;
|
|
w->self = pthread_self();
|
|
w->thread_ok = 1;
|
|
cbt_set_panic_sink(sink, w);
|
|
cbt_set_callback("emitChunk", (cb_fn)on_chunk, w);
|
|
cbt_set_callback("progress", (cb_fn)on_progress, NULL);
|
|
cbt_set_callback("note", (cb_fn)on_note, NULL);
|
|
cbt_set_callback("mix", (cb_fn)on_mix, NULL);
|
|
cbt_init();
|
|
fflush(stdout); /* keep the two independent init logs line-separated */
|
|
arrive(&inited, 2, 1);
|
|
stage_wait(1);
|
|
w->r1 = cbt_stream(3, 5); /* concurrent with A's stream */
|
|
arrive(&streamed, 2, 2);
|
|
stage_wait(3); /* A's instance is poisoned now; B's keeps answering */
|
|
w->r2 = cbt_stream(1, 4);
|
|
return NULL;
|
|
}
|
|
|
|
static void dump(const char *tag, const Worker *w) {
|
|
printf("%s: r1=%g r2=%g chunks=%d thread_ok=%d sink_calls=%d\n",
|
|
tag, w->r1, w->r2, w->count, w->thread_ok, w->sink_calls);
|
|
for (int i = 0; i < w->count; i++) {
|
|
printf(" seq=%u len=%zu bytes=%.*s\n", w->chunks[i].seq, w->chunks[i].len,
|
|
(int)w->chunks[i].len, w->chunks[i].bytes);
|
|
}
|
|
if (w->sink_calls > 0) {
|
|
printf(" sink code=%s symbol=%s ctx_ok=%d\n", w->sink_code, w->sink_symbol, w->sink_ctx_ok);
|
|
}
|
|
}
|
|
|
|
int main(void) {
|
|
pthread_t ta, tb;
|
|
pthread_create(&ta, NULL, worker_a, &workers[0]);
|
|
pthread_create(&tb, NULL, worker_b, &workers[1]);
|
|
pthread_join(ta, NULL);
|
|
pthread_join(tb, NULL);
|
|
dump("A", &workers[0]);
|
|
dump("B", &workers[1]);
|
|
return 0;
|
|
}
|