From 77202fb7f714af6e256c3017bd33a99cf5ac388f Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Thu, 30 Apr 2026 22:04:47 -0500 Subject: [PATCH] Checkpoint. --- STATUS.md | 15 ++++ runtime/include/setjmp.h | 7 +- runtime/src/libgcc.s | 20 +++--- scripts/smokeTest.sh | 147 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 179 insertions(+), 10 deletions(-) diff --git a/STATUS.md b/STATUS.md index 2a107c6..50dcf5b 100644 --- a/STATUS.md +++ b/STATUS.md @@ -105,6 +105,21 @@ push/pop over a static stack, snprintf "%ld", and strcmp to verify the end-to-end composition under a realistic-ish workload — adds, subs, muls, divs, and 3-deep operand stacks all work. +**setjmp / longjmp** (smoke #88) now work end-to-end: setjmp saves +SP / 24-bit ret addr / DP, longjmp restores them and returns the +val argument as setjmp's "second return". Required two fixes: +(a) the assembler misencoded `sta (dp), y` as absolute,Y instead of +DP-indirect-Y — switched to raw `.byte 0x91, 0xe0`; (b) added +`__attribute__((returns_twice))` to the setjmp declaration so the +optimizer doesn't constant-fold post-setjmp env reads to 0. + +**CRC32** (smoke #89) verifies the standard "123456789" → 0xCBF43926 +end-to-end — exercises uint32_t shifts, XORs, char-by-char loops. + +**Brainfuck interpreter** (smoke #90) executes a small bf program +and verifies the output bytes — exercises loop bracket matching, +pointer math (data pointer), branching on cell value. + The **DWARF sidecar** (`link816 --debug-out FILE`) now applies text/rodata/bss/init_array relocations to every `.debug_*` section before writing it. PC values in `.debug_addr` and `.debug_line` end diff --git a/runtime/include/setjmp.h b/runtime/include/setjmp.h index b03cf1b..34e5811 100644 --- a/runtime/include/setjmp.h +++ b/runtime/include/setjmp.h @@ -5,7 +5,10 @@ typedef unsigned char jmp_buf[8]; -int setjmp(jmp_buf env); -void longjmp(jmp_buf env, int val) __attribute__((noreturn)); +// `returns_twice` tells clang that setjmp may "return" a second time +// (via longjmp) — without this the optimizer may treat env as +// uninitialized after the call and constant-fold reads to 0. +int setjmp(jmp_buf env) __attribute__((returns_twice)); +void longjmp(jmp_buf env, int val) __attribute__((noreturn)); #endif diff --git a/runtime/src/libgcc.s b/runtime/src/libgcc.s index f34b22e..7c2ab9c 100644 --- a/runtime/src/libgcc.s +++ b/runtime/src/libgcc.s @@ -1146,6 +1146,10 @@ __negdi_b: ; setjmp returned 0 with all-callee-savable regs already preserved by ; setjmp's caller. ; -------------------------------------------------------------------- +; NOTE: llvm-mc misencodes `sta (dp), y` and `lda (dp), y` as the +; absolute-,Y opcodes (0x99 / 0xb9) instead of the DP-indirect-Y +; opcodes (0x91 / 0xb1). Use raw `.byte` for those. Y is supplied +; via LDY before each indirect access. .globl setjmp setjmp: sta 0xe0 ; jmp_buf addr -> DP scratch @@ -1153,18 +1157,18 @@ setjmp: clc adc #0x3 ; A = caller's SP (undo JSL push) ldy #0 - sta (0xe0), y ; env[0..1] = caller SP + .byte 0x91, 0xe0 ; sta (0xe0), y : env[0..1] = caller SP (16-bit M) lda 0x1, s ; A = retaddr lo:hi ldy #2 - sta (0xe0), y ; env[2..3] = retaddr lo:hi + .byte 0x91, 0xe0 ; sta (0xe0), y : env[2..3] = retaddr lo:hi sep #0x20 lda 0x3, s ; A_lo = bank ldy #4 - sta (0xe0), y ; env[4] = bank + .byte 0x91, 0xe0 ; sta (0xe0), y : env[4] = bank (8-bit M) rep #0x20 tdc ; A = DP ldy #5 - sta (0xe0), y ; env[5..6] = DP + .byte 0x91, 0xe0 ; sta (0xe0), y : env[5..6] = DP lda #0 ; setjmp returns 0 rtl @@ -1175,22 +1179,22 @@ longjmp: sta 0xe2 ; save val ; Restore SP: env[0..1] - 3 (so the upcoming PHAs land at the right slots). ldy #0 - lda (0xe0), y ; A = saved SP + .byte 0xb1, 0xe0 ; lda (0xe0), y : A = saved SP (16-bit) sec sbc #0x3 tcs ; SP = saved_SP - 3 ; Push retaddr: bank, then 16-bit lo:hi. RTL pulls lo, hi, bank. sep #0x20 ldy #4 - lda (0xe0), y ; bank + .byte 0xb1, 0xe0 ; lda (0xe0), y : bank (8-bit) pha rep #0x20 ldy #2 - lda (0xe0), y ; lo:hi + .byte 0xb1, 0xe0 ; lda (0xe0), y : lo:hi (16-bit) pha ; Restore DP. ldy #5 - lda (0xe0), y + .byte 0xb1, 0xe0 ; lda (0xe0), y : DP (16-bit) tcd ; Compute return value: val if nonzero, else 1. lda 0xe2 diff --git a/scripts/smokeTest.sh b/scripts/smokeTest.sh index b23a8f9..6e8385f 100755 --- a/scripts/smokeTest.sh +++ b/scripts/smokeTest.sh @@ -2531,6 +2531,153 @@ EOF fi rm -f "$cRpFile" "$oRpFile" "$binRpFile" + log "check: MAME runs setjmp/longjmp from nested recursion (#88)" + cSjFile="$(mktemp --suffix=.c)" + oSjFile="$(mktemp --suffix=.o)" + binSjFile="$(mktemp --suffix=.bin)" + cat > "$cSjFile" <<'EOF' +typedef unsigned char jmp_buf[8]; +int setjmp(jmp_buf env) __attribute__((returns_twice)); +void longjmp(jmp_buf env, int val) __attribute__((noreturn)); +__attribute__((noinline)) void switchToBank2(void) { + __asm__ volatile ("sep #0x20\n.byte 0xa9,0x02\npha\nplb\nrep #0x20\n"); +} +__attribute__((noinline)) +static void deep(jmp_buf env, int level) { + if (level >= 3) longjmp(env, 99); + deep(env, level + 1); +} +int main(void) { + jmp_buf env; + int v = setjmp(env); + unsigned short ok = 0; + if (v == 0) { + deep(env, 0); + ok = 0xDEAD; + } else if (v == 99) { + ok = 0x1234; + } + switchToBank2(); + *(volatile unsigned short *)0x5000 = ok; + while (1) {} +} +EOF + "$CLANG" --target=w65816 -O2 -ffunction-sections -c \ + "$cSjFile" -o "$oSjFile" + "$PROJECT_ROOT/tools/link816" -o "$binSjFile" --text-base 0x1000 \ + "$oCrt0F" "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oQsortF" \ + "$oExtrasF" "$oStrtokF" "$oMathF" "$oSfF" "$oSdF" \ + "$oLibgccFile" "$oSjFile" >/dev/null 2>&1 + if ! bash "$PROJECT_ROOT/scripts/runInMame.sh" "$binSjFile" --check \ + 0x025000=1234 >/dev/null 2>&1; then + die "MAME: setjmp/longjmp end-to-end != 0x1234 (#88)" + fi + rm -f "$cSjFile" "$oSjFile" "$binSjFile" + + log "check: MAME runs CRC32('123456789') == 0xCBF43926 (#89)" + cCrFile="$(mktemp --suffix=.c)" + oCrFile="$(mktemp --suffix=.o)" + binCrFile="$(mktemp --suffix=.bin)" + cat > "$cCrFile" <<'EOF' +typedef unsigned long uint32_t; +typedef unsigned char uint8_t; +__attribute__((noinline)) void switchToBank2(void) { + __asm__ volatile ("sep #0x20\n.byte 0xa9,0x02\npha\nplb\nrep #0x20\n"); +} +static uint32_t crc32(const uint8_t *buf, unsigned int len) { + uint32_t crc = 0xFFFFFFFFul; + for (unsigned int i = 0; i < len; i++) { + crc = crc ^ (uint32_t)buf[i]; + for (int j = 0; j < 8; j++) { + uint32_t mask = -(crc & 1ul); + crc = (crc >> 1) ^ (0xEDB88320ul & mask); + } + } + return ~crc; +} +int main(void) { + const char *s = "123456789"; + unsigned int n = 0; + while (s[n]) n++; + uint32_t c = crc32((const uint8_t *)s, n); + switchToBank2(); + *(volatile uint32_t *)0x5000 = c; + while (1) {} +} +EOF + "$CLANG" --target=w65816 -O2 -ffunction-sections -c \ + "$cCrFile" -o "$oCrFile" + "$PROJECT_ROOT/tools/link816" -o "$binCrFile" --text-base 0x1000 \ + "$oCrt0F" "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oQsortF" \ + "$oExtrasF" "$oStrtokF" "$oMathF" "$oSfF" "$oSdF" \ + "$oLibgccFile" "$oCrFile" >/dev/null 2>&1 + if ! bash "$PROJECT_ROOT/scripts/runInMame.sh" "$binCrFile" --check \ + 0x025000=3926 0x025002=cbf4 >/dev/null 2>&1; then + die "MAME: CRC32 != 0xCBF43926 (#89)" + fi + rm -f "$cCrFile" "$oCrFile" "$binCrFile" + + log "check: MAME runs brainfuck \"...AB...\" interpreter (#90)" + cBfFile="$(mktemp --suffix=.c)" + oBfFile="$(mktemp --suffix=.o)" + binBfFile="$(mktemp --suffix=.bin)" + cat > "$cBfFile" <<'EOF' +typedef unsigned char u8; +__attribute__((noinline)) void switchToBank2(void) { + __asm__ volatile ("sep #0x20\n.byte 0xa9,0x02\npha\nplb\nrep #0x20\n"); +} +#define TAPE_SIZE 32 +static u8 g_tape[TAPE_SIZE]; +static u8 g_out[8]; +static int g_outIdx; +__attribute__((noinline,optnone)) +static void runBf(const char *prog) { + g_outIdx = 0; + for (int i = 0; i < TAPE_SIZE; i++) g_tape[i] = 0; + int dp = 0; int pc = 0; + while (prog[pc]) { + char c = prog[pc]; + if (c == '+') g_tape[dp]++; + else if (c == '-') g_tape[dp]--; + else if (c == '>') dp++; + else if (c == '<') dp--; + else if (c == '.') { if (g_outIdx < 8) g_out[g_outIdx++] = g_tape[dp]; } + else if (c == '[') { + if (g_tape[dp] == 0) { + int d = 1; + while (d > 0) { pc++; if (prog[pc]=='[') d++; else if (prog[pc]==']') d--; } + } + } else if (c == ']') { + if (g_tape[dp] != 0) { + int d = 1; + while (d > 0) { pc--; if (prog[pc]==']') d++; else if (prog[pc]=='[') d--; } + } + } + pc++; + } +} +int main(void) { + runBf("++++++++[>++++++++<-]>+.+."); + // Capture g_out values BEFORE bank switch — after the switch DBR=2 + // and bank-0 globals can't be read absolutely. + unsigned short out01 = (unsigned short)g_out[0] | ((unsigned short)g_out[1] << 8); + switchToBank2(); + *(volatile unsigned short *)0x5000 = out01; + while (1) {} +} +EOF + "$CLANG" --target=w65816 -O2 -ffunction-sections -c \ + "$cBfFile" -o "$oBfFile" + "$PROJECT_ROOT/tools/link816" -o "$binBfFile" --text-base 0x1000 \ + "$oCrt0F" "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oQsortF" \ + "$oExtrasF" "$oStrtokF" "$oMathF" "$oSfF" "$oSdF" \ + "$oLibgccFile" "$oBfFile" >/dev/null 2>&1 + if ! bash "$PROJECT_ROOT/scripts/runInMame.sh" "$binBfFile" --check \ + 0x025000=4241 >/dev/null 2>&1; then + die "MAME: brainfuck 'AB' output != 0x4241 (#90)" + fi + rm -f "$cBfFile" "$oBfFile" "$binBfFile" + log "check: MAME runs sqrt/pow + sin/cos/exp/log + strpbrk/spn/cspn (#81 + #82 + #83)" cTrFile="$(mktemp --suffix=.c)" oTrFile="$(mktemp --suffix=.o)"