// Map a forked child's REAL capabilities on a fresh DP (concurrency step 2). // The leaf-only contract forbids "i32/i64 libcalls" -- but libcalls use the // child's OWN $E0-$FF DP scratch (write-before-read), which a fresh page // provides, so a child that CALLS A FUNCTION doing an i32 multiply (a real // __mulsi3 libcall) may already work without any DP-scratch setup. This probe // tests exactly that: a non-leaf child that JSLs a helper which does a genuine // 32-bit multiply. It deliberately does NOT touch &global (needs $BE) or a // function pointer (needs $B8 / the __jsl_indir operand) -- those are tested // separately, since they can wild-jump. Reading a global's VALUE (gA/gB) is // plain absolute/DBR addressing, not a 32-bit &global pointer. // // 0x025060 0001 child entered // 0x025062 0001 mul32(gA,gB) == 300000 (function call + i32 libcall worked) // 0x025064 E0DD child reached end (no crash) // 0x025066 5050 parent reaped the child #include extern int fork(void *subr); extern int wait(int *status); extern __attribute__((noreturn)) void _exit(int status); #define M(a) (*(volatile uint16_t *)(a)) #define BAD 0xDEAD static volatile uint32_t gA = 100000; static volatile uint32_t gB = 3; static uint32_t __attribute__((noinline)) mul32(uint32_t x, uint32_t y) { return x * y; // real __mulsi3 libcall (uses $E0-$FF) } static void child(void) { uint32_t r; M(0x025060UL) = 0x0001; // child entered on its fresh DP r = mul32(gA, gB); // fn-call + i32 libcall if (r == 300000UL) { M(0x025062UL) = 0x0001; } M(0x025064UL) = 0xE0DD; // reached end -> no crash _exit(0); } int main(void) { int pid; int st; M(0x025060UL) = BAD; M(0x025062UL) = BAD; M(0x025064UL) = BAD; M(0x025066UL) = BAD; pid = fork((void *)child); if (pid > 0) { wait(&st); M(0x025066UL) = 0x5050; } for (volatile unsigned long j = 0; j < 300000UL; j++) { } return 0; }