Checkpoint.

This commit is contained in:
Scott Duensing 2026-04-30 22:04:47 -05:00
parent e805911e18
commit 77202fb7f7
4 changed files with 179 additions and 10 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)"