65816-llvm-mos/demos/gnoForkWork.c
2026-07-07 21:11:28 -05:00

63 lines
2 KiB
C

// 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 <stdint.h>
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;
}