- fiber states (RUNNABLE/PARKED/DONE), intrusive FIFO run queue, wo_vm_spawn_fiber (calloc'd context, frame 0 set up like wo_vm_call) - reduction budget: WO_REDUCTIONS (default 4000), checked at loop BACK-EDGES AFTER the jump lands so the saved pc is the loop head — a pre-instruction save at budget 1 re-executes the jump into the same decrement and livelocks (found by reasoning, pinned by the budget-1 test; deviation from the spec's three-site wording, recorded in the yield macro's comment) - FIBER_DONE: main returning ends the program and reaps every remaining fiber through vm_unwind (drop maps run); a spawned fiber ending frees silently; its return value is discarded by contract - TRAPF: an uncaught trap in a spawned fiber kills that fiber ALONE (stderr report, program lives); in main it stays the program's death - WO_SYS_STOPPED reaps all fibers wherever it lands (main unlinked from the queue and unwound if a spawned fiber caught the stop) - vm_gc_roots walks the live fiber plus every queued one - test_fiber (45 checks, ASan): EXACT round-robin interleave at budget 1 across three fibers pushing tags into one shared multi; main-return reaps a spinning fiber holding an owned Big (ASan proves the free); a DIV0 fiber dies alone, main answers 0 - full battery green: wovm-test, oop-e2e 89/0, woc-test, log-watcher 7/0, employee 8/0, web-app 21/0, deps-accept 8/0 (scheduler dormant = one branch per back-edge) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
193 lines
6.9 KiB
C
193 lines
6.9 KiB
C
/* test_fiber — the arc's stage 1 Task 2: run queue + reduction budget.
|
|
*
|
|
* 1. Under WO_REDUCTIONS=1, three fibers pushing tags into one shared
|
|
* multi interleave in EXACT round-robin — the deterministic-
|
|
* scheduling criterion.
|
|
* 2. Main returning reaps a still-looping fiber holding an owned
|
|
* object: the drop map runs (ASan proves the free).
|
|
* 3. A spawned fiber's uncaught trap (DIV0) kills that fiber alone;
|
|
* main finishes with rc 0 — the isolation rule.
|
|
*/
|
|
#define _POSIX_C_SOURCE 200112L /* setenv/unsetenv under -std=c11 */
|
|
#include <stdlib.h>
|
|
|
|
#include "cont.h"
|
|
#include "gc.h"
|
|
#include "loader.h"
|
|
#include "t.h"
|
|
#include "vm.h"
|
|
#include "wob_build.h"
|
|
|
|
#define BIG 130 /* malloc-path class so ASan sees the free (test_unwind's trick) */
|
|
|
|
static wo_vm VM;
|
|
static uint8_t big_kinds[BIG];
|
|
|
|
/* Worker: argc=2 (r0 = shared multi as a raw word, r1 = tag), pushes the
|
|
* tag K times. The backward JMP is the budget's yield site.
|
|
* pc0 LOADK r2,#0 pc1 LOADK r3,#K
|
|
* pc2 LT r4,r2,r3 pc3 JZ r4,+4 -> pc8
|
|
* pc4 BUILTIN r4, base=0, MULTI_PUSH (container r0, element r1)
|
|
* pc5 LOADK r4,#1 pc6 ADD r2,r2,r4
|
|
* pc7 JMP -6 -> pc2 pc8 RET0 */
|
|
static void worker_code(uint32_t *code, uint32_t k0, uint32_t kK, uint32_t k1) {
|
|
code[0] = wo_ins_abx(WOP_LOADK, 2, (uint16_t)k0);
|
|
code[1] = wo_ins_abx(WOP_LOADK, 3, (uint16_t)kK);
|
|
code[2] = wo_ins_abc(WOP_LT, 4, 2, 3);
|
|
code[3] = wo_ins_asbx(WOP_JZ, 4, 4);
|
|
code[4] = wo_ins_abc(WOP_BUILTIN, 4, 0, WO_B_MULTI_PUSH);
|
|
code[5] = wo_ins_abx(WOP_LOADK, 4, (uint16_t)k1);
|
|
code[6] = wo_ins_abc(WOP_ADD, 2, 2, 4);
|
|
code[7] = wo_ins_asbx(WOP_JMP, 0, -6);
|
|
code[8] = wo_ins_abc(WOP_RET0, 0, 0, 0);
|
|
}
|
|
|
|
static void test_round_robin(void) {
|
|
wb_t *b = wb_new();
|
|
uint32_t kname = wb_const_text(b, "worker");
|
|
uint32_t k0 = wb_const_int(b, 0);
|
|
uint32_t kK = wb_const_int(b, 5);
|
|
uint32_t k1 = wb_const_int(b, 1);
|
|
uint32_t code[9];
|
|
worker_code(code, k0, kK, k1);
|
|
uint32_t lines[] = {0, 1};
|
|
wb_method(b, kname, WOB_NONE, 2, 8, code, 9, lines, 1, NULL, 0);
|
|
size_t len;
|
|
uint8_t *img = wb_finish(b, &len);
|
|
|
|
wo_module mod;
|
|
char lerr[256];
|
|
T_EQ(wo_load_buf(&mod, img, len, lerr, sizeof lerr), 0);
|
|
setenv("WO_REDUCTIONS", "1", 1);
|
|
T_EQ(wo_vm_init(&VM, &mod, 1 << 20), 0);
|
|
unsetenv("WO_REDUCTIONS");
|
|
|
|
wo_multi *m = wo_multi_new(&VM.rt, WO_K_SCALAR);
|
|
T_CHECK(m != NULL);
|
|
uint64_t a2[2] = {(uint64_t)(uintptr_t)m, 2};
|
|
uint64_t a3[2] = {(uint64_t)(uintptr_t)m, 3};
|
|
T_CHECK(wo_vm_spawn_fiber(&VM, 0, a2, 2) != NULL);
|
|
T_CHECK(wo_vm_spawn_fiber(&VM, 0, a3, 2) != NULL);
|
|
|
|
uint64_t ret = 0;
|
|
wo_err err = {0};
|
|
uint64_t a1[2] = {(uint64_t)(uintptr_t)m, 1};
|
|
T_EQ(wo_vm_call(&VM, 0, a1, 2, &ret, &err), 0);
|
|
|
|
/* budget 1: every backward JMP yields, so one push per turn — the
|
|
* order is main(1), fiber(2), fiber(3), repeated exactly */
|
|
T_EQ(m->len, 15u);
|
|
for (uint32_t i = 0; i < 15; i++) {
|
|
uint64_t v = 0;
|
|
T_EQ(wo_multi_get(m, i, &v), 0);
|
|
T_EQ(v, (uint64_t)(i % 3) + 1);
|
|
}
|
|
wo_drop_obj(&VM.rt, (wo_hdr *)m); /* the test owns m; drop frees items */
|
|
wo_vm_destroy(&VM);
|
|
wo_module_free(&mod);
|
|
free(img);
|
|
}
|
|
|
|
/* Main loops 3 times (yielding), then returns; the worker allocated a Big
|
|
* (owned, in its drop mask) and loops forever. Main's return must reap it
|
|
* drop-clean — ASan fails this test if the Big leaks. */
|
|
static void test_main_return_reaps(void) {
|
|
wb_t *b = wb_new();
|
|
uint32_t kbig = wb_const_text(b, "Big");
|
|
uint32_t kw = wb_const_text(b, "spin");
|
|
uint32_t km = wb_const_text(b, "main");
|
|
wb_class(b, kbig, 0, big_kinds, BIG);
|
|
/* spin: NEW r0 Big, then loop forever (backward JMP = yields) */
|
|
uint32_t wcode[] = {
|
|
wo_ins_abx(WOP_NEW, 0, 0),
|
|
wo_ins_asbx(WOP_JMP, 0, -1), /* pc1 -> pc1: jump to itself */
|
|
};
|
|
wb_drop wdrops[] = {{.pc = 1, .owned = 1u << 0, .gc = 0}};
|
|
uint32_t wl[] = {0, 1};
|
|
wb_method(b, kw, WOB_NONE, 0, 2, wcode, 2, wl, 1, wdrops, 1);
|
|
/* main: count 0..3 with backward jumps, then RET0 */
|
|
uint32_t k0 = wb_const_int(b, 0);
|
|
uint32_t kK = wb_const_int(b, 3);
|
|
uint32_t k1 = wb_const_int(b, 1);
|
|
uint32_t mcode[] = {
|
|
wo_ins_abx(WOP_LOADK, 0, (uint16_t)k0),
|
|
wo_ins_abx(WOP_LOADK, 1, (uint16_t)kK),
|
|
wo_ins_abc(WOP_LT, 2, 0, 1),
|
|
wo_ins_asbx(WOP_JZ, 2, 3),
|
|
wo_ins_abx(WOP_LOADK, 2, (uint16_t)k1),
|
|
wo_ins_abc(WOP_ADD, 0, 0, 2),
|
|
wo_ins_asbx(WOP_JMP, 0, -5),
|
|
wo_ins_abc(WOP_RET0, 0, 0, 0),
|
|
};
|
|
uint32_t ml[] = {0, 1};
|
|
wb_method(b, km, WOB_NONE, 0, 3, mcode, 8, ml, 1, NULL, 0);
|
|
size_t len;
|
|
uint8_t *img = wb_finish(b, &len);
|
|
|
|
wo_module mod;
|
|
char lerr[256];
|
|
T_EQ(wo_load_buf(&mod, img, len, lerr, sizeof lerr), 0);
|
|
setenv("WO_REDUCTIONS", "1", 1);
|
|
T_EQ(wo_vm_init(&VM, &mod, 1 << 20), 0);
|
|
unsetenv("WO_REDUCTIONS");
|
|
T_CHECK(wo_vm_spawn_fiber(&VM, 0, NULL, 0) != NULL); /* spin */
|
|
uint64_t ret = 0;
|
|
wo_err err = {0};
|
|
T_EQ(wo_vm_call(&VM, 1, NULL, 0, &ret, &err), 0); /* main */
|
|
wo_vm_destroy(&VM);
|
|
wo_module_free(&mod);
|
|
free(img); /* ASan: spin's Big must have been freed by the reap */
|
|
}
|
|
|
|
/* A spawned fiber divides by zero; the program (main) still answers 0. */
|
|
static void test_fiber_trap_isolated(void) {
|
|
wb_t *b = wb_new();
|
|
uint32_t kw = wb_const_text(b, "boom");
|
|
uint32_t km = wb_const_text(b, "main");
|
|
uint32_t kone = wb_const_int(b, 1);
|
|
uint32_t kzero = wb_const_int(b, 0);
|
|
uint32_t wcode[] = {
|
|
wo_ins_abx(WOP_LOADK, 0, (uint16_t)kone),
|
|
wo_ins_abx(WOP_LOADK, 1, (uint16_t)kzero),
|
|
wo_ins_abc(WOP_DIV, 2, 0, 1), /* DIV0: uncaught, fiber dies alone */
|
|
wo_ins_abc(WOP_RET0, 0, 0, 0),
|
|
};
|
|
uint32_t wl[] = {0, 1};
|
|
wb_method(b, kw, WOB_NONE, 0, 3, wcode, 4, wl, 1, NULL, 0);
|
|
uint32_t kK = wb_const_int(b, 3);
|
|
uint32_t mcode[] = {
|
|
wo_ins_abx(WOP_LOADK, 0, (uint16_t)kzero),
|
|
wo_ins_abx(WOP_LOADK, 1, (uint16_t)kK),
|
|
wo_ins_abc(WOP_LT, 2, 0, 1),
|
|
wo_ins_asbx(WOP_JZ, 2, 3),
|
|
wo_ins_abx(WOP_LOADK, 2, (uint16_t)kone),
|
|
wo_ins_abc(WOP_ADD, 0, 0, 2),
|
|
wo_ins_asbx(WOP_JMP, 0, -5),
|
|
wo_ins_abc(WOP_RET0, 0, 0, 0),
|
|
};
|
|
uint32_t ml[] = {0, 1};
|
|
wb_method(b, km, WOB_NONE, 0, 3, mcode, 8, ml, 1, NULL, 0);
|
|
size_t len;
|
|
uint8_t *img = wb_finish(b, &len);
|
|
|
|
wo_module mod;
|
|
char lerr[256];
|
|
T_EQ(wo_load_buf(&mod, img, len, lerr, sizeof lerr), 0);
|
|
setenv("WO_REDUCTIONS", "1", 1);
|
|
T_EQ(wo_vm_init(&VM, &mod, 1 << 20), 0);
|
|
unsetenv("WO_REDUCTIONS");
|
|
T_CHECK(wo_vm_spawn_fiber(&VM, 0, NULL, 0) != NULL); /* boom */
|
|
uint64_t ret = 0;
|
|
wo_err err = {0};
|
|
T_EQ(wo_vm_call(&VM, 1, NULL, 0, &ret, &err), 0);
|
|
wo_vm_destroy(&VM);
|
|
wo_module_free(&mod);
|
|
free(img);
|
|
}
|
|
|
|
int main(void) {
|
|
test_round_robin();
|
|
test_main_return_reaps();
|
|
test_fiber_trap_isolated();
|
|
return t_report("test_fiber");
|
|
}
|