JoeyLib fixes.
This commit is contained in:
parent
fb2f032923
commit
8c5c5ac1d2
7 changed files with 133 additions and 1 deletions
|
|
@ -4,6 +4,14 @@ Drop this into a new Claude Code session and say "read SESSION_STATE.md and
|
||||||
continue where we left off." Pairs with `LLVM_65816_DESIGN.md` (the design
|
continue where we left off." Pairs with `LLVM_65816_DESIGN.md` (the design
|
||||||
doc — read that second).
|
doc — read that second).
|
||||||
|
|
||||||
|
> **INCOMING ASK (2026-07-05, from the JoeyLib session):** see
|
||||||
|
> `JOEYLIB-ASKS.md` in this directory — loader/crt0 must MM-reserve
|
||||||
|
> segment BSS spans (their item 5, CRITICAL). New evidence: an app-side
|
||||||
|
> claim recipe that WORKS for reading the spans (DATA32 table over the
|
||||||
|
> `__bss_seg*` symbols) but a successful NewHandle claim over the app's
|
||||||
|
> own BSS kills the app — so the reservation has to happen loader-side.
|
||||||
|
> Blocks JoeyLib's IIgs sprite codegen and the Space Taxi port.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Project in one sentence
|
## 1. Project in one sentence
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,21 @@ __start:
|
||||||
.Lbss_done:
|
.Lbss_done:
|
||||||
rep #0x20 ; restore M=16
|
rep #0x20 ; restore M=16
|
||||||
|
|
||||||
|
; Back the C malloc heap with a locked Memory-Manager handle so the
|
||||||
|
; heap span is MM-owned -- otherwise a later NewHandle (ours or the
|
||||||
|
; system's) can place a block on top of live malloc data (JoeyLib
|
||||||
|
; item 5, heap channel). MMStartUp ($0202) returns our Memory-Manager
|
||||||
|
; user ID in A; pass it as arg0 to __heapInitMM (strong impl in libc.o;
|
||||||
|
; weak no-op in libgcc.s for programs that don't link libc). crt0.s /
|
||||||
|
; crt0Gno.s do NOT do this -- bare-metal has no Memory Manager and GNO
|
||||||
|
; manages its own heap; both keep malloc's link-time window.
|
||||||
|
rep #0x30
|
||||||
|
pha ; MMStartUp result space (word)
|
||||||
|
ldx #0x0202 ; MMStartUp call number
|
||||||
|
jsl 0xe10000 ; Tool Locator dispatch -> A = our MM user ID
|
||||||
|
pla
|
||||||
|
jsl __heapInitMM ; __heapInitMM(userID); arg0 in A
|
||||||
|
|
||||||
; Walk .init_array (C++ ctors). The `jsl __jsl_indir` and the
|
; Walk .init_array (C++ ctors). The `jsl __jsl_indir` and the
|
||||||
; `jsl main` below are relocated by the GS/OS Loader: omfEmit emits
|
; `jsl main` below are relocated by the GS/OS Loader: omfEmit emits
|
||||||
; a cRELOC (0xF5) IMM24 site for each intra-segment JSL, so the
|
; a cRELOC (0xF5) IMM24 site for each intra-segment JSL, so the
|
||||||
|
|
|
||||||
|
|
@ -537,6 +537,62 @@ static void mallocInitOnce(void) {
|
||||||
freeList = (FreeBlk *)0;
|
freeList = (FreeBlk *)0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GS/OS: back the malloc heap with a locked Memory-Manager handle so the
|
||||||
|
// heap span is owned by the MM and no later NewHandle (ours or the
|
||||||
|
// system's) can land on top of live malloc data. crt0Gsos calls this with
|
||||||
|
// our MM user ID in A, before .init_array. crt0.s (bare-metal, no Memory
|
||||||
|
// Manager) and crt0Gno.s do NOT call it, so -ffunction-sections GC drops
|
||||||
|
// this function -- and its NewHandle reference -- from those links. A weak
|
||||||
|
// no-op __heapInitMM in libgcc.s resolves crt0Gsos's `jsl` for GS/OS
|
||||||
|
// programs that don't link libc.o.
|
||||||
|
//
|
||||||
|
// On any failure -- NewHandle returns 0, a null master pointer, or a block
|
||||||
|
// that straddles a bank boundary -- it returns without setting bumpPtr, so
|
||||||
|
// mallocInitOnce falls back to the link-time [__heap_start,__heap_end)
|
||||||
|
// window. A tight-memory machine is therefore never worse off than before.
|
||||||
|
extern void *NewHandle(unsigned long size, unsigned short userID,
|
||||||
|
unsigned short attr, void *loc);
|
||||||
|
extern void DisposeHandle(void *h);
|
||||||
|
void __heapInitMM(unsigned short userID) {
|
||||||
|
if (bumpPtr) {
|
||||||
|
return; // already initialised
|
||||||
|
}
|
||||||
|
// Heap size: keep the link-time window when it is in a sane range,
|
||||||
|
// else a 32 KB default. The window is bank-0-bounded, so it never
|
||||||
|
// spans more than a single bank.
|
||||||
|
unsigned long want = (unsigned long)(void *)__heap_end
|
||||||
|
- (unsigned long)(void *)__heap_start;
|
||||||
|
if (want < 0x2000UL || want > 0xC000UL) {
|
||||||
|
want = 0x8000UL; // 32 KB
|
||||||
|
}
|
||||||
|
// attr 0x8014 = attrLocked | attrNoCross | attrPage. Every bit here is
|
||||||
|
// set in the proven-good desktop.c DP-block value 0xC015, minus
|
||||||
|
// attrFixed and attrBank -- so the MM is free to place the block in any
|
||||||
|
// bank and then pin it. attrLocked keeps the master pointer stable,
|
||||||
|
// which malloc relies on (it hands out raw pointers into the block).
|
||||||
|
void *h = NewHandle(want, userID, (unsigned short)0x8014, (void *)0);
|
||||||
|
if (!h) {
|
||||||
|
return; // fall back to link-time window
|
||||||
|
}
|
||||||
|
char *base = *(char **)h;
|
||||||
|
if (!base) {
|
||||||
|
DisposeHandle(h);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// The allocator does flat pointer arithmetic and assumes a single bank;
|
||||||
|
// reject (rather than corrupt on) a straddling block. This also makes
|
||||||
|
// correctness independent of the attrNoCross bit guess above.
|
||||||
|
unsigned long lo = (unsigned long)(void *)base;
|
||||||
|
unsigned long hi = lo + want - 1UL;
|
||||||
|
if ((lo & 0xFF0000UL) != (hi & 0xFF0000UL)) {
|
||||||
|
DisposeHandle(h);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bumpPtr = base;
|
||||||
|
heapEnd = base + want;
|
||||||
|
freeList = (FreeBlk *)0;
|
||||||
|
}
|
||||||
|
|
||||||
void *malloc(size_t n0) {
|
void *malloc(size_t n0) {
|
||||||
mallocInitOnce();
|
mallocInitOnce();
|
||||||
// Heap ceiling is ~32KB so anything > 0x7FF0 is unsatisfiable.
|
// Heap ceiling is ~32KB so anything > 0x7FF0 is unsatisfiable.
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,20 @@ __run_cxa_atexit:
|
||||||
__srandInitFromTime:
|
__srandInitFromTime:
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
; --------------------------------------------------------------------
|
||||||
|
; __heapInitMM — weak no-op fallback.
|
||||||
|
;
|
||||||
|
; crt0Gsos calls `jsl __heapInitMM` (Memory-Manager user ID in A) before
|
||||||
|
; .init_array to back the malloc heap with a locked MM handle. GS/OS
|
||||||
|
; programs that don't link libc.o (bare probes / link tests) must still
|
||||||
|
; resolve the symbol; the no-op leaves malloc on its link-time
|
||||||
|
; [__heap_start,__heap_end) window. libc.o overrides with the strong
|
||||||
|
; version. A is ignored.
|
||||||
|
; --------------------------------------------------------------------
|
||||||
|
.weak __heapInitMM
|
||||||
|
__heapInitMM:
|
||||||
|
rtl
|
||||||
|
|
||||||
; --------------------------------------------------------------------
|
; --------------------------------------------------------------------
|
||||||
; __mulhi3 — 16-bit multiply. A * (4,S) -> A.
|
; __mulhi3 — 16-bit multiply. A * (4,S) -> A.
|
||||||
; Signed and unsigned share an implementation: only the low 16 bits of
|
; Signed and unsigned share an implementation: only the low 16 bits of
|
||||||
|
|
|
||||||
|
|
@ -5643,7 +5643,11 @@ while pos < len(data):
|
||||||
kind = struct.unpack_from('<H', data, pos+20)[0]
|
kind = struct.unpack_from('<H', data, pos+20)[0]
|
||||||
ok_kinds.append(kind)
|
ok_kinds.append(kind)
|
||||||
pos += bytecnt
|
pos += bytecnt
|
||||||
expected = [0x8001, 0x1000, 0x1000]
|
# Root user seg (seg 2 of the OMF) carries the DYNAMIC bit (0x1100 =
|
||||||
|
# CODE|PRIV|DYNAMIC); non-root user segs stay 0x1000. Real GS/OS
|
||||||
|
# rejects a multi-seg file whose root lacks DYNAMIC (see omfEmit
|
||||||
|
# segKind logic) -- an earlier stale expectation here was [.,0x1000,.].
|
||||||
|
expected = [0x8001, 0x1100, 0x1000]
|
||||||
print('OK' if ok_kinds == expected else 'BAD ' + str(ok_kinds))
|
print('OK' if ok_kinds == expected else 'BAD ' + str(ok_kinds))
|
||||||
")
|
")
|
||||||
if [ "$nPhCSeg" != "OK" ]; then
|
if [ "$nPhCSeg" != "OK" ]; then
|
||||||
|
|
|
||||||
|
|
@ -2175,16 +2175,32 @@ struct Linker {
|
||||||
it->second < seg.base + seg.body.size()) {
|
it->second < seg.base + seg.body.size()) {
|
||||||
entryOff = it->second - seg.base;
|
entryOff = it->second - seg.base;
|
||||||
}
|
}
|
||||||
|
// BSS attribution: link() places ALL of rodata/bss/init_array
|
||||||
|
// in segment 1's bank (see the layout comment near link() —
|
||||||
|
// "Other sections ... stay in segment 1's bank"). So the
|
||||||
|
// whole merged BSS span belongs to segment 1 (segments[0]);
|
||||||
|
// every other segment carries no BSS. Emit bss_start (the
|
||||||
|
// absolute link address of BSS) and bss_size so omfEmit can
|
||||||
|
// embed the span as LCONST zeros — giving the GS/OS Loader's
|
||||||
|
// per-segment NewHandle coverage of BSS. Without this the BSS
|
||||||
|
// is phantom RAM the Memory Manager can hand to a later
|
||||||
|
// NewHandle (segment/heap overlap corruption), and it is never
|
||||||
|
// zeroed under GS/OS (crt0Gsos relies on the Loader LCONST-fill).
|
||||||
|
uint32_t segBssStart = (k == 0) ? lastLayout.bssBase : 0u;
|
||||||
|
uint32_t segBssSize = (k == 0) ? lastLayout.bssSize : 0u;
|
||||||
std::snprintf(buf, sizeof(buf),
|
std::snprintf(buf, sizeof(buf),
|
||||||
" {\n"
|
" {\n"
|
||||||
" \"num\": %u,\n"
|
" \"num\": %u,\n"
|
||||||
" \"name\": \"SEG%u\",\n"
|
" \"name\": \"SEG%u\",\n"
|
||||||
" \"base\": \"0x%06x\",\n"
|
" \"base\": \"0x%06x\",\n"
|
||||||
" \"size\": %zu,\n"
|
" \"size\": %zu,\n"
|
||||||
|
" \"bss_start\": \"0x%06x\",\n"
|
||||||
|
" \"bss_size\": %u,\n"
|
||||||
" \"image\": \"%s\",\n"
|
" \"image\": \"%s\",\n"
|
||||||
" \"entry_offset\": \"0x%04x\"\n"
|
" \"entry_offset\": \"0x%04x\"\n"
|
||||||
" }%s\n",
|
" }%s\n",
|
||||||
seg.segNum, seg.segNum, seg.base, seg.body.size(),
|
seg.segNum, seg.segNum, seg.base, seg.body.size(),
|
||||||
|
segBssStart, segBssSize,
|
||||||
imgPath.c_str(), entryOff,
|
imgPath.c_str(), entryOff,
|
||||||
(k + 1 < lastLayout.segments.size()) ? "," : "");
|
(k + 1 < lastLayout.segments.size()) ? "," : "");
|
||||||
mf << buf;
|
mf << buf;
|
||||||
|
|
|
||||||
|
|
@ -815,6 +815,8 @@ struct ManifestSeg {
|
||||||
uint32_t num = 0;
|
uint32_t num = 0;
|
||||||
uint32_t base = 0;
|
uint32_t base = 0;
|
||||||
uint32_t entryOff = 0;
|
uint32_t entryOff = 0;
|
||||||
|
uint32_t bssStart = 0; // absolute link addr of this seg's BSS (0 = none)
|
||||||
|
uint32_t bssSize = 0; // BSS span size in bytes (embedded as LCONST zeros)
|
||||||
std::string image;
|
std::string image;
|
||||||
std::string name;
|
std::string name;
|
||||||
};
|
};
|
||||||
|
|
@ -887,6 +889,8 @@ static std::vector<ManifestSeg> parseManifest(const std::string &path) {
|
||||||
seg.num = extractNumberField(block, "num");
|
seg.num = extractNumberField(block, "num");
|
||||||
seg.base = extractNumberField(block, "base");
|
seg.base = extractNumberField(block, "base");
|
||||||
seg.entryOff = extractNumberField(block, "entry_offset");
|
seg.entryOff = extractNumberField(block, "entry_offset");
|
||||||
|
seg.bssStart = extractNumberField(block, "bss_start"); // 0 if absent
|
||||||
|
seg.bssSize = extractNumberField(block, "bss_size"); // 0 if absent
|
||||||
seg.image = extractStringField(block, "image");
|
seg.image = extractStringField(block, "image");
|
||||||
seg.name = extractStringField(block, "name");
|
seg.name = extractStringField(block, "name");
|
||||||
if (seg.image.empty()) die("manifest segment missing 'image'");
|
if (seg.image.empty()) die("manifest segment missing 'image'");
|
||||||
|
|
@ -1048,6 +1052,21 @@ int main(int argc, char **argv) {
|
||||||
u.image = readFile(s.image);
|
u.image = readFile(s.image);
|
||||||
u.entryOffset = (k == 0) ? s.entryOff : 0;
|
u.entryOffset = (k == 0) ? s.entryOff : 0;
|
||||||
u.name = s.name;
|
u.name = s.name;
|
||||||
|
// Embed this segment's BSS as trailing LCONST zeros so the
|
||||||
|
// GS/OS Loader's per-segment NewHandle covers the BSS span
|
||||||
|
// (otherwise it is phantom RAM the Memory Manager can hand
|
||||||
|
// to a later NewHandle -> corruption; it is also never
|
||||||
|
// zeroed since crt0Gsos relies on the Loader's LCONST fill).
|
||||||
|
// Same computation as the single-seg path: bss_gap pads
|
||||||
|
// from image-end up to the link-layout BSS address.
|
||||||
|
if (s.bssSize > 0) {
|
||||||
|
u.bssSize = s.bssSize;
|
||||||
|
if (s.bssStart >= s.base) {
|
||||||
|
uint32_t bssOff = s.bssStart - s.base;
|
||||||
|
if (bssOff > u.image.size())
|
||||||
|
u.bssGap = bssOff - (uint32_t)u.image.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!relocFile.empty()) {
|
if (!relocFile.empty()) {
|
||||||
char perSegPath[512];
|
char perSegPath[512];
|
||||||
std::snprintf(perSegPath, sizeof(perSegPath),
|
std::snprintf(perSegPath, sizeof(perSegPath),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue