Multisegment OMF
This commit is contained in:
parent
3388f3c5a5
commit
09c99937e7
6 changed files with 863 additions and 95 deletions
0
link816.cpp
Normal file
0
link816.cpp
Normal file
17
patches/0008-elf-add-em-w65816.patch
Normal file
17
patches/0008-elf-add-em-w65816.patch
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
diff --git a/llvm/include/llvm/BinaryFormat/ELF.h b/llvm/include/llvm/BinaryFormat/ELF.h
|
||||
index b84b4e84f..4e45ece44 100644
|
||||
--- a/llvm/include/llvm/BinaryFormat/ELF.h
|
||||
+++ b/llvm/include/llvm/BinaryFormat/ELF.h
|
||||
@@ -326,6 +326,12 @@ enum {
|
||||
EM_CSKY = 252, // C-SKY 32-bit processor
|
||||
EM_LOONGARCH = 258, // LoongArch
|
||||
EM_MOS = 6502, // MOS Technologies 65xx
|
||||
+ EM_W65816 = 0xFF16, // WDC 65C816 / Apple IIgs (vendor-private, see
|
||||
+ // docs/USAGE.md "ELF e_machine value" section).
|
||||
+ // Sits in the 0xFF00-0xFFFF experimental/vendor
|
||||
+ // range reserved by the ELF spec; the "16" suffix
|
||||
+ // is mnemonic for "65816". Picked here because
|
||||
+ // 65816 itself (0x10118) does not fit Elf32_Half.
|
||||
};
|
||||
|
||||
// Object file classes.
|
||||
|
|
@ -5360,6 +5360,129 @@ EOF
|
|||
rm -f "$cMsegFile" "$oMsegFile" "$binMseg" "$mfMseg" \
|
||||
"${binMseg%.bin}".seg*.bin
|
||||
|
||||
# Phase A: per-segment placement attribute. A function tagged
|
||||
# __attribute__((section(".text.seg2"))) lands in seg 2 (bank-
|
||||
# aligned at the default segmentBankBase 0x040000), not greedy-
|
||||
# packed into seg 1. The main() in seg 1 calls into seg 2 via
|
||||
# a cross-bank JSL; the value round-trips through pinnedFn and
|
||||
# arrives at the check address.
|
||||
log "check: link816 honors __attribute__((section(\".text.seg2\")))"
|
||||
cPinFile="$(mktemp --suffix=.c)"
|
||||
oPinFile="$(mktemp --suffix=.o)"
|
||||
binPin="$(mktemp --suffix=.bin)"
|
||||
mfPin="$(mktemp --suffix=.json)"
|
||||
cat > "$cPinFile" <<'EOF'
|
||||
__attribute__((section(".text.seg2"), noinline))
|
||||
int pinnedFn(int x) { return x * 7 + 3; }
|
||||
volatile int gIn = 11;
|
||||
int main(void) {
|
||||
int r = pinnedFn(gIn);
|
||||
*(volatile unsigned short *)0x025000 = (unsigned short)r;
|
||||
while (1) {}
|
||||
}
|
||||
EOF
|
||||
"$CLANG" --target=w65816 -O2 -ffunction-sections -c "$cPinFile" -o "$oPinFile"
|
||||
"$PROJECT_ROOT/tools/link816" -o "$binPin" --text-base 0x1000 \
|
||||
--manifest "$mfPin" \
|
||||
"$oCrt0F" "$oLibgccFile" "$oPinFile" >/dev/null 2>&1
|
||||
if ! grep -q '"num": 2' "$mfPin"; then
|
||||
die "Phase A: .text.seg2 did not produce a seg 2 in the manifest"
|
||||
fi
|
||||
if ! grep -q '"base": "0x040000"' "$mfPin"; then
|
||||
die "Phase A: seg 2 base should be 0x040000 (segmentBankBase default)"
|
||||
fi
|
||||
if ! bash "$PROJECT_ROOT/scripts/runMultiSeg.sh" "$mfPin" --check \
|
||||
0x025000=0050 </dev/null >/dev/null 2>&1; then
|
||||
die "MAME: Phase A pinnedFn(11) cross-bank JSL != 0x50"
|
||||
fi
|
||||
rm -f "$cPinFile" "$oPinFile" "$binPin" "$mfPin" \
|
||||
"${binPin%.bin}".seg*.bin
|
||||
|
||||
# Phase B: opt-in far globals. Globals declared via
|
||||
# __attribute__((section(".rodata.far"))) / .data.far / .bss.far
|
||||
# get LDA_Long/STA_Long codegen (bank-explicit), so they read
|
||||
# correctly even when DBR points at a different bank. Layout:
|
||||
# rodata in bank 0, BSS in bank 2 (--bss-base 0x020000); crt0
|
||||
# sets DBR=2. A short-form LDA on the rodata symbol would read
|
||||
# bank 2:offset (garbage); the long-form reads bank-0:offset.
|
||||
log "check: link816 + AsmPrinter Phase B (far globals cross-bank)"
|
||||
cFarFile="$(mktemp --suffix=.c)"
|
||||
oFarFile="$(mktemp --suffix=.o)"
|
||||
binFar="$(mktemp --suffix=.bin)"
|
||||
cat > "$cFarFile" <<'EOF'
|
||||
__attribute__((section(".rodata.far")))
|
||||
const unsigned short gFarArr[3] = { 0xCAFE, 0xBABE, 0xF00D };
|
||||
__attribute__((section(".data.far")))
|
||||
volatile int gFarIdx = 1;
|
||||
int main(void) {
|
||||
*(volatile unsigned short *)0x025000 = gFarArr[gFarIdx];
|
||||
while (1) {}
|
||||
}
|
||||
EOF
|
||||
"$CLANG" --target=w65816 -O2 -ffunction-sections -c "$cFarFile" -o "$oFarFile"
|
||||
"$PROJECT_ROOT/tools/link816" -o "$binFar" --text-base 0x1000 \
|
||||
--bss-base 0x020000 "$oCrt0F" "$oLibgccFile" "$oFarFile" >/dev/null 2>&1
|
||||
if ! bash "$PROJECT_ROOT/scripts/runInMame.sh" "$binFar" --check \
|
||||
0x025000=babe </dev/null >/dev/null 2>&1; then
|
||||
die "MAME: Phase B far-global cross-bank read != 0xBABE"
|
||||
fi
|
||||
rm -f "$cFarFile" "$oFarFile" "$binFar"
|
||||
|
||||
# Phase C.1: omfEmit --manifest --expressload wraps a multi-seg
|
||||
# link816 manifest in a ~ExpressLoad OMF (KIND=0x1000 per user
|
||||
# seg) instead of the static-bake KIND=0x8800 path. Verifies
|
||||
# the structural output: 1 ExpressLoad seg + N user segs.
|
||||
# Cross-segment JSL (Phase C.2 cINTERSEG) NOT yet implemented;
|
||||
# cross-seg refs are still link-time-baked, so this OMF only
|
||||
# works under runMultiSeg.sh's mini Lua loader (static placement
|
||||
# honors link-time bases) — real GS/OS Loader launch needs C.2.
|
||||
log "check: omfEmit --manifest --expressload produces wrapped multi-seg OMF"
|
||||
cPhCFile="$(mktemp --suffix=.c)"
|
||||
oPhCFile="$(mktemp --suffix=.o)"
|
||||
binPhC="$(mktemp --suffix=.bin)"
|
||||
mfPhC="$(mktemp --suffix=.json)"
|
||||
omfPhC="$(mktemp --suffix=.omf)"
|
||||
cat > "$cPhCFile" <<'EOF'
|
||||
__attribute__((section(".text.seg2"), noinline))
|
||||
int pinnedFn(int x) { return x * 7 + 3; }
|
||||
volatile int gIn = 11;
|
||||
int main(void) {
|
||||
*(volatile unsigned short *)0x025000 = (unsigned short)pinnedFn(gIn);
|
||||
while (1) {}
|
||||
}
|
||||
EOF
|
||||
"$CLANG" --target=w65816 -O2 -ffunction-sections -c "$cPhCFile" -o "$oPhCFile"
|
||||
"$PROJECT_ROOT/tools/link816" -o "$binPhC" --text-base 0x1000 \
|
||||
--manifest "$mfPhC" \
|
||||
"$oCrt0F" "$oLibgccFile" "$oPhCFile" >/dev/null 2>&1
|
||||
"$PROJECT_ROOT/tools/omfEmit" --manifest "$mfPhC" --expressload \
|
||||
--output "$omfPhC" >/dev/null 2>&1
|
||||
# Verify exactly 3 segments and correct KINDs.
|
||||
nPhCSeg=$(python3 -c "
|
||||
import struct
|
||||
data = open('$omfPhC','rb').read()
|
||||
pos = 0; ok_kinds = []
|
||||
while pos < len(data):
|
||||
bytecnt = struct.unpack_from('<I', data, pos)[0]
|
||||
kind = struct.unpack_from('<H', data, pos+20)[0]
|
||||
ok_kinds.append(kind)
|
||||
pos += bytecnt
|
||||
expected = [0x8001, 0x1000, 0x1000]
|
||||
print('OK' if ok_kinds == expected else 'BAD ' + str(ok_kinds))
|
||||
")
|
||||
if [ "$nPhCSeg" != "OK" ]; then
|
||||
die "Phase C.1: ExpressLoad-multi-seg KIND sequence wrong (got $nPhCSeg)"
|
||||
fi
|
||||
# Round-trip via runMultiSeg.sh proves the per-seg link816 .bin
|
||||
# files are intact (this loader bypasses the OMF wrap; full
|
||||
# OMF launch under real GS/OS needs Phase C.2 cINTERSEG).
|
||||
if ! bash "$PROJECT_ROOT/scripts/runMultiSeg.sh" "$mfPhC" --check \
|
||||
0x025000=0050 </dev/null >/dev/null 2>&1; then
|
||||
die "MAME: Phase C.1 multi-seg .bin round-trip != 0x50"
|
||||
fi
|
||||
rm -f "$cPhCFile" "$oPhCFile" "$binPhC" "$mfPhC" "$omfPhC" \
|
||||
"${binPhC%.bin}".seg*.bin
|
||||
|
||||
rm -f "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oQsortF" \
|
||||
"$oExtrasF" "$oStrtokF" "$oMathF" "$oSfF" "$oSdF" "$oCrt0F"
|
||||
else
|
||||
|
|
@ -6394,6 +6517,58 @@ EOF
|
|||
"$omfGsf" "$testFileGsf"
|
||||
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
|
||||
# so link816 emits per-seg sidecars with the cross-seg JSL marked as
|
||||
# inter (and zeros the patch bytes). omfEmit --manifest --expressload
|
||||
# wraps in ExpressLoad with KIND=0x1000 per user seg and emits cINTERSEG
|
||||
# (0xF6) opcodes so the Loader patches the JSL after picking banks
|
||||
# dynamically. Under Finder launch the JSL must reach pinnedFn and the
|
||||
# marker at $70 reads 0x50 = 11*7+3. Skips under SMOKE_SKIP_PHASEC2=1.
|
||||
if [ "${SMOKE_SKIP_PHASEC2:-0}" != "1" ] \
|
||||
&& [ -x "$CLANG" ] && [ -x "$CADIUS" ] && [ -f "$SYSDISK" ] \
|
||||
&& command -v mame >/dev/null 2>&1; then
|
||||
log "check: Phase C.2 cINTERSEG cross-seg JSL via GS/OS Loader"
|
||||
cPhc2="$(mktemp --suffix=.c)"
|
||||
oPhc2="$(mktemp --suffix=.o)"
|
||||
binPhc2="$(mktemp --suffix=.bin)"
|
||||
mapPhc2="$(mktemp --suffix=.map)"
|
||||
relPhc2="$(mktemp --suffix=.reloc)"
|
||||
mfPhc2="$(mktemp --suffix=.json)"
|
||||
omfPhc2="$(mktemp --suffix=.omf)"
|
||||
cat > "$cPhc2" <<'EOF'
|
||||
__attribute__((section(".text.seg2"), noinline))
|
||||
int pinnedFn(int x) { return x * 7 + 3; }
|
||||
volatile int gIn = 11;
|
||||
int main(void) {
|
||||
int r = pinnedFn(gIn);
|
||||
*(volatile unsigned char *)0x70 = (unsigned char)r;
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
"$CLANG" --target=w65816 -I"$PROJECT_ROOT/runtime/include" -O2 \
|
||||
-ffunction-sections -c "$cPhc2" -o "$oPhc2"
|
||||
"$PROJECT_ROOT/tools/link816" -o "$binPhc2" --text-base 0x1000 \
|
||||
--bss-base 0xA000 --manifest "$mfPhc2" --map "$mapPhc2" \
|
||||
--reloc-out "$relPhc2" --multi-seg-expressload \
|
||||
"$PROJECT_ROOT/runtime/crt0Gsos.o" "$oPhc2" \
|
||||
"$PROJECT_ROOT/runtime/libgcc.o" >/dev/null 2>&1 \
|
||||
|| die "Phase C.2: link failed"
|
||||
"$PROJECT_ROOT/tools/omfEmit" --manifest "$mfPhc2" --expressload \
|
||||
--relocs "$relPhc2" --output "$omfPhc2" >/dev/null 2>&1 \
|
||||
|| die "Phase C.2: omfEmit failed"
|
||||
if ! bash "$PROJECT_ROOT/scripts/runViaFinder.sh" "$omfPhc2" \
|
||||
--check 0x70=0x50 >/dev/null 2>&1; then
|
||||
bash "$PROJECT_ROOT/scripts/runViaFinder.sh" "$omfPhc2" \
|
||||
--check 0x70=0x50 2>&1 | tail -5 >&2
|
||||
die "Phase C.2: cINTERSEG cross-seg JSL marker != 0x50"
|
||||
fi
|
||||
rm -f "$cPhc2" "$oPhc2" "$binPhc2" "$mapPhc2" "$relPhc2" \
|
||||
"$mfPhc2" "$omfPhc2" \
|
||||
"${binPhc2%.bin}".seg*.bin "${relPhc2}".seg*.reloc
|
||||
fi
|
||||
|
||||
# Stack-size end-to-end: omfEmit --stack-size must actually propagate a
|
||||
# larger DP/Stack chunk to the GS/OS Loader. Background: prior to the
|
||||
# 2026-06-02 fix, the ~Direct DP/Stack segment was appended to the OMF
|
||||
|
|
|
|||
|
|
@ -188,6 +188,27 @@ static std::vector<uint8_t> readFile(const std::string &path) {
|
|||
return buf;
|
||||
}
|
||||
|
||||
// Section-name parser for the per-segment placement attribute. Returns 0 if
|
||||
// the section is unpinned (default greedy packing), or N >= 1 if the section
|
||||
// is pinned to seg N. Convention: a text section whose name starts with
|
||||
// `.text.segN` (decimal, 1-based) — directly or with a trailing dotted
|
||||
// qualifier — pins that section's contents into segment N. Users author
|
||||
// via `__attribute__((section(".text.seg2")))` or similar. Rationale: the
|
||||
// existing sectionKind() classifier already treats `.text.<anything>` as
|
||||
// the "text" kind, so the new convention costs nothing in the kind axis.
|
||||
static uint32_t requestedSegment(const std::string &name) {
|
||||
const std::string prefix = ".text.seg";
|
||||
if (name.rfind(prefix, 0) != 0) return 0;
|
||||
size_t p = prefix.size();
|
||||
if (p >= name.size() || !std::isdigit((unsigned char)name[p])) return 0;
|
||||
size_t e = p;
|
||||
while (e < name.size() && std::isdigit((unsigned char)name[e])) ++e;
|
||||
if (e < name.size() && name[e] != '.') return 0; // `.text.seg2foo` is not a pin
|
||||
uint32_t n = static_cast<uint32_t>(std::stoul(name.substr(p, e - p)));
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
static std::string sectionKind(const std::string &name) {
|
||||
if (name == ".text" || name.rfind(".text.", 0) == 0) return "text";
|
||||
if (name == ".rodata" || name.rfind(".rodata.", 0) == 0) return "rodata";
|
||||
|
|
@ -411,6 +432,31 @@ static std::vector<Imm24Site> gImm24Sites;
|
|||
static uint32_t gTextBaseForSites = 0;
|
||||
static bool gRecordSites = false;
|
||||
|
||||
// Phase C.2 — per-segment + cross-segment IMM24 site tracking, used
|
||||
// when multi-segment ExpressLoad OMFs need the GS/OS Loader to patch
|
||||
// cross-seg JSLs after picking banks dynamically.
|
||||
//
|
||||
// gIntraImm24SitesPerSeg[N] = sites within seg N whose target is also
|
||||
// in seg N (cRELOC 0xF5 — Loader rewrites bytes to (segPlacedBase +
|
||||
// offsetRef) when seg N lands).
|
||||
//
|
||||
// gInterImm24Sites = sites whose patch is in one seg but target lives
|
||||
// in a different seg (cINTERSEG 0xF6 — Loader rewrites bytes to
|
||||
// (targetSegPlacedBase + offsetRef) after picking banks for both
|
||||
// segs). link816 bakes 0 at the patch site so the Loader's add does
|
||||
// not double-count.
|
||||
struct InterImm24Site {
|
||||
uint16_t patchSeg; // 1-based seg containing the patch
|
||||
uint16_t patchOff; // offset within patchSeg
|
||||
uint8_t targetSeg; // 1-based seg containing the target
|
||||
uint16_t targetOff; // offset within targetSeg
|
||||
uint8_t byteCnt; // 3 = IMM24 (only kind supported for cINTERSEG today)
|
||||
uint8_t bitShift; // 0
|
||||
};
|
||||
static std::map<uint32_t, std::vector<Imm24Site>> gIntraImm24SitesPerSeg;
|
||||
static std::vector<InterImm24Site> gInterImm24Sites;
|
||||
static bool gMultiSegRelocMode = false; // set when text layout has >1 segment
|
||||
|
||||
|
||||
// Record an intra-segment patch site for cRELOC emission. A target
|
||||
// below the text base is never intra-segment (it is an undefined-weak
|
||||
|
|
@ -431,6 +477,19 @@ static void recordCRelocSite(uint32_t patchAddr, uint32_t target,
|
|||
gImm24Sites.push_back(s);
|
||||
}
|
||||
|
||||
// Phase C.2 helper — find which text segment contains a 24-bit
|
||||
// address. Returns the 1-based segNum, or 0 if the address is
|
||||
// outside every text seg (e.g. rodata, bss, init_array, or an
|
||||
// absolute address like an MMIO register).
|
||||
static uint32_t findSegContaining(uint32_t addr,
|
||||
const std::vector<TextSeg> &segments) {
|
||||
for (const auto &seg : segments) {
|
||||
if (addr >= seg.base && addr < seg.base + seg.size)
|
||||
return seg.segNum;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Number of bytes patched by a given reloc type. Used by callers
|
||||
// that need to range-check a reloc offset against a buffer size
|
||||
// without re-deriving the width inline. Returns 0 for unknown
|
||||
|
|
@ -588,6 +647,110 @@ static void applyReloc(std::vector<uint8_t> &buf, uint32_t off,
|
|||
}
|
||||
}
|
||||
|
||||
// Phase C.2 — text-section reloc patcher with per-seg / cross-seg
|
||||
// classification. In multi-seg-reloc mode (gMultiSegRelocMode), an
|
||||
// IMM24 site whose target lives in a different text segment is
|
||||
// recorded as a cINTERSEG (Loader patches at load time after picking
|
||||
// banks); the 3 bytes at the patch site are zeroed so the Loader's
|
||||
// ADD computes (targetSegPlacedBase + targetOff) cleanly. Intra-seg
|
||||
// IMM24/IMM16 sites still bake link-time-absolute bytes and are
|
||||
// recorded as cRELOC (per-seg list). Sites whose target is outside
|
||||
// every text segment (rodata / bss / init_array, or an undefined-weak
|
||||
// resolving to 0) fall through to the legacy applyReloc path.
|
||||
//
|
||||
// Outside multi-seg-reloc mode, this is a thin wrapper that just
|
||||
// calls applyReloc + recordCRelocSite via the legacy code path.
|
||||
static void applyTextReloc(std::vector<uint8_t> &buf, uint32_t off,
|
||||
uint32_t patchAddr, uint32_t target,
|
||||
uint8_t rtype,
|
||||
const std::string &symName,
|
||||
uint32_t patchSeg,
|
||||
const std::vector<TextSeg> &segments) {
|
||||
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);
|
||||
// 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
|
||||
// per-seg list for seg 1, since rodata is in seg 1's bank by
|
||||
// design).
|
||||
if (targetSeg == 0) {
|
||||
applyReloc(buf, off, patchAddr, target, rtype, symName);
|
||||
// Also push into the per-seg list with the same gates as
|
||||
// recordCRelocSite (target in same bank as textBase and
|
||||
// >= textBase). Without these gates a target=0 reference
|
||||
// (undefined-weak) would underflow offsetRef.
|
||||
if (gRecordSites &&
|
||||
(rtype == R_W65816_IMM24 || rtype == R_W65816_DATA32 ||
|
||||
rtype == R_W65816_IMM16 || rtype == R_W65816_BANK16)) {
|
||||
uint32_t targetBank = target & 0xFF0000;
|
||||
uint32_t baseBank = gTextBaseForSites & 0xFF0000;
|
||||
if (targetBank == baseBank && target >= gTextBaseForSites) {
|
||||
Imm24Site s;
|
||||
s.patchOff = patchAddr - pseg.base;
|
||||
s.offsetRef = target - gTextBaseForSites;
|
||||
s.byteCnt = (rtype == R_W65816_IMM24 ||
|
||||
rtype == R_W65816_DATA32) ? 3 : 2;
|
||||
s.bitShift = (rtype == R_W65816_BANK16) ? 16 : 0;
|
||||
gIntraImm24SitesPerSeg[patchSeg].push_back(s);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Cross-segment target: emit cINTERSEG. Currently only IMM24
|
||||
// (3-byte JSL/JML/STAlong/LDAlong/DATA32) is supported. IMM16
|
||||
// cross-seg refs can't be safely patched at load time (the bank
|
||||
// byte the Loader could write doesn't fit), so they're rejected.
|
||||
if (targetSeg != patchSeg) {
|
||||
if (rtype != R_W65816_IMM24 && rtype != R_W65816_DATA32) {
|
||||
char msg[200];
|
||||
std::snprintf(msg, sizeof(msg),
|
||||
"cross-segment reloc to '%s' uses %u-byte form; only "
|
||||
"IMM24 / DATA32 (3-byte JSL/JML/STAlong/LDAlong) supports "
|
||||
"cINTERSEG patching under dynamic Loader placement",
|
||||
symName.c_str(), relocWidth(rtype));
|
||||
die(msg);
|
||||
}
|
||||
// Zero the 3 bytes so the Loader's cINTERSEG ADD doesn't
|
||||
// double-count. DATA32's high byte (the pad) stays untouched.
|
||||
buf[off] = 0;
|
||||
buf[off + 1] = 0;
|
||||
buf[off + 2] = 0;
|
||||
if (gRecordSites) {
|
||||
const TextSeg &tseg = segments[targetSeg - 1];
|
||||
InterImm24Site s;
|
||||
s.patchSeg = static_cast<uint16_t>(patchSeg);
|
||||
s.patchOff = static_cast<uint16_t>(patchAddr - pseg.base);
|
||||
s.targetSeg = static_cast<uint8_t>(targetSeg);
|
||||
s.targetOff = static_cast<uint16_t>(target - tseg.base);
|
||||
s.byteCnt = 3;
|
||||
s.bitShift = 0;
|
||||
gInterImm24Sites.push_back(s);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Intra-segment in multi-seg mode: bake bytes + record per-seg
|
||||
// cRELOC. Do the byte write via applyReloc (which handles the
|
||||
// type-specific layout) then push into the per-seg list.
|
||||
applyReloc(buf, off, patchAddr, target, rtype, symName);
|
||||
if (gRecordSites && (rtype == R_W65816_IMM24 || rtype == R_W65816_DATA32 ||
|
||||
rtype == R_W65816_IMM16 || rtype == R_W65816_BANK16)) {
|
||||
Imm24Site s;
|
||||
s.patchOff = patchAddr - pseg.base;
|
||||
s.offsetRef = target - pseg.base;
|
||||
s.byteCnt = (rtype == R_W65816_IMM24 || rtype == R_W65816_DATA32) ? 3 : 2;
|
||||
s.bitShift = (rtype == R_W65816_BANK16) ? 16 : 0;
|
||||
gIntraImm24SitesPerSeg[patchSeg].push_back(s);
|
||||
}
|
||||
}
|
||||
|
||||
struct Linker {
|
||||
std::vector<std::unique_ptr<InputObject>> objs;
|
||||
uint32_t textBase = 0x8000;
|
||||
|
|
@ -601,6 +764,13 @@ struct Linker {
|
|||
uint32_t segmentCap = 0;
|
||||
uint32_t segmentBankBase = 0x040000;
|
||||
std::string manifestPath;
|
||||
// Phase C.2 — opt-in for ExpressLoad-multi-seg cINTERSEG emission.
|
||||
// When true AND --reloc-out is set, link816 zeros cross-seg IMM24
|
||||
// patch sites and records them in gInterImm24Sites for omfEmit to
|
||||
// emit as cINTERSEG (0xF6) opcodes. When false, cross-seg IMM24
|
||||
// sites stay link-time-absolute (the existing static-multi-seg
|
||||
// behaviour used by runMultiSeg.sh).
|
||||
bool multiSegExpressLoad = false;
|
||||
// ProDOS file metadata for the resulting OMF. Set via --filetype /
|
||||
// --aux. Used by disk-image builders (e.g. cppo, Cadius) to set
|
||||
// the on-disk filetype/aux when copying the .bin onto a 2mg.
|
||||
|
|
@ -833,22 +1003,63 @@ struct Linker {
|
|||
std::vector<uint32_t> segSizes = {0}; // bytes packed into each segment (1-based; index 0 = seg 1)
|
||||
uint32_t curSeg = 1;
|
||||
computeLiveSet();
|
||||
|
||||
// Pass 1: place sections that carry an explicit `.text.segN` pin
|
||||
// (Phase A — per-segment placement attribute). Each pinned
|
||||
// section is placed directly into segSizes[N-1], growing the
|
||||
// segSizes vector as needed; the section's textBaseInMerged is
|
||||
// accumulated in source-order alongside non-pinned sections so
|
||||
// intra-object section ordering stays stable for `__text_*`
|
||||
// boundary symbols.
|
||||
auto growToSeg = [&](uint32_t seg) {
|
||||
while (segSizes.size() < seg) segSizes.push_back(0);
|
||||
};
|
||||
for (size_t fi = 0; fi < objs.size(); ++fi) {
|
||||
ObjOffsets &oo = objOff[fi];
|
||||
oo.textBaseInMerged = curText;
|
||||
for (uint32_t idx : objs[fi]->sectionsByKind("text")) {
|
||||
if (!isLive(fi, idx)) continue;
|
||||
uint32_t sz = objs[fi]->sections[idx].size;
|
||||
// If adding this section would exceed the cap, start a
|
||||
// new segment. Skip empty sections in the cap check
|
||||
// (they fit anywhere). Sections larger than the cap
|
||||
// get their own segment (we don't split a single
|
||||
// section across banks — it'd violate intra-section
|
||||
// PCREL and 16-bit absolute addressing).
|
||||
if (segmentCap && sz > 0 &&
|
||||
segSizes[curSeg - 1] > 0 &&
|
||||
segSizes[curSeg - 1] + sz > segmentCap) {
|
||||
uint32_t wanted = requestedSegment(objs[fi]->sections[idx].name);
|
||||
if (wanted == 0) continue; // unpinned — pass 2 handles it
|
||||
growToSeg(wanted);
|
||||
oo.textSegOf[idx] = wanted;
|
||||
oo.textWithin[idx] = segSizes[wanted - 1];
|
||||
segSizes[wanted - 1] += sz;
|
||||
curText += sz;
|
||||
if (segmentCap && segSizes[wanted - 1] > segmentCap) {
|
||||
char msg[200];
|
||||
std::snprintf(msg, sizeof(msg),
|
||||
"pinned text in seg %u exceeds --segment-cap %u "
|
||||
"(have %u bytes pinned, cap %u)",
|
||||
wanted, segmentCap,
|
||||
segSizes[wanted - 1], segmentCap);
|
||||
die(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: greedy-pack non-pinned text sections. When the cap is
|
||||
// exceeded — or when curSeg's slot is already full from pinned
|
||||
// content — advance to the next slot with room. Empty sections
|
||||
// fit anywhere; sections larger than the cap get their own seg
|
||||
// (we don't split a single section across banks — it'd violate
|
||||
// intra-section PCREL and 16-bit absolute addressing).
|
||||
for (size_t fi = 0; fi < objs.size(); ++fi) {
|
||||
ObjOffsets &oo = objOff[fi];
|
||||
for (uint32_t idx : objs[fi]->sectionsByKind("text")) {
|
||||
if (!isLive(fi, idx)) continue;
|
||||
if (requestedSegment(objs[fi]->sections[idx].name) != 0) continue;
|
||||
uint32_t sz = objs[fi]->sections[idx].size;
|
||||
if (sz > 0) {
|
||||
while (true) {
|
||||
if (curSeg > segSizes.size()) segSizes.push_back(0);
|
||||
if (segSizes[curSeg - 1] == 0) break;
|
||||
if (!segmentCap) break;
|
||||
if (segSizes[curSeg - 1] + sz <= segmentCap) break;
|
||||
curSeg++;
|
||||
}
|
||||
} else if (curSeg > segSizes.size()) {
|
||||
segSizes.push_back(0);
|
||||
}
|
||||
oo.textSegOf[idx] = curSeg;
|
||||
|
|
@ -856,6 +1067,10 @@ struct Linker {
|
|||
segSizes[curSeg - 1] += sz;
|
||||
curText += sz;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t fi = 0; fi < objs.size(); ++fi) {
|
||||
ObjOffsets &oo = objOff[fi];
|
||||
oo.rodataBaseInMerged = curRodata;
|
||||
for (uint32_t idx : objs[fi]->sectionsByKind("rodata")) {
|
||||
if (!isLive(fi, idx)) continue;
|
||||
|
|
@ -1295,8 +1510,8 @@ struct Linker {
|
|||
die(msg);
|
||||
}
|
||||
}
|
||||
applyReloc(textBuf, patchOff, patchAddr, target, r.type,
|
||||
resolvedName);
|
||||
applyTextReloc(textBuf, patchOff, patchAddr, target,
|
||||
r.type, resolvedName, segNum, L.segments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1723,6 +1938,9 @@ int main(int argc, char **argv) {
|
|||
} else if (a == "--manifest") {
|
||||
if (++i >= argc) usage(argv[0]);
|
||||
linker.manifestPath = argv[i++];
|
||||
} else if (a == "--multi-seg-expressload") {
|
||||
linker.multiSegExpressLoad = true;
|
||||
i++;
|
||||
} else if (a == "--filetype") {
|
||||
if (++i >= argc) usage(argv[0]);
|
||||
linker.fileType = (int32_t)parseInt(argv[i++]);
|
||||
|
|
@ -1750,7 +1968,17 @@ int main(int argc, char **argv) {
|
|||
gRecordSites = true;
|
||||
gTextBaseForSites = linker.textBase;
|
||||
gImm24Sites.clear();
|
||||
gIntraImm24SitesPerSeg.clear();
|
||||
gInterImm24Sites.clear();
|
||||
}
|
||||
// Phase C.2 — multi-seg-relocation mode is opt-in via
|
||||
// `--multi-seg-expressload`. It changes the cross-segment IMM24
|
||||
// bake from "link-time absolute address" (the static-multi-seg
|
||||
// default consumed by runMultiSeg.sh's Lua loader) to "zero bytes
|
||||
// + cINTERSEG sidecar entry" (consumed by omfEmit --manifest
|
||||
// --expressload, which emits cINTERSEG 0xF6 opcodes so the GS/OS
|
||||
// Loader patches cross-seg JSLs after picking banks dynamically).
|
||||
gMultiSegRelocMode = linker.multiSegExpressLoad;
|
||||
|
||||
std::vector<uint8_t> image;
|
||||
Layout L = linker.link(image);
|
||||
|
|
@ -1777,7 +2005,7 @@ int main(int argc, char **argv) {
|
|||
if (!mapPath.empty()) linker.writeMap(mapPath);
|
||||
if (!debugOutPath.empty()) linker.writeDebugSidecar(debugOutPath);
|
||||
if (!relocOutPath.empty()) {
|
||||
// Sidecar binary format (v3):
|
||||
// Single-flat sidecar binary format (v3):
|
||||
// u32 count
|
||||
// { u32 patchOff; u32 offsetRef; u8 byteCnt; u8 bitShift;
|
||||
// u8 pad[2]; } × count
|
||||
|
|
@ -1787,6 +2015,24 @@ int main(int argc, char **argv) {
|
|||
// with the bank byte instead of the offset). v2 used pad[0]=0
|
||||
// for byteCnt 2/3, so v3 is backwards-compatible: pad[0] was
|
||||
// always 0 then, equivalent to bitShift=0.
|
||||
//
|
||||
// Phase C.2 — under --multi-seg-expressload, we instead write a
|
||||
// per-segment sidecar file alongside the main one:
|
||||
// <relocOutPath>.seg<N>.reloc
|
||||
// Each per-seg file uses v4 format:
|
||||
// u32 intraCount
|
||||
// u32 interCount
|
||||
// intraCount × 12B (same v3 layout, offsets within this seg)
|
||||
// interCount × 12B inter entries:
|
||||
// u32 patchOff (in this seg)
|
||||
// u32 targetOff (in target seg)
|
||||
// u8 targetSeg
|
||||
// u8 byteCnt
|
||||
// u8 bitShift
|
||||
// u8 pad
|
||||
// omfEmit reads the per-seg files when --manifest+--expressload
|
||||
// is in effect; otherwise it reads the flat file (single-seg
|
||||
// back-compat).
|
||||
std::ofstream rf(relocOutPath, std::ios::binary);
|
||||
if (!rf) die("cannot open '" + relocOutPath + "' for writing");
|
||||
uint32_t count = (uint32_t)gImm24Sites.size();
|
||||
|
|
@ -1800,6 +2046,46 @@ int main(int argc, char **argv) {
|
|||
rf.write(reinterpret_cast<const char *>(&bs), 1);
|
||||
rf.write(reinterpret_cast<const char *>(pad), 2);
|
||||
}
|
||||
if (linker.multiSegExpressLoad) {
|
||||
for (size_t k = 0; k < L.segments.size(); k++) {
|
||||
uint32_t segNum = L.segments[k].segNum;
|
||||
char perSegPath[512];
|
||||
std::snprintf(perSegPath, sizeof(perSegPath),
|
||||
"%s.seg%u.reloc", relocOutPath.c_str(), segNum);
|
||||
std::ofstream pf(perSegPath, std::ios::binary);
|
||||
if (!pf) die(std::string("cannot open '") + perSegPath +
|
||||
"' for writing");
|
||||
const auto &intra = gIntraImm24SitesPerSeg[segNum];
|
||||
uint32_t interForSeg = 0;
|
||||
for (const auto &s : gInterImm24Sites) {
|
||||
if (s.patchSeg == segNum) interForSeg++;
|
||||
}
|
||||
uint32_t intraCount = (uint32_t)intra.size();
|
||||
pf.write(reinterpret_cast<const char *>(&intraCount), 4);
|
||||
pf.write(reinterpret_cast<const char *>(&interForSeg), 4);
|
||||
for (const auto &s : intra) {
|
||||
uint32_t po = s.patchOff, off = s.offsetRef;
|
||||
uint8_t bc = s.byteCnt, bs = s.bitShift, pad[2] = {0, 0};
|
||||
pf.write(reinterpret_cast<const char *>(&po), 4);
|
||||
pf.write(reinterpret_cast<const char *>(&off), 4);
|
||||
pf.write(reinterpret_cast<const char *>(&bc), 1);
|
||||
pf.write(reinterpret_cast<const char *>(&bs), 1);
|
||||
pf.write(reinterpret_cast<const char *>(pad), 2);
|
||||
}
|
||||
for (const auto &s : gInterImm24Sites) {
|
||||
if (s.patchSeg != segNum) continue;
|
||||
uint32_t po = s.patchOff;
|
||||
uint32_t to = s.targetOff;
|
||||
uint8_t ts = s.targetSeg, bc = s.byteCnt, bs = s.bitShift, pad = 0;
|
||||
pf.write(reinterpret_cast<const char *>(&po), 4);
|
||||
pf.write(reinterpret_cast<const char *>(&to), 4);
|
||||
pf.write(reinterpret_cast<const char *>(&ts), 1);
|
||||
pf.write(reinterpret_cast<const char *>(&bc), 1);
|
||||
pf.write(reinterpret_cast<const char *>(&bs), 1);
|
||||
pf.write(reinterpret_cast<const char *>(&pad), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Multi-segment: write per-segment images + manifest if there's
|
||||
// more than one segment OR --manifest was requested.
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ namespace {
|
|||
static constexpr uint8_t OMF_OP_LCONST = 0xF2;
|
||||
static constexpr uint8_t OMF_OP_CRELOC = 0xF5;
|
||||
static constexpr uint8_t OMF_OP_END = 0x00;
|
||||
static constexpr uint8_t OMF_OP_CINTERSEG = 0xF6;
|
||||
[[maybe_unused]] static constexpr uint8_t OMF_NUMLEN = 4;
|
||||
[[maybe_unused]] static constexpr uint8_t OMF_VERSION_V21 = 0x02;
|
||||
[[maybe_unused]] static constexpr uint32_t OMF_HDR_SIZE = 44;
|
||||
|
|
@ -68,6 +69,21 @@ struct RelocSite {
|
|||
};
|
||||
std::vector<RelocSite> gReloc24Sites;
|
||||
|
||||
// Phase C.2 — inter-segment IMM24 site for cINTERSEG (0xF6) emission.
|
||||
// Populated from link816's per-seg sidecar (`<reloc-out>.seg<N>.reloc`)
|
||||
// when --manifest+--expressload is in effect.
|
||||
struct InterRelocSite {
|
||||
uint16_t patchOff; // in this segment
|
||||
uint16_t targetOff; // in target segment
|
||||
uint8_t targetSeg; // 1-based target seg num (within this OMF)
|
||||
uint8_t byteCnt; // 3 = IMM24
|
||||
uint8_t bitShift; // 0
|
||||
};
|
||||
|
||||
// cINTERSEG opcode wire size: 0xF6 + ByteCnt + BitShift + OffsetPatch
|
||||
// + SegNum + OffsetReference = 1 + 1 + 1 + 2 + 1 + 2 = 8 bytes per site.
|
||||
static constexpr uint32_t OMF_CINTERSEG_BYTES_PER_SITE = 8;
|
||||
|
||||
static std::vector<uint8_t> readFile(const std::string &path) {
|
||||
std::ifstream f(path, std::ios::binary);
|
||||
if (!f) die("cannot open '" + path + "' for reading");
|
||||
|
|
@ -122,6 +138,18 @@ static void put16(std::vector<uint8_t> &v, uint16_t x) {
|
|||
// `kind` : OMF KIND field. Caller picks; v1 uses 0x8800 (STATIC |
|
||||
// ABSBANK | CODE) for multi-segment static placement, or
|
||||
// 0x0000 (CODE, dynamic) for single-segment legacy mode.
|
||||
static std::vector<uint8_t> emitOneSeg(const std::vector<uint8_t> &image,
|
||||
uint32_t entryOff,
|
||||
uint32_t org,
|
||||
uint16_t segNum,
|
||||
uint16_t kind,
|
||||
const std::string &name,
|
||||
uint32_t bssSize,
|
||||
uint32_t bssGap,
|
||||
const std::vector<RelocSite> &relocSites,
|
||||
const std::vector<InterRelocSite> &interSites);
|
||||
|
||||
// Adapter: legacy single-seg call sites that read from `gReloc24Sites`.
|
||||
static std::vector<uint8_t> emitOneSeg(const std::vector<uint8_t> &image,
|
||||
uint32_t entryOff,
|
||||
uint32_t org,
|
||||
|
|
@ -130,6 +158,21 @@ static std::vector<uint8_t> emitOneSeg(const std::vector<uint8_t> &image,
|
|||
const std::string &name,
|
||||
uint32_t bssSize = 0,
|
||||
uint32_t bssGap = 0) {
|
||||
static const std::vector<InterRelocSite> noInter;
|
||||
return emitOneSeg(image, entryOff, org, segNum, kind, name,
|
||||
bssSize, bssGap, gReloc24Sites, noInter);
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> emitOneSeg(const std::vector<uint8_t> &image,
|
||||
uint32_t entryOff,
|
||||
uint32_t org,
|
||||
uint16_t segNum,
|
||||
uint16_t kind,
|
||||
const std::string &name,
|
||||
uint32_t bssSize,
|
||||
uint32_t bssGap,
|
||||
const std::vector<RelocSite> &relocSites,
|
||||
const std::vector<InterRelocSite> &interSites) {
|
||||
std::vector<uint8_t> body;
|
||||
// Combined image: caller's LCONST data + zero-padding to bss-start
|
||||
// offset + bssSize zero bytes. We embed BSS-as-zeros in the LCONST
|
||||
|
|
@ -166,13 +209,33 @@ static std::vector<uint8_t> emitOneSeg(const std::vector<uint8_t> &image,
|
|||
// (segPlacedBase + OffsetReference) at load time. This is what
|
||||
// makes JSL/JML/STAlong/etc. with intra-segment targets work when
|
||||
// the Loader places us at non-zero bank.
|
||||
for (const auto &s : gReloc24Sites) {
|
||||
for (const auto &s : relocSites) {
|
||||
body.push_back(OMF_OP_CRELOC);
|
||||
body.push_back(s.byteCnt); // ByteCnt (2 or 3)
|
||||
body.push_back(s.bitShift); // BitShift (0 or 16)
|
||||
put16(body, s.patchOff); // OffsetPatch
|
||||
put16(body, s.offsetRef); // OffsetReference
|
||||
}
|
||||
// cINTERSEG opcodes (0xF6) for cross-segment patch sites. Format
|
||||
// per Apple GS/OS Toolbox Reference Vol 3, Appendix B:
|
||||
// 1B 0xF6 opcode
|
||||
// 1B ByteCnt (3 for IMM24 — the only width usable here)
|
||||
// 1B BitShift (0)
|
||||
// 2B OffsetPatch (in this segment)
|
||||
// 1B SegNum (target seg number; cINTERSEG is single-file)
|
||||
// 2B OffsetReference (in target seg)
|
||||
// The Loader patches segment[OffsetPatch..OffsetPatch+ByteCnt-1] to
|
||||
// (targetSegPlacedBase + OffsetReference) ADDED to the constant
|
||||
// already there. link816 zeroes the patch bytes so the ADD becomes
|
||||
// the bare resolved address — see applyTextReloc inter branch.
|
||||
for (const auto &s : interSites) {
|
||||
body.push_back(OMF_OP_CINTERSEG);
|
||||
body.push_back(s.byteCnt);
|
||||
body.push_back(s.bitShift);
|
||||
put16(body, s.patchOff);
|
||||
body.push_back(s.targetSeg);
|
||||
put16(body, s.targetOff);
|
||||
}
|
||||
body.push_back(OMF_OP_END); // END opcode
|
||||
|
||||
// Real OMF format (Merlin32 convention, verified GS/OS Loader-launchable):
|
||||
|
|
@ -404,6 +467,30 @@ static std::vector<uint8_t> emitOMF(const std::vector<uint8_t> &image,
|
|||
// +58.. : SEG_NAME (length-prefixed)
|
||||
//
|
||||
// All counts use NUMLEN=4 (4-byte length on LCONST opcodes).
|
||||
// Phase C — multi-user-segment ExpressLoad descriptor. Each entry
|
||||
// describes one user code segment (plus its optional BSS tail and
|
||||
// per-segment cRELOC sites recorded by link816). ENTRY_OFFSET is only
|
||||
// meaningful on the first user seg (= seg 2 of the OMF; the Loader
|
||||
// JSLs there to start the program). bssSize/bssGap follow the same
|
||||
// LCONST-embedded-zeros convention as emitOneSeg. relocSites is the
|
||||
// intra-segment cRELOC list — cross-segment references (cINTERSEG)
|
||||
// are NOT yet emitted; this descriptor is the prep for that work.
|
||||
struct UserSeg {
|
||||
std::vector<uint8_t> image;
|
||||
uint32_t entryOffset = 0;
|
||||
std::string name;
|
||||
uint32_t bssSize = 0;
|
||||
uint32_t bssGap = 0;
|
||||
std::vector<RelocSite> relocSites;
|
||||
std::vector<InterRelocSite> interSites; // Phase C.2 cINTERSEG sites
|
||||
};
|
||||
|
||||
static std::vector<uint8_t> emitOmfExpressLoad(
|
||||
const std::vector<UserSeg> &users,
|
||||
uint32_t stackSize = 0);
|
||||
|
||||
// Legacy single-user-seg adapter. Reads cRELOC sites from the global
|
||||
// `gReloc24Sites` (filled by --relocs).
|
||||
static std::vector<uint8_t> emitOmfExpressLoad(
|
||||
const std::vector<uint8_t> &image,
|
||||
uint32_t entryOffset,
|
||||
|
|
@ -411,12 +498,35 @@ static std::vector<uint8_t> emitOmfExpressLoad(
|
|||
uint32_t stackSize = 0,
|
||||
uint32_t bssSize = 0,
|
||||
uint32_t bssGap = 0) {
|
||||
UserSeg u;
|
||||
u.image = image;
|
||||
u.entryOffset = entryOffset;
|
||||
u.name = userSegName;
|
||||
u.bssSize = bssSize;
|
||||
u.bssGap = bssGap;
|
||||
u.relocSites = gReloc24Sites;
|
||||
return emitOmfExpressLoad(std::vector<UserSeg>{u}, stackSize);
|
||||
}
|
||||
|
||||
// Step 1: build the user segment using KIND=0x1000 (CODE|PRIV).
|
||||
// Same KIND emitOMF uses for single-segment apps. Verified
|
||||
// Loader-launchable via the Finder smoke path.
|
||||
auto userSeg = emitOneSeg(image, entryOffset, /*org*/0, /*segNum*/2,
|
||||
/*kind*/0x1000, userSegName, bssSize, bssGap);
|
||||
static std::vector<uint8_t> emitOmfExpressLoad(
|
||||
const std::vector<UserSeg> &users,
|
||||
uint32_t stackSize) {
|
||||
if (users.empty()) die("emitOmfExpressLoad: no user segments");
|
||||
|
||||
// Step 1: build every user segment using KIND=0x1000 (CODE|PRIV).
|
||||
// Same KIND emitOMF uses for single-segment apps. Each user seg
|
||||
// gets sequential SEGNUM starting at 2 (seg 1 is ~ExpressLoad).
|
||||
// The optional DP/Stack seg gets the trailing SEGNUM.
|
||||
std::vector<std::vector<uint8_t>> userSegs;
|
||||
userSegs.reserve(users.size());
|
||||
for (size_t k = 0; k < users.size(); k++) {
|
||||
const auto &u = users[k];
|
||||
userSegs.push_back(emitOneSeg(u.image, u.entryOffset, /*org*/0,
|
||||
/*segNum*/(uint16_t)(k + 2),
|
||||
/*kind*/OMF_KIND_CODE_PRIV,
|
||||
u.name, u.bssSize, u.bssGap,
|
||||
u.relocSites, u.interSites));
|
||||
}
|
||||
|
||||
// Optionally build the DP/Stack segment. If present it lives in the
|
||||
// file AFTER the user seg and gets its own ExpressLoad segtable +
|
||||
|
|
@ -463,35 +573,59 @@ static std::vector<uint8_t> emitOmfExpressLoad(
|
|||
// uses LABLEN=0 (length-prefixed name): 1 length byte + 12 chars.
|
||||
const std::string elName = "~ExpressLoad";
|
||||
const uint32_t elNameAreaSize = LOAD_NAME_SIZE + 1 + (uint32_t)elName.size();
|
||||
const uint32_t nSegs = haveDpStack ? 2 : 1; // non-ExpressLoad segs
|
||||
// Total non-ExpressLoad segs = N user segs + optional DP/Stack.
|
||||
// Rebuild dpStackSeg with the correct trailing segNum now that we
|
||||
// know how many user segs there are.
|
||||
if (haveDpStack) {
|
||||
dpStackSeg = emitDpStackSeg(stackSize,
|
||||
/*segNum*/(uint16_t)(users.size() + 2));
|
||||
}
|
||||
const uint32_t nSegs = (uint32_t)users.size() + (haveDpStack ? 1u : 0u);
|
||||
const uint32_t elDataSize = HEADER_BYTES + (SEGTAB_ENTRY + REMAP_ENTRY + HDR_INFO_ENTRY) * nSegs;
|
||||
// Body size = 1 byte LCONST opcode + 4 byte length + data + 1 byte END
|
||||
const uint32_t elBodySize = 1 + 4 + elDataSize + 1;
|
||||
const uint32_t elSegSize = HDR_SIZE + elNameAreaSize + elBodySize;
|
||||
|
||||
// User segment file offsets (after ExpressLoad seg).
|
||||
const uint32_t userSegStart = elSegSize;
|
||||
const uint32_t userBodyOpOff = userSegStart + HDR_SIZE + userNameAreaSize;
|
||||
const uint32_t userDataOff = userBodyOpOff + 5; // 1 op + 4 length
|
||||
// User segment file offsets (cascade after ExpressLoad seg). For
|
||||
// each user seg, dataOff = (segStart + HDR_SIZE + nameArea + 1B
|
||||
// LCONST opcode + 4B length).
|
||||
std::vector<uint32_t> userDataOffs;
|
||||
std::vector<uint32_t> userCRelocOffs;
|
||||
std::vector<uint32_t> userSegStarts;
|
||||
userDataOffs.reserve(users.size());
|
||||
userCRelocOffs.reserve(users.size());
|
||||
userSegStarts.reserve(users.size());
|
||||
uint32_t segStart = elSegSize;
|
||||
for (size_t k = 0; k < users.size(); k++) {
|
||||
userSegStarts.push_back(segStart);
|
||||
const uint32_t bodyOpOff = segStart + HDR_SIZE + userNameAreaSize;
|
||||
const uint32_t dataOff = bodyOpOff + 5;
|
||||
userDataOffs.push_back(dataOff);
|
||||
const uint32_t dataLen = (uint32_t)users[k].image.size()
|
||||
+ users[k].bssGap + users[k].bssSize;
|
||||
userCRelocOffs.push_back(dataOff + dataLen);
|
||||
segStart += (uint32_t)userSegs[k].size();
|
||||
}
|
||||
|
||||
// DP/Stack segment file offsets (after user seg). The DP/Stack body
|
||||
// mirrors the real GNO/ME ~_STACK seg format: an LCONST opcode + 4
|
||||
// byte length + `stackSize` zero bytes + END. ExpressLoad's
|
||||
// DP/Stack segment file offsets (after all user segs). The DP/Stack
|
||||
// body mirrors the real GNO/ME ~_STACK seg format: an LCONST opcode
|
||||
// + 4 byte length + `stackSize` zero bytes + END. ExpressLoad's
|
||||
// hdr_info entry has to point at the LCONST data so the Loader
|
||||
// copies the right number of zeros into the allocated chunk — a
|
||||
// body of just END (RESSPC-only) silently no-ops on the
|
||||
// ExpressLoad fast path, which is the bug this whole section fixes.
|
||||
const uint32_t dpStackSegStart = userSegStart + (uint32_t)userSeg.size();
|
||||
const uint32_t dpStackSegStart = segStart;
|
||||
const uint32_t dpStackBodyOff = dpStackSegStart + HDR_SIZE + (LOAD_NAME_SIZE + SEG_NAME_SIZE);
|
||||
const uint32_t dpStackDataOff = dpStackBodyOff + 5; // 1 op + 4 length
|
||||
|
||||
// Step 3: build the ExpressLoad LCONST data.
|
||||
std::vector<uint8_t> elData;
|
||||
// Header (6 bytes): reserved DWORD + count WORD. count = N-2 where
|
||||
// N = total segments in the file (including ExpressLoad). With a
|
||||
// DP/Stack seg N=3 so count=1; without it N=2 so count=0.
|
||||
// N = total segments in the file (including ExpressLoad). For a
|
||||
// 1-user-seg layout count=0 (N=2) or count=1 (N=3 with DP/Stack);
|
||||
// for K user segs count = K - 1 (+ 1 if DP/Stack).
|
||||
put32(elData, 0); // reserved
|
||||
put16(elData, (uint16_t)(haveDpStack ? 1 : 0)); // count = N-2
|
||||
put16(elData, (uint16_t)(nSegs - 1)); // count = N-2 = nSegs-1
|
||||
|
||||
// Segment list: one 8-byte entry per non-ExpressLoad segment. Each
|
||||
// entry's first WORD is the SELF-RELATIVE offset (from this entry's
|
||||
|
|
@ -508,53 +642,53 @@ static std::vector<uint8_t> emitOmfExpressLoad(
|
|||
}
|
||||
|
||||
// Remap list: 1 WORD per non-ExpressLoad seg, giving the new
|
||||
// segment number for each old segment position. Old seg 1 (user
|
||||
// code, would-be sole seg without ExpressLoad) → new seg 2.
|
||||
// Old seg 2 (DP/Stack, only present when --stack-size) → new seg 3.
|
||||
put16(elData, 2);
|
||||
if (haveDpStack) {
|
||||
put16(elData, 3);
|
||||
// segment number for each old segment position. Each non-EL seg
|
||||
// gets new SEGNUM = (positionInExpressLoad + 2), starting at 2 for
|
||||
// the first user seg. DP/Stack lands last.
|
||||
for (uint32_t i = 0; i < nSegs; i++) {
|
||||
put16(elData, (uint16_t)(i + 2));
|
||||
}
|
||||
|
||||
// Header info entry for the user segment.
|
||||
// data length = LCONST data size in the file. emitOneSeg embeds
|
||||
// bssGap bytes of zero padding + bssSize bytes of BSS-as-zeros in
|
||||
// the LCONST after the caller's image, so the on-disk data is
|
||||
// image.size() + bssGap + bssSize bytes.
|
||||
put32(elData, userDataOff); // data offset in file
|
||||
put32(elData, (uint32_t)image.size() + bssGap + bssSize); // data length
|
||||
// cRELOC opcodes (if any) are emitted by emitOneSeg directly after
|
||||
// the LCONST data and before the END opcode. Tell ExpressLoad
|
||||
// where they live so the Loader can apply them — without this the
|
||||
// Loader skips the body walk and intra-segment IMM24 references
|
||||
// (every JSL into our text) point at bank 0 even after relocation.
|
||||
if (gReloc24Sites.empty()) {
|
||||
// Header info entries — one per non-ExpressLoad segment. Each
|
||||
// entry is 68 bytes: 16B (dataOff,dataLen,relocOff,relocLen) + 32B
|
||||
// header copy + 10B LOAD_NAME + 10B SEG_NAME. data length = LCONST
|
||||
// data size in the file. emitOneSeg embeds bssGap bytes of zero
|
||||
// padding + bssSize bytes of BSS-as-zeros in the LCONST after the
|
||||
// caller's image, so the on-disk data is image.size() + bssGap +
|
||||
// bssSize bytes. cRELOC opcodes (if any) are emitted by emitOneSeg
|
||||
// directly after the LCONST data and before the END opcode; tell
|
||||
// ExpressLoad where they live so the Loader can apply them.
|
||||
for (size_t k = 0; k < users.size(); k++) {
|
||||
const auto &u = users[k];
|
||||
const auto &seg = userSegs[k];
|
||||
if (seg.size() < HDR_SIZE) die("internal: user seg too small");
|
||||
const uint32_t dataLen = (uint32_t)u.image.size() + u.bssGap + u.bssSize;
|
||||
put32(elData, userDataOffs[k]); // data offset in file
|
||||
put32(elData, dataLen); // data length
|
||||
const uint32_t relocLen =
|
||||
OMF_CRELOC_BYTES_PER_SITE * (uint32_t)u.relocSites.size() +
|
||||
OMF_CINTERSEG_BYTES_PER_SITE * (uint32_t)u.interSites.size();
|
||||
if (relocLen == 0) {
|
||||
put32(elData, 0); // reloc offset
|
||||
put32(elData, 0); // reloc length
|
||||
} else {
|
||||
const uint32_t crelocOff =
|
||||
userDataOff + (uint32_t)image.size() + bssGap + bssSize;
|
||||
const uint32_t crelocLen =
|
||||
OMF_CRELOC_BYTES_PER_SITE * (uint32_t)gReloc24Sites.size();
|
||||
put32(elData, crelocOff);
|
||||
put32(elData, crelocLen);
|
||||
put32(elData, userCRelocOffs[k]);
|
||||
put32(elData, relocLen);
|
||||
}
|
||||
|
||||
// Header copy: bytes [12..43] of user segment header, DISPDATA → 0.
|
||||
if (userSeg.size() < HDR_SIZE) die("internal: user seg too small");
|
||||
elData.insert(elData.end(), userSeg.begin() + 12, userSeg.begin() + HDR_SIZE);
|
||||
elData.insert(elData.end(), seg.begin() + 12, seg.begin() + HDR_SIZE);
|
||||
// DISPDATA is at offset 42..43 of the original header; in the copy
|
||||
// (which omits the first 12 bytes), it lands at offset 30..31.
|
||||
elData[elData.size() - 32 + 30] = 0;
|
||||
elData[elData.size() - 32 + 31] = 0;
|
||||
|
||||
// LOAD_NAME (10 bytes, space-padded — matches Merlin convention)
|
||||
for (int i = 0; i < (int)LOAD_NAME_SIZE; i++) elData.push_back(0x20);
|
||||
// SEG_NAME (10 bytes fixed-width, space-padded)
|
||||
std::string truncated = userSegName.substr(0, SEG_NAME_SIZE);
|
||||
std::string truncated = u.name.substr(0, SEG_NAME_SIZE);
|
||||
for (size_t i = 0; i < SEG_NAME_SIZE; i++) {
|
||||
elData.push_back(i < truncated.size() ? (uint8_t)truncated[i] : 0x20);
|
||||
}
|
||||
}
|
||||
|
||||
// Header info entry for the DP/Stack segment (when present).
|
||||
// data_off / data_len point at the LCONST zero bytes carried in the
|
||||
|
|
@ -627,14 +761,15 @@ static std::vector<uint8_t> emitOmfExpressLoad(
|
|||
if (elSeg.size() != elSegSize)
|
||||
die("internal: ExpressLoad segment size mismatch");
|
||||
|
||||
// Step 6: concatenate ExpressLoad + user segment + optional DP/Stack.
|
||||
// Step 6: concatenate ExpressLoad + user segments + optional DP/Stack.
|
||||
// The DP/Stack seg's presence is now also recorded in the
|
||||
// ExpressLoad load script (segtable + remap + header_info entries
|
||||
// above) so the Loader's fast path honors KIND=0x4012 instead of
|
||||
// silently dropping it to its default 4 KB DP/Stack allocation.
|
||||
std::vector<uint8_t> result;
|
||||
result.insert(result.end(), elSeg.begin(), elSeg.end());
|
||||
result.insert(result.end(), userSeg.begin(), userSeg.end());
|
||||
for (const auto &seg : userSegs)
|
||||
result.insert(result.end(), seg.begin(), seg.end());
|
||||
if (haveDpStack) {
|
||||
result.insert(result.end(), dpStackSeg.begin(), dpStackSeg.end());
|
||||
}
|
||||
|
|
@ -805,8 +940,9 @@ int main(int argc, char **argv) {
|
|||
die("--stack-size must be a multiple of 256 (page-aligned)");
|
||||
if (stackSize > 0xFFFF)
|
||||
die("--stack-size cannot exceed 65535 bytes (one bank)");
|
||||
if (!manifest.empty())
|
||||
die("--stack-size with --manifest not yet supported");
|
||||
// --stack-size + --manifest is allowed when ExpressLoad wraps
|
||||
// the multi-seg file (auto-enabled below); the DP/Stack seg
|
||||
// gets the trailing SEGNUM after all user segs.
|
||||
// Plain (non-ExpressLoad) multi-segment OMFs do not launch
|
||||
// correctly under the GS/OS 6.0.2 Loader — verified empirically:
|
||||
// the bare DP/Stack + code combo is rejected (program never
|
||||
|
|
@ -857,6 +993,107 @@ int main(int argc, char **argv) {
|
|||
// Multi-segment mode.
|
||||
if (!manifest.empty()) {
|
||||
auto segs = parseManifest(manifest);
|
||||
// --manifest + --expressload: wrap N user segments in a single
|
||||
// ~ExpressLoad descriptor (Phase C.1). Each user seg gets
|
||||
// KIND=0x1000 (CODE|PRIV), so the Loader picks banks dynamically
|
||||
// — programs MUST NOT depend on link-time bank placement of
|
||||
// cross-segment IMM24 targets (those need cINTERSEG, deferred
|
||||
// to Phase C.2). Intra-segment cRELOC is supported via the
|
||||
// global gReloc24Sites (single-sidecar path; per-seg sidecars
|
||||
// also deferred to Phase C.2).
|
||||
if (expressload) {
|
||||
std::vector<UserSeg> users;
|
||||
users.reserve(segs.size());
|
||||
// Phase C.2 — when link816 is invoked with
|
||||
// --multi-seg-expressload --reloc-out FILE, it writes per-seg
|
||||
// sidecars at FILE.seg<N>.reloc with intra + inter site
|
||||
// lists. Look for them next to the user-supplied relocFile.
|
||||
for (size_t k = 0; k < segs.size(); ++k) {
|
||||
const auto &s = segs[k];
|
||||
UserSeg u;
|
||||
u.image = readFile(s.image);
|
||||
u.entryOffset = (k == 0) ? s.entryOff : 0;
|
||||
u.name = s.name;
|
||||
if (!relocFile.empty()) {
|
||||
char perSegPath[512];
|
||||
std::snprintf(perSegPath, sizeof(perSegPath),
|
||||
"%s.seg%u.reloc", relocFile.c_str(),
|
||||
s.num);
|
||||
std::ifstream pf(perSegPath, std::ios::binary);
|
||||
if (pf) {
|
||||
std::vector<uint8_t> raw(
|
||||
(std::istreambuf_iterator<char>(pf)),
|
||||
std::istreambuf_iterator<char>());
|
||||
if (raw.size() < 8)
|
||||
die(std::string(perSegPath) + ": too small");
|
||||
uint32_t intraCount = (uint32_t)raw[0]
|
||||
| ((uint32_t)raw[1] << 8)
|
||||
| ((uint32_t)raw[2] << 16)
|
||||
| ((uint32_t)raw[3] << 24);
|
||||
uint32_t interCount = (uint32_t)raw[4]
|
||||
| ((uint32_t)raw[5] << 8)
|
||||
| ((uint32_t)raw[6] << 16)
|
||||
| ((uint32_t)raw[7] << 24);
|
||||
size_t want = 8 + 12 * (size_t)intraCount
|
||||
+ 12 * (size_t)interCount;
|
||||
if (raw.size() != want)
|
||||
die(std::string(perSegPath) + ": size mismatch");
|
||||
size_t off = 8;
|
||||
for (uint32_t i = 0; i < intraCount; i++) {
|
||||
uint32_t po = (uint32_t)raw[off]
|
||||
| ((uint32_t)raw[off+1] << 8)
|
||||
| ((uint32_t)raw[off+2] << 16)
|
||||
| ((uint32_t)raw[off+3] << 24);
|
||||
uint32_t orf = (uint32_t)raw[off+4]
|
||||
| ((uint32_t)raw[off+5] << 8)
|
||||
| ((uint32_t)raw[off+6] << 16)
|
||||
| ((uint32_t)raw[off+7] << 24);
|
||||
RelocSite r;
|
||||
r.patchOff = (uint16_t)po;
|
||||
r.offsetRef = (uint16_t)orf;
|
||||
r.byteCnt = raw[off+8];
|
||||
r.bitShift = raw[off+9];
|
||||
u.relocSites.push_back(r);
|
||||
off += 12;
|
||||
}
|
||||
for (uint32_t i = 0; i < interCount; i++) {
|
||||
uint32_t po = (uint32_t)raw[off]
|
||||
| ((uint32_t)raw[off+1] << 8)
|
||||
| ((uint32_t)raw[off+2] << 16)
|
||||
| ((uint32_t)raw[off+3] << 24);
|
||||
uint32_t to = (uint32_t)raw[off+4]
|
||||
| ((uint32_t)raw[off+5] << 8)
|
||||
| ((uint32_t)raw[off+6] << 16)
|
||||
| ((uint32_t)raw[off+7] << 24);
|
||||
InterRelocSite is;
|
||||
is.patchOff = (uint16_t)po;
|
||||
is.targetOff = (uint16_t)to;
|
||||
// Remap link816 seg N → OMF SEGNUM (N+1):
|
||||
// ExpressLoad occupies OMF seg 1, so each
|
||||
// user seg's OMF SEGNUM is link816 seg + 1.
|
||||
is.targetSeg = (uint8_t)(raw[off+8] + 1);
|
||||
is.byteCnt = raw[off+9];
|
||||
is.bitShift = raw[off+10];
|
||||
u.interSites.push_back(is);
|
||||
off += 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Back-compat for layouts without per-seg sidecars: seg
|
||||
// 1 inherits the global --relocs flat list.
|
||||
if (u.relocSites.empty() && u.interSites.empty() && k == 0)
|
||||
u.relocSites = gReloc24Sites;
|
||||
users.push_back(std::move(u));
|
||||
}
|
||||
auto blob = emitOmfExpressLoad(users, stackSize);
|
||||
std::ofstream f(output, std::ios::binary);
|
||||
if (!f) die("cannot open '" + output + "' for writing");
|
||||
f.write(reinterpret_cast<const char *>(blob.data()), blob.size());
|
||||
std::fprintf(stderr,
|
||||
"OMF: ExpressLoad + %zu user segments -> %s (%zu bytes total)\n",
|
||||
segs.size(), output.c_str(), blob.size());
|
||||
return 0;
|
||||
}
|
||||
std::vector<uint8_t> blob;
|
||||
size_t totalPayload = 0;
|
||||
for (size_t k = 0; k < segs.size(); ++k) {
|
||||
|
|
|
|||
|
|
@ -110,6 +110,22 @@ static constexpr unsigned kFrameRecordSize = 12;
|
|||
static constexpr unsigned kJslRtlBytes = 3;
|
||||
static constexpr const char *kFrameSection = ".debug_frame_w65816";
|
||||
|
||||
// Phase B — opt-in far globals. A global declared via
|
||||
// `__attribute__((section("X")))` where X contains ".far" (canonically
|
||||
// .rodata.far / .data.far / .bss.far) routes through LDA_Long / STA_Long
|
||||
// instead of LDA_Abs / STA_Abs at AsmPrinter pseudo expansion, so its
|
||||
// bank byte is encoded explicitly and the linker patches the full
|
||||
// 24-bit address via R_W65816_IMM24. This is the opt-in path for
|
||||
// programs whose data overflows the small-data bank (DBR is pinned at
|
||||
// the BSS-seg-0 bank by crt0; large rodata/BSS in other banks needs
|
||||
// long-form addressing).
|
||||
static bool isFarGlobalOperand(const llvm::MachineOperand &MO) {
|
||||
if (!MO.isGlobal()) return false;
|
||||
const llvm::GlobalValue *GV = MO.getGlobal();
|
||||
if (!GV->hasSection()) return false;
|
||||
return GV->getSection().find(".far") != llvm::StringRef::npos;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class W65816AsmPrinter : public AsmPrinter {
|
||||
|
|
@ -655,6 +671,16 @@ void W65816AsmPrinter::emitInstruction(const MachineInstr *MI) {
|
|||
// like *(uint16*)0x70).
|
||||
// $100..$FFFF -> Abs-form (DBR-relative).
|
||||
// non-zero bank -> Long-form (4-byte, bank-explicit).
|
||||
// Far global -> Long-form (Phase B opt-in: a global declared
|
||||
// via __attribute__((section("X")))
|
||||
// where X contains ".far" — usually
|
||||
// .rodata.far, .data.far, or .bss.far
|
||||
// — gets bank-explicit 24-bit access
|
||||
// regardless of DBR. This is how a
|
||||
// program with > 60 KB of data spreads
|
||||
// it across multiple banks while keeping
|
||||
// DBR pinned at the small-data bank for
|
||||
// the rest of the program.)
|
||||
// The DP pickup is what makes the bank-0-anchored __indirTarget
|
||||
// slot at $00:00B8 (see runtime/src/libgcc.s) addressable correctly
|
||||
// from codegen-emitted indirect-call sequences when DBR != 0 (crt0
|
||||
|
|
@ -680,8 +706,12 @@ void W65816AsmPrinter::emitInstruction(const MachineInstr *MI) {
|
|||
return;
|
||||
}
|
||||
}
|
||||
bool IsFarGlobal = AddrOp.isGlobal() &&
|
||||
AddrOp.getGlobal()->hasSection() &&
|
||||
AddrOp.getGlobal()->getSection().find(".far") !=
|
||||
StringRef::npos;
|
||||
EmitToStreamer(*OutStreamer,
|
||||
MCInstBuilder(OpAbs)
|
||||
MCInstBuilder(IsFarGlobal ? OpLong : OpAbs)
|
||||
.addOperand(lowerOperand(AddrOp, MCInstLowering)));
|
||||
return;
|
||||
}
|
||||
|
|
@ -778,8 +808,12 @@ void W65816AsmPrinter::emitInstruction(const MachineInstr *MI) {
|
|||
case W65816::LDA8long: {
|
||||
// i8 absolute load — same M=8 wrap as LDA_Abs; LDA8long uses
|
||||
// LDA_Long (0xAF, bank-explicit) for const-int MMIO addresses.
|
||||
// A global declared in a `.far*` section also routes through
|
||||
// LDA_Long so its bank byte is encoded explicitly (Phase B).
|
||||
bool IsLong = MI->getOpcode() == W65816::LDA8long;
|
||||
MCOperand Addr = lowerOperand(MI->getOperand(1), MCInstLowering);
|
||||
const MachineOperand &AddrMO = MI->getOperand(1);
|
||||
IsLong = IsLong || isFarGlobalOperand(AddrMO);
|
||||
MCOperand Addr = lowerOperand(AddrMO, MCInstLowering);
|
||||
if (IsLong && Addr.isImm()) {
|
||||
// 16-bit pointer sign-extended into i32 imm — mask back to 16 bits
|
||||
// so the encoded bank byte is 0. See STA8long for the rationale.
|
||||
|
|
@ -795,20 +829,27 @@ void W65816AsmPrinter::emitInstruction(const MachineInstr *MI) {
|
|||
case W65816::LDA8absX: {
|
||||
// i8 indexed-global load: SEP #0x20 ; LDA <addr>, X ; REP #0x20
|
||||
// X holds the index (set up by CopyToReg before this MI).
|
||||
// A `.far*` global routes through LDA_LongX (0xBF) for explicit
|
||||
// 24-bit bank addressing (Phase B).
|
||||
const MachineOperand &AddrMO = MI->getOperand(0);
|
||||
bool IsFar = isFarGlobalOperand(AddrMO);
|
||||
emitSepM();
|
||||
EmitToStreamer(*OutStreamer,
|
||||
MCInstBuilder(W65816::LDA_AbsX)
|
||||
.addOperand(lowerOperand(MI->getOperand(0), MCInstLowering)));
|
||||
MCInstBuilder(IsFar ? W65816::LDA_LongX : W65816::LDA_AbsX)
|
||||
.addOperand(lowerOperand(AddrMO, MCInstLowering)));
|
||||
emitRepM();
|
||||
return;
|
||||
}
|
||||
case W65816::STA8absX: {
|
||||
// i8 indexed-global store: SEP #0x20 ; STA <addr>, X ; REP #0x20
|
||||
// A holds the value, X holds the index.
|
||||
// A holds the value, X holds the index. `.far*` global routes
|
||||
// through STA_LongX (0x9F) for explicit 24-bit bank addressing.
|
||||
const MachineOperand &AddrMO = MI->getOperand(0);
|
||||
bool IsFar = isFarGlobalOperand(AddrMO);
|
||||
emitSepM();
|
||||
EmitToStreamer(*OutStreamer,
|
||||
MCInstBuilder(W65816::STA_AbsX)
|
||||
.addOperand(lowerOperand(MI->getOperand(0), MCInstLowering)));
|
||||
MCInstBuilder(IsFar ? W65816::STA_LongX : W65816::STA_AbsX)
|
||||
.addOperand(lowerOperand(AddrMO, MCInstLowering)));
|
||||
emitRepM();
|
||||
return;
|
||||
}
|
||||
|
|
@ -821,8 +862,10 @@ void W65816AsmPrinter::emitInstruction(const MachineInstr *MI) {
|
|||
// from STA8abs only in the underlying opcode (0x8F vs 0x8D): long
|
||||
// is bank-explicit, abs is DBR-relative. Long is required for
|
||||
// const-int MMIO addresses since the data bank is non-zero under
|
||||
// GS/OS Loader.
|
||||
bool IsLong = MI->getOpcode() == W65816::STA8long;
|
||||
// GS/OS Loader. Far globals (`.far*` section) also route through
|
||||
// STA_Long for explicit bank addressing (Phase B).
|
||||
bool IsLong = MI->getOpcode() == W65816::STA8long ||
|
||||
isFarGlobalOperand(MI->getOperand(1));
|
||||
bool UsesAcc8 = MI->getMF()
|
||||
->getInfo<W65816MachineFunctionInfo>()
|
||||
->getUsesAcc8();
|
||||
|
|
@ -857,8 +900,12 @@ void W65816AsmPrinter::emitInstruction(const MachineInstr *MI) {
|
|||
case W65816::SBCabs: {
|
||||
bool IsSub = MI->getOpcode() == W65816::SBCabs;
|
||||
emitOp(IsSub ? W65816::SEC : W65816::CLC);
|
||||
bool IsFar = isFarGlobalOperand(MI->getOperand(2));
|
||||
unsigned Mc = IsSub
|
||||
? (IsFar ? W65816::SBC_Long : W65816::SBC_Abs)
|
||||
: (IsFar ? W65816::ADC_Long : W65816::ADC_Abs);
|
||||
EmitToStreamer(*OutStreamer,
|
||||
MCInstBuilder(IsSub ? W65816::SBC_Abs : W65816::ADC_Abs)
|
||||
MCInstBuilder(Mc)
|
||||
.addOperand(lowerOperand(MI->getOperand(2), MCInstLowering)));
|
||||
return;
|
||||
}
|
||||
|
|
@ -866,8 +913,12 @@ void W65816AsmPrinter::emitInstruction(const MachineInstr *MI) {
|
|||
case W65816::SBCEabs: {
|
||||
// Chained variant — no CLC/SEC prefix.
|
||||
bool IsSub = MI->getOpcode() == W65816::SBCEabs;
|
||||
bool IsFar = isFarGlobalOperand(MI->getOperand(2));
|
||||
unsigned Mc = IsSub
|
||||
? (IsFar ? W65816::SBC_Long : W65816::SBC_Abs)
|
||||
: (IsFar ? W65816::ADC_Long : W65816::ADC_Abs);
|
||||
EmitToStreamer(*OutStreamer,
|
||||
MCInstBuilder(IsSub ? W65816::SBC_Abs : W65816::ADC_Abs)
|
||||
MCInstBuilder(Mc)
|
||||
.addOperand(lowerOperand(MI->getOperand(2), MCInstLowering)));
|
||||
return;
|
||||
}
|
||||
|
|
@ -888,8 +939,9 @@ void W65816AsmPrinter::emitInstruction(const MachineInstr *MI) {
|
|||
return;
|
||||
}
|
||||
case W65816::CMPabs: {
|
||||
bool IsFar = isFarGlobalOperand(MI->getOperand(1));
|
||||
EmitToStreamer(*OutStreamer,
|
||||
MCInstBuilder(W65816::CMP_Abs)
|
||||
MCInstBuilder(IsFar ? W65816::CMP_Long : W65816::CMP_Abs)
|
||||
.addOperand(lowerOperand(MI->getOperand(1), MCInstLowering)));
|
||||
return;
|
||||
}
|
||||
|
|
@ -912,11 +964,12 @@ void W65816AsmPrinter::emitInstruction(const MachineInstr *MI) {
|
|||
case W65816::ANDabs:
|
||||
case W65816::ORAabs:
|
||||
case W65816::EORabs: {
|
||||
bool IsFar = isFarGlobalOperand(MI->getOperand(2));
|
||||
unsigned mc = 0;
|
||||
switch (MI->getOpcode()) {
|
||||
case W65816::ANDabs: mc = W65816::AND_Abs; break;
|
||||
case W65816::ORAabs: mc = W65816::ORA_Abs; break;
|
||||
case W65816::EORabs: mc = W65816::EOR_Abs; break;
|
||||
case W65816::ANDabs: mc = IsFar ? W65816::AND_Long : W65816::AND_Abs; break;
|
||||
case W65816::ORAabs: mc = IsFar ? W65816::ORA_Long : W65816::ORA_Abs; break;
|
||||
case W65816::EORabs: mc = IsFar ? W65816::EOR_Long : W65816::EOR_Abs; break;
|
||||
}
|
||||
EmitToStreamer(*OutStreamer,
|
||||
MCInstBuilder(mc).addOperand(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue