Far pointer code is now more efficient. More code gen errors fixed.
This commit is contained in:
parent
9ca7f1d0de
commit
deb1c96f73
25 changed files with 2273 additions and 133 deletions
|
|
@ -4,16 +4,18 @@
|
|||
// the program must run to the end AND read the array back as all-zero.
|
||||
#include <stdint.h>
|
||||
|
||||
// ~5.75 KB. With the current GNO libc the runtime BSS base already sits at
|
||||
// ~0xD000 (well above the old ~0xB100 heartbeat threshold), so this array's job
|
||||
// is to keep __bss_end large (near the bank-0 top) while still fitting bank 0.
|
||||
static volatile uint8_t bigBss[0x1700];
|
||||
// 4 KB. The GNO runtime's code already pushes the BSS base to ~0xD000 (well
|
||||
// above the old ~0xB100 heartbeat threshold), so this array only has to keep
|
||||
// __bss_end large while the whole BSS still fits under the bank-0 top: the
|
||||
// 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) {
|
||||
*(volatile uint16_t *)0x025010UL = 0xBEEF;
|
||||
uint16_t zeroed = 1;
|
||||
unsigned i;
|
||||
for (i = 0; i < 0x1700; i++) {
|
||||
for (i = 0; i < BIG_BSS_BYTES; i++) {
|
||||
if (bigBss[i] != 0) {
|
||||
zeroed = 0;
|
||||
break;
|
||||
|
|
@ -21,7 +23,7 @@ int main(void) {
|
|||
}
|
||||
*(volatile uint16_t *)0x025012UL = zeroed ? 0x0001 : 0xDEAD;
|
||||
bigBss[0] = 1; // retain
|
||||
bigBss[0x16FF] = 2;
|
||||
bigBss[BIG_BSS_BYTES - 1u] = 2;
|
||||
*(volatile uint16_t *)0x025000UL = 0xC0DE;
|
||||
for (volatile unsigned long j = 0; j < 600000UL; j++) {
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -165,15 +165,10 @@ __start:
|
|||
; 3. returns whatever main returned.
|
||||
jsl __gnoStartup
|
||||
|
||||
; Run global dtors registered via __cxa_atexit (LIFO order) before
|
||||
; QUIT. A holds main()'s return value (exit status), which _exit
|
||||
; consumes — preserve it across the call.
|
||||
pha
|
||||
jsl __run_cxa_atexit
|
||||
pla
|
||||
|
||||
; ---- Terminate via _exit (libcGno.c) ----
|
||||
; __gnoStartup returned with A = main()'s exit status. _exit issues
|
||||
; ---- Terminate via exit -> _exit (libcGno.c) ----
|
||||
; __gnoStartup returned with A = main()'s exit status. exit() (libc.c)
|
||||
; runs the atexit handlers, then the C++ dtors registered through
|
||||
; __cxa_atexit, then _exit, which issues
|
||||
; GS/OS QUIT through GNO's INLINE dispatch (__gnoGsosCall), which is
|
||||
; REQUIRED under GNO: the old stack-based QUIT here (ldx #$2029 ;
|
||||
; 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,
|
||||
; the process was left restartable, and GNO RE-RAN main() a second
|
||||
; 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
|
||||
; like a wild crash) or off the end of the segment.
|
||||
.Lhang:
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@
|
|||
; - No language-card RAM enable (GS/OS configures memory).
|
||||
; - No stack base reset (GS/OS allocated and set our SP).
|
||||
; - Honors GS/OS's DBR=our-bank, DP=allocated-page setup.
|
||||
; - On main() return, calls GS/OS QUIT(pcount=0) to return control to
|
||||
; the launching program (Finder / shell / boot chain) — the standard
|
||||
; GS/OS application exit.
|
||||
; - On main() return, calls exit(), which runs the atexit handlers and
|
||||
; C++ dtors and then _exit (below): GS/OS QUIT(pcount=0), returning
|
||||
; 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):
|
||||
; E=0 (native), M=0 (16-bit accumulator), X=0 (16-bit index)
|
||||
|
|
@ -154,35 +155,51 @@ __start:
|
|||
rep #0x30
|
||||
jsl main
|
||||
|
||||
; Run global dtors registered via __cxa_atexit (LIFO order) before
|
||||
; QUIT. A holds main()'s return value; QUIT itself doesn't consume
|
||||
; it (we set up the parm block immediately below), but keep the
|
||||
; save/restore for parity with crt0Gno.
|
||||
pha
|
||||
jsl __run_cxa_atexit
|
||||
pla
|
||||
; Returning from main is exit(main's value) (C99 5.1.2.2.3): exit runs
|
||||
; the atexit handlers, then the C++ static dtors registered through
|
||||
; __cxa_atexit, then _exit below. A holds main()'s return value, which
|
||||
; is exit's argument.
|
||||
jsl exit
|
||||
|
||||
; ---- QUIT (pcount=0): return to the launching program ----------
|
||||
; 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
|
||||
; exit does not return. Spin rather than fall into what follows.
|
||||
.Lhang:
|
||||
bra .Lhang
|
||||
|
||||
.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
|
||||
|
|
|
|||
|
|
@ -806,33 +806,33 @@ void *realloc(void *ptr, size_t n) {
|
|||
|
||||
// ---- atexit / exit ----
|
||||
//
|
||||
// Standard exit() halts via BRK after running any registered atexit
|
||||
// handler. Programs running under the IIgs runtime typically would
|
||||
// call back into GS/OS Quit; here we just wedge the CPU. Single-slot
|
||||
// atexit (the storage and registration function are below).
|
||||
// exit() runs the atexit handlers (last registered first), then the C++
|
||||
// static destructors registered through __cxa_atexit, then _exit.
|
||||
// Returning from main is the same exit: the GS/OS and GNO crt0s call exit
|
||||
// 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);
|
||||
static AtexitFn __atexitFn = (AtexitFn)0;
|
||||
|
||||
// BRK $00 then spin -- halts a 65816 in BRK so MAME's debugger catches
|
||||
// it; the spin loop guards against the (rare) case where BRK returns.
|
||||
static void __halt(void) __attribute__((noreturn));
|
||||
static void __halt(void) {
|
||||
__asm__ volatile (".byte 0x00, 0x00");
|
||||
while (1) {}
|
||||
}
|
||||
// C99 7.20.4.2 promises at least 32 registrations.
|
||||
#define ATEXIT_MAX 32
|
||||
static AtexitFn __atexitFns[ATEXIT_MAX];
|
||||
static unsigned int __atexitCount = 0;
|
||||
|
||||
void _exit(int code) __attribute__((noreturn));
|
||||
void __run_cxa_atexit(void);
|
||||
|
||||
|
||||
void exit(int code) {
|
||||
(void)code;
|
||||
// C99 7.20.4.3: exit() must invoke registered atexit handlers in
|
||||
// reverse-registration order before terminating.
|
||||
if (__atexitFn) {
|
||||
AtexitFn fn = __atexitFn;
|
||||
__atexitFn = (AtexitFn)0; // prevent re-entry if fn calls exit
|
||||
fn();
|
||||
// Pop before calling, so a handler that registers another one has it
|
||||
// run next and none runs twice.
|
||||
while (__atexitCount > 0) {
|
||||
__atexitCount--;
|
||||
__atexitFns[__atexitCount]();
|
||||
}
|
||||
__halt();
|
||||
__run_cxa_atexit();
|
||||
_exit(code);
|
||||
}
|
||||
|
||||
// ---- errno ----
|
||||
|
|
@ -1447,10 +1447,11 @@ void abort(void) {
|
|||
exit(127);
|
||||
}
|
||||
|
||||
// ---- atexit (single slot; storage + exit() invocation above) ----
|
||||
// ---- atexit (storage + exit() above) ----
|
||||
int atexit(AtexitFn fn) {
|
||||
if (__atexitFn) return -1;
|
||||
__atexitFn = fn;
|
||||
if (__atexitCount >= ATEXIT_MAX) return -1;
|
||||
__atexitFns[__atexitCount] = fn;
|
||||
__atexitCount++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -1458,24 +1459,22 @@ int atexit(AtexitFn fn) {
|
|||
//
|
||||
// _Exit terminates without invoking atexit handlers (unlike exit).
|
||||
// quick_exit terminates after invoking at_quick_exit handlers (a
|
||||
// separate chain from atexit). We share the single-slot pattern
|
||||
// with atexit — single-shot handler, second registration fails.
|
||||
// separate chain from atexit). at_quick_exit keeps a single slot;
|
||||
// a second registration fails.
|
||||
|
||||
static AtexitFn __quickFn = (AtexitFn)0;
|
||||
|
||||
void _Exit(int code) {
|
||||
(void)code;
|
||||
__halt();
|
||||
_exit(code);
|
||||
}
|
||||
|
||||
void quick_exit(int code) {
|
||||
(void)code;
|
||||
if (__quickFn) {
|
||||
AtexitFn fn = __quickFn;
|
||||
__quickFn = (AtexitFn)0;
|
||||
fn();
|
||||
}
|
||||
__halt();
|
||||
_exit(code);
|
||||
}
|
||||
|
||||
int at_quick_exit(AtexitFn fn) {
|
||||
|
|
|
|||
|
|
@ -100,6 +100,29 @@ __jsl_indir:
|
|||
__run_cxa_atexit:
|
||||
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.
|
||||
;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
#!/usr/bin/env bash
|
||||
# runFileCheckTests.sh - run W65816 backend regression tests.
|
||||
#
|
||||
# Walks src/llvm/test/CodeGen/W65816/*.ll and for each:
|
||||
# - reads RUN: lines from the test header (lit-compatible syntax)
|
||||
# Walks src/llvm/test/CodeGen/W65816/*.ll and *.mir and for each:
|
||||
# - 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
|
||||
# - any non-zero exit fails the run.
|
||||
#
|
||||
|
|
@ -32,7 +33,7 @@ if [ $# -gt 0 ]; then
|
|||
files+=("$TEST_DIR/$f")
|
||||
done
|
||||
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
|
||||
|
||||
pass=0
|
||||
|
|
@ -42,7 +43,7 @@ for f in "${files[@]}"; do
|
|||
[ -f "$f" ] || { echo "skip missing: $f"; continue; }
|
||||
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
|
||||
echo "SKIP $name (no RUN: line)"
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -6713,6 +6713,68 @@ EOF
|
|||
"$omfGsf" "$testFileGsf"
|
||||
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.
|
||||
# Links a 2-user-seg program (main in seg 1, pinnedFn in seg 2 via
|
||||
# __attribute__((section(".text.seg2")))), uses --multi-seg-expressload
|
||||
|
|
|
|||
|
|
@ -764,22 +764,7 @@ static void applyTextReloc(std::vector<uint8_t> &buf, uint32_t off,
|
|||
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);
|
||||
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+):
|
||||
// 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
|
||||
|
|
@ -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
|
||||
// offset and seg1 (KIND=0x1000) always bank-aligns, so the low16 is
|
||||
// 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) {
|
||||
uint32_t targetOff = target - segments[targetSeg - 1].base;
|
||||
auto tIt = gTrampMap.find(TrampKey{targetSeg, targetOff});
|
||||
|
|
@ -800,6 +790,16 @@ static void applyTextReloc(std::vector<uint8_t> &buf, uint32_t off,
|
|||
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
|
||||
// the legacy applyReloc path (which records via recordCRelocSite
|
||||
// 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;
|
||||
for (uint8_t i = 0; i < cnt; ++i)
|
||||
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;
|
||||
s.patchSeg = static_cast<uint16_t>(patchSeg);
|
||||
s.patchOff = static_cast<uint16_t>(patchAddr - pseg.base);
|
||||
s.targetSeg = 1;
|
||||
s.targetOff = static_cast<uint16_t>(target);
|
||||
s.targetOff = static_cast<uint16_t>(target - segments[0].base);
|
||||
s.byteCnt = cnt;
|
||||
s.bitShift = (rtype == R_W65816_BANK16) ? 16 : 0;
|
||||
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
|
||||
// different bank than seg1, whose bank is what $00BE/&func will use)
|
||||
// and reserve one `JML func` trampoline in seg1 per distinct target.
|
||||
|
|
@ -1382,7 +1387,7 @@ struct Linker {
|
|||
gTrampMap.clear();
|
||||
gTrampList.clear();
|
||||
gTrampBase = 0;
|
||||
if (gMultiSegRelocMode) {
|
||||
if (segSizes.size() > 1) {
|
||||
std::map<std::string, TrampKey> symSeg; // name -> (seg, off)
|
||||
std::map<std::string, bool> symStrong;
|
||||
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.
|
||||
// Each entry is `JML func` (0x5C + 24-bit target, zeroed); its cINTERSEG
|
||||
// site makes the Loader patch the target to func's runtime bank:offset.
|
||||
// Each entry is `JML func` (0x5C + 24-bit target). Under ExpressLoad
|
||||
// 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),
|
||||
// so entry i lands at offset gTrampBase + 4*i, and applyTextReloc
|
||||
// redirects each cross-seg IMM16 &func to that offset.
|
||||
if (!gTrampList.empty()) {
|
||||
auto &b0 = segTextBufs[0];
|
||||
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(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) {
|
||||
InterImm24Site s;
|
||||
s.patchSeg = 1;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ add_llvm_target(W65816CodeGen
|
|||
W65816Subtarget.cpp
|
||||
W65816StackSlotCleanup.cpp
|
||||
W65816SepRepCleanup.cpp
|
||||
W65816FarPtrStageReuse.cpp
|
||||
W65816BranchExpand.cpp
|
||||
W65816TiedDefSpill.cpp
|
||||
W65816ABridgeViaX.cpp
|
||||
|
|
|
|||
|
|
@ -66,6 +66,12 @@ FunctionPass *createW65816StackSlotCleanup();
|
|||
// that cancel. See W65816SepRepCleanup.cpp.
|
||||
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
|
||||
// `INVERTED_Bxx skip ; BRA target ; skip:` pattern when the byte
|
||||
// distance to the target exceeds the +/-128 reach of an 8-bit-PCREL
|
||||
|
|
@ -195,6 +201,7 @@ void initializeW65816DAGToDAGISelLegacyPass(PassRegistry &);
|
|||
void initializeW65816StackSlotCleanupPass(PassRegistry &);
|
||||
void initializeW65816I32IncFoldPass(PassRegistry &);
|
||||
void initializeW65816SepRepCleanupPass(PassRegistry &);
|
||||
void initializeW65816FarPtrStageReusePass(PassRegistry &);
|
||||
void initializeW65816BranchExpandPass(PassRegistry &);
|
||||
void initializeW65816TiedDefSpillPass(PassRegistry &);
|
||||
void initializeW65816ABridgeViaXPass(PassRegistry &);
|
||||
|
|
|
|||
1237
src/llvm/lib/Target/W65816/W65816FarPtrStageReuse.cpp
Normal file
1237
src/llvm/lib/Target/W65816/W65816FarPtrStageReuse.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -3018,6 +3018,22 @@ emitRROp(MachineInstr &MI, MachineBasicBlock *BB, unsigned StoreOp,
|
|||
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 *
|
||||
W65816TargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI,
|
||||
MachineBasicBlock *BB) const {
|
||||
|
|
@ -3106,7 +3122,7 @@ W65816TargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI,
|
|||
BuildMI(*BB, MI.getIterator(), DL,
|
||||
TII.get(W65816::LDY_Imm16)).addImm(0);
|
||||
BuildMI(*BB, MI.getIterator(), DL,
|
||||
TII.get(W65816::LDA_DPIndLongY)).addImm(0xE0);
|
||||
TII.get(ptrLoadOpcode(MI))).addImm(0xE0);
|
||||
BuildMI(*BB, MI.getIterator(), DL,
|
||||
TII.get(TargetOpcode::COPY), Dst).addReg(W65816::A);
|
||||
} else {
|
||||
|
|
@ -3361,7 +3377,8 @@ W65816TargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI,
|
|||
// 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
|
||||
// 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);
|
||||
if (PeelOff) {
|
||||
BuildMI(*BB, MI.getIterator(), DL, TII.get(TargetOpcode::COPY), PtrLo)
|
||||
|
|
@ -3602,7 +3619,7 @@ W65816TargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI,
|
|||
BuildMI(*BB, MI.getIterator(), DL,
|
||||
TII.get(W65816::LDY_Imm16)).addImm(PeelOff);
|
||||
BuildMI(*BB, MI.getIterator(), DL,
|
||||
TII.get(W65816::LDA_DPIndLongY)).addImm(0xE0);
|
||||
TII.get(ptrLoadOpcode(MI))).addImm(0xE0);
|
||||
BuildMI(*BB, MI.getIterator(), DL,
|
||||
TII.get(TargetOpcode::COPY), Dst).addReg(W65816::A);
|
||||
} else {
|
||||
|
|
@ -3694,7 +3711,7 @@ W65816TargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI,
|
|||
BuildMI(*BB, MI.getIterator(), DL,
|
||||
TII.get(W65816::LDY_Imm16)).addImm(Off);
|
||||
BuildMI(*BB, MI.getIterator(), DL,
|
||||
TII.get(W65816::LDA_DPIndLongY)).addImm(0xE0);
|
||||
TII.get(ptrLoadOpcode(MI))).addImm(0xE0);
|
||||
BuildMI(*BB, MI.getIterator(), DL,
|
||||
TII.get(TargetOpcode::COPY), Dst).addReg(W65816::A);
|
||||
} else {
|
||||
|
|
@ -3770,7 +3787,7 @@ W65816TargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI,
|
|||
BuildMI(*BB, MI.getIterator(), DL,
|
||||
TII.get(W65816::LDY_Imm16)).addImm(0);
|
||||
BuildMI(*BB, MI.getIterator(), DL,
|
||||
TII.get(W65816::LDA_DPIndLongY)).addImm(0xE0);
|
||||
TII.get(ptrLoadOpcode(MI))).addImm(0xE0);
|
||||
BuildMI(*BB, MI.getIterator(), DL,
|
||||
TII.get(TargetOpcode::COPY), Dst).addReg(W65816::A);
|
||||
} else {
|
||||
|
|
@ -3945,7 +3962,7 @@ W65816TargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI,
|
|||
BuildMI(*BB, MI.getIterator(), DL,
|
||||
TII.get(W65816::LDY_Imm16)).addImm(0);
|
||||
BuildMI(*BB, MI.getIterator(), DL,
|
||||
TII.get(W65816::LDA_DPIndLongY)).addImm(0xE0);
|
||||
TII.get(ptrLoadOpcode(MI))).addImm(0xE0);
|
||||
BuildMI(*BB, MI.getIterator(), DL,
|
||||
TII.get(TargetOpcode::COPY), Dst).addReg(W65816::A);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -25,9 +25,10 @@ using namespace llvm;
|
|||
|
||||
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
|
||||
// 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
|
||||
// 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
|
||||
|
|
@ -35,13 +36,22 @@ void W65816InstrInfo::anchor() {}
|
|||
// REP coalescer and the AsmPrinter's LDAi8imm mode-tracking behave exactly
|
||||
// as they did for the old three-instruction form.
|
||||
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;
|
||||
}
|
||||
MachineBasicBlock &MBB = *MI.getParent();
|
||||
DebugLoc DL = MI.getDebugLoc();
|
||||
int64_t Addr = MI.getOperand(0).getImm();
|
||||
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);
|
||||
MI.eraseFromParent();
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -1200,6 +1200,18 @@ def STB_DPIndLongY : W65816Pseudo<(outs), (ins addrDP:$addr),
|
|||
"# 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
|
||||
// `(store val, (add ptr, $off))` come up for struct field access and
|
||||
// array indexing with small constant offsets. Without these patterns,
|
||||
|
|
|
|||
|
|
@ -988,7 +988,35 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) {
|
|||
// (t != 0) instead of (i != 0) and runs forever.
|
||||
Opc == W65816::TAX || Opc == W65816::TXA ||
|
||||
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) {
|
||||
// Stack-relative ops read/write at S+disp. PHP decrements S by 1,
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ LLVMInitializeW65816Target() {
|
|||
initializeW65816DAGToDAGISelLegacyPass(PR);
|
||||
initializeW65816StackSlotCleanupPass(PR);
|
||||
initializeW65816SepRepCleanupPass(PR);
|
||||
initializeW65816FarPtrStageReusePass(PR);
|
||||
initializeW65816BranchExpandPass(PR);
|
||||
initializeW65816TiedDefSpillPass(PR);
|
||||
initializeW65816ABridgeViaXPass(PR);
|
||||
|
|
@ -344,6 +345,10 @@ void W65816PassConfig::addPreEmitPass() {
|
|||
if (!getenv("W65816_DISABLE_STACKOPT")) {
|
||||
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(
|
||||
|
|
|
|||
465
src/llvm/test/CodeGen/W65816/far-ptr-stage-reuse.mir
Normal file
465
src/llvm/test/CodeGen/W65816/far-ptr-stage-reuse.mir
Normal 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
|
||||
...
|
||||
112
src/llvm/test/CodeGen/W65816/srl8a-flag-corrupt-wrap.mir
Normal file
112
src/llvm/test/CodeGen/W65816/srl8a-flag-corrupt-wrap.mir
Normal 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
|
||||
...
|
||||
76
src/llvm/test/CodeGen/W65816/volatile-byte-load-8bit.ll
Normal file
76
src/llvm/test/CodeGen/W65816/volatile-byte-load-8bit.ll
Normal 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
|
||||
}
|
||||
|
|
@ -30,10 +30,13 @@ sanity check on our backend's code-quality.
|
|||
bash tests/coremark/build.sh --layer2
|
||||
```
|
||||
|
||||
`--layer2` enables `-mllvm -w65816-dbr-safe-ptrs`. This is **required**
|
||||
to fit the binary in a single bank; without it, text crosses the IO
|
||||
window at `0xC000`. CoreMark only touches malloc/static-array memory,
|
||||
so the dbr-safe-ptrs assumption is correct.
|
||||
`--layer2` enables `-mllvm -w65816-dbr-safe-ptrs` (smaller, faster
|
||||
pointer code; correct here because CoreMark only touches malloc/static-
|
||||
array memory). It is optional: CoreMark plus the runtime it pulls in
|
||||
(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
|
||||
publishable scores:
|
||||
|
|
|
|||
27
tests/coremark/coreMark.manifest.json
Normal file
27
tests/coremark/coreMark.manifest.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -16,20 +16,26 @@ cd "$SCRIPT_DIR"
|
|||
rm -rf "$CM_OBJ"
|
||||
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 \
|
||||
--segment-cap 0xB000 \
|
||||
--segment-bank-base 0x040000 \
|
||||
--manifest coreMark.manifest.json \
|
||||
"$RT/crt0.o" \
|
||||
"$RT/libc.o" "$RT/libgcc.o" "$RT/softFloat.o" "$RT/softDouble.o" \
|
||||
"$RT/math.o" "$RT/strtol.o" "$RT/snprintf.o" \
|
||||
"$RT/qsort.o" "$RT/timeExt.o" "$RT/extras.o" \
|
||||
$CM_OBJ/*.o
|
||||
|
||||
ls -la coreMark.bin
|
||||
ls -la coreMark*.bin
|
||||
echo ""
|
||||
|
||||
# 0xC0DE sentinel = success; otherwise 0xBEEF = mid-run hang.
|
||||
# Elapsed ticks at 0x025002..0x025005 (low/hi).
|
||||
MAME_CHECK_FRAME=900 MAME_SECS=30 MAME_TIMEOUT=300 \
|
||||
bash "$PROJECT_ROOT/scripts/runInMame.sh" \
|
||||
"$SCRIPT_DIR/coreMark.bin" \
|
||||
MAME_CHECK_FRAME=900 MAME_SECS=30 MAME_TIMEOUT=300 MAME_RAMSIZE=4M \
|
||||
bash "$PROJECT_ROOT/scripts/runMultiSeg.sh" \
|
||||
"$SCRIPT_DIR/coreMark.manifest.json" \
|
||||
--check 0x025000=c0de
|
||||
|
|
|
|||
|
|
@ -38,14 +38,20 @@ discussion in [`../../LLVM_65816_DESIGN.md`](../../LLVM_65816_DESIGN.md).
|
|||
|
||||
## Runtime execution status
|
||||
|
||||
The compiled binary loads under MAME and reaches its entry point
|
||||
(verified via direct `mame` invocation showing `MAME-READY pc=0x001000`).
|
||||
Wrapping the run inside `scripts/runMultiSeg.sh` from the agent shell
|
||||
crashes with SIGSEGV before producing output — this is sandbox-related,
|
||||
not a code defect. The Lua API smoke test (push 3 ints, sum, check
|
||||
strings, close state) compiles and links to 5 segments (~200 KB) so a
|
||||
full Lua interpreter session is feasible if the harness is run in a
|
||||
plain shell.
|
||||
`runLuaTest.sh` passes: the API smoke test (create a state, push three
|
||||
integers, sum them, check a string, pop, close) writes the `0xC0DE`
|
||||
sentinel under MAME.
|
||||
|
||||
It used to crash, and not because of any sandbox. The link is
|
||||
multi-segment (`--segment-cap`, five segments), and clang forms a
|
||||
function pointer as a 16-bit offset plus the bank in DP `$BE` -- bank 0.
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@
|
|||
"num": 1,
|
||||
"name": "SEG1",
|
||||
"base": "0x001000",
|
||||
"size": 42092,
|
||||
"size": 25189,
|
||||
"bss_start": "0x008400",
|
||||
"bss_size": 4248,
|
||||
"image": "luaTest.bin",
|
||||
"entry_offset": "0x0000"
|
||||
},
|
||||
|
|
@ -15,7 +17,9 @@
|
|||
"num": 2,
|
||||
"name": "SEG2",
|
||||
"base": "0x040000",
|
||||
"size": 45028,
|
||||
"size": 41006,
|
||||
"bss_start": "0x000000",
|
||||
"bss_size": 0,
|
||||
"image": "luaTest.seg2.bin",
|
||||
"entry_offset": "0x0000"
|
||||
},
|
||||
|
|
@ -23,9 +27,21 @@
|
|||
"num": 3,
|
||||
"name": "SEG3",
|
||||
"base": "0x050000",
|
||||
"size": 36298,
|
||||
"size": 43429,
|
||||
"bss_start": "0x000000",
|
||||
"bss_size": 0,
|
||||
"image": "luaTest.seg3.bin",
|
||||
"entry_offset": "0x0000"
|
||||
},
|
||||
{
|
||||
"num": 4,
|
||||
"name": "SEG4",
|
||||
"base": "0x060000",
|
||||
"size": 26491,
|
||||
"bss_start": "0x000000",
|
||||
"bss_size": 0,
|
||||
"image": "luaTest.seg4.bin",
|
||||
"entry_offset": "0x0000"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue