diff --git a/docs/superpowers/plans/2026-11-11-tms34010-m2-ti-abi-and-rom-calls.md b/docs/superpowers/plans/2026-11-11-tms34010-m2-ti-abi-and-rom-calls.md index 90666b7..8bccbec 100644 --- a/docs/superpowers/plans/2026-11-11-tms34010-m2-ti-abi-and-rom-calls.md +++ b/docs/superpowers/plans/2026-11-11-tms34010-m2-ti-abi-and-rom-calls.md @@ -20,12 +20,12 @@ - Callee: save used A registers except A8 and the old FP on the system stack; `FP = STK`; `ADDI frame_bits,STK`; the first explicit argument is at `FP(-32)`, the second at `FP(-64)`, and so on; locally allocated objects are at positive FP offsets; restore `STK` from the saved slot; restore registers; `RETS 2` if the function has explicit arguments, `RETS 0` otherwise. - The saved old STK sits at `SP + (n+1)*32` after `n` system-stack pushes (return PC is at `SP + n*32`). `RETS N` pops the PC and advances SP by `32 + 16N`. - `FS1` must be 32 at function entry and after every call. Emit `SETF 32,0,1` in the prologue and immediately after each `CALLA`. -- **M2 ABI scope:** `i32` and pointer arguments, each already a 32-bit stack slot; `void`, `i32` and pointer returns. Reject i8/i16 parameters (M3 adds the widen/truncate rules), i64, float/double, structs, vectors, varargs, `sret`, `byval`, `inreg`, `nest`, `returned`, tail calls, and indirect calls **loudly**, with a message beginning `M2`. Narrow loads/stores inside a function remain supported (M1's `MOVB`/FS0 `MOVE`). +- **M2 ABI scope:** `i32` and pointer arguments, each already a 32-bit stack slot; `void`, `i32` and pointer returns. Reject i8/i16 parameters (M3 adds the widen/truncate rules), i64, float/double, structs, vectors, varargs, `sret`, `byval`, `inreg`, `nest`, tail calls, and indirect calls (the optimizer's `returned` hint is accepted: it is not ABI-affecting) **loudly**, with a message beginning `M2`. Narrow loads/stores inside a function remain supported (M1's `MOVB`/FS0 `MOVE`). - **M2 control-flow scope:** straight-line functions only. Conditional/unconditional branches, function pointers, spills requiring branch fixups, floats, structs and varargs are M3+. A branch that reaches ISel must fail loudly (`Cannot select: br` is acceptable). - Output is **assembly only**: `llc -march=tms34010 -filetype=asm`. No LLVM object writer, `MCCodeEmitter`, asm parser or linker in M2. GSPA assembles, GSPLNK links, the existing COFF loader loads. - M2 assembles with the **proven 1987 GSPA/GSPLNK 2.00 under DOSBox**, exactly as `scripts/m1/run.sh` does. Keep the linker command file CRLF. `.text` and `.data` link in the `prog` region at bit `018A0000`; `.bss` in `ram` at bit `018A9000`. - **`.data`/`.rodata`/string literals:** initialized globals and private constant data go to `.data` (the loader copies it); zero-initialized globals stay in `.bss`. The object format is GSPA 2.00, so emit `.byte` for i8 scalars/byte arrays, `.word` for i16 scalars, `.long` for i32 scalars/pointers, and symbol+bit-addend expressions; never ELF `.rodata`/`.asciz`/`.p2align`. A scalar's directive must match its width so the next global's offset is correct. -- **ROM imports are per-ROM-revision.** v2.2 first. `symbols/vt1200-v22.syms` is the source of truth; M2 needs only `strlen 0x3FB458D0`, implemented as an absolute-jump stub (`_strlen: ja 3FB458D0h`) so the ROM's `RETS` returns directly to our caller. Do not add Xlib calls in M2. +- **ROM imports are per-ROM-revision.** v2.2 first. `symbols/vt1200-v22.syms` is the source of truth; M2 needs only `strlen 0x3FB458D0`, implemented as an absolute-jump stub (`_strlen: jauc 3FB458D0h`; GSPA 2.00 rejects bare `ja`) so the ROM's `RETS` returns directly to our caller. Do not add Xlib calls in M2. - Symbols use the GSPA/TI leading underscore (`_main`, `_strlen`); code and data are position-dependent and linked at fixed bit addresses. - The M1 test suite and M1 GSPA harness encode the retired M1 register convention. On branch `m2` the lit suite is replaced by `m2-*` tests; `scripts/m1/` is retained as historical reference and is **not** an M2 gate. The `m1` branch keeps the original suite. - Tests: LLVM lit/FileCheck for asm; a GSPA/GSPLNK assemble-link step; a MAME run with marker words; a `dis34` round-trip; a stored TI-compiler golden check. @@ -46,7 +46,7 @@ **Files:** - Delete: `build/llvm-clean/llvm/test/CodeGen/TMS34010/m1-*.ll` (compiler repo; the `m1` branch keeps them) - Create: `build/llvm-clean/llvm/test/CodeGen/TMS34010/m2-add.ll`, `m2-load.ll`, `m2-char.ll`, `m2-i16.ll`, `m2-store.ll`, `m2-global.ll`, `m2-late-gep.ll`, `m2-reject-i64.ll`, `m2-reject-struct.ll`, `m2-reject-vararg.ll`, `m2-reject-sret.ll`, `m2-reject-structret.ll` -- Create: `m2-args.ll`, `m2-noret.ll`, `m2-locals.ll`, `m2-args-locals.ll`, `m2-call.ll`, `m2-call3.ll`, `m2-callee-save.ll`, `m2-fs1.ll`, `m2-spill.ll`, `m2-data.ll`, `m2-data-avail.ll`, `m2-string.ll`, `m2-ptrinit.ll`, `m2-reject-arg8.ll`, `m2-reject-i8ret.ll`, `m2-reject-float.ll`, `m2-reject-call-indirect.ll`, `m2-reject-call-arg8.ll`, `m2-reject-call-vararg.ll` +- Create: `m2-args.ll`, `m2-noret.ll`, `m2-locals.ll`, `m2-args-locals.ll`, `m2-call.ll`, `m2-call3.ll`, `m2-callee-save.ll`, `m2-fs1.ll`, `m2-spill.ll`, `m2-data.ll`, `m2-data-avail.ll`, `m2-string.ll`, `m2-ptrinit.ll`, `m2-reject-arg8.ll`, `m2-reject-i8ret.ll`, `m2-reject-float.ll`, `m2-returned.ll`, `m2-data-align.ll`, `m2-reject-call-indirect.ll`, `m2-reject-call-arg8.ll`, `m2-reject-call-vararg.ll` - Test: the `m2-*` filter in `build/llvm-clean-build/test/CodeGen/TMS34010` **Interfaces:** @@ -78,7 +78,7 @@ Delete the `m1-*.ll` files. Copy the still-valid positive tests from the `m1` br - `m2-store.ll`: same IR as `m1-store.ll`; both functions have arguments, so both endings are `CHECK: RETS 2`. - `m2-global.ll`: same IR as `m1-global.ll`; the return is A8, so `CHECK: MOVI _arr+32, A{{[0-9]+}}` and `CHECK: RETS 0`; keep `CHECK: .bss _arr,128,16`. - `m2-late-gep.ll`: same IR as `m1-late-gep.ll` (`CHECK: ADDI 24`, `CHECK-NOT: ADDI 192`). -- `m2-reject-i64.ll`, `m2-reject-struct.ll`, `m2-reject-vararg.ll`, `m2-reject-sret.ll`, `m2-reject-structret.ll`: same IR as the corresponding `m1-*` negatives, with the `CHECK` messages changed to the M2 text in Task 2/3 (`M2 supports only i32 and pointer arguments`, `M2 does not support varargs`, `M2 supports only plain arguments (no sret/byval/inreg/nest/returned)`, `M2 supports only void/i32/pointer returns`). Keep both `-O0` and `-O2` RUN lines. +- `m2-reject-i64.ll`, `m2-reject-struct.ll`, `m2-reject-vararg.ll`, `m2-reject-sret.ll`, `m2-reject-structret.ll`: same IR as the corresponding `m1-*` negatives, with the `CHECK` messages changed to the M2 text in Task 2/3 (`M2 supports only i32 and pointer arguments`, `M2 does not support varargs`, `M2 supports only plain arguments (no sret/byval/inreg/nest)`, `M2 supports only void/i32/pointer returns`). Keep both `-O0` and `-O2` RUN lines. Add the new tests below. These are the red tests for Tasks 2–4. @@ -549,7 +549,7 @@ if (N->getOpcode() == ISD::FrameIndex) { `LowerFormalArguments`: -- Keep the M1 IR-signature validation, changing messages to `M2 ...`: reject varargs (`M2 does not support varargs`), i8/i16 parameters (`M2 supports only i32 and pointer arguments`), i64/float/double/aggregates/vectors (the same message), and `sret`/`byval`/`inreg`/`nest`/`returned` (`M2 supports only plain arguments (no sret/byval/inreg/nest/returned)`). Allow only i32 and pointers. +- Keep the M1 IR-signature validation, changing messages to `M2 ...`: reject varargs (`M2 does not support varargs`), i8/i16 parameters (`M2 supports only i32 and pointer arguments`), i64/float/double/aggregates/vectors (the same message), and `sret`/`byval`/`inreg`/`nest`/`returned` (`M2 supports only plain arguments (no sret/byval/inreg/nest)`). Allow only i32 and pointers. - For argument `i` (0-based), create a fixed object of 4 bytes at byte offset `-(i+1)*4` (`MFI.CreateFixedObject(4, -(i+1)*4, true)`), load `i32` from it with alignment 2, and push the result to `InVals`. M2 supports only i32 and pointer arguments; the IR-signature validation above rejects narrow parameters. - Do not use M1's A0–A3 `CCValAssign` register path. @@ -689,7 +689,7 @@ In `TMS34010AsmPrinter::emitGlobalVariable`: - Use `GV->hasLocalLinkage()`/`isConstant()` to decide whether to emit a global symbol attribute, as the `.bss` path does. - Fail loudly for any initializer shape M2 cannot print (e.g. floats, aggregates, vector constants) with `report_fatal_error("M2 .data supports only integer, pointer and byte-array initializers")`. - Map read-only/private constants to `.data`; do not emit `.rodata`, `.asciz`, `.p2align`, `.section` or ELF directives. -- `.word` values use a leading `0` (`0%04Xh`) so high-bit values assemble; `.byte` lines wrap every 16 values; zero aggregates dispatch by element width (`.byte 0`/`.word 0`/`.long 0`). +- `.word` values use a leading `0` (`0%04Xh`) so high-bit values assemble; `.byte` lines wrap every 16 values; zero aggregates dispatch by element width (`.byte 0`/`.word 0`/`.long 0`); emit `.even` before a `.data` global whose alignment is ≥16 bits so the label and the `.word`/`.long` agree. - Accept both `GEPOperator` and the normalized `inttoptr(add(ptrtoint(@sym), addend))` pointer-initializer shape (the `tms-bitaddr --normalize` form). - Skip `available_externally` globals (`isDeclarationForLinker()`); they are resolved externally, not definitions. - If GSPA rejects a private symbol whose name contains `.` — it does: `_.str:` gives `INVALID OPCODE` and `INVALID EXPRESSION` on the reference — sanitize private/local symbol names in `TMS34010ObjectFile` (replace `.` with `_`, or use a unique `Lstr` label) and keep the `m2-string`/`m2-data` checks matching the sanitized name. GSPA 2.00 accepted `_.str` neither as a label nor in an expression; do not rely on ELF-style names. @@ -743,7 +743,7 @@ awk ' name = $1 addr = $2 sub(/^0x/, "", addr) - printf "\t.globl _%s\n_%s:\n\tja %sh\n", name, name, addr + printf "\t.globl _%s\n_%s:\n\tjauc %sh\n", name, name, addr } ' "$SYMS" >> "$OUT" ``` @@ -908,7 +908,7 @@ Print `M2: check=... first=... ...` and exactly `M2 MAME: PASS` on success. `scripts/m2/dis-roundtrip.sh` adapts the M1 COFF parser and symbol lookup. It must require, at the linked addresses: -- `_strlen` stub: `ja 3FB458D0` +- `_strlen` stub: `jauc 3FB458D0` - `_first`: `MOVEFP` materialisation (`MOVE A13, A?`) plus `ADDI -32`, and `RETS 2` - `_second`: `ADDI -64` and `RETS 2` - `_check` or `_keep`: `MOVE A14,-*SP,1`, `MOVE ...,*A14+,1`, `CALLA`, `SETF 32,0,1`, `MOVE *SP(...),A14,1`, `RETS 2`