Register allocation and BSS > 64k fixed. Maybe.
This commit is contained in:
parent
eabfaf3fcb
commit
b3c5b9b241
2 changed files with 152 additions and 24 deletions
|
|
@ -574,7 +574,15 @@ void *malloc(size_t n0) {
|
||||||
}
|
}
|
||||||
// Bump-allocate from the high end.
|
// Bump-allocate from the high end.
|
||||||
char *p = bumpPtr;
|
char *p = bumpPtr;
|
||||||
if (p + HDR_SZ + n > heapEnd) return (void *)0;
|
// Bounds check as a 16-bit SAME-BANK difference (avail = heapEnd - p),
|
||||||
|
// NOT `p + HDR_SZ + n > heapEnd`: that 24-bit pointer compare goes through
|
||||||
|
// the same i32 pattern the backend miscompiles at -O2 (see the size-round
|
||||||
|
// note above), so an oversized request returned a non-NULL OVER-HEAP
|
||||||
|
// pointer instead of NULL. On the tiny IIgs heap that let, e.g., a 16 KB
|
||||||
|
// scratch malloc "succeed" on a ~3 KB heap; the returned pointer + free()
|
||||||
|
// then trampled the IO window / language card / GS-OS stack and crashed.
|
||||||
|
// The heap lives in one bank, so the 16-bit offset difference is exact.
|
||||||
|
if ((u16)(heapEnd - p) < (u16)((u16)HDR_SZ + n)) return (void *)0;
|
||||||
*(u16 *)p = (u16)n;
|
*(u16 *)p = (u16)n;
|
||||||
bumpPtr = p + HDR_SZ + n;
|
bumpPtr = p + HDR_SZ + n;
|
||||||
return p + HDR_SZ;
|
return p + HDR_SZ;
|
||||||
|
|
|
||||||
|
|
@ -711,7 +711,51 @@ static void applyTextReloc(std::vector<uint8_t> &buf, uint32_t off,
|
||||||
uint8_t rtype,
|
uint8_t rtype,
|
||||||
const std::string &symName,
|
const std::string &symName,
|
||||||
uint32_t patchSeg,
|
uint32_t patchSeg,
|
||||||
const std::vector<TextSeg> &segments) {
|
const std::vector<TextSeg> &segments,
|
||||||
|
bool targetIsWeakNull = false) {
|
||||||
|
// UNDEFINED-WEAK reference resolved to absolute 0 (resolveSym's
|
||||||
|
// STB_WEAK branch). This is the `if (fp) fp();` null-stub pattern:
|
||||||
|
// e.g. libc's getchar() does `if (__getByte) return __getByte();`
|
||||||
|
// against the weak, link-time-unresolved __getByte console hook, so
|
||||||
|
// clang emits a 2-byte near JSR whose IMM16 operand is the hook's
|
||||||
|
// address -- 0 -- guarded by a null test that is dead at runtime.
|
||||||
|
//
|
||||||
|
// Two things force special handling here rather than the normal
|
||||||
|
// classification below:
|
||||||
|
// 1. The baked operand MUST stay literally 0 so the `if (fp)` test
|
||||||
|
// keeps failing. A cRELOC/cINTERSEG that rewrote those bytes to
|
||||||
|
// a placed segment base would make the null test pass and run the
|
||||||
|
// dead call into garbage. So we emit NO relocation record.
|
||||||
|
// 2. With --text-base 0 the null address (0) falls inside segment
|
||||||
|
// 1's [base, base+size) range, so findSegContaining() reports
|
||||||
|
// targetSeg == 1. A patch site in an overflow segment
|
||||||
|
// (patchSeg > 1) then looks like a cross-segment reference and
|
||||||
|
// the 2-byte IMM16/PCREL form is rejected further down (a near
|
||||||
|
// JSR has no room for the bank byte the Loader would need). With
|
||||||
|
// a non-zero --text-base the same reference resolves cleanly via
|
||||||
|
// the targetSeg == 0 path; baking the literal 0 here restores
|
||||||
|
// that behaviour regardless of the text base or the patch seg.
|
||||||
|
//
|
||||||
|
// Bake the literal little-endian value (0, or 0 + a weak addend) into
|
||||||
|
// the absolute/data operand and return with no side effects. PCREL
|
||||||
|
// forms (a branch to a null symbol) are nonsensical and never weak-
|
||||||
|
// null in practice, so they fall through to the normal path.
|
||||||
|
if (targetIsWeakNull) {
|
||||||
|
switch (rtype) {
|
||||||
|
case R_W65816_IMM8:
|
||||||
|
case R_W65816_IMM16:
|
||||||
|
case R_W65816_BANK16:
|
||||||
|
case R_W65816_IMM24:
|
||||||
|
case R_W65816_DATA32: {
|
||||||
|
uint32_t w = relocWidth(rtype);
|
||||||
|
for (uint32_t i = 0; i < w; ++i)
|
||||||
|
buf[off + i] = static_cast<uint8_t>((target >> (8 * i)) & 0xFF);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!gMultiSegRelocMode) {
|
if (!gMultiSegRelocMode) {
|
||||||
applyReloc(buf, off, patchAddr, target, rtype, symName);
|
applyReloc(buf, off, patchAddr, target, rtype, symName);
|
||||||
return;
|
return;
|
||||||
|
|
@ -987,9 +1031,17 @@ struct Linker {
|
||||||
// referenced section is one we don't track (e.g. another .debug_*
|
// referenced section is one we don't track (e.g. another .debug_*
|
||||||
// section); strict callers should die() on false, lenient callers
|
// section); strict callers should die() on false, lenient callers
|
||||||
// (the DWARF sidecar) should leave the bytes object-local.
|
// (the DWARF sidecar) should leave the bytes object-local.
|
||||||
|
// `weakNull` (optional) reports whether the symbol was an UNDEFINED
|
||||||
|
// WEAK reference that ELF semantics resolve to absolute 0 (the
|
||||||
|
// `if (fp) fp();` null-stub pattern -- see the STB_WEAK branch below).
|
||||||
|
// The text-reloc patcher needs to know this because, with --text-base
|
||||||
|
// 0, that 0 collides with segment 1's bank-0 address range and would
|
||||||
|
// otherwise be mis-classified as a real cross-segment reference.
|
||||||
bool resolveSym(const InputObject &obj, const ObjOffsets &oo,
|
bool resolveSym(const InputObject &obj, const ObjOffsets &oo,
|
||||||
const Reloc &r,
|
const Reloc &r,
|
||||||
uint32_t &target, std::string &resolvedName) const {
|
uint32_t &target, std::string &resolvedName,
|
||||||
|
bool *weakNull = nullptr) const {
|
||||||
|
if (weakNull) *weakNull = false;
|
||||||
if (r.symIdx >= obj.symbols.size())
|
if (r.symIdx >= obj.symbols.size())
|
||||||
die(obj.path + ": reloc symIdx out of range");
|
die(obj.path + ": reloc symIdx out of range");
|
||||||
const Symbol &sym = obj.symbols[r.symIdx];
|
const Symbol &sym = obj.symbols[r.symIdx];
|
||||||
|
|
@ -1047,6 +1099,7 @@ struct Linker {
|
||||||
resolvedName = sym.name;
|
resolvedName = sym.name;
|
||||||
if (sym.bind == STB_WEAK) {
|
if (sym.bind == STB_WEAK) {
|
||||||
target = 0 + r.addend;
|
target = 0 + r.addend;
|
||||||
|
if (weakNull) *weakNull = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -1288,9 +1341,15 @@ struct Linker {
|
||||||
if (!gTrampList.empty()) {
|
if (!gTrampList.empty()) {
|
||||||
gTrampBase = segSizes[0];
|
gTrampBase = segSizes[0];
|
||||||
segSizes[0] += kTrampBytes * (uint32_t)gTrampList.size();
|
segSizes[0] += kTrampBytes * (uint32_t)gTrampList.size();
|
||||||
if (segmentCap && segSizes[0] > segmentCap)
|
// The trampolines are executable code appended to seg 1's text,
|
||||||
die("cross-segment trampoline table pushes seg 1 over "
|
// so the HARD limit is the IO window ($C000), not the soft
|
||||||
"--segment-cap");
|
// --segment-cap (a text-distribution target that leaves margin
|
||||||
|
// below the window). The small trampoline table may use that
|
||||||
|
// margin; an actual IO-window crossing is still caught here
|
||||||
|
// (and again by the textSize check below).
|
||||||
|
if (segSizes[0] > kIoWindowStart)
|
||||||
|
die("seg 1 text + cross-segment trampoline table crosses "
|
||||||
|
"the IIgs IO window 0xC000");
|
||||||
std::fprintf(stderr,
|
std::fprintf(stderr,
|
||||||
"link816: %zu cross-seg function trampolines (%u bytes) "
|
"link816: %zu cross-seg function trampolines (%u bytes) "
|
||||||
"in seg 1\n", gTrampList.size(),
|
"in seg 1\n", gTrampList.size(),
|
||||||
|
|
@ -1545,6 +1604,14 @@ struct Linker {
|
||||||
// constant.
|
// constant.
|
||||||
constexpr uint32_t kBank0HeapTop = kIoWindowStart - 0x100; // $BF00
|
constexpr uint32_t kBank0HeapTop = kIoWindowStart - 0x100; // $BF00
|
||||||
constexpr uint32_t MIN_HEAP = 512;
|
constexpr uint32_t MIN_HEAP = 512;
|
||||||
|
// Prefer a bank-0 heap below the IO window. Pushing the heap into the
|
||||||
|
// bank-0 LC ($D000-$FFFF) is a LAST RESORT and unreliable: GS/OS uses
|
||||||
|
// that RAM, corrupting heap-resident state (a surface struct landing
|
||||||
|
// there had its palette read back as garbage -- both the $D000-$DFFF
|
||||||
|
// bank-switched half AND the $E000-$FFFF common half are unsafe). The
|
||||||
|
// driver (clang-build.sh) avoids it by lowering the multi-seg
|
||||||
|
// --segment-cap until rodata + BSS + heap fit below the IO window; this
|
||||||
|
// LC fallback only fires for a program too large for even that.
|
||||||
uint32_t heapStart = L.bssBase + L.bssSize;
|
uint32_t heapStart = L.bssBase + L.bssSize;
|
||||||
if (heapStart >= kBank0HeapTop && heapStart < kIoWindowEnd) {
|
if (heapStart >= kBank0HeapTop && heapStart < kIoWindowEnd) {
|
||||||
heapStart = kIoWindowEnd; // skip IO window + tiny tail
|
heapStart = kIoWindowEnd; // skip IO window + tiny tail
|
||||||
|
|
@ -1619,17 +1686,22 @@ struct Linker {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (sym.bind == STB_LOCAL) {
|
if (sym.bind == STB_LOCAL) {
|
||||||
// Locals get tracked separately under a per-object
|
// Per-object disambiguated key for writeMap()/--map-locals.
|
||||||
// disambiguated key so writeMap() can list them
|
|
||||||
// with their provenance when --map-locals is on.
|
|
||||||
// The shared globalSyms path below still includes
|
|
||||||
// the local under its bare name for backwards-
|
|
||||||
// compat with smoke tests that grep the map for
|
|
||||||
// file-static names (e.g. ctor1).
|
|
||||||
std::string base = obj.path;
|
std::string base = obj.path;
|
||||||
size_t slash = base.find_last_of('/');
|
size_t slash = base.find_last_of('/');
|
||||||
if (slash != std::string::npos) base = base.substr(slash + 1);
|
if (slash != std::string::npos) base = base.substr(slash + 1);
|
||||||
localSyms[sym.name + "@" + base] = addr;
|
localSyms[sym.name + "@" + base] = addr;
|
||||||
|
// Also expose under the bare name (FIRST-WINS) for smoke
|
||||||
|
// tests that grep the map for file-static names (e.g.
|
||||||
|
// ctor1) -- but do NOT enter the strong/weak resolution
|
||||||
|
// below: file-local statics are file-scoped, so the SAME
|
||||||
|
// static name in two TUs (e.g. uber.c and surface.c each
|
||||||
|
// with `static gStage`) is NOT a multiple-definition error.
|
||||||
|
// A real global of the same name still overrides this (the
|
||||||
|
// `sit == end` branch below replaces the bare entry).
|
||||||
|
if (globalSyms.find(sym.name) == globalSyms.end())
|
||||||
|
globalSyms[sym.name] = addr;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
bool thisStrong = (sym.bind != STB_WEAK);
|
bool thisStrong = (sym.bind != STB_WEAK);
|
||||||
auto sit = isStrong.find(sym.name);
|
auto sit = isStrong.find(sym.name);
|
||||||
|
|
@ -1715,7 +1787,9 @@ struct Linker {
|
||||||
uint32_t patchAddr = segBase + patchOff;
|
uint32_t patchAddr = segBase + patchOff;
|
||||||
uint32_t target;
|
uint32_t target;
|
||||||
std::string resolvedName;
|
std::string resolvedName;
|
||||||
if (!resolveSym(obj, oo, r, target, resolvedName))
|
bool targetWeakNull = false;
|
||||||
|
if (!resolveSym(obj, oo, r, target, resolvedName,
|
||||||
|
&targetWeakNull))
|
||||||
die(obj.path + ": .text reloc to unresolved '"
|
die(obj.path + ": .text reloc to unresolved '"
|
||||||
+ resolvedName + "'");
|
+ resolvedName + "'");
|
||||||
// PCREL relocs can't span banks (the displacement
|
// PCREL relocs can't span banks (the displacement
|
||||||
|
|
@ -1725,8 +1799,12 @@ struct Linker {
|
||||||
// bank, which we keep at 0 by default (so refs
|
// bank, which we keep at 0 by default (so refs
|
||||||
// to bank-0 data work from any code segment),
|
// to bank-0 data work from any code segment),
|
||||||
// and we can't statically know the target bank
|
// and we can't statically know the target bank
|
||||||
// intent anyway.
|
// intent anyway. An undefined-weak null target
|
||||||
if (segmentCap && (r.type == R_W65816_PCREL16 ||
|
// (address 0) is exempt: it is not a real cross-bank
|
||||||
|
// reference, just the `if (fp) fp();` null sentinel,
|
||||||
|
// and applyTextReloc bakes the literal 0 for it.
|
||||||
|
if (segmentCap && !targetWeakNull &&
|
||||||
|
(r.type == R_W65816_PCREL16 ||
|
||||||
r.type == R_W65816_PCREL8)) {
|
r.type == R_W65816_PCREL8)) {
|
||||||
uint32_t targetSegBank = target & 0xFF0000;
|
uint32_t targetSegBank = target & 0xFF0000;
|
||||||
uint32_t patchSegBank = segBase & 0xFF0000;
|
uint32_t patchSegBank = segBase & 0xFF0000;
|
||||||
|
|
@ -1742,7 +1820,8 @@ struct Linker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
applyTextReloc(textBuf, patchOff, patchAddr, target,
|
applyTextReloc(textBuf, patchOff, patchAddr, target,
|
||||||
r.type, resolvedName, segNum, L.segments);
|
r.type, resolvedName, segNum, L.segments,
|
||||||
|
targetWeakNull);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1754,10 +1833,46 @@ struct Linker {
|
||||||
lastLayout = L;
|
lastLayout = L;
|
||||||
|
|
||||||
// 4b. Apply relocations to rodata/data buffer. Globals like
|
// 4b. Apply relocations to rodata/data buffer. Globals like
|
||||||
// `int *p = &v;` need their initializer patched at link time
|
// `int *p = &v;` or `static const char *p = "x";` need their
|
||||||
// (the .o emits a placeholder 0 + a R_W65816_IMM16 reloc).
|
// initializer patched (the .o emits a placeholder 0 + an
|
||||||
// Without this, every initialized pointer or function-pointer
|
// R_W65816_IMM24 / R_W65816_DATA32 reloc — pointers are 24-bit
|
||||||
// table in the program reads 0 at runtime.
|
// far on this target, so a `char *` initializer is a 3-byte
|
||||||
|
// bank:offset value, not a 2-byte near offset). Without this,
|
||||||
|
// every initialized pointer or function-pointer table reads 0.
|
||||||
|
//
|
||||||
|
// This MUST go through applyTextReloc (NOT a bare applyReloc) so
|
||||||
|
// that, in the multi-segment ExpressLoad model, the pointer's
|
||||||
|
// BANK byte is patched at LOAD time the same way text-segment
|
||||||
|
// pointer operands are. The merged .data/.rodata image lives in
|
||||||
|
// segment 1 (the entry/load bank) — it is appended to seg 1's
|
||||||
|
// output image after the seg-1 text (see the outImage compose
|
||||||
|
// below) — so patchSeg is always 1 here. Routing through
|
||||||
|
// applyTextReloc lets the existing per-segment classifier fire:
|
||||||
|
//
|
||||||
|
// - target in .data/.rodata (outside every text seg's [base,
|
||||||
|
// base+textSize) range, so findSegContaining()==0): recorded
|
||||||
|
// as an intra-seg cRELOC in gIntraImm24SitesPerSeg[1]. The
|
||||||
|
// GS/OS Loader rewrites the 3 bytes to (seg1PlacedBase +
|
||||||
|
// offsetRef), supplying the runtime LOAD bank. This is the
|
||||||
|
// gCurName case: the link-time value is $00:offset but the
|
||||||
|
// image loads at e.g. bank $08, and only the cRELOC carries
|
||||||
|
// the $08 to the pointer's bank byte.
|
||||||
|
// - target in a seg2+ text bank (a function the layout placed
|
||||||
|
// in an overflow bank): recorded as a cINTERSEG so the Loader
|
||||||
|
// supplies that segment's placed bank.
|
||||||
|
//
|
||||||
|
// Before this change the .data path recorded only into the FLAT
|
||||||
|
// gImm24Sites list, which omfEmit consumes ONLY in single-segment
|
||||||
|
// mode; in --multi-seg-expressload it reads the per-seg sidecars
|
||||||
|
// (and suppresses the flat fallback for seg 1 once that per-seg
|
||||||
|
// list is non-empty from the text relocs), so every .data pointer
|
||||||
|
// initializer silently kept its link-time bank ($00) and crashed
|
||||||
|
// the moment it was dereferenced from the real load bank.
|
||||||
|
//
|
||||||
|
// Outside multi-seg mode applyTextReloc is a thin wrapper that
|
||||||
|
// delegates straight to applyReloc, so the single-segment path
|
||||||
|
// (and its flat gImm24Sites cRELOC list) is byte-for-byte
|
||||||
|
// unchanged.
|
||||||
for (size_t fi = 0; fi < objs.size(); ++fi) {
|
for (size_t fi = 0; fi < objs.size(); ++fi) {
|
||||||
const auto &obj = *objs[fi];
|
const auto &obj = *objs[fi];
|
||||||
const auto &oo = objOff[fi];
|
const auto &oo = objOff[fi];
|
||||||
|
|
@ -1771,11 +1886,16 @@ struct Linker {
|
||||||
uint32_t patchAddr = L.rodataBase + patchOff;
|
uint32_t patchAddr = L.rodataBase + patchOff;
|
||||||
uint32_t target;
|
uint32_t target;
|
||||||
std::string resolvedName;
|
std::string resolvedName;
|
||||||
if (!resolveSym(obj, oo, r, target, resolvedName))
|
bool targetWeakNull = false;
|
||||||
|
if (!resolveSym(obj, oo, r, target, resolvedName,
|
||||||
|
&targetWeakNull))
|
||||||
die(obj.path + ": .rodata reloc to unresolved '"
|
die(obj.path + ": .rodata reloc to unresolved '"
|
||||||
+ resolvedName + "'");
|
+ resolvedName + "'");
|
||||||
applyReloc(rodataBuf, patchOff, patchAddr, target,
|
// patchSeg = 1: the merged rodata/.data image is part
|
||||||
r.type, resolvedName);
|
// of segment 1's output image (text || gap || rodata).
|
||||||
|
applyTextReloc(rodataBuf, patchOff, patchAddr, target,
|
||||||
|
r.type, resolvedName, /*patchSeg=*/1,
|
||||||
|
L.segments, targetWeakNull);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue