diff --git a/packages/arm-emulator/CHANGELOG.md b/packages/arm-emulator/CHANGELOG.md index d82faa9..95e03f9 100644 --- a/packages/arm-emulator/CHANGELOG.md +++ b/packages/arm-emulator/CHANGELOG.md @@ -1,5 +1,7 @@ # @gba-kit/arm-emulator +## 0.5.0 + ## 0.4.0 ## 0.3.0 diff --git a/packages/arm-emulator/package.json b/packages/arm-emulator/package.json index 4a8a49d..67f3b7f 100644 --- a/packages/arm-emulator/package.json +++ b/packages/arm-emulator/package.json @@ -1,6 +1,6 @@ { "name": "@gba-kit/arm-emulator", - "version": "0.4.0", + "version": "0.5.0", "description": "ARM7TDMI CPU emulator supporting Thumb and ARM instruction sets", "author": "macabeus", "type": "module", diff --git a/packages/debug-info/CHANGELOG.md b/packages/debug-info/CHANGELOG.md index 821e43a..cd51e08 100644 --- a/packages/debug-info/CHANGELOG.md +++ b/packages/debug-info/CHANGELOG.md @@ -1,5 +1,27 @@ # @gba-kit/debug-info +## 0.5.0 + +### Minor Changes + +- Report an array's RANK, not just its flattened element count. + + `variableShape()`'s array arm and `struct()`'s array members now carry `dims` — the + per-dimension extents, outermost first (`u16 g[4][0x400]` → `[4, 1024]`). `length` is + unchanged: it stays the product, which is what sizes the object. + + The two readings answer different questions, and only `dims` answers the one a consumer + spelling C needs: `g[i]` on a rank-2 array is a **row**, not an element. A consumer that + knows only the flat count writes a single subscript, which against the project's own header + is either a type error or — where the row address flows into an integer context — silently + the wrong address. + + `null` marks an unbounded dimension. On a `DW_AT_declaration` a leading extent of 1 is + GCC 2.95's spelling of an unsized outer bound (`extern T x[]`, `extern T x[][4]`) and is + reported as `null`, mirroring the rule `length` already applies; the inner extents are + written down and survive. A rank-1 array reports a one-entry `dims`, so an absent key + always means "not an array", never "rank unknown". + ## 0.4.0 ### Minor Changes diff --git a/packages/debug-info/package.json b/packages/debug-info/package.json index 119ea95..defd34a 100644 --- a/packages/debug-info/package.json +++ b/packages/debug-info/package.json @@ -1,6 +1,6 @@ { "name": "@gba-kit/debug-info", - "version": "0.4.0", + "version": "0.5.0", "description": "Parse ELF symbols and DWARF debug info (line tables) for GBA decomp debugging", "author": "macabeus", "type": "module", diff --git a/packages/debug-info/src/__tests__/producer-quirks.spec.ts b/packages/debug-info/src/__tests__/producer-quirks.spec.ts index 43866be..089ec78 100644 --- a/packages/debug-info/src/__tests__/producer-quirks.spec.ts +++ b/packages/debug-info/src/__tests__/producer-quirks.spec.ts @@ -88,6 +88,7 @@ describe('3. the 0xffffffff upper bound means zero-length, never 2^32 elements', elemSize: 1, elemSigned: false, length: null, + dims: [null], // rank 1, the single extent unbounded volatile: false, const: false, }); @@ -96,7 +97,15 @@ describe('3. the 0xffffffff upper bound means zero-length, never 2^32 elements', it('a zero-length trailing member reads exactly like modern flexible arrays', () => { // Mirrors the devkitarm-min `Blob.data` pin: stride reported, size and length not. const data = agbcc.struct('Flex')!.members.find((m) => m.name === 'data')!; - expect(data).toEqual({ name: 'data', offset: 4, size: null, signed: null, elemSize: 1, elemSigned: false }); + expect(data).toEqual({ + name: 'data', + offset: 4, + size: null, + signed: null, + elemSize: 1, + elemSigned: false, + dims: [null], + }); expect(agbcc.resolveVariable('g_flex.data')).toBeNull(); }); @@ -117,6 +126,7 @@ describe('4. an unsized extern array is not [1]', () => { elemSize: 2, elemSigned: true, length: null, + dims: [null], // the rank IS known (1); only its extent is not volatile: false, const: true, }); @@ -128,8 +138,40 @@ describe('4. an unsized extern array is not [1]', () => { elemSize: 2, elemSigned: true, length: 1, + dims: [1], volatile: false, const: false, }); }); }); + +describe('5. an array RANK is not its element count', () => { + // `length` is the PRODUCT of the subranges, so it cannot say how many subscripts an + // element access takes — and `g[i]` on a `[2][3]` is a ROW, not an element. A consumer + // that only knows the flat count cannot spell an access that type-checks against the + // project's own header, which is exactly how a flattened rank surfaces: as a compile + // error in the world the header lives in, never as a wrong number. + it('reports every dimension of a fully-bounded array, outermost first', () => { + expect(agbcc.types.variableShape('g_grid3')).toMatchObject({ dims: [2, 3, 4], length: 24 }); + expect(agbcc.types.variableShape('g_init_table')).toMatchObject({ dims: [2, 2], length: 4 }); + expect(agbcc.types.variableShape('g_fwd_sized_table')).toMatchObject({ dims: [3, 2], length: 6 }); + }); + + it('a DECLARATION keeps its INNER extents and loses only the unsized outer one', () => { + // `extern const short g_ext_grid[][4];` — agbcc spells the outer bound as upper_bound 0 + // (indistinguishable from a real [1]), but the inner 4 IS written down, and the inner + // extents are the ones an element access needs. + expect(agbcc.types.variableShape('g_ext_grid')).toMatchObject({ dims: [null, 4] }); + }); + + it('reports the rank of an array MEMBER too', () => { + const cells = agbcc.struct('Grid')!.members.find((m) => m.name === 'cells')!; + expect(cells).toMatchObject({ offset: 4, size: 12, elemSize: 2, length: 6, dims: [2, 3] }); + }); + + it('control: a rank-1 array reports a one-entry rank, not none', () => { + // Absence of `dims` must mean "this is not an array", never "rank unknown" — a consumer + // gating on key presence would otherwise read every plain table as unranked. + expect(agbcc.types.variableShape('g_rom_table')).toMatchObject({ dims: [3] }); + }); +}); diff --git a/packages/debug-info/src/__tests__/real-projects.spec.ts b/packages/debug-info/src/__tests__/real-projects.spec.ts index 082c783..7cae60b 100644 --- a/packages/debug-info/src/__tests__/real-projects.spec.ts +++ b/packages/debug-info/src/__tests__/real-projects.spec.ts @@ -136,7 +136,7 @@ describe.each(ARM_PROJECTS)('DebugInfo vs binutils oracle on $label', (project) { name: 'flags', offset: 8, size: 2, signed: true }, // char[6] → `size` is the WHOLE member (element size × length); the element facts are // what an indexed read into it needs, and `signed` stays null (an array is not a base type) - { name: 'name', offset: 10, size: 6, signed: null, elemSize: 1, elemSigned: false, length: 6 }, + { name: 'name', offset: 10, size: 6, signed: null, elemSize: 1, elemSigned: false, length: 6, dims: [6] }, // pointer → 4 bytes, and the pointee facts pointer arithmetic scales by { name: 'ptr', offset: 16, size: 4, signed: null, pointer: true, pointeeSize: 4, pointeeSigned: true }, { name: 'inner', offset: 20, size: 8, signed: null }, // nested struct @@ -221,6 +221,7 @@ describe.each(ARM_PROJECTS)('DebugInfo vs binutils oracle on $label', (project) elemSize: 2, elemSigned: true, length: 3, + dims: [3], volatile: false, const: true, }); @@ -385,7 +386,15 @@ describe('DebugInfo on devkitarm-min-only shapes', () => { // The member still declares an element STRIDE — what it has no bound. So `elemSize` is // reported and `length` is absent, the two facts being independent. const data = di.struct('Blob')!.members.find((m) => m.name === 'data')!; - expect(data).toEqual({ name: 'data', offset: 4, size: null, signed: null, elemSize: 1, elemSigned: false }); + expect(data).toEqual({ + name: 'data', + offset: 4, + size: null, + signed: null, + elemSize: 1, + elemSigned: false, + dims: [null], + }); }); it('names an UNNAMED pointee by the typedef that aliases it', () => { @@ -517,7 +526,7 @@ describe.each(BE_PROJECTS)('DebugInfo vs binutils oracle on $label', (project) = { name: 'tag', offset: 0, size: 1, signed: false }, { name: 'count', offset: 4, size: 4, signed: true }, { name: 'flags', offset: 8, size: 2, signed: true }, - { name: 'name', offset: 10, size: 6, signed: null, elemSize: 1, elemSigned: false, length: 6 }, + { name: 'name', offset: 10, size: 6, signed: null, elemSize: 1, elemSigned: false, length: 6, dims: [6] }, { name: 'ptr', offset: 16, size: 4, signed: null, pointer: true, pointeeSize: 4, pointeeSigned: true }, { name: 'inner', offset: 20, size: 8, signed: null }, // nested struct { name: 'tail', offset: 28, size: 4, signed: true }, @@ -606,6 +615,7 @@ describe.each(BE_PROJECTS)('DebugInfo vs binutils oracle on $label', (project) = elemSize: 2, elemSigned: true, length: 4, + dims: [4], volatile: false, const: false, }); @@ -615,6 +625,7 @@ describe.each(BE_PROJECTS)('DebugInfo vs binutils oracle on $label', (project) = elemSize: 2, elemSigned: true, length: 3, + dims: [3], volatile: false, const: true, }); @@ -685,7 +696,7 @@ describe('cross-endian equivalence (same declarations, four toolchains, both byt // Every type/global any of the four declares. The ones not present in all four are // enumerated by name below, so a project that genuinely lacks a shape is skipped // EXPLICITLY rather than dropped silently. - const CANDIDATE_TYPES = ['Probe', 'Inner', 'Bits', 'Cv', 'UtilPair', 'Pair', 'Shape', 'Blob']; + const CANDIDATE_TYPES = ['Probe', 'Inner', 'Bits', 'Cv', 'UtilPair', 'Grid', 'Pair', 'Shape', 'Blob']; const CANDIDATE_GLOBALS = [ 'g_counter', 'g_probe', @@ -693,6 +704,9 @@ describe('cross-endian equivalence (same declarations, four toolchains, both byt 'g_cv', 'g_rom_table', 'g_util_pair', + 'g_grid3', + 'g_grid', + 'g_ext_grid', // agbcc-min only — the unsized-outer-bound spelling is a GCC 2.95 quirk 'g_pair', // little-endian sources only 'g_color', 'g_mode', @@ -735,8 +749,17 @@ describe('cross-endian equivalence (same declarations, four toolchains, both byt }); it('shares exactly this declaration set — every other shape is skipped BY NAME', () => { - expect(sharedTypes).toEqual(['Probe', 'Inner', 'Bits', 'Cv', 'UtilPair']); - expect(sharedGlobals).toEqual(['g_counter', 'g_probe', 'g_bits', 'g_cv', 'g_rom_table', 'g_util_pair']); + expect(sharedTypes).toEqual(['Probe', 'Inner', 'Bits', 'Cv', 'UtilPair', 'Grid']); + expect(sharedGlobals).toEqual([ + 'g_counter', + 'g_probe', + 'g_bits', + 'g_cv', + 'g_rom_table', + 'g_util_pair', + 'g_grid3', + 'g_grid', + ]); // The rest, and who lacks each. These are SOURCE facts (the big-endian projects // declare a different set of globals; agbcc/GCC 2.95 rejects anonymous unions and // flexible array members), not parser gaps — pinned so a shape silently vanishing @@ -747,6 +770,7 @@ describe('cross-endian equivalence (same declarations, four toolchains, both byt Blob: ['agbcc-min', 'mips-min', 'ppc-min'], }); expect(skipped(CANDIDATE_GLOBALS, hasGlobal)).toEqual({ + g_ext_grid: ['devkitarm-min', 'mips-min', 'ppc-min'], g_pair: ['mips-min', 'ppc-min'], g_color: ['mips-min', 'ppc-min'], g_mode: ['mips-min', 'ppc-min'], @@ -773,6 +797,7 @@ describe('cross-endian equivalence (same declarations, four toolchains, both byt Bits: { size: 8, members: 'hearts@0:1 stars@0:1 cross@0:2 wide@1:1 after@4:4' }, Cv: { size: 4, members: 'level@0:1 gain@2:2' }, UtilPair: { size: 4, members: 'lo@0:2 hi@2:2' }, + Grid: { size: 16, members: 'id@0:4 cells@4:12' }, }; it.each(sharedTypes)('every project reports the same byte layout for %s', (type) => { @@ -811,8 +836,21 @@ describe('cross-endian equivalence (same declarations, four toolchains, both byt g_probe: { kind: 'struct', structName: 'Probe', size: 32, volatile: false, const: false }, g_bits: { kind: 'struct', structName: 'Bits', size: 8, volatile: false, const: false }, g_cv: { kind: 'struct', structName: 'Cv', size: 4, volatile: true, const: false }, - g_rom_table: { kind: 'array', elemSize: 2, elemSigned: true, length: 3, volatile: false, const: true }, + g_rom_table: { kind: 'array', elemSize: 2, elemSigned: true, length: 3, dims: [3], volatile: false, const: true }, g_util_pair: { kind: 'struct', structName: 'UtilPair', size: 4, volatile: false, const: false }, + // RANK is byte-order- and producer-independent: the dimensions come from the + // DW_TAG_subrange chain, which every producer spells in declaration order. A reader that + // multiplied them away (or read them under the wrong endianness) shows up right here. + g_grid3: { + kind: 'array', + elemSize: 1, + elemSigned: false, + length: 24, + dims: [2, 3, 4], + volatile: false, + const: false, + }, + g_grid: { kind: 'struct', structName: 'Grid', size: 16, volatile: false, const: false }, }; it.each(sharedGlobals)('every project classifies %s to the same shape — TypeIndex.variableShape', (name) => { diff --git a/packages/debug-info/src/types.ts b/packages/debug-info/src/types.ts index bf53125..5fc75a9 100644 --- a/packages/debug-info/src/types.ts +++ b/packages/debug-info/src/types.ts @@ -178,6 +178,24 @@ export interface StructMember { * array member, `char data[]`, which declares a stride but no length). */ length?: number; + /** + * Array member only: the per-dimension extents, outermost first — the RANK that {@link length} + * multiplies away (`u16 x[4][8]` → `[4, 8]`, length 32). One entry per DW_TAG_subrange_type, + * `null` where that dimension has no bound (a flexible one). Absent when the member is not an + * array or the DWARF gave the array no subranges at all. + * + * The rank is not decoration: `x[i]` on a `[4][8]` member is a ROW, not an element, so a + * consumer that only knows the flat count cannot spell an element access that type-checks + * against the project's own header. + * + * Reported for SYMMETRY with {@link VariableShape}'s array arm — a member and a global are the + * same DWARF array type, and a reader that answers the rank question for one should answer it + * for the other. It is the consumer that differs: a consumer synthesizing its OWN struct + * declaration may flatten the member, since its access and its declaration then agree by + * construction and no foreign header is involved. Rank only becomes load-bearing where the + * declaration belongs to someone else. + */ + dims?: (number | null)[]; } /** The width/signedness facts of one declared type — shared by a parameter and a return type. */ @@ -238,6 +256,11 @@ export type VariableShape = elemSize: number | null; elemSigned: boolean | null; length: number | null; + /** the per-dimension extents, outermost first — the RANK `length` multiplies away + * (`u16 g[4][0x400]` → `[4, 1024]`, length 4096). `null` where a dimension is + * unbounded, and null overall when the rank itself is unknown (an unsized 1-D + * extern) or the type is not subranged. `g[i]` on a rank-2 array is a ROW. */ + dims: (number | null)[] | null; volatile: boolean; const: boolean; } @@ -549,6 +572,12 @@ export class TypeIndex { // near-information-free fact. A DEFINED [1] keeps its length — the definition // is the witness. length: isDeclaration(variable) && length === 1 ? null : length, + // The RANK, under the same declaration rule applied per-dimension: on a DECLARATION + // a LEADING extent of 1 is that same unsized spelling (`extern T x[]`, `extern T + // x[][4]`) and reports null, while the inner extents ARE written down and survive — + // they are exactly what an element access needs. Only the outermost dimension can be + // unsized in C, so only it is normalized. + dims: declaredDims(variable, die), ...cv, }; } @@ -828,16 +857,18 @@ export class TypeIndex { /** An array member's element facts. Each is omitted when the DWARF does not determine it — an * unsized element type has no stride, a flexible array member no length — so a present key is * always a fact, never a default. */ - #arrayFacts(arrayDie: Die): Pick { + #arrayFacts(arrayDie: Die): Pick { const elemRef = arrayDie.attrs.get(DW_AT_type); const elem = this.#stripTypedefs(elemRef); const elemSize = this.#typeRefSize(elemRef); const elemSigned = elem ? baseTypeSignedness(elem) : null; const length = arrayLength(arrayDie); + const dims = arrayDims(arrayDie); return { ...(elemSize !== null ? { elemSize } : {}), ...(elemSigned !== null ? { elemSigned } : {}), ...(length !== null ? { length } : {}), + ...(dims !== null ? { dims } : {}), }; } @@ -936,30 +967,57 @@ function bitfieldAbsBitOffset(member: Die, typeSize: number | null, littleEndian } /** - * Element count of an array DIE (product of its DW_TAG_subrange_type dimensions), - * or null if any dimension is flexible (no bound) or zero-length — those have no - * fixed read size, so the member's size should surface as null, not 0. + * Per-dimension extents of an array DIE, outermost first — one entry per + * DW_TAG_subrange_type, `null` where that dimension is flexible (no bound) or + * zero-length. Null overall when the DIE carries no subranges at all. + * + * This is the RANK, which {@link arrayLength} multiplies away. Both readings are needed: + * the flat count sizes the object, the rank says how many subscripts an element access + * takes — and `x[i]` on a `[4][8]` is a row, not an element. */ -function arrayLength(arrayDie: Die): number | null { - let count = 1; - let sawDimension = false; +function arrayDims(arrayDie: Die): (number | null)[] | null { + const dims: (number | null)[] = []; for (const child of arrayDie.children) { if (child.tag !== DW_TAG_subrange_type) { continue; } - sawDimension = true; const explicit = numberAttr(child, DW_AT_count); const rawUpper = numberAttr(child, DW_AT_upper_bound); // GCC 2.95 stores a zero-length array's -1 upper bound in UNSIGNED DW_FORM_data4; // read raw, upper+1 would claim 2^32 elements. Normalize to the modern sdata reading. const upper = rawUpper === 0xffffffff ? -1 : rawUpper; const dim = explicit !== null ? explicit : upper !== null ? upper + 1 : null; - if (dim === null || dim <= 0) { - return null; - } - count *= dim; + dims.push(dim === null || dim <= 0 ? null : dim); + } + return dims.length > 0 ? dims : null; +} + +/** + * {@link arrayDims} for a VARIABLE, with the declaration quirk normalized: GCC 2.95 + * encodes an unsized extern's outermost bound as upper_bound 0, byte-identical to a + * real `[1]`, so on a DW_AT_declaration a leading extent of 1 is reported as unknown. + * The rare genuine `extern T x[1]` / `extern T x[1][4]` loses a near-information-free + * fact; a DEFINITION keeps its 1, since the definition is the witness. + */ +function declaredDims(variable: Die, arrayDie: Die): (number | null)[] | null { + const dims = arrayDims(arrayDie); + if (dims === null || !isDeclaration(variable) || dims[0] !== 1) { + return dims; + } + return [null, ...dims.slice(1)]; +} + +/** + * Element count of an array DIE (product of its DW_TAG_subrange_type dimensions), + * or null if any dimension is flexible (no bound) or zero-length — those have no + * fixed read size, so the member's size should surface as null, not 0. + */ +function arrayLength(arrayDie: Die): number | null { + const dims = arrayDims(arrayDie); + if (dims === null || dims.some((d) => d === null)) { + return null; } - return sawDimension ? count : null; + return dims.reduce((a, d) => a * d!, 1); } /** Signedness of a base type from DW_AT_encoding (DW_ATE_signed/signed_char = signed; diff --git a/packages/debug-info/test-projects/agbcc-min/build/min.elf b/packages/debug-info/test-projects/agbcc-min/build/min.elf index f6facf1..5effc18 100755 Binary files a/packages/debug-info/test-projects/agbcc-min/build/min.elf and b/packages/debug-info/test-projects/agbcc-min/build/min.elf differ diff --git a/packages/debug-info/test-projects/agbcc-min/build/oracle.json b/packages/debug-info/test-projects/agbcc-min/build/oracle.json index 95fb490..1b0efc7 100644 --- a/packages/debug-info/test-projects/agbcc-min/build/oracle.json +++ b/packages/debug-info/test-projects/agbcc-min/build/oracle.json @@ -1,45 +1,49 @@ { "symbols": { - ".gcc2_compiled.": 134217736, + ".gcc2_compiled.": 134218052, "__gccmain": 134217734, "_start": 134217728, "add": 134217736, "bump": 134217748, "gAbsGlobal": 50336308, - "g_bits": 50331736, + "g_bits": 50331792, "g_color": 50331708, "g_counter": 50331648, - "g_cv": 50331728, - "g_ext_table": 134218032, + "g_cv": 50331760, + "g_ext_grid": 134218076, + "g_ext_table": 134218068, "g_flex": 50331652, - "g_fwd_pay": 50331744, + "g_fwd_pay": 50331800, "g_fwd_ptr": 50331696, - "g_fwd_sized_table": 134218054, - "g_init_table": 134218046, + "g_fwd_sized_table": 134218106, + "g_grid": 50331776, + "g_grid3": 50331728, + "g_init_table": 134218098, "g_mmio": 50331712, "g_mode": 50331656, "g_one_def": 50331700, - "g_pair": 50331720, + "g_pair": 50331752, "g_probe": 50331664, - "g_rom_table": 134218040, - "g_util_pair": 50331752, + "g_rom_table": 134218092, + "g_util_pair": 50331808, "g_zero": 50331704, "main": 134217880, "poke": 134217916, + "rank_poke": 134218016, "square": 134217740, - "triple": 134218016 + "triple": 134218052 }, "lines": { - "0x8000120": { - "func": "triple", - "file": "util.c", - "line": 16 - }, "0x8000008": { "func": "add", "file": "main.c", "line": 94 }, + "0x8000144": { + "func": "triple", + "file": "util.c", + "line": 16 + }, "0x8000006": { "func": "__gccmain", "file": "??", @@ -65,6 +69,11 @@ "file": "main.c", "line": 162 }, + "0x8000120": { + "func": "rank_poke", + "file": "main.c", + "line": 188 + }, "0x800000c": { "func": "square", "file": "main.c", diff --git a/packages/debug-info/test-projects/agbcc-min/crt0.s b/packages/debug-info/test-projects/agbcc-min/crt0.s index a376a6c..d4561cb 100644 --- a/packages/debug-info/test-projects/agbcc-min/crt0.s +++ b/packages/debug-info/test-projects/agbcc-min/crt0.s @@ -26,3 +26,11 @@ __gccmain: .align 1 g_ext_table: .hword 10, 20, 30, 40 + +@ The same idiom with a RANK: main.c declares `extern const short g_ext_grid[][4];`, +@ so only the inner extent is knowable from the DWARF. + .global g_ext_grid + .align 1 +g_ext_grid: + .hword 1, 2, 3, 4 + .hword 5, 6, 7, 8 diff --git a/packages/debug-info/test-projects/agbcc-min/main.c b/packages/debug-info/test-projects/agbcc-min/main.c index a1e20a9..b7a7c75 100644 --- a/packages/debug-info/test-projects/agbcc-min/main.c +++ b/packages/debug-info/test-projects/agbcc-min/main.c @@ -166,3 +166,26 @@ int poke(int i) { /* keeps every shape above live in the DWARF */ } static const unsigned short g_fwd_sized_table[][2] = {{5, 6}, {7, 8}, {9, 10}}; + +/* ---- RANK shapes. A flat element count cannot express how many subscripts an + * element access takes, and `g[i]` on a `[2][3]` is a ROW, not an element — so the + * per-dimension extents are their own fact (variableShape().dims / StructMember.dims). + * g_grid3 — a fully-bounded rank 3, the ordinary case (dims [2,3,4], length 24); + * g_ext_grid — the decomp `extern u8 tbl[][0x40]` idiom, defined outside C: its OUTER + * bound is unknowable but the INNER one is written down and is exactly + * what an element access needs, so the rank must survive as [null, 4]; + * Grid.cells — the same fact one level down, on a struct member. */ +unsigned char g_grid3[2][3][4]; +extern const short g_ext_grid[][4]; + +struct Grid { + int id; + unsigned short cells[2][3]; +}; +struct Grid g_grid; + +int rank_poke(int i) { /* keeps the shapes above live in the DWARF */ + g_grid3[1][2][3] = (unsigned char) i; + g_grid.cells[1][2] = (unsigned short) g_ext_grid[i][1]; + return g_grid.id; +} diff --git a/packages/debug-info/test-projects/devkitarm-min/build/macinfo.o b/packages/debug-info/test-projects/devkitarm-min/build/macinfo.o index 9f00185..e0fd0e1 100644 Binary files a/packages/debug-info/test-projects/devkitarm-min/build/macinfo.o and b/packages/debug-info/test-projects/devkitarm-min/build/macinfo.o differ diff --git a/packages/debug-info/test-projects/devkitarm-min/build/min.elf b/packages/debug-info/test-projects/devkitarm-min/build/min.elf index 05fe577..a29b825 100755 Binary files a/packages/debug-info/test-projects/devkitarm-min/build/min.elf and b/packages/debug-info/test-projects/devkitarm-min/build/min.elf differ diff --git a/packages/debug-info/test-projects/devkitarm-min/build/oracle.json b/packages/debug-info/test-projects/devkitarm-min/build/oracle.json index b7f913a..3d8c9ce 100644 --- a/packages/debug-info/test-projects/devkitarm-min/build/oracle.json +++ b/packages/debug-info/test-projects/devkitarm-min/build/oracle.json @@ -1,36 +1,39 @@ { "symbols": { "add": 134217756, - "__bss_end__": 134222088, - "_bss_end__": 134222088, - "__bss_start": 134221992, - "__bss_start__": 134221992, + "__bss_end__": 134222152, + "_bss_end__": 134222152, + "__bss_start": 134222016, + "__bss_start__": 134222016, "bump": 134217768, - "__data_start": 134221988, - "_edata": 134221992, - "__end__": 134222088, - "_end": 134222088, + "__data_start": 134222008, + "_edata": 134222012, + "__end__": 134222152, + "_end": 134222152, "gAbsGlobal": 50336308, - "g_bits": 134222040, - "g_blob": 134222072, - "g_color": 134222036, - "g_counter": 134221992, - "g_cv": 134222048, - "g_cv_ptr": 134222080, - "g_cv_vptr": 134222076, - "g_mmio": 134222052, - "g_mode": 134222037, - "g_pair": 134222028, - "g_pair_ptr": 134221988, - "g_probe": 134221996, - "g_rom_table": 134217884, - "g_shape": 134222056, - "g_util_pair": 134222084, - "g_wide": 134222064, + "g_bits": 134222064, + "g_blob": 134222096, + "g_color": 134222060, + "g_counter": 134222016, + "g_cv": 134222072, + "g_cv_ptr": 134222144, + "g_cv_vptr": 134222140, + "g_grid": 134222124, + "g_grid3": 134222100, + "g_mmio": 134222076, + "g_mode": 134222061, + "g_pair": 134222052, + "g_pair_ptr": 134222008, + "g_probe": 134222020, + "g_rom_table": 134217904, + "g_shape": 134222080, + "g_util_pair": 134222148, + "g_wide": 134222088, "main": 134217728, + "rank_poke": 134217868, "square": 134217760, "_stack": 524288, - "triple": 134217868 + "triple": 134217888 }, "lines": { "0x800001c": { @@ -48,12 +51,17 @@ "file": "main.c", "line": 143 }, + "0x800008c": { + "func": "rank_poke", + "file": "main.c", + "line": 178 + }, "0x8000020": { "func": "square", "file": "main.c", "line": 124 }, - "0x800008c": { + "0x80000a0": { "func": "triple", "file": "util.c", "line": 17 diff --git a/packages/debug-info/test-projects/devkitarm-min/source/main.c b/packages/debug-info/test-projects/devkitarm-min/source/main.c index c975fd4..5a7616d 100644 --- a/packages/debug-info/test-projects/devkitarm-min/source/main.c +++ b/packages/debug-info/test-projects/devkitarm-min/source/main.c @@ -159,3 +159,23 @@ int main(void) { #define EWRAM_BASE 0x02000000 #define CLAMP(x, lo, hi) ((x) < (lo) ? (lo) : (x) > (hi) ? (hi) : (x)) #define NO_BODY + +// ---- RANK. A flat element count cannot say how many subscripts reach an ELEMENT, and +// `g[i]` on a `[2][3]` is a ROW. The per-dimension extents are their own fact +// (variableShape().dims / StructMember.dims), and they are ABI- and byte-order-independent, +// so all four toolchains must report them identically. (agbcc-min additionally carries the +// `extern T x[][4]` idiom, whose unsized outer bound is a GCC 2.95 ENCODING quirk — modern +// producers spell it differently, so it is pinned there rather than shared.) +unsigned char g_grid3[2][3][4]; // fully-bounded rank 3: dims [2,3,4], length 24 + +struct Grid { // Grid: id @0 (4) cells @4 (12: [2][3] of u16) size 16 + int id; + unsigned short cells[2][3]; // the same fact one level down, on a member +}; +struct Grid g_grid; + +__attribute__((noinline)) int rank_poke(int i) { // keeps the two shapes above live + g_grid3[1][2][3] = (unsigned char) i; + g_grid.cells[1][2] = (unsigned short) g_grid.id; + return g_grid.id; +} diff --git a/packages/debug-info/test-projects/mips-min/build/min.elf b/packages/debug-info/test-projects/mips-min/build/min.elf index 45b5a4c..cd3ce49 100755 Binary files a/packages/debug-info/test-projects/mips-min/build/min.elf and b/packages/debug-info/test-projects/mips-min/build/min.elf differ diff --git a/packages/debug-info/test-projects/mips-min/build/oracle.json b/packages/debug-info/test-projects/mips-min/build/oracle.json index a2a0f9a..03a03e0 100644 --- a/packages/debug-info/test-projects/mips-min/build/oracle.json +++ b/packages/debug-info/test-projects/mips-min/build/oracle.json @@ -1,34 +1,36 @@ { "symbols": { - "_GLOBAL_OFFSET_TABLE_": 4260480, - "__bss_start": 4260496, - "_edata": 4260492, - "_end": 4260576, - "_fbss": 4260496, - "_fdata": 4260464, + "_GLOBAL_OFFSET_TABLE_": 4260496, + "__bss_start": 4260512, + "_edata": 4260508, + "_end": 4260640, + "_fbss": 4260512, + "_fdata": 4260480, "_ftext": 4194640, - "_gp": 4293232, + "_gp": 4293248, "add": 4194704, "bump": 4194720, - "g_bits": 4260500, - "g_counter": 4260556, - "g_cv": 4260496, - "g_probe": 4260508, - "g_probe_ptr": 4260464, - "g_ptr": 4260552, - "g_rom_table": 4194912, - "g_table": 4260544, - "g_util_pair": 4260560, - "g_vol": 4260540, + "g_bits": 4260556, + "g_counter": 4260612, + "g_cv": 4260552, + "g_grid": 4260512, + "g_grid3": 4260528, + "g_probe": 4260564, + "g_probe_ptr": 4260480, + "g_ptr": 4260608, + "g_rom_table": 4194928, + "g_table": 4260600, + "g_util_pair": 4260624, + "g_vol": 4260596, "main": 4194640, "square": 4194712, - "triple": 4194880 + "triple": 4194896 }, "lines": { "0x400150": { "func": "main", "file": "main.c", - "line": 97 + "line": 113 }, "0x400190": { "func": "add", @@ -38,14 +40,14 @@ "0x4001a0": { "func": "bump", "file": "main.c", - "line": 86 + "line": 100 }, "0x400198": { "func": "square", "file": "main.c", "line": 83 }, - "0x400240": { + "0x400250": { "func": "triple", "file": "util.c", "line": 15 diff --git a/packages/debug-info/test-projects/mips-min/main.c b/packages/debug-info/test-projects/mips-min/main.c index 6db4311..62f6f60 100644 --- a/packages/debug-info/test-projects/mips-min/main.c +++ b/packages/debug-info/test-projects/mips-min/main.c @@ -82,6 +82,20 @@ int square(int n) { return n * n; } +/* ---- RANK. A flat element count cannot say how many subscripts reach an ELEMENT, and + * `g[i]` on a `[2][3]` is a ROW. The per-dimension extents are their own fact + * (variableShape().dims / StructMember.dims), and they are ABI- and byte-order-independent, + * so all four toolchains must report them identically. (agbcc-min additionally carries the + * `extern T x[][4]` idiom, whose unsized outer bound is a GCC 2.95 ENCODING quirk — modern + * producers spell it differently, so it is pinned there rather than shared.) */ +unsigned char g_grid3[2][3][4]; /* fully-bounded rank 3: dims [2,3,4], length 24 */ + +struct Grid { /* Grid: id @0 (4) cells @4 (12: [2][3] of u16) size 16 */ + int id; + unsigned short cells[2][3]; /* the same fact one level down, on a member */ +}; +struct Grid g_grid; + void bump(void) { g_counter += 1; g_ptr = &g_counter; /* keep the pointer global live */ @@ -92,6 +106,8 @@ void bump(void) { g_bits.after = g_counter; g_cv.level = (signed char) g_counter; /* keep struct Cv + its quals live */ g_probe.count = g_rom_table[g_counter & 1]; /* keep the const table live */ + g_grid3[1][2][3] = (unsigned char) g_counter; /* keep the rank-3 array live */ + g_grid.cells[1][2] = (unsigned short) g_grid.id; /* keep struct Grid + its 2-D member live */ } int main(void) { diff --git a/packages/debug-info/test-projects/ppc-min/build/main.o b/packages/debug-info/test-projects/ppc-min/build/main.o index 44febdc..947c299 100644 Binary files a/packages/debug-info/test-projects/ppc-min/build/main.o and b/packages/debug-info/test-projects/ppc-min/build/main.o differ diff --git a/packages/debug-info/test-projects/ppc-min/build/min.elf b/packages/debug-info/test-projects/ppc-min/build/min.elf index 44fee28..8108423 100755 Binary files a/packages/debug-info/test-projects/ppc-min/build/min.elf and b/packages/debug-info/test-projects/ppc-min/build/min.elf differ diff --git a/packages/debug-info/test-projects/ppc-min/build/oracle-obj.json b/packages/debug-info/test-projects/ppc-min/build/oracle-obj.json index cabcc37..fca0882 100644 --- a/packages/debug-info/test-projects/ppc-min/build/oracle-obj.json +++ b/packages/debug-info/test-projects/ppc-min/build/oracle-obj.json @@ -5,6 +5,8 @@ "g_bits": 4, "g_counter": 28, "g_cv": 0, + "g_grid": 56, + "g_grid3": 32, "g_probe": 0, "g_probe_ptr": 0, "g_ptr": 24, @@ -23,7 +25,7 @@ "0x20": { "func": "bump", "file": "main.c", - "line": 85 + "line": 100 }, "0x10": { "func": "square", diff --git a/packages/debug-info/test-projects/ppc-min/build/oracle.json b/packages/debug-info/test-projects/ppc-min/build/oracle.json index c638700..9012e00 100644 --- a/packages/debug-info/test-projects/ppc-min/build/oracle.json +++ b/packages/debug-info/test-projects/ppc-min/build/oracle.json @@ -4,12 +4,14 @@ "__GNU_EH_FRAME_HDR": 268435968, "__bss_start": 268439564, "_edata": 268439562, - "_end": 268439632, + "_end": 268439672, "add": 268435776, "bump": 268435808, "g_bits": 268439568, "g_counter": 268439592, "g_cv": 268439564, + "g_grid": 268439656, + "g_grid3": 268439632, "g_probe": 268439600, "g_probe_ptr": 268439552, "g_ptr": 268439588, @@ -30,12 +32,12 @@ "0x10000160": { "func": "bump", "file": "main.c", - "line": 85 + "line": 100 }, "0x10000100": { "func": "main", "file": "main.c", - "line": 97 + "line": 113 }, "0x10000150": { "func": "square", diff --git a/packages/debug-info/test-projects/ppc-min/main.c b/packages/debug-info/test-projects/ppc-min/main.c index 6db4311..62f6f60 100644 --- a/packages/debug-info/test-projects/ppc-min/main.c +++ b/packages/debug-info/test-projects/ppc-min/main.c @@ -82,6 +82,20 @@ int square(int n) { return n * n; } +/* ---- RANK. A flat element count cannot say how many subscripts reach an ELEMENT, and + * `g[i]` on a `[2][3]` is a ROW. The per-dimension extents are their own fact + * (variableShape().dims / StructMember.dims), and they are ABI- and byte-order-independent, + * so all four toolchains must report them identically. (agbcc-min additionally carries the + * `extern T x[][4]` idiom, whose unsized outer bound is a GCC 2.95 ENCODING quirk — modern + * producers spell it differently, so it is pinned there rather than shared.) */ +unsigned char g_grid3[2][3][4]; /* fully-bounded rank 3: dims [2,3,4], length 24 */ + +struct Grid { /* Grid: id @0 (4) cells @4 (12: [2][3] of u16) size 16 */ + int id; + unsigned short cells[2][3]; /* the same fact one level down, on a member */ +}; +struct Grid g_grid; + void bump(void) { g_counter += 1; g_ptr = &g_counter; /* keep the pointer global live */ @@ -92,6 +106,8 @@ void bump(void) { g_bits.after = g_counter; g_cv.level = (signed char) g_counter; /* keep struct Cv + its quals live */ g_probe.count = g_rom_table[g_counter & 1]; /* keep the const table live */ + g_grid3[1][2][3] = (unsigned char) g_counter; /* keep the rank-3 array live */ + g_grid.cells[1][2] = (unsigned short) g_grid.id; /* keep struct Grid + its 2-D member live */ } int main(void) { diff --git a/packages/gba-browser/CHANGELOG.md b/packages/gba-browser/CHANGELOG.md index c1815c9..79a7b72 100644 --- a/packages/gba-browser/CHANGELOG.md +++ b/packages/gba-browser/CHANGELOG.md @@ -1,5 +1,12 @@ # @gba-kit/gba-browser +## 0.5.0 + +### Patch Changes + +- @gba-kit/gba-emulator@0.5.0 +- @gba-kit/arm-emulator@0.5.0 + ## 0.4.0 ### Patch Changes diff --git a/packages/gba-browser/package.json b/packages/gba-browser/package.json index f5a16a4..5165f78 100644 --- a/packages/gba-browser/package.json +++ b/packages/gba-browser/package.json @@ -1,6 +1,6 @@ { "name": "@gba-kit/gba-browser", - "version": "0.4.0", + "version": "0.5.0", "description": "Browser runtime for GBA emulation", "author": "macabeus", "type": "module", diff --git a/packages/gba-emulator/CHANGELOG.md b/packages/gba-emulator/CHANGELOG.md index 5f353fb..3134261 100644 --- a/packages/gba-emulator/CHANGELOG.md +++ b/packages/gba-emulator/CHANGELOG.md @@ -1,5 +1,13 @@ # @gba-kit/gba-emulator +## 0.5.0 + +### Patch Changes + +- Updated dependencies + - @gba-kit/debug-info@0.5.0 + - @gba-kit/arm-emulator@0.5.0 + ## 0.4.0 ### Patch Changes diff --git a/packages/gba-emulator/package.json b/packages/gba-emulator/package.json index 1aedc03..6b7f946 100644 --- a/packages/gba-emulator/package.json +++ b/packages/gba-emulator/package.json @@ -1,6 +1,6 @@ { "name": "@gba-kit/gba-emulator", - "version": "0.4.0", + "version": "0.5.0", "description": "Full GBA hardware emulation", "author": "macabeus", "type": "module", diff --git a/packages/gba-node/CHANGELOG.md b/packages/gba-node/CHANGELOG.md index 5ec7b36..5b25638 100644 --- a/packages/gba-node/CHANGELOG.md +++ b/packages/gba-node/CHANGELOG.md @@ -1,5 +1,12 @@ # @gba-kit/gba-node +## 0.5.0 + +### Patch Changes + +- @gba-kit/gba-emulator@0.5.0 +- @gba-kit/arm-emulator@0.5.0 + ## 0.4.0 ### Patch Changes diff --git a/packages/gba-node/package.json b/packages/gba-node/package.json index 15472b1..efa47c5 100644 --- a/packages/gba-node/package.json +++ b/packages/gba-node/package.json @@ -1,6 +1,6 @@ { "name": "@gba-kit/gba-node", - "version": "0.4.0", + "version": "0.5.0", "description": "Headless Node.js runtime for scripted GBA emulation", "author": "macabeus", "type": "module", diff --git a/packages/gba-react/CHANGELOG.md b/packages/gba-react/CHANGELOG.md index 6ec5987..c14ba15 100644 --- a/packages/gba-react/CHANGELOG.md +++ b/packages/gba-react/CHANGELOG.md @@ -1,5 +1,11 @@ # @gba-kit/gba-react +## 0.5.0 + +### Patch Changes + +- @gba-kit/gba-browser@0.5.0 + ## 0.4.0 ### Patch Changes diff --git a/packages/gba-react/package.json b/packages/gba-react/package.json index f65d585..0f66c75 100644 --- a/packages/gba-react/package.json +++ b/packages/gba-react/package.json @@ -1,6 +1,6 @@ { "name": "@gba-kit/gba-react", - "version": "0.4.0", + "version": "0.5.0", "description": "React hooks for GBA emulation", "author": "macabeus", "type": "module",