Far pointer code is now more efficient. More code gen errors fixed.

This commit is contained in:
Scott Duensing 2026-09-28 21:04:28 -05:00
parent 9ca7f1d0de
commit deb1c96f73
25 changed files with 2273 additions and 133 deletions

View file

@ -4,16 +4,18 @@
// the program must run to the end AND read the array back as all-zero. // the program must run to the end AND read the array back as all-zero.
#include <stdint.h> #include <stdint.h>
// ~5.75 KB. With the current GNO libc the runtime BSS base already sits at // 4 KB. The GNO runtime's code already pushes the BSS base to ~0xD000 (well
// ~0xD000 (well above the old ~0xB100 heartbeat threshold), so this array's job // above the old ~0xB100 heartbeat threshold), so this array only has to keep
// is to keep __bss_end large (near the bank-0 top) while still fitting bank 0. // __bss_end large while the whole BSS still fits under the bank-0 top: the
static volatile uint8_t bigBss[0x1700]; // runtime's own BSS grows, and at 0x1700 the link overflowed by 236 bytes.
#define BIG_BSS_BYTES 0x1000u
static volatile uint8_t bigBss[BIG_BSS_BYTES];
int main(void) { int main(void) {
*(volatile uint16_t *)0x025010UL = 0xBEEF; *(volatile uint16_t *)0x025010UL = 0xBEEF;
uint16_t zeroed = 1; uint16_t zeroed = 1;
unsigned i; unsigned i;
for (i = 0; i < 0x1700; i++) { for (i = 0; i < BIG_BSS_BYTES; i++) {
if (bigBss[i] != 0) { if (bigBss[i] != 0) {
zeroed = 0; zeroed = 0;
break; break;
@ -21,7 +23,7 @@ int main(void) {
} }
*(volatile uint16_t *)0x025012UL = zeroed ? 0x0001 : 0xDEAD; *(volatile uint16_t *)0x025012UL = zeroed ? 0x0001 : 0xDEAD;
bigBss[0] = 1; // retain bigBss[0] = 1; // retain
bigBss[0x16FF] = 2; bigBss[BIG_BSS_BYTES - 1u] = 2;
*(volatile uint16_t *)0x025000UL = 0xC0DE; *(volatile uint16_t *)0x025000UL = 0xC0DE;
for (volatile unsigned long j = 0; j < 600000UL; j++) { for (volatile unsigned long j = 0; j < 600000UL; j++) {
} }

Binary file not shown.

View file

@ -165,15 +165,10 @@ __start:
; 3. returns whatever main returned. ; 3. returns whatever main returned.
jsl __gnoStartup jsl __gnoStartup
; Run global dtors registered via __cxa_atexit (LIFO order) before ; ---- Terminate via exit -> _exit (libcGno.c) ----
; QUIT. A holds main()'s return value (exit status), which _exit ; __gnoStartup returned with A = main()'s exit status. exit() (libc.c)
; consumes — preserve it across the call. ; runs the atexit handlers, then the C++ dtors registered through
pha ; __cxa_atexit, then _exit, which issues
jsl __run_cxa_atexit
pla
; ---- Terminate via _exit (libcGno.c) ----
; __gnoStartup returned with A = main()'s exit status. _exit issues
; GS/OS QUIT through GNO's INLINE dispatch (__gnoGsosCall), which is ; GS/OS QUIT through GNO's INLINE dispatch (__gnoGsosCall), which is
; REQUIRED under GNO: the old stack-based QUIT here (ldx #$2029 ; ; REQUIRED under GNO: the old stack-based QUIT here (ldx #$2029 ;
; jsl $e100a8 with the pblock pushed) was misread by GNO's OurGSOS ; jsl $e100a8 with the pblock pushed) was misread by GNO's OurGSOS
@ -181,9 +176,9 @@ __start:
; bytes after the jsl, not the stack — so the QUIT was mis-dispatched, ; bytes after the jsl, not the stack — so the QUIT was mis-dispatched,
; the process was left restartable, and GNO RE-RAN main() a second ; the process was left restartable, and GNO RE-RAN main() a second
; time (silently draining redirected stdin on the first pass). ; time (silently draining redirected stdin on the first pass).
jsl _exit jsl exit
; _exit does not return. If it ever does, spin rather than fall ; exit does not return. If it ever does, spin rather than fall
; into a BRK (headless MAME mis-vectors BRK to $0000, which looks ; into a BRK (headless MAME mis-vectors BRK to $0000, which looks
; like a wild crash) or off the end of the segment. ; like a wild crash) or off the end of the segment.
.Lhang: .Lhang:

View file

@ -6,9 +6,10 @@
; - No language-card RAM enable (GS/OS configures memory). ; - No language-card RAM enable (GS/OS configures memory).
; - No stack base reset (GS/OS allocated and set our SP). ; - No stack base reset (GS/OS allocated and set our SP).
; - Honors GS/OS's DBR=our-bank, DP=allocated-page setup. ; - Honors GS/OS's DBR=our-bank, DP=allocated-page setup.
; - On main() return, calls GS/OS QUIT(pcount=0) to return control to ; - On main() return, calls exit(), which runs the atexit handlers and
; the launching program (Finder / shell / boot chain) — the standard ; C++ dtors and then _exit (below): GS/OS QUIT(pcount=0), returning
; GS/OS application exit. ; control to the launching program (Finder / shell / boot chain) --
; the standard GS/OS application exit.
; ;
; Entry from the System Loader (per Apple IIgs Toolbox Reference): ; Entry from the System Loader (per Apple IIgs Toolbox Reference):
; E=0 (native), M=0 (16-bit accumulator), X=0 (16-bit index) ; E=0 (native), M=0 (16-bit accumulator), X=0 (16-bit index)
@ -154,35 +155,51 @@ __start:
rep #0x30 rep #0x30
jsl main jsl main
; Run global dtors registered via __cxa_atexit (LIFO order) before ; Returning from main is exit(main's value) (C99 5.1.2.2.3): exit runs
; QUIT. A holds main()'s return value; QUIT itself doesn't consume ; the atexit handlers, then the C++ static dtors registered through
; it (we set up the parm block immediately below), but keep the ; __cxa_atexit, then _exit below. A holds main()'s return value, which
; save/restore for parity with crt0Gno. ; is exit's argument.
pha jsl exit
jsl __run_cxa_atexit
pla
; ---- QUIT (pcount=0): return to the launching program ---------- ; exit does not return. Spin rather than fall into what follows.
; The standard GS/OS application exit. A pcount=0 parm block (just
; a zero pcount word) tells QUIT to return control to whoever
; launched us (Finder, a shell, the boot chain) with default flags
; (non-restartable) — no hardcoded chain path needed. A program
; that wants to chain elsewhere can provide its own _exit/crt0.
rep #0x30
stz 0x80 ; pcount = 0 (parm block at DP $0080, bank 0)
tdc
clc
adc #0x80
pha ; parm-block ptr: low 16 (offset)
pea 0 ; parm-block ptr: high 16 (bank 0)
ldx #0x2029 ; QUIT class-1 call number
jsl 0xe100a8 ; GS/OS dispatcher
; QUIT only returns on failure. Clean up the pushed pointer and
; spin (don't fall into a BRK, which headless MAME mis-vectors).
pla
pla
.Lhang: .Lhang:
bra .Lhang bra .Lhang
.size __start, . - __start .size __start, . - __start
; ---------------------------------------------------------------------
; void _exit(int status) -- GS/OS QUIT (pcount=0): return control to
; whoever launched us (Finder, a shell, the boot chain) with default
; flags (non-restartable). The status is not passed on.
;
; GS/OS takes class-1 calls in the INLINE form (see iigsGsos.s): the call
; number and the parm-block pointer follow the jsl, and the dispatcher
; returns past them. This used to push the pointer and load the call
; number into X for the same $E100A8 entry, so GS/OS read the code after
; the jsl as the call, failed, and returned into the middle of the next
; instructions -- every program that returned from main crashed into the
; monitor (JoeyLib's LLVM816-ASKS #6). The parm block is a zero pcount
; word at DP $80 (bank 0); its address is patched into the inline slot,
; like gsosDispatch does.
; ---------------------------------------------------------------------
.globl _exit
_exit:
rep #0x30
phk
plb ; DBR := PBR: the stores below patch this code
stz 0x80 ; pcount = 0
tdc
clc
adc #0x80
sta .Lquit_pb ; parm-block pointer, low 16
stz .Lquit_pb+2 ; bank 0, pad 0
jsl 0xe100a8
.word 0x2029 ; QUIT
.Lquit_pb:
.long 0
; QUIT only returns on failure. Spin (a BRK is mis-vectored by
; headless MAME and reads as a wild crash).
.Lquit_failed:
bra .Lquit_failed
.size _exit, . - _exit

View file

@ -806,33 +806,33 @@ void *realloc(void *ptr, size_t n) {
// ---- atexit / exit ---- // ---- atexit / exit ----
// //
// Standard exit() halts via BRK after running any registered atexit // exit() runs the atexit handlers (last registered first), then the C++
// handler. Programs running under the IIgs runtime typically would // static destructors registered through __cxa_atexit, then _exit.
// call back into GS/OS Quit; here we just wedge the CPU. Single-slot // Returning from main is the same exit: the GS/OS and GNO crt0s call exit
// atexit (the storage and registration function are below). // with main's value. _exit is the platform's way out -- crt0Gsos.s issues
// GS/OS QUIT, libcGno.c GNO's QUIT, and the weak fallback in libgcc.s
// halts bare metal (BRK so MAME's debugger catches it, then spin).
typedef void (*AtexitFn)(void); typedef void (*AtexitFn)(void);
static AtexitFn __atexitFn = (AtexitFn)0;
// BRK $00 then spin -- halts a 65816 in BRK so MAME's debugger catches // C99 7.20.4.2 promises at least 32 registrations.
// it; the spin loop guards against the (rare) case where BRK returns. #define ATEXIT_MAX 32
static void __halt(void) __attribute__((noreturn)); static AtexitFn __atexitFns[ATEXIT_MAX];
static void __halt(void) { static unsigned int __atexitCount = 0;
__asm__ volatile (".byte 0x00, 0x00");
while (1) {} void _exit(int code) __attribute__((noreturn));
} void __run_cxa_atexit(void);
void exit(int code) { void exit(int code) {
(void)code; // Pop before calling, so a handler that registers another one has it
// C99 7.20.4.3: exit() must invoke registered atexit handlers in // run next and none runs twice.
// reverse-registration order before terminating. while (__atexitCount > 0) {
if (__atexitFn) { __atexitCount--;
AtexitFn fn = __atexitFn; __atexitFns[__atexitCount]();
__atexitFn = (AtexitFn)0; // prevent re-entry if fn calls exit
fn();
} }
__halt(); __run_cxa_atexit();
_exit(code);
} }
// ---- errno ---- // ---- errno ----
@ -1447,10 +1447,11 @@ void abort(void) {
exit(127); exit(127);
} }
// ---- atexit (single slot; storage + exit() invocation above) ---- // ---- atexit (storage + exit() above) ----
int atexit(AtexitFn fn) { int atexit(AtexitFn fn) {
if (__atexitFn) return -1; if (__atexitCount >= ATEXIT_MAX) return -1;
__atexitFn = fn; __atexitFns[__atexitCount] = fn;
__atexitCount++;
return 0; return 0;
} }
@ -1458,24 +1459,22 @@ int atexit(AtexitFn fn) {
// //
// _Exit terminates without invoking atexit handlers (unlike exit). // _Exit terminates without invoking atexit handlers (unlike exit).
// quick_exit terminates after invoking at_quick_exit handlers (a // quick_exit terminates after invoking at_quick_exit handlers (a
// separate chain from atexit). We share the single-slot pattern // separate chain from atexit). at_quick_exit keeps a single slot;
// with atexit — single-shot handler, second registration fails. // a second registration fails.
static AtexitFn __quickFn = (AtexitFn)0; static AtexitFn __quickFn = (AtexitFn)0;
void _Exit(int code) { void _Exit(int code) {
(void)code; _exit(code);
__halt();
} }
void quick_exit(int code) { void quick_exit(int code) {
(void)code;
if (__quickFn) { if (__quickFn) {
AtexitFn fn = __quickFn; AtexitFn fn = __quickFn;
__quickFn = (AtexitFn)0; __quickFn = (AtexitFn)0;
fn(); fn();
} }
__halt(); _exit(code);
} }
int at_quick_exit(AtexitFn fn) { int at_quick_exit(AtexitFn fn) {

View file

@ -100,6 +100,29 @@ __jsl_indir:
__run_cxa_atexit: __run_cxa_atexit:
rtl rtl
; --------------------------------------------------------------------
; exit / _exit -- weak fallbacks.
;
; crt0Gsos.s and crt0Gno.s end a program by calling exit() with main()'s
; value in A. libc.c has the real exit (atexit handlers, then C++ dtors,
; then _exit); this weak one serves programs that link no libc: dtors,
; then _exit. _exit is the platform's way out: crt0Gsos.s (GS/OS QUIT)
; and libcGno.c (GNO QUIT) override the weak halt below, which is bare
; metal's -- BRK so MAME's debugger catches it, then spin.
; --------------------------------------------------------------------
.weak exit
exit:
pha ; status
jsl __run_cxa_atexit
pla
jsl _exit ; does not return
.weak _exit
_exit:
.byte 0x00, 0x00 ; BRK
.L_exitSpin:
bra .L_exitSpin
; -------------------------------------------------------------------- ; --------------------------------------------------------------------
; __heapLock / __heapUnlock -- weak no-op heap-serialization hooks. ; __heapLock / __heapUnlock -- weak no-op heap-serialization hooks.
; ;

View file

@ -1,8 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# runFileCheckTests.sh - run W65816 backend regression tests. # runFileCheckTests.sh - run W65816 backend regression tests.
# #
# Walks src/llvm/test/CodeGen/W65816/*.ll and for each: # Walks src/llvm/test/CodeGen/W65816/*.ll and *.mir and for each:
# - reads RUN: lines from the test header (lit-compatible syntax) # - reads RUN: lines from the test header (lit-compatible syntax; `;`
# comments in .ll files, `#` comments in .mir files)
# - executes them with %s -> the test path # - executes them with %s -> the test path
# - any non-zero exit fails the run. # - any non-zero exit fails the run.
# #
@ -32,7 +33,7 @@ if [ $# -gt 0 ]; then
files+=("$TEST_DIR/$f") files+=("$TEST_DIR/$f")
done done
else else
mapfile -t files < <(find "$TEST_DIR" -maxdepth 1 -name '*.ll' | sort) mapfile -t files < <(find "$TEST_DIR" -maxdepth 1 \( -name '*.ll' -o -name '*.mir' \) | sort)
fi fi
pass=0 pass=0
@ -42,7 +43,7 @@ for f in "${files[@]}"; do
[ -f "$f" ] || { echo "skip missing: $f"; continue; } [ -f "$f" ] || { echo "skip missing: $f"; continue; }
name="$(basename "$f")" name="$(basename "$f")"
runs=$(grep -E '^[[:space:]]*;[[:space:]]*RUN:' "$f" | sed -E 's/^[[:space:]]*;[[:space:]]*RUN:[[:space:]]*//') runs=$(grep -E '^[[:space:]]*[;#][[:space:]]*RUN:' "$f" | sed -E 's/^[[:space:]]*[;#][[:space:]]*RUN:[[:space:]]*//')
if [ -z "$runs" ]; then if [ -z "$runs" ]; then
echo "SKIP $name (no RUN: line)" echo "SKIP $name (no RUN: line)"
continue continue

View file

@ -6713,6 +6713,68 @@ EOF
"$omfGsf" "$testFileGsf" "$omfGsf" "$testFileGsf"
fi fi
# GS/OS program exit: returning from main is exit(main's value), which must
# run the atexit handlers (last registered first), then the C++ dtors, then
# QUIT back to the Finder. crt0Gsos used to issue QUIT with the pointer
# pushed onto the stack at the INLINE entry $E100A8, so GS/OS misread the
# call and returned into the middle of the following code: every program
# that returned from main crashed into the monitor (text mode), and the
# atexit handlers never ran at all. Three handlers fold their ids into $70
# (LIFO gives 0x39); main marks $71; NEWVIDEO ($C029) reads 0xC1 only if
# the Finder's Super Hi-Res desktop is back (the monitor leaves 0x01).
# Skips under SMOKE_SKIP_GSOSEXIT=1.
if [ "${SMOKE_SKIP_GSOSEXIT:-0}" != "1" ] \
&& [ -x "$CLANG" ] && [ -x "$CADIUS" ] && [ -f "$SYSDISK" ] \
&& command -v mame >/dev/null 2>&1; then
log "check: GS/OS exit runs atexit handlers LIFO and QUITs to the Finder"
cGsx="$(mktemp --suffix=.c)"
oGsx="$(mktemp --suffix=.o)"
binGsx="$(mktemp --suffix=.bin)"
mapGsx="$(mktemp --suffix=.map)"
relGsx="$(mktemp --suffix=.reloc)"
omfGsx="$(mktemp --suffix=.omf)"
cat > "$cGsx" <<'EOF'
#include <stdlib.h>
#define MB ((volatile unsigned char *)0x70)
static void a(void) { MB[0] = (unsigned char)((MB[0] << 2) | 1u); }
static void b(void) { MB[0] = (unsigned char)((MB[0] << 2) | 2u); }
static void c(void) { MB[0] = (unsigned char)((MB[0] << 2) | 3u); }
int main(void) {
MB[0] = 0u;
atexit(a);
atexit(b);
atexit(c);
MB[1] = 0xA5u;
return 0;
}
EOF
"$CLANG" --target=w65816 -I"$PROJECT_ROOT/runtime/include" -O2 -ffunction-sections -c \
"$cGsx" -o "$oGsx"
"$PROJECT_ROOT/tools/link816" -o "$binGsx" --text-base 0x1000 \
--map "$mapGsx" --reloc-out "$relGsx" \
"$PROJECT_ROOT/runtime/crt0Gsos.o" "$oGsx" \
"$PROJECT_ROOT/runtime/libc.o" \
"$PROJECT_ROOT/runtime/snprintf.o" \
"$PROJECT_ROOT/runtime/extras.o" \
"$PROJECT_ROOT/runtime/softFloat.o" \
"$PROJECT_ROOT/runtime/softDouble.o" \
"$PROJECT_ROOT/runtime/iigsGsos.o" \
"$PROJECT_ROOT/runtime/iigsToolbox.o" "$PROJECT_ROOT/runtime/iigsToolboxExtra.o" \
"$PROJECT_ROOT/runtime/libgcc.o" >/dev/null \
|| die "GS/OS exit smoke: link failed"
"$PROJECT_ROOT/tools/omfEmit" --input "$binGsx" --map "$mapGsx" \
--base 0x1000 --entry __start --output "$omfGsx" \
--name HELLO --expressload --relocs "$relGsx" >/dev/null 2>&1
[ -s "$omfGsx" ] || die "GS/OS exit smoke: omfEmit produced empty/missing OMF"
if ! bash "$PROJECT_ROOT/scripts/runViaFinder.sh" "$omfGsx" \
--check 0x70=0x39 --check 0x71=0xa5 --check 0xc029=0xc1 >/dev/null 2>&1; then
bash "$PROJECT_ROOT/scripts/runViaFinder.sh" "$omfGsx" \
--check 0x70=0x39 --check 0x71=0xa5 --check 0xc029=0xc1 2>&1 | tail -5 >&2
die "GS/OS exit smoke FAILED (atexit order / QUIT to the Finder)"
fi
rm -f "$cGsx" "$oGsx" "$binGsx" "$mapGsx" "$relGsx" "$omfGsx"
fi
# Phase C.2: cINTERSEG cross-segment JSL under real GS/OS Loader. # Phase C.2: cINTERSEG cross-segment JSL under real GS/OS Loader.
# Links a 2-user-seg program (main in seg 1, pinnedFn in seg 2 via # Links a 2-user-seg program (main in seg 1, pinnedFn in seg 2 via
# __attribute__((section(".text.seg2")))), uses --multi-seg-expressload # __attribute__((section(".text.seg2")))), uses --multi-seg-expressload

View file

@ -764,22 +764,7 @@ static void applyTextReloc(std::vector<uint8_t> &buf, uint32_t off,
break; break;
} }
} }
if (!gMultiSegRelocMode) {
applyReloc(buf, off, patchAddr, target, rtype, symName);
return;
}
if (patchSeg == 0 || patchSeg > segments.size()) {
applyReloc(buf, off, patchAddr, target, rtype, symName);
return;
}
const TextSeg &pseg = segments[patchSeg - 1];
uint32_t targetSeg = findSegContaining(target, segments); uint32_t targetSeg = findSegContaining(target, segments);
gDbgTextCalls++;
// TEMP TRACE (RetroNet hunt): decisions for the mirror-pack refs.
if (symName.find("gRnInPack") != std::string::npos) {
std::fprintf(stderr, "TRACE %s rtype=%u patchSeg=%u targetSeg=%u target=0x%X rec=%d\n",
symName.c_str(), rtype, patchSeg, targetSeg, target, (int)gRecordSites);
}
// Cross-segment FUNCTION POINTER (IMM16 &func to a function in seg2+): // Cross-segment FUNCTION POINTER (IMM16 &func to a function in seg2+):
// clang forms the pointer as func's 16-bit offset + the $00BE seg1 bank, // clang forms the pointer as func's 16-bit offset + the $00BE seg1 bank,
// which is wrong for a function in another bank. Redirect the IMM16 to // which is wrong for a function in another bank. Redirect the IMM16 to
@ -790,6 +775,11 @@ static void applyTextReloc(std::vector<uint8_t> &buf, uint32_t off,
// non-seg1 bank) is the real key. No cRELOC: the baked value is a seg1 // non-seg1 bank) is the real key. No cRELOC: the baked value is a seg1
// offset and seg1 (KIND=0x1000) always bank-aligns, so the low16 is // offset and seg1 (KIND=0x1000) always bank-aligns, so the low16 is
// already the runtime offset (matches the --text-base 0 IMM16 contract). // already the runtime offset (matches the --text-base 0 IMM16 contract).
// Any multi-segment layout needs this, not just the GS/OS ExpressLoad
// one: a bare-metal --segment-cap link (fixed bank bases, no Loader)
// forms &func the same way, and without the trampoline a pointer to a
// seg2+ function is called in bank 0 (Lua's allocator, the first thing
// lua_newstate calls, ran into vfprintf and the machine went wild).
if (rtype == R_W65816_IMM16 && targetSeg > 1) { if (rtype == R_W65816_IMM16 && targetSeg > 1) {
uint32_t targetOff = target - segments[targetSeg - 1].base; uint32_t targetOff = target - segments[targetSeg - 1].base;
auto tIt = gTrampMap.find(TrampKey{targetSeg, targetOff}); auto tIt = gTrampMap.find(TrampKey{targetSeg, targetOff});
@ -800,6 +790,16 @@ static void applyTextReloc(std::vector<uint8_t> &buf, uint32_t off,
return; return;
} }
} }
if (!gMultiSegRelocMode) {
applyReloc(buf, off, patchAddr, target, rtype, symName);
return;
}
if (patchSeg == 0 || patchSeg > segments.size()) {
applyReloc(buf, off, patchAddr, target, rtype, symName);
return;
}
const TextSeg &pseg = segments[patchSeg - 1];
gDbgTextCalls++;
// Non-text target (rodata / bss / init_array / weak-zero) — use // Non-text target (rodata / bss / init_array / weak-zero) — use
// the legacy applyReloc path (which records via recordCRelocSite // the legacy applyReloc path (which records via recordCRelocSite
// into gImm24Sites for back-compat AND also pushes into the new // into gImm24Sites for back-compat AND also pushes into the new
@ -821,11 +821,16 @@ static void applyTextReloc(std::vector<uint8_t> &buf, uint32_t off,
uint8_t cnt = (rtype == R_W65816_IMM24 || rtype == R_W65816_DATA32) ? 3 : 2; uint8_t cnt = (rtype == R_W65816_IMM24 || rtype == R_W65816_DATA32) ? 3 : 2;
for (uint8_t i = 0; i < cnt; ++i) for (uint8_t i = 0; i < cnt; ++i)
buf[off + i] = 0; buf[off + i] = 0;
// The Loader adds seg 1's load address to an OFFSET within seg 1,
// so subtract seg 1's link base. (Passing the link address only
// worked at --text-base 0; at 0x1000 every such data reference
// landed 0x1000 past its variable -- smoke Phase C.2 read an
// initialised global as 0.)
InterImm24Site s; InterImm24Site s;
s.patchSeg = static_cast<uint16_t>(patchSeg); s.patchSeg = static_cast<uint16_t>(patchSeg);
s.patchOff = static_cast<uint16_t>(patchAddr - pseg.base); s.patchOff = static_cast<uint16_t>(patchAddr - pseg.base);
s.targetSeg = 1; s.targetSeg = 1;
s.targetOff = static_cast<uint16_t>(target); s.targetOff = static_cast<uint16_t>(target - segments[0].base);
s.byteCnt = cnt; s.byteCnt = cnt;
s.bitShift = (rtype == R_W65816_BANK16) ? 16 : 0; s.bitShift = (rtype == R_W65816_BANK16) ? 16 : 0;
gInterImm24Sites.push_back(s); gInterImm24Sites.push_back(s);
@ -1370,7 +1375,7 @@ struct Linker {
} }
} }
// ---- TRAMPOLINE pre-pass (multi-seg only). Find every IMM16 // ---- TRAMPOLINE pre-pass (multi-segment text only). Find every IMM16
// address-take of a function the layout placed in a text seg N>1 (a // address-take of a function the layout placed in a text seg N>1 (a
// different bank than seg1, whose bank is what $00BE/&func will use) // different bank than seg1, whose bank is what $00BE/&func will use)
// and reserve one `JML func` trampoline in seg1 per distinct target. // and reserve one `JML func` trampoline in seg1 per distinct target.
@ -1382,7 +1387,7 @@ struct Linker {
gTrampMap.clear(); gTrampMap.clear();
gTrampList.clear(); gTrampList.clear();
gTrampBase = 0; gTrampBase = 0;
if (gMultiSegRelocMode) { if (segSizes.size() > 1) {
std::map<std::string, TrampKey> symSeg; // name -> (seg, off) std::map<std::string, TrampKey> symSeg; // name -> (seg, off)
std::map<std::string, bool> symStrong; std::map<std::string, bool> symStrong;
for (size_t fi = 0; fi < objs.size(); ++fi) { for (size_t fi = 0; fi < objs.size(); ++fi) {
@ -1902,16 +1907,24 @@ struct Linker {
} }
} }
// ---- Emit the cross-seg trampoline table at the end of seg 1's text. // ---- Emit the cross-seg trampoline table at the end of seg 1's text.
// Each entry is `JML func` (0x5C + 24-bit target, zeroed); its cINTERSEG // Each entry is `JML func` (0x5C + 24-bit target). Under ExpressLoad
// site makes the Loader patch the target to func's runtime bank:offset. // the target is zeroed and its cINTERSEG site makes the Loader patch
// func's runtime bank:offset; a bare-metal link's segment bases are
// final, so the absolute target is baked in.
// segTextBufs[0] holds exactly gTrampBase bytes here (all seg-1 text), // segTextBufs[0] holds exactly gTrampBase bytes here (all seg-1 text),
// so entry i lands at offset gTrampBase + 4*i, and applyTextReloc // so entry i lands at offset gTrampBase + 4*i, and applyTextReloc
// redirects each cross-seg IMM16 &func to that offset. // redirects each cross-seg IMM16 &func to that offset.
if (!gTrampList.empty()) { if (!gTrampList.empty()) {
auto &b0 = segTextBufs[0]; auto &b0 = segTextBufs[0];
for (size_t i = 0; i < gTrampList.size(); ++i) { for (size_t i = 0; i < gTrampList.size(); ++i) {
uint32_t tgt = gMultiSegRelocMode
? 0u
: L.segments[gTrampList[i].seg - 1].base
+ gTrampList[i].off;
b0.push_back(0x5C); // JML abs-long opcode b0.push_back(0x5C); // JML abs-long opcode
b0.push_back(0); b0.push_back(0); b0.push_back(0); // 24-bit tgt b0.push_back(static_cast<uint8_t>(tgt & 0xFF));
b0.push_back(static_cast<uint8_t>((tgt >> 8) & 0xFF));
b0.push_back(static_cast<uint8_t>((tgt >> 16) & 0xFF));
if (gRecordSites) { if (gRecordSites) {
InterImm24Site s; InterImm24Site s;
s.patchSeg = 1; s.patchSeg = 1;

View file

@ -26,6 +26,7 @@ add_llvm_target(W65816CodeGen
W65816Subtarget.cpp W65816Subtarget.cpp
W65816StackSlotCleanup.cpp W65816StackSlotCleanup.cpp
W65816SepRepCleanup.cpp W65816SepRepCleanup.cpp
W65816FarPtrStageReuse.cpp
W65816BranchExpand.cpp W65816BranchExpand.cpp
W65816TiedDefSpill.cpp W65816TiedDefSpill.cpp
W65816ABridgeViaX.cpp W65816ABridgeViaX.cpp

View file

@ -66,6 +66,12 @@ FunctionPass *createW65816StackSlotCleanup();
// that cancel. See W65816SepRepCleanup.cpp. // that cancel. See W65816SepRepCleanup.cpp.
FunctionPass *createW65816SepRepCleanup(); FunctionPass *createW65816SepRepCleanup();
// Pre-emit pass (last): deletes far-pointer re-staging into DP $E0..$E3
// when the scratch already holds the same pointer, plus the loads,
// transfers and stack temporaries that only fed it. See
// W65816FarPtrStageReuse.cpp.
FunctionPass *createW65816FarPtrStageReuse();
// Pre-emit pass: expands long conditional branches into the // Pre-emit pass: expands long conditional branches into the
// `INVERTED_Bxx skip ; BRA target ; skip:` pattern when the byte // `INVERTED_Bxx skip ; BRA target ; skip:` pattern when the byte
// distance to the target exceeds the +/-128 reach of an 8-bit-PCREL // distance to the target exceeds the +/-128 reach of an 8-bit-PCREL
@ -195,6 +201,7 @@ void initializeW65816DAGToDAGISelLegacyPass(PassRegistry &);
void initializeW65816StackSlotCleanupPass(PassRegistry &); void initializeW65816StackSlotCleanupPass(PassRegistry &);
void initializeW65816I32IncFoldPass(PassRegistry &); void initializeW65816I32IncFoldPass(PassRegistry &);
void initializeW65816SepRepCleanupPass(PassRegistry &); void initializeW65816SepRepCleanupPass(PassRegistry &);
void initializeW65816FarPtrStageReusePass(PassRegistry &);
void initializeW65816BranchExpandPass(PassRegistry &); void initializeW65816BranchExpandPass(PassRegistry &);
void initializeW65816TiedDefSpillPass(PassRegistry &); void initializeW65816TiedDefSpillPass(PassRegistry &);
void initializeW65816ABridgeViaXPass(PassRegistry &); void initializeW65816ABridgeViaXPass(PassRegistry &);

File diff suppressed because it is too large Load diff

View file

@ -3018,6 +3018,22 @@ emitRROp(MachineInstr &MI, MachineBasicBlock *BB, unsigned StoreOp,
return BB; return BB;
} }
// The `[dp],Y` access a pointer-load pseudo expands to. A volatile byte
// load gets the atomic 8-bit LDB_DPIndLongY: the usual 16-bit load + mask
// also reads address+1, a phantom read of the next register when the
// pointer is I/O. Everything else keeps the cheaper 16-bit LDA.
static bool isVolatileByteLoad(const MachineInstr &MI) {
for (const MachineMemOperand *MMO : MI.memoperands())
if (MMO->isLoad() && MMO->isVolatile() &&
MMO->getSize() == LocationSize::precise(1))
return true;
return false;
}
static unsigned ptrLoadOpcode(const MachineInstr &MI) {
return isVolatileByteLoad(MI) ? W65816::LDB_DPIndLongY
: W65816::LDA_DPIndLongY;
}
MachineBasicBlock * MachineBasicBlock *
W65816TargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI, W65816TargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI,
MachineBasicBlock *BB) const { MachineBasicBlock *BB) const {
@ -3106,7 +3122,7 @@ W65816TargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI,
BuildMI(*BB, MI.getIterator(), DL, BuildMI(*BB, MI.getIterator(), DL,
TII.get(W65816::LDY_Imm16)).addImm(0); TII.get(W65816::LDY_Imm16)).addImm(0);
BuildMI(*BB, MI.getIterator(), DL, BuildMI(*BB, MI.getIterator(), DL,
TII.get(W65816::LDA_DPIndLongY)).addImm(0xE0); TII.get(ptrLoadOpcode(MI))).addImm(0xE0);
BuildMI(*BB, MI.getIterator(), DL, BuildMI(*BB, MI.getIterator(), DL,
TII.get(TargetOpcode::COPY), Dst).addReg(W65816::A); TII.get(TargetOpcode::COPY), Dst).addReg(W65816::A);
} else { } else {
@ -3361,7 +3377,8 @@ W65816TargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI,
// a STAfi of sub_lo and an LDAfi_indY/STAfi_indY deref via the // a STAfi of sub_lo and an LDAfi_indY/STAfi_indY deref via the
// 16-bit stack-rel-indirect-Y opcode (0xB3 / 0x93). ~4 instr per // 16-bit stack-rel-indirect-Y opcode (0xB3 / 0x93). ~4 instr per
// deref saved vs the heavy [dp],Y indirect-long path. // deref saved vs the heavy [dp],Y indirect-long path.
if (DbrSafePtrs) { // (Not for a volatile byte load: `lda (sr,s),y` is 16-bit too.)
if (DbrSafePtrs && !(IsLoad && isVolatileByteLoad(MI))) {
Register PtrLo = MRI.createVirtualRegister(&W65816::Wide16RegClass); Register PtrLo = MRI.createVirtualRegister(&W65816::Wide16RegClass);
if (PeelOff) { if (PeelOff) {
BuildMI(*BB, MI.getIterator(), DL, TII.get(TargetOpcode::COPY), PtrLo) BuildMI(*BB, MI.getIterator(), DL, TII.get(TargetOpcode::COPY), PtrLo)
@ -3602,7 +3619,7 @@ W65816TargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI,
BuildMI(*BB, MI.getIterator(), DL, BuildMI(*BB, MI.getIterator(), DL,
TII.get(W65816::LDY_Imm16)).addImm(PeelOff); TII.get(W65816::LDY_Imm16)).addImm(PeelOff);
BuildMI(*BB, MI.getIterator(), DL, BuildMI(*BB, MI.getIterator(), DL,
TII.get(W65816::LDA_DPIndLongY)).addImm(0xE0); TII.get(ptrLoadOpcode(MI))).addImm(0xE0);
BuildMI(*BB, MI.getIterator(), DL, BuildMI(*BB, MI.getIterator(), DL,
TII.get(TargetOpcode::COPY), Dst).addReg(W65816::A); TII.get(TargetOpcode::COPY), Dst).addReg(W65816::A);
} else { } else {
@ -3694,7 +3711,7 @@ W65816TargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI,
BuildMI(*BB, MI.getIterator(), DL, BuildMI(*BB, MI.getIterator(), DL,
TII.get(W65816::LDY_Imm16)).addImm(Off); TII.get(W65816::LDY_Imm16)).addImm(Off);
BuildMI(*BB, MI.getIterator(), DL, BuildMI(*BB, MI.getIterator(), DL,
TII.get(W65816::LDA_DPIndLongY)).addImm(0xE0); TII.get(ptrLoadOpcode(MI))).addImm(0xE0);
BuildMI(*BB, MI.getIterator(), DL, BuildMI(*BB, MI.getIterator(), DL,
TII.get(TargetOpcode::COPY), Dst).addReg(W65816::A); TII.get(TargetOpcode::COPY), Dst).addReg(W65816::A);
} else { } else {
@ -3770,7 +3787,7 @@ W65816TargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI,
BuildMI(*BB, MI.getIterator(), DL, BuildMI(*BB, MI.getIterator(), DL,
TII.get(W65816::LDY_Imm16)).addImm(0); TII.get(W65816::LDY_Imm16)).addImm(0);
BuildMI(*BB, MI.getIterator(), DL, BuildMI(*BB, MI.getIterator(), DL,
TII.get(W65816::LDA_DPIndLongY)).addImm(0xE0); TII.get(ptrLoadOpcode(MI))).addImm(0xE0);
BuildMI(*BB, MI.getIterator(), DL, BuildMI(*BB, MI.getIterator(), DL,
TII.get(TargetOpcode::COPY), Dst).addReg(W65816::A); TII.get(TargetOpcode::COPY), Dst).addReg(W65816::A);
} else { } else {
@ -3945,7 +3962,7 @@ W65816TargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI,
BuildMI(*BB, MI.getIterator(), DL, BuildMI(*BB, MI.getIterator(), DL,
TII.get(W65816::LDY_Imm16)).addImm(0); TII.get(W65816::LDY_Imm16)).addImm(0);
BuildMI(*BB, MI.getIterator(), DL, BuildMI(*BB, MI.getIterator(), DL,
TII.get(W65816::LDA_DPIndLongY)).addImm(0xE0); TII.get(ptrLoadOpcode(MI))).addImm(0xE0);
BuildMI(*BB, MI.getIterator(), DL, BuildMI(*BB, MI.getIterator(), DL,
TII.get(TargetOpcode::COPY), Dst).addReg(W65816::A); TII.get(TargetOpcode::COPY), Dst).addReg(W65816::A);
} else { } else {

View file

@ -25,9 +25,10 @@ using namespace llvm;
void W65816InstrInfo::anchor() {} void W65816InstrInfo::anchor() {}
// Expand STB_DPIndLongY -> SEP #$20 ; sta [dp],y ; REP #$20. Runs in the // Expand STB_DPIndLongY -> SEP #$20 ; sta [dp],y ; REP #$20 and
// LDB_DPIndLongY -> SEP #$20 ; lda [dp],y ; REP #$20. Runs in the
// post-RA pseudo-expansion pass (after the fast/greedy allocator, before // post-RA pseudo-expansion pass (after the fast/greedy allocator, before
// SepRepCleanup and the AsmPrinter). Keeping the byte store as a single // SepRepCleanup and the AsmPrinter). Keeping the byte access as a single
// pseudo through register allocation is what fixes the -O0 miscompile: the // pseudo through register allocation is what fixes the -O0 miscompile: the
// fast allocator used to wedge a 16-bit reload between a hand-emitted SEP // fast allocator used to wedge a 16-bit reload between a hand-emitted SEP
// and REP, running it in M=8 and truncating a live value. Expanding here // and REP, running it in M=8 and truncating a live value. Expanding here
@ -35,13 +36,22 @@ void W65816InstrInfo::anchor() {}
// REP coalescer and the AsmPrinter's LDAi8imm mode-tracking behave exactly // REP coalescer and the AsmPrinter's LDAi8imm mode-tracking behave exactly
// as they did for the old three-instruction form. // as they did for the old three-instruction form.
bool W65816InstrInfo::expandPostRAPseudo(MachineInstr &MI) const { bool W65816InstrInfo::expandPostRAPseudo(MachineInstr &MI) const {
if (MI.getOpcode() != W65816::STB_DPIndLongY) unsigned Access;
switch (MI.getOpcode()) {
case W65816::STB_DPIndLongY:
Access = W65816::STA_DPIndLongY;
break;
case W65816::LDB_DPIndLongY:
Access = W65816::LDA_DPIndLongY;
break;
default:
return false; return false;
}
MachineBasicBlock &MBB = *MI.getParent(); MachineBasicBlock &MBB = *MI.getParent();
DebugLoc DL = MI.getDebugLoc(); DebugLoc DL = MI.getDebugLoc();
int64_t Addr = MI.getOperand(0).getImm(); int64_t Addr = MI.getOperand(0).getImm();
BuildMI(MBB, MI, DL, get(W65816::SEP)).addImm(0x20); BuildMI(MBB, MI, DL, get(W65816::SEP)).addImm(0x20);
BuildMI(MBB, MI, DL, get(W65816::STA_DPIndLongY)).addImm(Addr); BuildMI(MBB, MI, DL, get(Access)).addImm(Addr);
BuildMI(MBB, MI, DL, get(W65816::REP)).addImm(0x20); BuildMI(MBB, MI, DL, get(W65816::REP)).addImm(0x20);
MI.eraseFromParent(); MI.eraseFromParent();
return true; return true;

View file

@ -1200,6 +1200,18 @@ def STB_DPIndLongY : W65816Pseudo<(outs), (ins addrDP:$addr),
"# STB_DPIndLongY [${addr}], y", []>; "# STB_DPIndLongY [${addr}], y", []>;
} }
// Atomic i8-load-through-[dp],Y for VOLATILE byte loads, expanded post-RA
// to `SEP #$20 ; lda [dp],y ; REP #$20` for the same reason as the store
// above. A byte load is otherwise a 16-bit `lda [dp],y` + mask, which also
// reads address+1: harmless for memory, but for I/O it is a phantom read of
// the neighbouring register (an IIgs SCC data read at $C03A popped channel
// A's receive FIFO at $C03B). A's high byte is left as it was: zextload
// still masks, anyext does not care.
let mayLoad = 1, hasSideEffects = 1, Uses = [Y], Defs = [A, P] in {
def LDB_DPIndLongY : W65816Pseudo<(outs), (ins addrDP:$addr),
"# LDB_DPIndLongY [${addr}], y", []>;
}
// Pointer access with constant offset. `(load (add ptr, $off))` and // Pointer access with constant offset. `(load (add ptr, $off))` and
// `(store val, (add ptr, $off))` come up for struct field access and // `(store val, (add ptr, $off))` come up for struct field access and
// array indexing with small constant offsets. Without these patterns, // array indexing with small constant offsets. Without these patterns,

View file

@ -988,7 +988,35 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) {
// (t != 0) instead of (i != 0) and runs forever. // (t != 0) instead of (i != 0) and runs forever.
Opc == W65816::TAX || Opc == W65816::TXA || Opc == W65816::TAX || Opc == W65816::TXA ||
Opc == W65816::TAY || Opc == W65816::TYA || Opc == W65816::TAY || Opc == W65816::TYA ||
Opc == W65816::TXY || Opc == W65816::TYX; Opc == W65816::TXY || Opc == W65816::TYX ||
Opc == W65816::TSC || Opc == W65816::TDC ||
Opc == W65816::TSX ||
// The byte-move and negate pseudos, for the same reason as the
// i8 logic ops above: all set N/Z, none models Defs=[P].
// SRL8A / SHL8A expand to XBA ; AND #imm. Live symptom: a
// loop's `if (started && ...) break` lowered to `cmp #1`, then
// five inlined `(uint8_t)(v >> 8)` mailbox byte splits, then
// `bne`. SRL8A was taken as THE test, nothing was wrapped, and
// the BNE read the last AND's Z, so the break never fired
// (JoeyLib's IIgs serial-loss probe, 2026-09-28).
Opc == W65816::SRL8A || Opc == W65816::SHL8A ||
Opc == W65816::XBA || Opc == W65816::XBA16 ||
Opc == W65816::NEGA16 || Opc == W65816::NEGA8 ||
// Every other load, pseudo or already lowered: a load is never
// the test here, any more than LDA_DP above. Custom inserters
// emit real addressing modes before this pass runs (the far-
// pointer load is LDA_DPIndLongY, or the atomic 8-bit
// LDB_DPIndLongY for a volatile byte).
Opc == W65816::LDB_DPIndLongY ||
Opc == W65816::LDAabs || Opc == W65816::LDA8abs ||
Opc == W65816::LDA8absX || Opc == W65816::LDA8long ||
Opc == W65816::LDA_AbsX || Opc == W65816::LDA_AbsY ||
Opc == W65816::LDA_DPX || Opc == W65816::LDA_DPInd ||
Opc == W65816::LDA_DPIndX || Opc == W65816::LDA_DPIndY ||
Opc == W65816::LDA_DPIndLong || Opc == W65816::LDA_DPIndLongY ||
Opc == W65816::LDA_LongX || Opc == W65816::LDA_Imm16_Bank ||
Opc == W65816::LDX_AbsY || Opc == W65816::LDX_DPY ||
Opc == W65816::LDY_AbsX || Opc == W65816::LDY_DPX;
}; };
auto isStackRel = [](unsigned Opc) { auto isStackRel = [](unsigned Opc) {
// Stack-relative ops read/write at S+disp. PHP decrements S by 1, // Stack-relative ops read/write at S+disp. PHP decrements S by 1,

View file

@ -50,6 +50,7 @@ LLVMInitializeW65816Target() {
initializeW65816DAGToDAGISelLegacyPass(PR); initializeW65816DAGToDAGISelLegacyPass(PR);
initializeW65816StackSlotCleanupPass(PR); initializeW65816StackSlotCleanupPass(PR);
initializeW65816SepRepCleanupPass(PR); initializeW65816SepRepCleanupPass(PR);
initializeW65816FarPtrStageReusePass(PR);
initializeW65816BranchExpandPass(PR); initializeW65816BranchExpandPass(PR);
initializeW65816TiedDefSpillPass(PR); initializeW65816TiedDefSpillPass(PR);
initializeW65816ABridgeViaXPass(PR); initializeW65816ABridgeViaXPass(PR);
@ -344,6 +345,10 @@ void W65816PassConfig::addPreEmitPass() {
if (!getenv("W65816_DISABLE_STACKOPT")) { if (!getenv("W65816_DISABLE_STACKOPT")) {
addPass(createW65816StackRelToImg()); addPass(createW65816StackRelToImg());
} }
// Last: only now is every instruction concrete (far frames decided,
// stack slots promoted), so the far-pointer staging into $E0..$E3 can be
// tracked across the whole function. Branch distances only shrink.
addPass(createW65816FarPtrStageReuse());
} }
MachineFunctionInfo *W65816TargetMachine::createMachineFunctionInfo( MachineFunctionInfo *W65816TargetMachine::createMachineFunctionInfo(

View file

@ -0,0 +1,465 @@
# Pin W65816FarPtrStageReuse (pre-emit): the far-pointer staging into DP
# $E0 (224, low word) / $E2 (226, bank word) is deleted only when both words
# provably already hold the value being stored, and the loads, transfers and
# stack temporaries that fed a deleted staging go with it.
#
# The staging shape is the one the LDAptr32 inserter leaves after RA:
# LDA_DP lo ; TAX ; LDA_DP bank ; STA_StackRel t ; TXA ; STA_DP 224 ;
# LDA_StackRel t ; STA_DP 226
#
# reuse_diamond: staged in the entry block, re-staged in both arms and
# at the join -> only the entry staging survives.
# kept_after_call: a call forgets everything -> re-staging kept.
# kept_other_ptr: a different source word -> kept.
# kept_src_rewritten: the source word is stored in between -> kept.
# kept_dpx_store: a DP-indexed store may hit any DP word -> kept.
# loop_invariant: pointer not written in the loop -> the in-loop staging
# goes (the preheader's still holds on the back edge).
# loop_variant: low word bumped every iteration -> its staging kept,
# the bank word's goes.
# nz_live: a load of the value A already holds stays when a
# branch reads its N/Z.
# stale_liveness: a redundant load at the top of the successor goes, and
# the definition in the predecessor that now reaches its
# user must stay (deleting both used to be one step).
# dse_dead_slot: a stack temporary nothing reads is deleted; a read one
# is kept.
# dse_escape: TSX takes a stack address -> no stack store is touched.
# ret_void: RTL reads only its implicit uses: a staging leftover
# (LDA ; TAX) before a void return is dead.
# ret_i32: RTL implicit $a, implicit $x: X is the high word, kept.
#
# RUN: llc -mtriple=w65816 -run-pass=w65816-farptr-reuse -o - %s | FileCheck %s
--- |
target triple = "w65816"
declare void @g()
define void @reuse_diamond() { ret void }
define void @kept_after_call() { ret void }
define void @kept_other_ptr() { ret void }
define void @kept_src_rewritten() { ret void }
define void @kept_dpx_store() { ret void }
define void @loop_invariant() { ret void }
define void @loop_variant() { ret void }
define void @nz_live() { ret void }
define void @stale_liveness() { ret void }
define void @dse_dead_slot() { ret void }
define void @dse_escape() { ret void }
define void @ret_void() { ret void }
define void @ret_i32() { ret void }
...
---
# CHECK-LABEL: name: reuse_diamond
# CHECK: bb.0:
# CHECK: STA_DP 224
# CHECK: STA_DP 226
# CHECK: bb.1:
# CHECK-NEXT: successors
# CHECK-NEXT: {{^ *$}}
# CHECK-NEXT: LDY_Imm16 2
# CHECK-NEXT: LDA_DPIndLongY 224
# CHECK: bb.2:
# CHECK-NEXT: successors
# CHECK-NEXT: {{^ *$}}
# CHECK-NEXT: LDY_Imm16 4
# CHECK-NEXT: LDA_DPIndLongY 224
# CHECK: bb.3:
# CHECK-NEXT: STA_DP 214
# CHECK-NEXT: LDY_Imm16 6
# CHECK-NEXT: LDA_DPIndLongY 224
name: reuse_diamond
tracksRegLiveness: false
body: |
bb.0:
successors: %bb.2, %bb.1
REP 48
TSC
SEC
SBC_Imm16 16
TCS
LDA_DP 210
TAX implicit-def $x, implicit $a
LDA_DP 208
STA_StackRel 17
TXA implicit-def $a, implicit $x
STA_DP 224
LDA_StackRel 17
STA_DP 226
LDY_Imm16 0, implicit-def $y
LDA_DPIndLongY 224, implicit-def $a, implicit $y
STA_DP 212
LDA_DP 200
BEQ %bb.2, implicit $p
bb.1:
successors: %bb.3
LDA_DP 210
TAX implicit-def $x, implicit $a
LDA_DP 208
STA_StackRel 13
TXA implicit-def $a, implicit $x
STA_DP 224
LDA_StackRel 13
STA_DP 226
LDY_Imm16 2, implicit-def $y
LDA_DPIndLongY 224, implicit-def $a, implicit $y
CLC
ADC_DP 212
BRA %bb.3
bb.2:
successors: %bb.3
LDA_DP 210
TAX implicit-def $x, implicit $a
LDA_DP 208
STA_StackRel 11
TXA implicit-def $a, implicit $x
STA_DP 224
LDA_StackRel 11
STA_DP 226
LDY_Imm16 4, implicit-def $y
LDA_DPIndLongY 224, implicit-def $a, implicit $y
SEC
SBC_DP 212
bb.3:
STA_DP 214
LDA_DP 210
TAX implicit-def $x, implicit $a
LDA_DP 208
STA_StackRel 7
TXA implicit-def $a, implicit $x
STA_DP 224
LDA_StackRel 7
STA_DP 226
LDY_Imm16 6, implicit-def $y
LDA_DPIndLongY 224, implicit-def $a, implicit $y
CLC
ADC_DP 214
TAY implicit-def $y, implicit $a
TSC
CLC
ADC_Imm16 16
TCS
TYA implicit-def $a, implicit $y
RTL
...
---
# CHECK-LABEL: name: kept_after_call
# CHECK: JSLpseudo32
# CHECK-NEXT: REP 48
# CHECK-NEXT: LDA_DP 210
# CHECK-NEXT: STA_DP 224
# CHECK-NEXT: LDA_DP 208
# CHECK-NEXT: STA_DP 226
name: kept_after_call
tracksRegLiveness: false
body: |
bb.0:
REP 48
LDA_DP 210
STA_DP 224
LDA_DP 208
STA_DP 226
LDY_Imm16 0, implicit-def $y
LDA_DPIndLongY 224, implicit-def $a, implicit $y
STA_DP 212
JSLpseudo32 @g
REP 48
LDA_DP 210
STA_DP 224
LDA_DP 208
STA_DP 226
LDY_Imm16 2, implicit-def $y
LDA_DPIndLongY 224, implicit-def $a, implicit $y
RTL
...
---
# The low word changes and is re-staged; the bank word does not.
# CHECK-LABEL: name: kept_other_ptr
# CHECK: LDY_Imm16 0
# CHECK: LDA_DP 216
# CHECK-NEXT: STA_DP 224
# CHECK-NEXT: LDY_Imm16 2
name: kept_other_ptr
tracksRegLiveness: false
body: |
bb.0:
REP 48
LDA_DP 210
STA_DP 224
LDA_DP 208
STA_DP 226
LDY_Imm16 0, implicit-def $y
LDA_DPIndLongY 224, implicit-def $a, implicit $y
STA_DP 212
LDA_DP 216
STA_DP 224
LDA_DP 208
STA_DP 226
LDY_Imm16 2, implicit-def $y
LDA_DPIndLongY 224, implicit-def $a, implicit $y
RTL
...
---
# A already holds what was just stored to 210, so the reload goes too.
# CHECK-LABEL: name: kept_src_rewritten
# CHECK: STA_DP 210
# CHECK-NEXT: STA_DP 224
# CHECK-NEXT: LDY_Imm16 2
name: kept_src_rewritten
tracksRegLiveness: false
body: |
bb.0:
REP 48
LDA_DP 210
STA_DP 224
LDA_DP 208
STA_DP 226
LDY_Imm16 0, implicit-def $y
LDA_DPIndLongY 224, implicit-def $a, implicit $y
STA_DP 210
LDA_DP 210
STA_DP 224
LDA_DP 208
STA_DP 226
LDY_Imm16 2, implicit-def $y
LDA_DPIndLongY 224, implicit-def $a, implicit $y
RTL
...
---
# CHECK-LABEL: name: kept_dpx_store
# CHECK: STA_DPX 0
# CHECK-NEXT: LDA_DP 210
# CHECK-NEXT: STA_DP 224
# CHECK-NEXT: LDA_DP 208
# CHECK-NEXT: STA_DP 226
name: kept_dpx_store
tracksRegLiveness: false
body: |
bb.0:
REP 48
LDA_DP 210
STA_DP 224
LDA_DP 208
STA_DP 226
LDY_Imm16 0, implicit-def $y
LDA_DPIndLongY 224, implicit-def $a, implicit $y
LDX_DP 218
STA_DPX 0, implicit $a, implicit $x
LDA_DP 210
STA_DP 224
LDA_DP 208
STA_DP 226
LDY_Imm16 2, implicit-def $y
LDA_DPIndLongY 224, implicit-def $a, implicit $y
RTL
...
---
# CHECK-LABEL: name: loop_invariant
# CHECK: bb.1:
# CHECK-NEXT: successors
# CHECK-NEXT: {{^ *$}}
# CHECK-NEXT: LDY_Imm16 0
# CHECK-NEXT: LDA_DPIndLongY 224
name: loop_invariant
tracksRegLiveness: false
body: |
bb.0:
successors: %bb.1
REP 48
LDA_DP 210
STA_DP 224
LDA_DP 208
STA_DP 226
bb.1:
successors: %bb.1, %bb.2
LDA_DP 210
STA_DP 224
LDA_DP 208
STA_DP 226
LDY_Imm16 0, implicit-def $y
LDA_DPIndLongY 224, implicit-def $a, implicit $y
STA_DP 212
DEC_DP 214
BNE %bb.1, implicit $p
bb.2:
RTL
...
---
# The low word is bumped with CLC ; ADC #2 (ADC_Imm16 carries TableGen's
# default mayStore, which must not end the bank word's reuse).
# CHECK-LABEL: name: loop_variant
# CHECK: bb.1:
# CHECK-NEXT: successors
# CHECK-NEXT: {{^ *$}}
# CHECK-NEXT: LDA_DP 210
# CHECK-NEXT: STA_DP 224
# CHECK-NEXT: LDY_Imm16 0
name: loop_variant
tracksRegLiveness: false
body: |
bb.0:
successors: %bb.1
REP 48
LDA_DP 210
STA_DP 224
LDA_DP 208
STA_DP 226
bb.1:
successors: %bb.1, %bb.2
LDA_DP 210
STA_DP 224
LDA_DP 208
STA_DP 226
LDY_Imm16 0, implicit-def $y
LDA_DPIndLongY 224, implicit-def $a, implicit $y
STA_DP 212
LDA_DP 210
CLC
ADC_Imm16 2
STA_DP 210
DEC_DP 214
BNE %bb.1, implicit $p
bb.2:
RTL
...
---
# CHECK-LABEL: name: nz_live
# CHECK: LDA_DP 210
# CHECK-NEXT: STA_DP 212
# CHECK-NEXT: LDA_DP 210
# CHECK-NEXT: BEQ
name: nz_live
tracksRegLiveness: false
body: |
bb.0:
successors: %bb.1, %bb.2
REP 48
LDA_DP 210
STA_DP 212
LDA_DP 210
BEQ %bb.2, implicit $p
bb.1:
successors: %bb.2
STA_DP 214
bb.2:
LDA_Imm16 0, implicit-def $a
RTL
...
---
# CHECK-LABEL: name: stale_liveness
# CHECK: bb.0:
# CHECK: LDA_DP 210
# CHECK-NEXT: BRA %bb.1
# CHECK: bb.1:
# CHECK-NEXT: STA_DP 212
name: stale_liveness
tracksRegLiveness: false
body: |
bb.0:
successors: %bb.1
REP 48
LDA_DP 210
BRA %bb.1
bb.1:
LDA_DP 210
STA_DP 212
LDA_DP 216
RTL
...
---
# CHECK-LABEL: name: dse_dead_slot
# CHECK: LDA_DP 210
# CHECK-NEXT: STA_StackRel 3
# CHECK-NEXT: LDA_DP 216
# CHECK-NEXT: STA_DP 214
# CHECK-NEXT: LDA_StackRel 3
name: dse_dead_slot
tracksRegLiveness: false
body: |
bb.0:
REP 48
TSC
SEC
SBC_Imm16 4
TCS
LDA_DP 210
STA_StackRel 1
STA_StackRel 3
LDA_DP 216
STA_DP 214
LDA_StackRel 3
STA_DP 212
TSC
CLC
ADC_Imm16 4
TCS
RTL
...
---
# CHECK-LABEL: name: dse_escape
# CHECK: LDA_DP 210
# CHECK-NEXT: STA_StackRel 1
# CHECK-NEXT: STA_StackRel 3
name: dse_escape
tracksRegLiveness: false
body: |
bb.0:
REP 48
TSC
SEC
SBC_Imm16 4
TCS
LDA_DP 210
STA_StackRel 1
STA_StackRel 3
LDA_DP 216
STA_DP 214
LDA_StackRel 3
STA_DP 212
TSX implicit-def $x, implicit $sp
STX_DP 218
TSC
CLC
ADC_Imm16 4
TCS
RTL
...
---
# CHECK-LABEL: name: ret_void
# CHECK: REP 48
# CHECK-NEXT: STA_DP 212
# CHECK-NEXT: RTL
name: ret_void
tracksRegLiveness: false
body: |
bb.0:
REP 48
STA_DP 212
LDA_DP 210
TAX implicit-def $x, implicit $a
RTL
...
---
# CHECK-LABEL: name: ret_i32
# CHECK: LDA_DP 210
# CHECK-NEXT: TAX
# CHECK-NEXT: LDA_DP 208
# CHECK-NEXT: RTL implicit $a, implicit $x
name: ret_i32
tracksRegLiveness: false
body: |
bb.0:
REP 48
LDA_DP 210
TAX implicit-def $x, implicit $a
LDA_DP 208
RTL implicit $a, implicit $x
...

View file

@ -0,0 +1,112 @@
# Pin: the byte-move pseudos (SRL8A / SHL8A, which expand to XBA ; AND #imm)
# and every load form count as flag CORRUPTION, not as the flag-defining
# test, in the PHP/PLP wrap (W65816StackSlotCleanup Pass -2.5).
#
# None of them models Defs=[P], so a branch can never take its modelled $p
# from one; while they were missing from the corrupter list, isFlagDefining's
# default claimed them as THE test, the backward walk stopped there and the
# pass declined to wrap. Same family as andi8-flag-corrupt-wrap.mir.
#
# Live symptom: a loop's `if (started && ...) break` lowered to
# cmp #1 ; lda n,s ; xba ; and #$ff ; sta m,s ; (x5) ; bne
# -- five inlined `(uint8_t)(v >> 8)` mailbox byte splits scheduled between
# the compare and its branch. The BNE read the last AND's Z instead of the
# CMP's, so the break never fired (JoeyLib's IIgs serial-loss probe).
#
# srl8a: the reported shape: reload + SRL8A + spill between CMP and BNE.
# The spill after the last corrupter stays outside the wrap.
# shl8a: same for SHL8A.
# far_load: the far-pointer load the LDAptr32 inserter emits (a real
# LDA_DPIndLongY, not a pseudo) between CMP and BEQ; the wrap
# starts at the index load in front of it.
#
# RUN: llc -mtriple=w65816 -run-pass=w65816-stack-slot-cleanup -o - %s | FileCheck %s
--- |
target triple = "w65816"
define void @srl8a() { ret void }
define void @shl8a() { ret void }
define void @far_load() { ret void }
...
---
# CHECK-LABEL: name: srl8a
# CHECK: CMPi16imm killed $a, 3, implicit-def $p
# CHECK-NEXT: PHP
# CHECK-NEXT: $a = LDAfi %stack.1, 1
# CHECK-NEXT: $a = SRL8A killed $a
# CHECK-NEXT: PLP
# CHECK-NEXT: STAfi killed $a, %stack.2, 0
# CHECK-NEXT: BNE %bb.1, implicit $p
name: srl8a
tracksRegLiveness: false
stack:
- { id: 0, size: 2, alignment: 1 }
- { id: 1, size: 2, alignment: 1 }
- { id: 2, size: 2, alignment: 1 }
body: |
bb.0:
successors: %bb.1, %bb.2
$a = LDAfi %stack.0, 0
CMPi16imm killed $a, 3, implicit-def $p
$a = LDAfi %stack.1, 0
$a = SRL8A killed $a
STAfi killed $a, %stack.2, 0, implicit-def $a
BNE %bb.1, implicit $p
BRA %bb.2
bb.1:
RTL
bb.2:
RTL
...
---
# CHECK-LABEL: name: shl8a
# CHECK: CMPi16imm killed $a, 5, implicit-def $p
# CHECK-NEXT: PHP
# CHECK-NEXT: $a = LDAfi %stack.1, 1
# CHECK-NEXT: $a = SHL8A killed $a
# CHECK-NEXT: PLP
# CHECK-NEXT: BEQ %bb.1, implicit $p
name: shl8a
tracksRegLiveness: false
stack:
- { id: 0, size: 2, alignment: 1 }
- { id: 1, size: 2, alignment: 1 }
body: |
bb.0:
successors: %bb.1, %bb.2
$a = LDAfi %stack.0, 0
CMPi16imm killed $a, 5, implicit-def $p
$a = LDAfi %stack.1, 0
$a = SHL8A killed $a
BEQ %bb.1, implicit $p
BRA %bb.2
bb.1:
RTL
bb.2:
RTL
...
---
# CHECK-LABEL: name: far_load
# CHECK: CMPi16imm killed $a, 7, implicit-def $p
# CHECK-NEXT: PHP
# CHECK-NEXT: LDY_Imm16 2
# CHECK-NEXT: LDA_DPIndLongY 224
# CHECK-NEXT: PLP
# CHECK-NEXT: BEQ %bb.1, implicit $p
name: far_load
tracksRegLiveness: false
stack:
- { id: 0, size: 2, alignment: 1 }
body: |
bb.0:
successors: %bb.1, %bb.2
$a = LDAfi %stack.0, 0
CMPi16imm killed $a, 7, implicit-def $p
LDY_Imm16 2, implicit-def $y
LDA_DPIndLongY 224, implicit-def $a, implicit $y
BEQ %bb.1, implicit $p
BRA %bb.2
bb.1:
RTL
bb.2:
RTL
...

View file

@ -0,0 +1,76 @@
; RUN: llc -mtriple=w65816 -O2 < %s | FileCheck %s
;
; Pin: a VOLATILE i8 load through a far pointer is a true 8-bit bus read,
; `sep #$20 ; lda [$e0],y ; rep #$20` (the atomic LDB_DPIndLongY pseudo).
;
; A byte load is otherwise a 16-bit `lda [$e0],y` followed by a mask, which
; also reads address+1. For memory that is harmless and cheaper, so plain
; byte loads keep it; for I/O it is a phantom read of the neighbouring
; register with that register's read side effects. On the IIgs SCC (four
; adjacent registers at $C038-$C03B) a channel-B data read at $C03A popped
; channel A's receive FIFO at $C03B (claudeDocs/LLVM816-ASKS.md #7 in
; JoeyLib). Constant-address byte loads and every byte store were already
; 8-bit.
;
; vol_byte: plain pointer, LDAptr32.
; vol_byte_off: pointer + constant, LDAptr32Off (offset goes in Y).
; vol_sbyte: sign-extending volatile byte load, same 8-bit read.
; plain_byte: non-volatile byte load keeps the 16-bit read + mask.
; vol_word: volatile i16 load is a 16-bit read, no mode switch.
target datalayout = "e-m:e-p:32:16-i16:16-i32:16-i64:16-f32:16-f64:16-a:8-n8:16-S8"
target triple = "w65816"
; CHECK-LABEL: vol_byte:
; CHECK: sep #0x20
; CHECK-NEXT: lda [0xe0], y
; CHECK-NEXT: rep #0x20
; CHECK-NEXT: and #0xff
define zeroext i8 @vol_byte(ptr %p) {
entry:
%v = load volatile i8, ptr %p, align 1
ret i8 %v
}
; CHECK-LABEL: vol_byte_off:
; CHECK: ldy #0x2
; CHECK-NEXT: sep #0x20
; CHECK-NEXT: lda [0xe0], y
; CHECK-NEXT: rep #0x20
define zeroext i8 @vol_byte_off(ptr %p) {
entry:
%a = getelementptr inbounds i8, ptr %p, i32 2
%v = load volatile i8, ptr %a, align 1
ret i8 %v
}
; CHECK-LABEL: vol_sbyte:
; CHECK: sep #0x20
; CHECK-NEXT: lda [0xe0], y
; CHECK-NEXT: rep #0x20
define signext i8 @vol_sbyte(ptr %p) {
entry:
%v = load volatile i8, ptr %p, align 1
ret i8 %v
}
; CHECK-LABEL: plain_byte:
; CHECK-NOT: sep
; CHECK: lda [0xe0], y
; CHECK-NEXT: and #0xff
define zeroext i8 @plain_byte(ptr %p) {
entry:
%v = load i8, ptr %p, align 1
ret i8 %v
}
; CHECK-LABEL: vol_word:
; CHECK-NOT: sep
; CHECK: lda [0xe0], y
; CHECK-NOT: sep
; CHECK: rtl
define zeroext i16 @vol_word(ptr %p) {
entry:
%v = load volatile i16, ptr %p, align 2
ret i16 %v
}

View file

@ -30,10 +30,13 @@ sanity check on our backend's code-quality.
bash tests/coremark/build.sh --layer2 bash tests/coremark/build.sh --layer2
``` ```
`--layer2` enables `-mllvm -w65816-dbr-safe-ptrs`. This is **required** `--layer2` enables `-mllvm -w65816-dbr-safe-ptrs` (smaller, faster
to fit the binary in a single bank; without it, text crosses the IO pointer code; correct here because CoreMark only touches malloc/static-
window at `0xC000`. CoreMark only touches malloc/static-array memory, array memory). It is optional: CoreMark plus the runtime it pulls in
so the dbr-safe-ptrs assumption is correct. (printf, soft-float) no longer fits the ~44 KB below the IO window at
`0xC000` either way, so `runCoreMark.sh` links it multi-segment like the
Lua test (`--segment-cap`, extra segments at bank 4+) and runs it through
`scripts/runMultiSeg.sh`.
Default iteration count is 1 (smallest valid run). Override for Default iteration count is 1 (smallest valid run). Override for
publishable scores: publishable scores:

View file

@ -0,0 +1,27 @@
{
"version": 1,
"main": "coreMark.bin",
"entry": "__start",
"segments": [
{
"num": 1,
"name": "SEG1",
"base": "0x001000",
"size": 44929,
"bss_start": "0x00d800",
"bss_size": 6268,
"image": "coreMark.bin",
"entry_offset": "0x0000"
},
{
"num": 2,
"name": "SEG2",
"base": "0x040000",
"size": 16546,
"bss_start": "0x000000",
"bss_size": 0,
"image": "coreMark.seg2.bin",
"entry_offset": "0x0000"
}
]
}

View file

@ -16,20 +16,26 @@ cd "$SCRIPT_DIR"
rm -rf "$CM_OBJ" rm -rf "$CM_OBJ"
bash "$SCRIPT_DIR/build.sh" "$@" bash "$SCRIPT_DIR/build.sh" "$@"
# Link. CoreMark is ~50KB compiled; should fit single bank. # Link. CoreMark plus the runtime it pulls in (printf, soft-float) is more
# than the ~44 KB bank 0 has below the IO window, so it links multi-segment
# like the Lua test: text packs into 48 KB chunks from bank 0, the rest at
# bank 4+ (runMultiSeg.sh places each segment from the manifest).
"$PROJECT_ROOT/tools/link816" -o coreMark.bin --text-base 0x1000 \ "$PROJECT_ROOT/tools/link816" -o coreMark.bin --text-base 0x1000 \
--segment-cap 0xB000 \
--segment-bank-base 0x040000 \
--manifest coreMark.manifest.json \
"$RT/crt0.o" \ "$RT/crt0.o" \
"$RT/libc.o" "$RT/libgcc.o" "$RT/softFloat.o" "$RT/softDouble.o" \ "$RT/libc.o" "$RT/libgcc.o" "$RT/softFloat.o" "$RT/softDouble.o" \
"$RT/math.o" "$RT/strtol.o" "$RT/snprintf.o" \ "$RT/math.o" "$RT/strtol.o" "$RT/snprintf.o" \
"$RT/qsort.o" "$RT/timeExt.o" "$RT/extras.o" \ "$RT/qsort.o" "$RT/timeExt.o" "$RT/extras.o" \
$CM_OBJ/*.o $CM_OBJ/*.o
ls -la coreMark.bin ls -la coreMark*.bin
echo "" echo ""
# 0xC0DE sentinel = success; otherwise 0xBEEF = mid-run hang. # 0xC0DE sentinel = success; otherwise 0xBEEF = mid-run hang.
# Elapsed ticks at 0x025002..0x025005 (low/hi). # Elapsed ticks at 0x025002..0x025005 (low/hi).
MAME_CHECK_FRAME=900 MAME_SECS=30 MAME_TIMEOUT=300 \ MAME_CHECK_FRAME=900 MAME_SECS=30 MAME_TIMEOUT=300 MAME_RAMSIZE=4M \
bash "$PROJECT_ROOT/scripts/runInMame.sh" \ bash "$PROJECT_ROOT/scripts/runMultiSeg.sh" \
"$SCRIPT_DIR/coreMark.bin" \ "$SCRIPT_DIR/coreMark.manifest.json" \
--check 0x025000=c0de --check 0x025000=c0de

View file

@ -38,14 +38,20 @@ discussion in [`../../LLVM_65816_DESIGN.md`](../../LLVM_65816_DESIGN.md).
## Runtime execution status ## Runtime execution status
The compiled binary loads under MAME and reaches its entry point `runLuaTest.sh` passes: the API smoke test (create a state, push three
(verified via direct `mame` invocation showing `MAME-READY pc=0x001000`). integers, sum them, check a string, pop, close) writes the `0xC0DE`
Wrapping the run inside `scripts/runMultiSeg.sh` from the agent shell sentinel under MAME.
crashes with SIGSEGV before producing output — this is sandbox-related,
not a code defect. The Lua API smoke test (push 3 ints, sum, check It used to crash, and not because of any sandbox. The link is
strings, close state) compiles and links to 5 segments (~200 KB) so a multi-segment (`--segment-cap`, five segments), and clang forms a
full Lua interpreter session is feasible if the harness is run in a function pointer as a 16-bit offset plus the bank in DP `$BE` -- bank 0.
plain shell. link816 only redirected pointers to seg2+ functions through bank-0 `JML`
trampolines for GS/OS ExpressLoad links, so here Lua's allocator (the
first thing `lua_newstate` calls, through a pointer) was called in bank 0,
landed in `vfprintf`, and the machine ran wild; MAME itself then died of
the I/O the runaway stack wrote. link816 now builds the trampolines for
every multi-segment link (fixed bank bases: the absolute target is baked
in).
## Files ## Files

View file

@ -7,7 +7,9 @@
"num": 1, "num": 1,
"name": "SEG1", "name": "SEG1",
"base": "0x001000", "base": "0x001000",
"size": 42092, "size": 25189,
"bss_start": "0x008400",
"bss_size": 4248,
"image": "luaTest.bin", "image": "luaTest.bin",
"entry_offset": "0x0000" "entry_offset": "0x0000"
}, },
@ -15,7 +17,9 @@
"num": 2, "num": 2,
"name": "SEG2", "name": "SEG2",
"base": "0x040000", "base": "0x040000",
"size": 45028, "size": 41006,
"bss_start": "0x000000",
"bss_size": 0,
"image": "luaTest.seg2.bin", "image": "luaTest.seg2.bin",
"entry_offset": "0x0000" "entry_offset": "0x0000"
}, },
@ -23,9 +27,21 @@
"num": 3, "num": 3,
"name": "SEG3", "name": "SEG3",
"base": "0x050000", "base": "0x050000",
"size": 36298, "size": 43429,
"bss_start": "0x000000",
"bss_size": 0,
"image": "luaTest.seg3.bin", "image": "luaTest.seg3.bin",
"entry_offset": "0x0000" "entry_offset": "0x0000"
},
{
"num": 4,
"name": "SEG4",
"base": "0x060000",
"size": 26491,
"bss_start": "0x000000",
"bss_size": 0,
"image": "luaTest.seg4.bin",
"entry_offset": "0x0000"
} }
] ]
} }