From 2d4919db36213eb01f288051987f9c85b7f89406 Mon Sep 17 00:00:00 2001 From: arti Date: Wed, 11 Nov 2026 17:36:17 +0000 Subject: [PATCH] docs: finalize M2 ABI and tail-call notes --- README.md | 38 ++++++++++++------- ...-11-11-tms34010-m2-ti-abi-and-rom-calls.md | 9 +++-- scripts/m2/dis-roundtrip.sh | 1 + 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 40840e6..695e7c1 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,12 @@ M2 replaces the M1 test convention with the TI ABI and stops at assembly: functions with calls, frames and locals. - A caller saves `A14` (STK) on the system stack, pushes arguments on the program stack **in reverse order** (each a 32-bit slot), emits `CALLA`, - then re-establishes `FS1`. -- A callee saves the used A0–A7/A9–A12 registers and the old FP on the system - stack, sets `FP = STK`, allocates the frame with `ADDI`, and restores both - stacks in the epilogue. The first explicit argument is at `FP(-32)`, the - second at `FP(-64)`, and so on; locals live at positive FP offsets. + then re-establishes both field sizes (`SETF 16,0,0`, `SETF 32,0,1`). +- A callee starts with `SETF 16,0,0`/`SETF 32,0,1`, saves the used + A0–A7/A9–A12 registers and the old FP on the system stack, sets `FP = STK`, + allocates the frame with `ADDI`, and restores both stacks in the epilogue. + The first explicit argument is at `FP(-32)`, the second at `FP(-64)`, and + so on; locals live at positive FP offsets. - `void`, `i32` and pointer returns, in `A8`; `RETS 2` when the function has explicit arguments, `RETS 0` otherwise. - Initialized globals, string/byte-array literals and pointer initializers in @@ -56,9 +57,11 @@ M2 replaces the M1 test convention with the TI ABI and stops at assembly: `strlen` only: `_strlen: jauc 3FB458D0h`. `CALLA _strlen` pushes the return PC, the stub jumps to the ROM, and the ROM's `RETS 2` returns directly to our caller. -- Thirty-three `llvm-lit`/FileCheck tests under +- Thirty-seven `llvm-lit`/FileCheck tests under `llvm/test/CodeGen/TMS34010/m2-*.ll`, including negative tests that pin the - loud refusals. + loud refusals. The ABI/data coverage includes `m2-returned`, + `m2-returned-call`, `m2-call-noret`, `m2-data-align`, `m2-data-avail`, + `m2-data-z64` and `m2-data-i1`. - End-to-end MAME and dis34 gates, plus a differential check against assembly generated once by the 1987 TI compiler (see below). @@ -73,13 +76,15 @@ M2 replaces the M1 test convention with the TI ABI and stops at assembly: | Locals | none | positive FP offsets | | Return instruction | `RETS 0` | `RETS 2` with arguments, `RETS 0` without | | Calls, callee-saves, stack args | rejected | required | -| Field size | harness sets FS1/FS0 before entry | `FS1 = 32` (32-bit words), set in the prologue and after every call | +| Field size | harness sets FS1/FS0 before entry | `FS0 = 16` and `FS1 = 32`, set in the prologue and after every call | The callee-saved set is `A0`–`A7` and `A9`–`A12`; `A8` is the return register and `A13`–`A15` are reserved. After `n` system-stack pushes the saved old STK is at `SP + (n+1)*32`, and `RETS N` pops the return PC and advances `SP` by -`32 + 16N`. `FS1` must be 32 at function entry and immediately after every -`CALLA` (`SETF 32,0,1`), because ROM code may change the field-size state. +`32 + 16N`. `FS0` must be 16 and `FS1` must be 32 at function entry and +immediately after every `CALLA` (`SETF 16,0,0` then `SETF 32,0,1`), because +ROM code may change either field size. FS0 drives `short` loads/stores and FS1 +drives 32-bit words. ## Run the M2 gates @@ -91,7 +96,7 @@ convention. # 1. Build llc, opt, clang and the test tools ninja -C build/llvm-clean-build llc opt clang llvm-config FileCheck count not -# 2. LLVM codegen tests: 33 passed +# 2. LLVM codegen tests: 37 passed build/llvm-clean-build/bin/llvm-lit -v \ build/llvm-clean-build/test/CodeGen/TMS34010 --filter='m2-' @@ -148,8 +153,12 @@ GSPA 2.00 has no ELF sections. M2 emits raw `.data` text, never `.rodata`, label and the following `.word`/`.long` agree. A scalar's directive must match its width, otherwise the next global's offset -is wrong. Private/local symbol names are sanitized to GSPA-safe names -(`_HH` for non-alphanumerics). Unsupported initializer shapes fail with +is wrong. GSPA's `.even` aligns to a 16-bit boundary only, so an explicit +`align 4`/`align 8` on a `.data` object is **not** honored in M2: the target's +standalone object alignment is 2 bytes. Private/local symbol names are +sanitized to GSPA-safe names (`_HH` for non-alphanumerics). Unsupported +initializer shapes — including arrays whose element width has no GSPA +directive (`i1`, `i64`) — fail with `M2 .data supports only integer, pointer and byte-array initializers`. Zero-initialized mutable globals remain `.bss` and are zeroed by the loader. @@ -180,6 +189,9 @@ M2 deliberately rejects or omits: - **Aggregates:** structs/unions by value, `sret`, `byval`, bitfields (M4/M5). - **Varargs:** `...` functions and calls (M5). - **Function pointers and indirect calls** (M4). +- **Tail calls:** ordinary `tail` markers are demoted to ordinary calls; + `musttail` is not implemented (M2 C does not emit it). Real tail calls are + M3. - **Narrow ABI types:** `i8`/`i16` parameters, `i64`, and narrow returns; M3 adds the widen/truncate rules. Narrow loads/stores inside a function remain supported. 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 78e2248..e142204 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 @@ -19,8 +19,8 @@ - Caller: if it passes arguments, `MOVE A14,-*SP,1` (save old STK on the system stack); push arguments on the program stack **in reverse order**, each widened to 32 bits; `CALLA`; then `SETF 32,0,1`. - 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`, 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`). + - `FS0` must be 16 and `FS1` must be 32 at function entry and after every call. Emit `SETF 16,0,0` then `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`, and indirect calls (the optimizer's `returned` hint is accepted: it is not ABI-affecting) **loudly**, with a message beginning `M2`. Ordinary `tail` markers are demoted to ordinary calls (`LowerCall` clears `IsTailCall`); `musttail` is not implemented (M2 C does not emit it). Real tail calls are M3. 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`. @@ -34,10 +34,11 @@ 1. **Argument order and slot offsets:** a call with two or more arguments must place the first at `FP(-32)`, the second at `FP(-64)`, etc.; a reversed push order silently swaps arguments. Tests in Task 3 (`m2-call3`) and Task 5 (`_first(20,22) == 20`, `_second(20,22) == 22`). 2. **Old-STK restore offset:** the callee's `MOVE *SP((n+1)*32),A14` offset must count the return PC, the saved old STK, and every saved register/FP. A wrong count corrupts A14 after return and the next call uses the wrong stack. Tests in Task 2 (`m2-callee-save` shape), Task 3 (`m2-call` shape), Task 5's `dis34` push-count check, and Task 5's consecutive calls to `_first`, `_second`, `_caller`, `_locals` and `_keep`. -3. **FS1 after a ROM call:** Xlib/ROM code clobbers the field-size state. A missing `SETF 32,0,1` after `CALLA` miscompiles every later 32-bit access. Tests in Task 3 (`m2-fs1`) and Task 5 (`strlen` followed by more calls). +3. **FS0/FS1 after a ROM call:** Xlib/ROM code clobbers the field-size state. A missing `SETF 16,0,0`/`SETF 32,0,1` pair after `CALLA` miscompiles a later `short` or 32-bit access. The prologue and every call emit both. Tests in Task 3 (`m2-fs1`, `m2-i16`) and Task 5 (`strlen` followed by more calls). 4. **Callee-saved preservation:** a value live across a call must survive; TI preserves A0–A12 except A8, and A13. Tests in Task 2 (`m2-callee-save`) and Task 5 (`_keep(10) == 16`, using an initialized `.data` global loaded before the call). 5. **`RETS N` matches the function's own arguments:** `RETS 2` for a function with arguments, `RETS 0` without; a mismatch unbalances the system stack on return. Tests in Task 2 (`m2-args`, `m2-noret`) and Task 5 (mixed calls in one entry). -6. **`.data` bytes and bit addends:** a string literal must be byte-exact, and a pointer initializer must carry a bit addend (`_arr+32`, not `_arr+4`). Tests in Task 4 (`m2-data`, `m2-ptrinit`) and Task 5 (`strlen("0::0")`). +6. **`.data` bytes and bit addends:** a string literal must be byte-exact, and a pointer initializer must carry a bit addend (`_arr+32`, not `_arr+4`). Array element widths must have a matching GSPA directive (`i8`, `i16`, `i32`, pointer); `i1`/`i64` must fail loudly. Tests in Task 4 (`m2-data`, `m2-ptrinit`, `m2-data-z64`, `m2-data-i1`) and Task 5 (`strlen("0::0")`). +7. **Tail calls:** ordinary `tail` markers are demoted to ordinary calls; `musttail` is not implemented (M2 C does not emit it). Real tail calls are M3, so the docs must not claim a loud failure. --- diff --git a/scripts/m2/dis-roundtrip.sh b/scripts/m2/dis-roundtrip.sh index 5444bdd..b018e1d 100755 --- a/scripts/m2/dis-roundtrip.sh +++ b/scripts/m2/dis-roundtrip.sh @@ -268,6 +268,7 @@ expect_seq "$TMP/keep.txt" \ 'move[[:space:]]+a14,[[:space:]]*-\*sp,[[:space:]]*1' \ 'move[[:space:]]+[^,]+,[[:space:]]*\*a14\+,[[:space:]]*1' \ 'calla' \ + 'setf[[:space:]]+16,[[:space:]]*0,[[:space:]]*0' \ 'setf[[:space:]]+32,[[:space:]]*0,[[:space:]]*1' \ 'move[[:space:]]+\*sp\(0x[0-9a-f]+\),[[:space:]]*a14,[[:space:]]*1' \ 'rets[[:space:]]+2([[:space:]]*(;|$))'