Multisegment OMF

This commit is contained in:
Scott Duensing 2026-06-24 19:23:30 -05:00
parent 3388f3c5a5
commit 09c99937e7
6 changed files with 863 additions and 95 deletions

0
link816.cpp Normal file
View file

View 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.

View file

@ -5360,6 +5360,129 @@ EOF
rm -f "$cMsegFile" "$oMsegFile" "$binMseg" "$mfMseg" \ rm -f "$cMsegFile" "$oMsegFile" "$binMseg" "$mfMseg" \
"${binMseg%.bin}".seg*.bin "${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" \ rm -f "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oQsortF" \
"$oExtrasF" "$oStrtokF" "$oMathF" "$oSfF" "$oSdF" "$oCrt0F" "$oExtrasF" "$oStrtokF" "$oMathF" "$oSfF" "$oSdF" "$oCrt0F"
else else
@ -6394,6 +6517,58 @@ EOF
"$omfGsf" "$testFileGsf" "$omfGsf" "$testFileGsf"
fi 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 # 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 # 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 # 2026-06-02 fix, the ~Direct DP/Stack segment was appended to the OMF

View file

@ -188,6 +188,27 @@ static std::vector<uint8_t> readFile(const std::string &path) {
return buf; 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) { static std::string sectionKind(const std::string &name) {
if (name == ".text" || name.rfind(".text.", 0) == 0) return "text"; if (name == ".text" || name.rfind(".text.", 0) == 0) return "text";
if (name == ".rodata" || name.rfind(".rodata.", 0) == 0) return "rodata"; 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 uint32_t gTextBaseForSites = 0;
static bool gRecordSites = false; 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 // Record an intra-segment patch site for cRELOC emission. A target
// below the text base is never intra-segment (it is an undefined-weak // 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); 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 // Number of bytes patched by a given reloc type. Used by callers
// that need to range-check a reloc offset against a buffer size // that need to range-check a reloc offset against a buffer size
// without re-deriving the width inline. Returns 0 for unknown // 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 { struct Linker {
std::vector<std::unique_ptr<InputObject>> objs; std::vector<std::unique_ptr<InputObject>> objs;
uint32_t textBase = 0x8000; uint32_t textBase = 0x8000;
@ -601,6 +764,13 @@ struct Linker {
uint32_t segmentCap = 0; uint32_t segmentCap = 0;
uint32_t segmentBankBase = 0x040000; uint32_t segmentBankBase = 0x040000;
std::string manifestPath; 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 / // ProDOS file metadata for the resulting OMF. Set via --filetype /
// --aux. Used by disk-image builders (e.g. cppo, Cadius) to set // --aux. Used by disk-image builders (e.g. cppo, Cadius) to set
// the on-disk filetype/aux when copying the .bin onto a 2mg. // the on-disk filetype/aux when copying the .bin onto a 2mg.
@ -833,29 +1003,74 @@ struct Linker {
std::vector<uint32_t> segSizes = {0}; // bytes packed into each segment (1-based; index 0 = seg 1) std::vector<uint32_t> segSizes = {0}; // bytes packed into each segment (1-based; index 0 = seg 1)
uint32_t curSeg = 1; uint32_t curSeg = 1;
computeLiveSet(); 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) { for (size_t fi = 0; fi < objs.size(); ++fi) {
ObjOffsets &oo = objOff[fi]; ObjOffsets &oo = objOff[fi];
oo.textBaseInMerged = curText; oo.textBaseInMerged = curText;
for (uint32_t idx : objs[fi]->sectionsByKind("text")) { for (uint32_t idx : objs[fi]->sectionsByKind("text")) {
if (!isLive(fi, idx)) continue; if (!isLive(fi, idx)) continue;
uint32_t sz = objs[fi]->sections[idx].size; uint32_t sz = objs[fi]->sections[idx].size;
// If adding this section would exceed the cap, start a uint32_t wanted = requestedSegment(objs[fi]->sections[idx].name);
// new segment. Skip empty sections in the cap check if (wanted == 0) continue; // unpinned — pass 2 handles it
// (they fit anywhere). Sections larger than the cap growToSeg(wanted);
// get their own segment (we don't split a single oo.textSegOf[idx] = wanted;
// section across banks — it'd violate intra-section oo.textWithin[idx] = segSizes[wanted - 1];
// PCREL and 16-bit absolute addressing). segSizes[wanted - 1] += sz;
if (segmentCap && sz > 0 && curText += sz;
segSizes[curSeg - 1] > 0 && if (segmentCap && segSizes[wanted - 1] > segmentCap) {
segSizes[curSeg - 1] + sz > segmentCap) { char msg[200];
curSeg++; 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); segSizes.push_back(0);
} }
oo.textSegOf[idx] = curSeg; oo.textSegOf[idx] = curSeg;
oo.textWithin[idx] = segSizes[curSeg - 1]; oo.textWithin[idx] = segSizes[curSeg - 1];
segSizes[curSeg - 1] += sz; segSizes[curSeg - 1] += sz;
curText += sz; curText += sz;
} }
}
for (size_t fi = 0; fi < objs.size(); ++fi) {
ObjOffsets &oo = objOff[fi];
oo.rodataBaseInMerged = curRodata; oo.rodataBaseInMerged = curRodata;
for (uint32_t idx : objs[fi]->sectionsByKind("rodata")) { for (uint32_t idx : objs[fi]->sectionsByKind("rodata")) {
if (!isLive(fi, idx)) continue; if (!isLive(fi, idx)) continue;
@ -1295,8 +1510,8 @@ struct Linker {
die(msg); die(msg);
} }
} }
applyReloc(textBuf, patchOff, patchAddr, target, r.type, applyTextReloc(textBuf, patchOff, patchAddr, target,
resolvedName); r.type, resolvedName, segNum, L.segments);
} }
} }
} }
@ -1723,6 +1938,9 @@ int main(int argc, char **argv) {
} else if (a == "--manifest") { } else if (a == "--manifest") {
if (++i >= argc) usage(argv[0]); if (++i >= argc) usage(argv[0]);
linker.manifestPath = argv[i++]; linker.manifestPath = argv[i++];
} else if (a == "--multi-seg-expressload") {
linker.multiSegExpressLoad = true;
i++;
} else if (a == "--filetype") { } else if (a == "--filetype") {
if (++i >= argc) usage(argv[0]); if (++i >= argc) usage(argv[0]);
linker.fileType = (int32_t)parseInt(argv[i++]); linker.fileType = (int32_t)parseInt(argv[i++]);
@ -1750,7 +1968,17 @@ int main(int argc, char **argv) {
gRecordSites = true; gRecordSites = true;
gTextBaseForSites = linker.textBase; gTextBaseForSites = linker.textBase;
gImm24Sites.clear(); 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; std::vector<uint8_t> image;
Layout L = linker.link(image); Layout L = linker.link(image);
@ -1777,7 +2005,7 @@ int main(int argc, char **argv) {
if (!mapPath.empty()) linker.writeMap(mapPath); if (!mapPath.empty()) linker.writeMap(mapPath);
if (!debugOutPath.empty()) linker.writeDebugSidecar(debugOutPath); if (!debugOutPath.empty()) linker.writeDebugSidecar(debugOutPath);
if (!relocOutPath.empty()) { if (!relocOutPath.empty()) {
// Sidecar binary format (v3): // Single-flat sidecar binary format (v3):
// u32 count // u32 count
// { u32 patchOff; u32 offsetRef; u8 byteCnt; u8 bitShift; // { u32 patchOff; u32 offsetRef; u8 byteCnt; u8 bitShift;
// u8 pad[2]; } × count // 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 // 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 // for byteCnt 2/3, so v3 is backwards-compatible: pad[0] was
// always 0 then, equivalent to bitShift=0. // 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); std::ofstream rf(relocOutPath, std::ios::binary);
if (!rf) die("cannot open '" + relocOutPath + "' for writing"); if (!rf) die("cannot open '" + relocOutPath + "' for writing");
uint32_t count = (uint32_t)gImm24Sites.size(); 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 *>(&bs), 1);
rf.write(reinterpret_cast<const char *>(pad), 2); 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 // Multi-segment: write per-segment images + manifest if there's
// more than one segment OR --manifest was requested. // more than one segment OR --manifest was requested.

View file

@ -38,6 +38,7 @@ namespace {
static constexpr uint8_t OMF_OP_LCONST = 0xF2; static constexpr uint8_t OMF_OP_LCONST = 0xF2;
static constexpr uint8_t OMF_OP_CRELOC = 0xF5; static constexpr uint8_t OMF_OP_CRELOC = 0xF5;
static constexpr uint8_t OMF_OP_END = 0x00; 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_NUMLEN = 4;
[[maybe_unused]] static constexpr uint8_t OMF_VERSION_V21 = 0x02; [[maybe_unused]] static constexpr uint8_t OMF_VERSION_V21 = 0x02;
[[maybe_unused]] static constexpr uint32_t OMF_HDR_SIZE = 44; [[maybe_unused]] static constexpr uint32_t OMF_HDR_SIZE = 44;
@ -68,6 +69,21 @@ struct RelocSite {
}; };
std::vector<RelocSite> gReloc24Sites; 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) { static std::vector<uint8_t> readFile(const std::string &path) {
std::ifstream f(path, std::ios::binary); std::ifstream f(path, std::ios::binary);
if (!f) die("cannot open '" + path + "' for reading"); 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 | // `kind` : OMF KIND field. Caller picks; v1 uses 0x8800 (STATIC |
// ABSBANK | CODE) for multi-segment static placement, or // ABSBANK | CODE) for multi-segment static placement, or
// 0x0000 (CODE, dynamic) for single-segment legacy mode. // 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, static std::vector<uint8_t> emitOneSeg(const std::vector<uint8_t> &image,
uint32_t entryOff, uint32_t entryOff,
uint32_t org, uint32_t org,
@ -130,6 +158,21 @@ static std::vector<uint8_t> emitOneSeg(const std::vector<uint8_t> &image,
const std::string &name, const std::string &name,
uint32_t bssSize = 0, uint32_t bssSize = 0,
uint32_t bssGap = 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; std::vector<uint8_t> body;
// Combined image: caller's LCONST data + zero-padding to bss-start // Combined image: caller's LCONST data + zero-padding to bss-start
// offset + bssSize zero bytes. We embed BSS-as-zeros in the LCONST // 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 // (segPlacedBase + OffsetReference) at load time. This is what
// makes JSL/JML/STAlong/etc. with intra-segment targets work when // makes JSL/JML/STAlong/etc. with intra-segment targets work when
// the Loader places us at non-zero bank. // 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(OMF_OP_CRELOC);
body.push_back(s.byteCnt); // ByteCnt (2 or 3) body.push_back(s.byteCnt); // ByteCnt (2 or 3)
body.push_back(s.bitShift); // BitShift (0 or 16) body.push_back(s.bitShift); // BitShift (0 or 16)
put16(body, s.patchOff); // OffsetPatch put16(body, s.patchOff); // OffsetPatch
put16(body, s.offsetRef); // OffsetReference 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 body.push_back(OMF_OP_END); // END opcode
// Real OMF format (Merlin32 convention, verified GS/OS Loader-launchable): // 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) // +58.. : SEG_NAME (length-prefixed)
// //
// All counts use NUMLEN=4 (4-byte length on LCONST opcodes). // 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( static std::vector<uint8_t> emitOmfExpressLoad(
const std::vector<uint8_t> &image, const std::vector<uint8_t> &image,
uint32_t entryOffset, uint32_t entryOffset,
@ -411,12 +498,35 @@ static std::vector<uint8_t> emitOmfExpressLoad(
uint32_t stackSize = 0, uint32_t stackSize = 0,
uint32_t bssSize = 0, uint32_t bssSize = 0,
uint32_t bssGap = 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). static std::vector<uint8_t> emitOmfExpressLoad(
// Same KIND emitOMF uses for single-segment apps. Verified const std::vector<UserSeg> &users,
// Loader-launchable via the Finder smoke path. uint32_t stackSize) {
auto userSeg = emitOneSeg(image, entryOffset, /*org*/0, /*segNum*/2, if (users.empty()) die("emitOmfExpressLoad: no user segments");
/*kind*/0x1000, userSegName, bssSize, bssGap);
// 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 // Optionally build the DP/Stack segment. If present it lives in the
// file AFTER the user seg and gets its own ExpressLoad segtable + // 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. // uses LABLEN=0 (length-prefixed name): 1 length byte + 12 chars.
const std::string elName = "~ExpressLoad"; const std::string elName = "~ExpressLoad";
const uint32_t elNameAreaSize = LOAD_NAME_SIZE + 1 + (uint32_t)elName.size(); 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; 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 // Body size = 1 byte LCONST opcode + 4 byte length + data + 1 byte END
const uint32_t elBodySize = 1 + 4 + elDataSize + 1; const uint32_t elBodySize = 1 + 4 + elDataSize + 1;
const uint32_t elSegSize = HDR_SIZE + elNameAreaSize + elBodySize; const uint32_t elSegSize = HDR_SIZE + elNameAreaSize + elBodySize;
// User segment file offsets (after ExpressLoad seg). // User segment file offsets (cascade after ExpressLoad seg). For
const uint32_t userSegStart = elSegSize; // each user seg, dataOff = (segStart + HDR_SIZE + nameArea + 1B
const uint32_t userBodyOpOff = userSegStart + HDR_SIZE + userNameAreaSize; // LCONST opcode + 4B length).
const uint32_t userDataOff = userBodyOpOff + 5; // 1 op + 4 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 // DP/Stack segment file offsets (after all user segs). The DP/Stack
// mirrors the real GNO/ME ~_STACK seg format: an LCONST opcode + 4 // body mirrors the real GNO/ME ~_STACK seg format: an LCONST opcode
// byte length + `stackSize` zero bytes + END. ExpressLoad's // + 4 byte length + `stackSize` zero bytes + END. ExpressLoad's
// hdr_info entry has to point at the LCONST data so the Loader // hdr_info entry has to point at the LCONST data so the Loader
// copies the right number of zeros into the allocated chunk — a // copies the right number of zeros into the allocated chunk — a
// body of just END (RESSPC-only) silently no-ops on the // body of just END (RESSPC-only) silently no-ops on the
// ExpressLoad fast path, which is the bug this whole section fixes. // 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 dpStackBodyOff = dpStackSegStart + HDR_SIZE + (LOAD_NAME_SIZE + SEG_NAME_SIZE);
const uint32_t dpStackDataOff = dpStackBodyOff + 5; // 1 op + 4 length const uint32_t dpStackDataOff = dpStackBodyOff + 5; // 1 op + 4 length
// Step 3: build the ExpressLoad LCONST data. // Step 3: build the ExpressLoad LCONST data.
std::vector<uint8_t> elData; std::vector<uint8_t> elData;
// Header (6 bytes): reserved DWORD + count WORD. count = N-2 where // Header (6 bytes): reserved DWORD + count WORD. count = N-2 where
// N = total segments in the file (including ExpressLoad). With a // N = total segments in the file (including ExpressLoad). For a
// DP/Stack seg N=3 so count=1; without it N=2 so count=0. // 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 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 // Segment list: one 8-byte entry per non-ExpressLoad segment. Each
// entry's first WORD is the SELF-RELATIVE offset (from this entry's // entry's first WORD is the SELF-RELATIVE offset (from this entry's
@ -508,52 +642,52 @@ static std::vector<uint8_t> emitOmfExpressLoad(
} }
// Remap list: 1 WORD per non-ExpressLoad seg, giving the new // Remap list: 1 WORD per non-ExpressLoad seg, giving the new
// segment number for each old segment position. Old seg 1 (user // segment number for each old segment position. Each non-EL seg
// code, would-be sole seg without ExpressLoad) → new seg 2. // gets new SEGNUM = (positionInExpressLoad + 2), starting at 2 for
// Old seg 2 (DP/Stack, only present when --stack-size) → new seg 3. // the first user seg. DP/Stack lands last.
put16(elData, 2); for (uint32_t i = 0; i < nSegs; i++) {
if (haveDpStack) { put16(elData, (uint16_t)(i + 2));
put16(elData, 3);
} }
// Header info entry for the user segment. // Header info entries — one per non-ExpressLoad segment. Each
// data length = LCONST data size in the file. emitOneSeg embeds // entry is 68 bytes: 16B (dataOff,dataLen,relocOff,relocLen) + 32B
// bssGap bytes of zero padding + bssSize bytes of BSS-as-zeros in // header copy + 10B LOAD_NAME + 10B SEG_NAME. data length = LCONST
// the LCONST after the caller's image, so the on-disk data is // data size in the file. emitOneSeg embeds bssGap bytes of zero
// image.size() + bssGap + bssSize bytes. // padding + bssSize bytes of BSS-as-zeros in the LCONST after the
put32(elData, userDataOff); // data offset in file // caller's image, so the on-disk data is image.size() + bssGap +
put32(elData, (uint32_t)image.size() + bssGap + bssSize); // data length // bssSize bytes. cRELOC opcodes (if any) are emitted by emitOneSeg
// cRELOC opcodes (if any) are emitted by emitOneSeg directly after // directly after the LCONST data and before the END opcode; tell
// the LCONST data and before the END opcode. Tell ExpressLoad // ExpressLoad where they live so the Loader can apply them.
// where they live so the Loader can apply them — without this the for (size_t k = 0; k < users.size(); k++) {
// Loader skips the body walk and intra-segment IMM24 references const auto &u = users[k];
// (every JSL into our text) point at bank 0 even after relocation. const auto &seg = userSegs[k];
if (gReloc24Sites.empty()) { if (seg.size() < HDR_SIZE) die("internal: user seg too small");
put32(elData, 0); // reloc offset const uint32_t dataLen = (uint32_t)u.image.size() + u.bssGap + u.bssSize;
put32(elData, 0); // reloc length put32(elData, userDataOffs[k]); // data offset in file
} else { put32(elData, dataLen); // data length
const uint32_t crelocOff = const uint32_t relocLen =
userDataOff + (uint32_t)image.size() + bssGap + bssSize; OMF_CRELOC_BYTES_PER_SITE * (uint32_t)u.relocSites.size() +
const uint32_t crelocLen = OMF_CINTERSEG_BYTES_PER_SITE * (uint32_t)u.interSites.size();
OMF_CRELOC_BYTES_PER_SITE * (uint32_t)gReloc24Sites.size(); if (relocLen == 0) {
put32(elData, crelocOff); put32(elData, 0); // reloc offset
put32(elData, crelocLen); put32(elData, 0); // reloc length
} } else {
put32(elData, userCRelocOffs[k]);
// Header copy: bytes [12..43] of user segment header, DISPDATA → 0. put32(elData, relocLen);
if (userSeg.size() < HDR_SIZE) die("internal: user seg too small"); }
elData.insert(elData.end(), userSeg.begin() + 12, userSeg.begin() + HDR_SIZE); // Header copy: bytes [12..43] of user segment header, DISPDATA → 0.
// DISPDATA is at offset 42..43 of the original header; in the copy elData.insert(elData.end(), seg.begin() + 12, seg.begin() + HDR_SIZE);
// (which omits the first 12 bytes), it lands at offset 30..31. // DISPDATA is at offset 42..43 of the original header; in the copy
elData[elData.size() - 32 + 30] = 0; // (which omits the first 12 bytes), it lands at offset 30..31.
elData[elData.size() - 32 + 31] = 0; elData[elData.size() - 32 + 30] = 0;
elData[elData.size() - 32 + 31] = 0;
// LOAD_NAME (10 bytes, space-padded — matches Merlin convention) // LOAD_NAME (10 bytes, space-padded — matches Merlin convention)
for (int i = 0; i < (int)LOAD_NAME_SIZE; i++) elData.push_back(0x20); for (int i = 0; i < (int)LOAD_NAME_SIZE; i++) elData.push_back(0x20);
// SEG_NAME (10 bytes fixed-width, space-padded) // 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++) { for (size_t i = 0; i < SEG_NAME_SIZE; i++) {
elData.push_back(i < truncated.size() ? (uint8_t)truncated[i] : 0x20); elData.push_back(i < truncated.size() ? (uint8_t)truncated[i] : 0x20);
}
} }
// Header info entry for the DP/Stack segment (when present). // Header info entry for the DP/Stack segment (when present).
@ -627,14 +761,15 @@ static std::vector<uint8_t> emitOmfExpressLoad(
if (elSeg.size() != elSegSize) if (elSeg.size() != elSegSize)
die("internal: ExpressLoad segment size mismatch"); 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 // The DP/Stack seg's presence is now also recorded in the
// ExpressLoad load script (segtable + remap + header_info entries // ExpressLoad load script (segtable + remap + header_info entries
// above) so the Loader's fast path honors KIND=0x4012 instead of // above) so the Loader's fast path honors KIND=0x4012 instead of
// silently dropping it to its default 4 KB DP/Stack allocation. // silently dropping it to its default 4 KB DP/Stack allocation.
std::vector<uint8_t> result; std::vector<uint8_t> result;
result.insert(result.end(), elSeg.begin(), elSeg.end()); 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) { if (haveDpStack) {
result.insert(result.end(), dpStackSeg.begin(), dpStackSeg.end()); 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)"); die("--stack-size must be a multiple of 256 (page-aligned)");
if (stackSize > 0xFFFF) if (stackSize > 0xFFFF)
die("--stack-size cannot exceed 65535 bytes (one bank)"); die("--stack-size cannot exceed 65535 bytes (one bank)");
if (!manifest.empty()) // --stack-size + --manifest is allowed when ExpressLoad wraps
die("--stack-size with --manifest not yet supported"); // 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 // Plain (non-ExpressLoad) multi-segment OMFs do not launch
// correctly under the GS/OS 6.0.2 Loader — verified empirically: // correctly under the GS/OS 6.0.2 Loader — verified empirically:
// the bare DP/Stack + code combo is rejected (program never // the bare DP/Stack + code combo is rejected (program never
@ -857,6 +993,107 @@ int main(int argc, char **argv) {
// Multi-segment mode. // Multi-segment mode.
if (!manifest.empty()) { if (!manifest.empty()) {
auto segs = parseManifest(manifest); 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; std::vector<uint8_t> blob;
size_t totalPayload = 0; size_t totalPayload = 0;
for (size_t k = 0; k < segs.size(); ++k) { for (size_t k = 0; k < segs.size(); ++k) {

View file

@ -110,6 +110,22 @@ static constexpr unsigned kFrameRecordSize = 12;
static constexpr unsigned kJslRtlBytes = 3; static constexpr unsigned kJslRtlBytes = 3;
static constexpr const char *kFrameSection = ".debug_frame_w65816"; 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 { namespace {
class W65816AsmPrinter : public AsmPrinter { class W65816AsmPrinter : public AsmPrinter {
@ -655,6 +671,16 @@ void W65816AsmPrinter::emitInstruction(const MachineInstr *MI) {
// like *(uint16*)0x70). // like *(uint16*)0x70).
// $100..$FFFF -> Abs-form (DBR-relative). // $100..$FFFF -> Abs-form (DBR-relative).
// non-zero bank -> Long-form (4-byte, bank-explicit). // 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 // The DP pickup is what makes the bank-0-anchored __indirTarget
// slot at $00:00B8 (see runtime/src/libgcc.s) addressable correctly // slot at $00:00B8 (see runtime/src/libgcc.s) addressable correctly
// from codegen-emitted indirect-call sequences when DBR != 0 (crt0 // from codegen-emitted indirect-call sequences when DBR != 0 (crt0
@ -680,8 +706,12 @@ void W65816AsmPrinter::emitInstruction(const MachineInstr *MI) {
return; return;
} }
} }
bool IsFarGlobal = AddrOp.isGlobal() &&
AddrOp.getGlobal()->hasSection() &&
AddrOp.getGlobal()->getSection().find(".far") !=
StringRef::npos;
EmitToStreamer(*OutStreamer, EmitToStreamer(*OutStreamer,
MCInstBuilder(OpAbs) MCInstBuilder(IsFarGlobal ? OpLong : OpAbs)
.addOperand(lowerOperand(AddrOp, MCInstLowering))); .addOperand(lowerOperand(AddrOp, MCInstLowering)));
return; return;
} }
@ -778,8 +808,12 @@ void W65816AsmPrinter::emitInstruction(const MachineInstr *MI) {
case W65816::LDA8long: { case W65816::LDA8long: {
// i8 absolute load — same M=8 wrap as LDA_Abs; LDA8long uses // i8 absolute load — same M=8 wrap as LDA_Abs; LDA8long uses
// LDA_Long (0xAF, bank-explicit) for const-int MMIO addresses. // 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; 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()) { if (IsLong && Addr.isImm()) {
// 16-bit pointer sign-extended into i32 imm — mask back to 16 bits // 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. // 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: { case W65816::LDA8absX: {
// i8 indexed-global load: SEP #0x20 ; LDA <addr>, X ; REP #0x20 // i8 indexed-global load: SEP #0x20 ; LDA <addr>, X ; REP #0x20
// X holds the index (set up by CopyToReg before this MI). // 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(); emitSepM();
EmitToStreamer(*OutStreamer, EmitToStreamer(*OutStreamer,
MCInstBuilder(W65816::LDA_AbsX) MCInstBuilder(IsFar ? W65816::LDA_LongX : W65816::LDA_AbsX)
.addOperand(lowerOperand(MI->getOperand(0), MCInstLowering))); .addOperand(lowerOperand(AddrMO, MCInstLowering)));
emitRepM(); emitRepM();
return; return;
} }
case W65816::STA8absX: { case W65816::STA8absX: {
// i8 indexed-global store: SEP #0x20 ; STA <addr>, X ; REP #0x20 // 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(); emitSepM();
EmitToStreamer(*OutStreamer, EmitToStreamer(*OutStreamer,
MCInstBuilder(W65816::STA_AbsX) MCInstBuilder(IsFar ? W65816::STA_LongX : W65816::STA_AbsX)
.addOperand(lowerOperand(MI->getOperand(0), MCInstLowering))); .addOperand(lowerOperand(AddrMO, MCInstLowering)));
emitRepM(); emitRepM();
return; return;
} }
@ -821,8 +862,10 @@ void W65816AsmPrinter::emitInstruction(const MachineInstr *MI) {
// from STA8abs only in the underlying opcode (0x8F vs 0x8D): long // from STA8abs only in the underlying opcode (0x8F vs 0x8D): long
// is bank-explicit, abs is DBR-relative. Long is required for // is bank-explicit, abs is DBR-relative. Long is required for
// const-int MMIO addresses since the data bank is non-zero under // const-int MMIO addresses since the data bank is non-zero under
// GS/OS Loader. // GS/OS Loader. Far globals (`.far*` section) also route through
bool IsLong = MI->getOpcode() == W65816::STA8long; // STA_Long for explicit bank addressing (Phase B).
bool IsLong = MI->getOpcode() == W65816::STA8long ||
isFarGlobalOperand(MI->getOperand(1));
bool UsesAcc8 = MI->getMF() bool UsesAcc8 = MI->getMF()
->getInfo<W65816MachineFunctionInfo>() ->getInfo<W65816MachineFunctionInfo>()
->getUsesAcc8(); ->getUsesAcc8();
@ -857,8 +900,12 @@ void W65816AsmPrinter::emitInstruction(const MachineInstr *MI) {
case W65816::SBCabs: { case W65816::SBCabs: {
bool IsSub = MI->getOpcode() == W65816::SBCabs; bool IsSub = MI->getOpcode() == W65816::SBCabs;
emitOp(IsSub ? W65816::SEC : W65816::CLC); 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, EmitToStreamer(*OutStreamer,
MCInstBuilder(IsSub ? W65816::SBC_Abs : W65816::ADC_Abs) MCInstBuilder(Mc)
.addOperand(lowerOperand(MI->getOperand(2), MCInstLowering))); .addOperand(lowerOperand(MI->getOperand(2), MCInstLowering)));
return; return;
} }
@ -866,8 +913,12 @@ void W65816AsmPrinter::emitInstruction(const MachineInstr *MI) {
case W65816::SBCEabs: { case W65816::SBCEabs: {
// Chained variant — no CLC/SEC prefix. // Chained variant — no CLC/SEC prefix.
bool IsSub = MI->getOpcode() == W65816::SBCEabs; 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, EmitToStreamer(*OutStreamer,
MCInstBuilder(IsSub ? W65816::SBC_Abs : W65816::ADC_Abs) MCInstBuilder(Mc)
.addOperand(lowerOperand(MI->getOperand(2), MCInstLowering))); .addOperand(lowerOperand(MI->getOperand(2), MCInstLowering)));
return; return;
} }
@ -888,8 +939,9 @@ void W65816AsmPrinter::emitInstruction(const MachineInstr *MI) {
return; return;
} }
case W65816::CMPabs: { case W65816::CMPabs: {
bool IsFar = isFarGlobalOperand(MI->getOperand(1));
EmitToStreamer(*OutStreamer, EmitToStreamer(*OutStreamer,
MCInstBuilder(W65816::CMP_Abs) MCInstBuilder(IsFar ? W65816::CMP_Long : W65816::CMP_Abs)
.addOperand(lowerOperand(MI->getOperand(1), MCInstLowering))); .addOperand(lowerOperand(MI->getOperand(1), MCInstLowering)));
return; return;
} }
@ -912,11 +964,12 @@ void W65816AsmPrinter::emitInstruction(const MachineInstr *MI) {
case W65816::ANDabs: case W65816::ANDabs:
case W65816::ORAabs: case W65816::ORAabs:
case W65816::EORabs: { case W65816::EORabs: {
bool IsFar = isFarGlobalOperand(MI->getOperand(2));
unsigned mc = 0; unsigned mc = 0;
switch (MI->getOpcode()) { switch (MI->getOpcode()) {
case W65816::ANDabs: mc = W65816::AND_Abs; break; case W65816::ANDabs: mc = IsFar ? W65816::AND_Long : W65816::AND_Abs; break;
case W65816::ORAabs: mc = W65816::ORA_Abs; break; case W65816::ORAabs: mc = IsFar ? W65816::ORA_Long : W65816::ORA_Abs; break;
case W65816::EORabs: mc = W65816::EOR_Abs; break; case W65816::EORabs: mc = IsFar ? W65816::EOR_Long : W65816::EOR_Abs; break;
} }
EmitToStreamer(*OutStreamer, EmitToStreamer(*OutStreamer,
MCInstBuilder(mc).addOperand( MCInstBuilder(mc).addOperand(