docs: finalize M2 ABI and tail-call notes
This commit is contained in:
1 parent
f1a604d70e
commit
2d4919db36
3 files changed
+31
-17
No files matched your search
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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:]]*(;|$))'
|
||||
|
||||
Reference in new issue
Block a user