Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9c593ddd5 | ||
|
|
466b7ec23b | ||
|
|
e0e81b722e | ||
|
|
04ec6b1419 | ||
|
|
af2e1ded7a | ||
|
|
a5f0c56bf2 | ||
|
|
f1f8ffbb61 | ||
|
|
53293c399a | ||
|
|
78cd63b234 | ||
|
|
e14fcf5682 | ||
|
|
b3e6d7389c | ||
|
|
b166d892e7 | ||
|
|
96391dc024 | ||
|
|
2604471eb6 | ||
|
|
cd074251fd | ||
|
|
bbc3b21421 | ||
|
|
eab7503d76 | ||
|
|
40f5520e3d | ||
|
|
f1450ebe7e | ||
|
|
d0d1d6e101 | ||
|
|
a34b8a3fb7 | ||
|
|
ea154fa696 | ||
|
|
df6f6fa9d6 | ||
|
|
67bd4c7dda | ||
|
|
fd41299260 |
No files matched your search
+430
-84
@@ -1,14 +1,17 @@
|
||||
# LLVM 23.1.5 fork for TMS34010
|
||||
|
||||
Branch `m4` is `llvmorg-23.1.5` plus the TMS34010 target and clang
|
||||
Branch `m5` is `llvmorg-23.1.5` plus the TMS34010 target and clang
|
||||
bit-address changes (M0), a minimal TMS34010 backend (M1), the TI ABI, calls
|
||||
and `.data` support (M2), integer control flow plus the first compiled C Xlib
|
||||
window (M3), and M4's function pointers, multiply/divide, zero-extending
|
||||
narrow loads, the XImage/event client and the pinned termtris integration.
|
||||
This branch is the canonical source for the target; the project's
|
||||
`patches/llvm` mirror is frozen at M0. Branch `m3` is the previous milestone
|
||||
snapshot and the fork's default branch; branch `m2` is the M2 tip and branch
|
||||
`tms34010` keeps the M1 backend and its `m1-*` suite.
|
||||
window (M3), M4's function pointers, multiply/divide, zero-extending narrow
|
||||
loads, the XImage/event client and the pinned termtris integration, and M5's
|
||||
native TI-COFF object path, full MC layer, `JR`/relaxation, `MMTM`/`MMFM`,
|
||||
`alloca`/`byval`/narrow returns/mem intrinsics, and the pinned conformance
|
||||
tier. This branch is the canonical source for the target; the project's
|
||||
`patches/llvm` mirror is frozen at M0. Branch `m4` is the previous milestone
|
||||
snapshot and the fork's default branch; branch `m3` is before that, branch
|
||||
`m2` is the M2 tip and branch `tms34010` keeps the M1 backend and its `m1-*`
|
||||
suite.
|
||||
|
||||
## M2 state
|
||||
|
||||
@@ -26,8 +29,8 @@ The calling convention is the **TI ABI**:
|
||||
- Caller: if it passes arguments, save the old STK on the system stack with
|
||||
`MOVE A14, -*SP, 1`; push arguments on the program stack **in reverse
|
||||
order** (`MOVE <arg>, *A14+, 1`), each already a 32-bit slot; `CALLA`; then
|
||||
re-establish both field sizes with `SETF 16, 0, 0` and `SETF 32, 0, 1`.
|
||||
- Callee: set `SETF 16, 0, 0` and `SETF 32, 0, 1`; save the old FP and each
|
||||
re-establish both field sizes with `SETF 16, 1, 0` and `SETF 32, 0, 1`.
|
||||
- Callee: set `SETF 16, 1, 0` and `SETF 32, 0, 1`; save the old FP and each
|
||||
used callee-saved register on the system stack; `FP = STK`; allocate the
|
||||
frame with `ADDI <bits>, STK`. The first explicit argument is at `FP(-32)`, the second at
|
||||
`FP(-64)`, and so on; locals are at positive FP offsets.
|
||||
@@ -44,7 +47,7 @@ The calling convention is the **TI ABI**:
|
||||
all other state except the reserved registers.
|
||||
- `FS0` must be 16 (for `short` accesses) and `FS1` must be 32 (for 32-bit
|
||||
words) at function entry and after every call. The prologue emits
|
||||
`SETF 16, 0, 0` then `SETF 32, 0, 1`; `LowerCall` emits the same pair
|
||||
`SETF 16, 1, 0` then `SETF 32, 0, 1`; `LowerCall` emits the same pair
|
||||
immediately after each `CALLA`.
|
||||
|
||||
Supported signatures are `i32` and pointer arguments, and `void`, `i32` or
|
||||
@@ -64,7 +67,7 @@ rejection of the ABI-affecting attributes.
|
||||
|
||||
### Implemented selection and printing
|
||||
|
||||
- `ADD` for `i32`; `MOVI` for constants; `SETF 16, 0, 0` and
|
||||
- `ADD` for `i32`; `MOVI` for constants; `SETF 16, 1, 0` and
|
||||
`SETF 32, 0, 1`; `RETS N`.
|
||||
- `MOVE ..., 1` (FS1) for 32-bit load/store, `MOVE ..., 0` (FS0) for `i16`,
|
||||
sign-extending `MOVB` for `i8`.
|
||||
@@ -99,12 +102,12 @@ never reports a fatal error for a spill.
|
||||
The prologue saves `A13` and each callee-saved register used by the function as
|
||||
individual system-stack pushes (`MOVE <reg>, -*SP, 1`), and the epilogue pops
|
||||
them in reverse order after restoring STK with
|
||||
`MOVE *SP((n+1)*32), A14, 1`. This is a deliberate decision: the stack layout
|
||||
is identical to `MMTM`/`MMFM` (one 32-bit slot per register, old STK at
|
||||
`SP+(n+1)*32`), so the ABI-visible contract is unchanged, while the asm-only
|
||||
printer does not need a variable register-list operand. Callee-saved spill slots
|
||||
are not allocated on the program stack; the prologue/epilogue own the saves and
|
||||
restores.
|
||||
`MOVE *SP((n+1)*32), A14, 1`. M4 chose this form because the stack layout is
|
||||
identical to `MMTM`/`MMFM` (one 32-bit slot per register, old STK at
|
||||
`SP+(n+1)*32`), so the ABI-visible contract is unchanged; M5 replaces it with
|
||||
the one-instruction register-list form without moving any slot (see "M5
|
||||
state"). Callee-saved spill slots are not allocated on the program stack; the
|
||||
prologue/epilogue own the saves and restores.
|
||||
|
||||
### `.data` and string literals
|
||||
|
||||
@@ -174,9 +177,9 @@ with the first compiled C Xlib client. It is still assembly-only and still
|
||||
- **`-O2` loop exit-value rewriting is not lowered.** IndVarSimplify can turn
|
||||
`sumto`'s loop into a closed-form `i33` multiply, which M3 cannot select, so
|
||||
the M3 harness builds `branch.c` with `-replexitval=never`. Ordinary
|
||||
`while`/`for` loops that keep a real back-edge and phi work. M4/M5 must add
|
||||
multiply/`i64` lowering, a target-level pass policy, or a target diagnostic
|
||||
instead of the raw abort.
|
||||
`while`/`for` loops that keep a real back-edge and phi work. M4 adds 32-bit
|
||||
multiply/divide and moves the `-replexitval=never` policy into
|
||||
`tms-clang`; i64 remains M6. See "M4 state".
|
||||
|
||||
### Branches, CMP and the status register
|
||||
|
||||
@@ -307,10 +310,11 @@ TI's `JRLE`/`JRUC` spellings, which the check accepts alongside `JALE`/`JAUC`.
|
||||
|
||||
### M4 state
|
||||
|
||||
M4 closes the remaining Xlib path to a real image/event client and a
|
||||
full-game integration. It is still **assembly-only**: GSPA 2.00 and GSPLNK
|
||||
2.00 under DOSBox produce the COFF. There is no `MCCodeEmitter`, object
|
||||
writer, `llc -filetype=obj` or `clang -c`.
|
||||
M4 closed the remaining Xlib path to a real image/event client and a
|
||||
full-game integration. On the `m4` branch it is still **assembly-only**: GSPA
|
||||
2.00 and GSPLNK 2.00 under DOSBox produce the COFF. There is no
|
||||
`MCCodeEmitter`, object writer, `llc -filetype=obj` or `clang -c` on `m4`;
|
||||
M5 adds them (see "M5 state").
|
||||
|
||||
#### Function pointers and `CALL Rs`
|
||||
|
||||
@@ -320,7 +324,7 @@ global function pointer is a `.data` word (`.long _dec`). `LowerCall` accepts
|
||||
a callee that is not a `GlobalAddress` and emits the one-word register call
|
||||
`CALL Rs` (GSPA encodes it as `0x0920`; `dis34` prints `call aN`). The call
|
||||
still saves the old STK (`MOVE A14, -*SP, 1`), pushes its arguments
|
||||
last-to-first on the program stack, and re-establishes `SETF 16, 0, 0` /
|
||||
last-to-first on the program stack, and re-establishes `SETF 16, 1, 0` /
|
||||
`SETF 32, 0, 1`; the callee's `RETS 2` restores A14. Direct calls remain
|
||||
`CALLA _sym`. `m4-fnptr.ll` pins the selection, `scripts/tests/m4/fnptr.sh`
|
||||
runs the indirect path in MAME and checks `dis34` at the linked address, and
|
||||
@@ -376,9 +380,12 @@ file.ll` uses the stable `<stdin>` fallback. `m4-private.ll` and the two-TU
|
||||
`scripts/tests/m4/private.sh` pin it.
|
||||
|
||||
GSPA 2.00 truncates identifiers at 31 characters. A private name long enough
|
||||
to lose the `_<8 hex>` token at that limit will collide loudly (a duplicate
|
||||
definition from GSPA), not silently; the naming scheme is unchanged in M4 and
|
||||
a long-name two-TU link test is an M5 follow-up.
|
||||
to lose the `_<8 hex>` token at that limit would collide loudly (a duplicate
|
||||
definition from GSPA), not silently. M5 fixes that: it caps the sanitized base
|
||||
at 22 characters and folds a longer name into a second 8-hex token, leaving the
|
||||
module token last in a 31-character label. `m5-private-long.ll` and the two-TU
|
||||
`scripts/tests/m5/private-long.sh` pin it. Public names longer than 31
|
||||
characters are still truncated by GSPA.
|
||||
|
||||
#### `i8`/`i16` fixed arguments
|
||||
|
||||
@@ -416,16 +423,20 @@ static definition in a TU where the optimizer can see all callers).
|
||||
|
||||
#### Driver policies
|
||||
|
||||
`scripts/tms-clang` invokes `opt` with two explicit policies:
|
||||
`scripts/tms-clang` invoked `opt` with two explicit policies on `m4`:
|
||||
|
||||
- `-replexitval=never` — the i33 exit-value multiply described above.
|
||||
- `-replexitval=never` — the i33 exit-value multiply described above. M5 keeps
|
||||
this as a documented policy and adds an llc diagnostic for the raw pipeline.
|
||||
- `-switch-to-lookup=false` — the optimizer's switch-to-lookup transform
|
||||
creates a *new* byte-offset GEP after `tms-bitaddr --scale`, and
|
||||
`--normalize` can only treat surviving GEP offsets as bit offsets. Such a
|
||||
`--normalize` could only treat surviving GEP offsets as bit offsets. Such a
|
||||
GEP would index the table one bit per slot (an 8× address bug). The backend
|
||||
also has no jump-table lowering, so compare/branch is correct. termtris's
|
||||
five-case X-event switch is the motivating case; `m4-switch.ll` pins the
|
||||
target-level behaviour and `scripts/m4/switch.c` is the driver probe.
|
||||
target-level behaviour and `scripts/m4/switch.c` is the driver probe. **M5
|
||||
removes this flag**: `tms-bitaddr --normalize` recognizes the
|
||||
`switch.table.*` GEP and shifts its byte/element index by 3 (see "M5
|
||||
state").
|
||||
|
||||
#### XImage, XEvent and the ROM layouts
|
||||
|
||||
@@ -492,9 +503,10 @@ Two server/Xlib constraints shape the drawing path:
|
||||
`XCopyArea`'d to window x=12 (screen x=32). The 12-pixel prefix is filled
|
||||
with the preceding cell's colour. The checker cells remain 32 pixels; the
|
||||
phase inside the (20,20) window is 12 pixels. `XCopyArea` copies
|
||||
`IMG_W - 12` = 628 columns, so the rightmost 12 source columns (the final
|
||||
12 px of the last cell) are not displayed; the last visible cell is 20 px,
|
||||
not 32.
|
||||
`IMG_W - 12` = 628 source columns (0–627) to window x = 12–639, so the
|
||||
image's rightmost 12 source columns (628–639, the final 12 px of the last
|
||||
32-px cell) are never displayed; that last cell is visible only as its left
|
||||
20 px. The window itself stays 640 columns wide.
|
||||
|
||||
The 38,400-byte image and the 1 KiB XG block live in `.bss` at `0x01A00000`
|
||||
(the termtris-proven region); `prog` stays at `0x018A0000`. The client primes
|
||||
@@ -511,10 +523,13 @@ commit `be5b323f5d2cc4f7a7d25e7143c67bd9828ccbdf` (branch `termtris`). Six
|
||||
translation units (`main`, `game`, `scoredb`, `x11`, `compat`, `xgfix`) are
|
||||
compiled with this compiler, assembled and linked by the 1987 GSPA/GSPLNK
|
||||
2.00 with the 1987 `rts.lib`, and run in MAME. The regions are the
|
||||
0x40000-bit `prog` at `0x018A0000` and `ram` at `0x01A00000`. The final map
|
||||
0x40000-bit `prog` at `0x018A0000` and `ram` at `0x01A00000`. The M4 map
|
||||
is `.text` 0x2E210 bits (23,618 bytes), `.data` 0x21A0 bits, `.bss` 0x1DBD0
|
||||
bits; it fits `prog` and the loader reports `status=0 (OK)` with no
|
||||
`UNDEFED` symbols. The gate runs board/move/panels/quit modes with real key
|
||||
`UNDEFED` symbols. (M5's `JR`/`MMTM`/switch changes shrink the current `m5`
|
||||
native map to `.text` 0x28AD0 bits, `.data` 0x2540 bits and the same
|
||||
0x1DBD0-bit `.bss`; see "M5 state".) The gate runs board/move/panels/quit
|
||||
modes with real key
|
||||
injection and enforces `loops >= 100` (board/move/panels/quit), `events >= 6`
|
||||
for move/panels/quit (board `>= 2`), `quit = 1` only for quit, focus = the
|
||||
created termtris window (`0x00400001`), loader status 0 and a non-empty
|
||||
@@ -535,38 +550,345 @@ M4 closes three of the five M3 carry-forwards:
|
||||
`JANE`/`JALT`/`JALO`).
|
||||
|
||||
The new M4 harness and `scripts/tests/m4/dis-roundtrip.sh` are
|
||||
operand/register-independent, and `layout.sh` was tightened. The M3 regression
|
||||
is deliberately unchanged: `m3-cmpi.ll` still uses `CHECK-DAG: MOVI 0` /
|
||||
`MOVI 1` (the 1/0 order is not pinned), and `scripts/m3/dis-roundtrip.sh`
|
||||
still hardcodes the nine `XCreateSimpleWindow` push registers
|
||||
(`A2`/`A0`). Tightening those M3 tests is a test-hardening follow-up; the M3
|
||||
suite (`m3-*.ll` and `scripts/tests/m3/`) passes as-is, with only the obsolete
|
||||
zext-reject test removed.
|
||||
operand/register-independent, and `layout.sh` was tightened. M5 completes the
|
||||
remaining M3 test-hardening carry-forwards: `m3-cmpi.ll` now pins the
|
||||
`-O0`/`-O2` MOVI order with `CHECK`/`CHECK-NEXT`, and
|
||||
`scripts/m3/dis-roundtrip.sh` parses the nine `XCreateSimpleWindow` push
|
||||
values/registers instead of hardcoding `A2`/`A0`. The M3 MAME values are
|
||||
unchanged.
|
||||
|
||||
## M4 non-goals and the M5 boundary
|
||||
## M5 state
|
||||
|
||||
M4 deliberately stops before:
|
||||
M5 keeps the M4 assembly path as the oracle and adds the native object path
|
||||
beside it. It is still an integer/pointer milestone: floats, `long long`,
|
||||
general `va_list` varargs, bitfields, `sret`, aggregate returns and debug info
|
||||
are M6. `byval` arguments are the one aggregate form M5 adds; struct fields
|
||||
through pointers are supported.
|
||||
|
||||
- **Native object output:** no `MCCodeEmitter`, object writer,
|
||||
`llc -filetype=obj`, `clang -c` or native COFF. GSPA/GSPLNK still produce
|
||||
the COFF. The `JR`/branch-relaxation code-size pass is M5, as are
|
||||
MMTM/MMFM register-list saves (the current per-register system-stack pushes
|
||||
have the identical ABI layout).
|
||||
- **Floats and `long long`:** no float/double or `i64` arguments, returns,
|
||||
arithmetic or branches.
|
||||
### Native TI-COFF object path
|
||||
|
||||
`llc -march=tms34010 -filetype=obj` now emits a relocatable TI-COFF object.
|
||||
`TMS34010COFFObjectWriter` and `TMS34010MCStreamer` are target code because TI
|
||||
COFF is not one of LLVM's built-in formats; `createMCStreamer` installs them
|
||||
for object output, while `-filetype=asm` keeps the GSPA raw-text path
|
||||
unchanged.
|
||||
|
||||
- Sections are `.text`, `.data` and `.bss` in that order. `SizeOfRawData` is
|
||||
in **bits**, `PointerToRawData`/`PointerToRelocations` are byte offsets, and
|
||||
`.bss` is virtual (`scnptr = 0`). Empty `.text`/`.data` still get a header
|
||||
and a raw pointer, matching GSPA.
|
||||
- The symbol table starts with three `C_STAT` section symbols with 18-byte aux
|
||||
records (size in bits, relocation count, line count 0), so their record
|
||||
indices are 0, 2 and 4. Defined globals are `C_EXT` with a section-relative
|
||||
**bit** value; undefined externals are `C_EXT` with section 0. Names over 8
|
||||
bytes use the COFF string table.
|
||||
- Relocations are 10-byte `R-RELLONG` (`0x11`) records at a bit
|
||||
`VirtualAddress`. Same-section references use symbol index `0xFFFF` and a
|
||||
section-relative bit field; cross-section references use the target section
|
||||
symbol and the target symbol's bit offset; undefined externals use the
|
||||
`C_EXT` index and the expression addend. Symbol differences and non-32-bit
|
||||
symbol relocations fail loudly.
|
||||
- `.data` and `.bss` allocations are at least 2-byte aligned and padded to a
|
||||
16-bit word: the loader rejects a section whose size is not whole words.
|
||||
Object-mode `.data` is emitted as real MC bytes/values with fixups; the asm
|
||||
path still emits raw GSPA `.data` text.
|
||||
- The writer accepts only `.text`/`.data`/`.bss`. A C-side `.cinit` section is
|
||||
rejected loudly; termtris's `.cinit` is the hand-written GSPA `cinit.asm`,
|
||||
assembled into the extra entry object (the asm path concatenates it with the
|
||||
generated assembly).
|
||||
|
||||
GSPLNK 2.00 links the object with a GSPA-assembled entry and the existing dwt
|
||||
loader runs the image. Native xshow and the six-TU termtris match the asm
|
||||
markers; the native and asm linked `.text` decode to the same instruction
|
||||
streams (753 xshow and 6,356 termtris instructions), and the native termtris
|
||||
links `.text` 166,608 bits (20,826 bytes), `.data` 9,536 bits (1,192 bytes)
|
||||
and `.bss` 121,808 bits with loader status 0. `scripts/m4/run.sh` gains
|
||||
`M4_NATIVE=1` (the default is unchanged), `scripts/m5/native.sh` is the
|
||||
compile/link driver, and
|
||||
`scripts/tests/m5/native.sh` is the gate. `scripts/tests/m5/object-smoke.sh`
|
||||
proves the native and GSPA `.text` are byte-identical for the frame/short- and
|
||||
long-`MOVI` cases; `scripts/tests/m5/native-link.sh` proves the `.data`/`.bss`
|
||||
relocations, branch loop, same-section calls and a section-0 external call
|
||||
(`value=42 combo=152 bump1=1 bump2=2 loop=10 ext=77`), then `dis34`s the linked
|
||||
address.
|
||||
|
||||
Limits: `llvm-mc -filetype=obj` uses LLVM's generic ELF writer, not TI COFF;
|
||||
only `llc -filetype=obj` produces the native object. Public symbols longer than
|
||||
GSPA's 31 characters are still truncated.
|
||||
|
||||
### MC encoder, assembler and disassembler
|
||||
|
||||
`TMS34010MCCodeEmitter` covers the supported selection table with the GSPA
|
||||
short/long immediate choice, one's-complement `CMPI`/`SUBI`/`ANDI` forms, the
|
||||
SRA/SRL `(-K) & 31` encodings, the cross-file `MOVE` bit and 32-bit absolute
|
||||
fields. `TMS34010AsmParser` parses the GSPA grammar (A/B registers, `SP`/`b15`,
|
||||
`*reg`, `*reg+`, `-*reg`, `*reg(disp)`, `@addr`, immediates, symbol
|
||||
expressions, MMTM/MMFM lists, `JRUC` and the `JAC`/`JANC`/`JRC`/`JRNC`
|
||||
aliases) and rejects mixed register-file operands for encodings that share bit
|
||||
4. `TMS34010Disassembler` decodes the inverse table, including the shared
|
||||
`CLR`/`XOR` encoding, short immediates, register-file repair and register
|
||||
lists. `TMS34010InstPrinter` prints `A15` as `SP`.
|
||||
|
||||
The expected bytes in `llvm/test/MC/TMS34010/` come from GSPA 2.00 listings
|
||||
and the 1987 `dis34`, not from the new encoder: `encodings.s` pins 126
|
||||
`-show-encoding` instructions, `disassembler.s` 77 inverse checks, and
|
||||
`roundtrip.s` 88 instructions round-trip through `dis34`. The MC table is
|
||||
separate from the `isCodeGenOnly` selection definitions, and moving this layer
|
||||
to object output also fixed the pre-existing selection `CMPI`/`ANDI` long
|
||||
forms, which had dropped their `!xor` field assignment in the object path. The
|
||||
10-byte `MOVE @a,@b`/`MOVB @a,@b` forms are deliberately unmodeled (the shared
|
||||
`bits<48> Inst` cannot hold 80 bits); they fail loudly rather than decode
|
||||
wrongly.
|
||||
|
||||
### `JR` and branch relaxation
|
||||
|
||||
Selection still emits the three-word absolute `JAUC`/`JA<cc>` forms. The
|
||||
pre-emit `TMS34010BranchRelaxation` pass then switches every local branch to
|
||||
the one-word `JR`/`JR<cc>` form, lays the function out with real instruction
|
||||
sizes, and grows a branch back to `JA*` when its signed 8-bit word displacement
|
||||
is out of range, zero, odd, or its target is unknown. Growing only pushes a
|
||||
branch further out, so the loop is monotone.
|
||||
|
||||
`JR` is `1100 cccc dddddddd`: `target = pc + 16 + disp*16`, with `disp` in
|
||||
`[-127,-1] ∪ [1,127]` (zero selects the 16-bit form and `0x80` marks `JA`).
|
||||
The MC backend independently handles a symbolic `JR` through the `FK_JR8`
|
||||
fixup: `mayNeedRelaxation`/`fixupNeedsRelaxationAdvanced` widen an unencodable
|
||||
displacement to the `JA*` counterpart. Relative to the M4 938-byte M3 image, 14
|
||||
relaxed branches save 56 bytes and the MMTM/MMFM change saves another 8,
|
||||
giving 874 (the current all-`JA` baseline is 930).
|
||||
`m5-jr.ll` pins a short loop (`JRLT`/`JRUC`, no `JALT`) and a 217-word far
|
||||
branch that stays `JAEQ`; `scripts/tests/m5/jr.sh` checks the M3 loop, the
|
||||
`llvm-mc`/object byte equality and the MC relaxation.
|
||||
|
||||
### `MMTM`/`MMFM` register-list saves
|
||||
|
||||
The prologue emits `PUSH32 A13` then one `MMTM SP, <list>` for the callee-saved
|
||||
set; the epilogue emits one `MMFM SP, <list>` then `POP32 A13`. `MMTM` numbers
|
||||
A0 at bit 15 (mask `0xd400` for `A0,A1,A3,A5`), `MMFM` numbers A0 at bit 0
|
||||
(mask `0x002b`). `MMTM` predecrements SP per selected register walking
|
||||
A0→A15, so the low→high slot order is `A5,A3,A1,A0`, exactly the old ascending
|
||||
per-register push order; `MMFM` loads back from the same address upward. The
|
||||
old-STK load stays `MOVE *SP((1 + CSI + 1)*32), A14, 1` and `RETS 2` is
|
||||
unchanged.
|
||||
|
||||
For n saved registers the old sequence cost 4n bytes (two-byte push/pop pairs)
|
||||
and the new pair costs 8: n=1 grows by 4, n=2 breaks even, n=3 saves 4 and n=4
|
||||
saves 8. `m5-mmtm.ll` (4+3 registers) went 184→172 bytes and the unchanged M3
|
||||
image 882→874. `scripts/tests/m5/mmtm.sh` checks the list, the mask, the
|
||||
absent per-register saves, the old-STK offset, `RETS 2`, native/asm byte
|
||||
equality and the M2/M3 MAME markers.
|
||||
|
||||
### `alloca`, `byval`, narrow returns and memory intrinsics
|
||||
|
||||
- `ISD::DYNAMIC_STACKALLOC` is custom on `i32`; the program stack grows upward
|
||||
and `A14` holds a **bit** address, so the requested size is shifted by 3 and
|
||||
the alignment is rounded up in bits. `StackPointerRegisterToSaveRestore` is
|
||||
A14, `STACKSAVE`/`STACKRESTORE` are expanded, and a no-argument function with
|
||||
a VLA rebuilds A14 from FP (`MOVE A13, A14` + `ADDI frame_bits, A14`) in the
|
||||
epilogue; an argument function keeps the caller's old-STK restore.
|
||||
- A `byval` argument is copied to the current A14 with an always-inline
|
||||
memcpy, A14 advances by `alignTo(size,2)` **bytes** (×8 bits), and the callee
|
||||
receives the copy's address. Fixed objects are laid out by the caller's push
|
||||
order: argument 0 is directly below FP, argument i starts at the sum of the
|
||||
preceding slot sizes (4 bytes for a scalar, `alignTo(size,2)` for a byval).
|
||||
- Returns accept void, pointer and any integer ≤32 bits. `zeroext i8` uses
|
||||
`MOVB`/`ANDI 255`, `signext i16` uses the FS0 `MOVE ...,0` (sign-extending),
|
||||
and the caller applies the IR's truncation/extension. `i64`, float/double
|
||||
and aggregate returns are still rejected.
|
||||
- `findOptimalMemOpLowering` returns false so LLVM's byte-offset memory-op
|
||||
expansion is not used on bit-address pointers.
|
||||
`TMS34010SelectionDAGInfo` lowers a constant `memcpy`/`memset` to
|
||||
word/byte copies with bit-scaled offsets. A target IR pass expands every
|
||||
remaining `memmove` (including constant sizes <= 64 bytes, which PreISel
|
||||
leaves alone) into the overlap-safe forward/backward loops, and the scaling
|
||||
pass shifts the i8 GEPs in `dynamic-/static-memcpy-expansion-`,
|
||||
`dynamic-/static-memset-expansion-` and `memmove_*` loops by 3. No
|
||||
`CALLA _memcpy`/`_memset`/`_memmove` is emitted. This fixed a silent 8×
|
||||
miscompile in constant copies.
|
||||
|
||||
`scripts/tests/m5/robust.sh` runs `robust.c` at `-O0` in both paths and
|
||||
requires `alloca=408 byval=64 narrow=1056 mem=438 alloca_use=1496 byval6=64 memmove=2148`; the four
|
||||
M5 lit tests are `m5-alloca`, `m5-byval`, `m5-narrow-ret` and `m5-memintrin`.
|
||||
|
||||
### Driver policy and the unsupported-IR boundary
|
||||
|
||||
`-switch-to-lookup=false` is removed. `tms-bitaddr --normalize` now recognizes
|
||||
SimplifyCFG's `switch.table.<function>` base (after stripping pointer casts)
|
||||
and uses shift 3 for its byte/element index; `scale_constant_gep` uses the same
|
||||
per-base shift and the normalize check accepts the typed `[N x i32]` table.
|
||||
Post-fix code emits `SLL 3` before the index add, and the M4 switch/termtris
|
||||
lookups are correct under a plain `tms-clang -O2`.
|
||||
|
||||
`-replexitval=never` remains a documented `tms-clang` TMS34010 policy until
|
||||
i64/M6. `TMS34010RejectUnsupportedIR` (a pre-ISel function pass) turns the raw
|
||||
`opt`-without-policy shapes into clear `LLVM ERROR` diagnostics instead of
|
||||
`Cannot select`/assertions:
|
||||
|
||||
- an exactly-33-bit `mul` (IndVarSimplify's closed-form exit value) names
|
||||
`-replexitval=never`;
|
||||
- a `mul` wider than 33 bits is the i64/M6 path and says so;
|
||||
- a local (`internal`/`private`) variadic definition at a non-`-O0` llc level
|
||||
is rejected; `tms-clang -O2` rejects it before opt too. External definitions
|
||||
such as `sprintf` are unaffected, and `-O0` keeps the M4 varargs gate.
|
||||
|
||||
`m5-policy.ll`, `m5-policy-i64.ll` and `m5-reject-static-vararg.ll` pin the
|
||||
diagnostics; `scripts/tests/m5/policy.sh` checks the default `-O2` switch
|
||||
table, the driver trace, the raw i33 failure, the static-vararg reject and the
|
||||
external `sprintf` acceptance.
|
||||
|
||||
### Private-label length cap
|
||||
|
||||
GSPA keeps only 31 identifier characters. `appendModuleToken()` reserves the
|
||||
9-character `_<8 hex>` module token, caps the sanitized base at 22 characters,
|
||||
and if the base is longer keeps its first 13 characters and folds the full
|
||||
name into another 8-hex token (`13 + '_' + 8 = 22`), then appends the module
|
||||
token last for exactly 31. Two TUs with the same long static name now link.
|
||||
`m5-private-long.ll` and the two-TU `scripts/tests/m5/private-long.sh` pin the
|
||||
capped labels and distinct module tokens. Public long names are still
|
||||
truncated by GSPA.
|
||||
|
||||
### Correctness fixes exposed by the tier
|
||||
|
||||
- **FS0 sign extension.** `SETF16` is now `SETF 16, 1, 0` (`0x0570`): the
|
||||
second SETF operand is the field-extension bit (`fe`), and `fe=0`
|
||||
zero-extends. Several c-torture tests (`20030128-1`, `920721-1`,
|
||||
`20000412-1`, `20020911-1`) failed until the short load sign-extended.
|
||||
`m5-sext-i16.ll` pins it; the zero-extending lowering still masks with
|
||||
`ANDI 255/65535`, so it remains correct.
|
||||
- **Compare/branch adjacency.** A register-to-register `MOVE` writes the
|
||||
status flags; a PHI copy on a loop back edge could land between `CMP` and
|
||||
`Jcc` (`20000422-1` timed out at `-O2`). Selection now emits the fused
|
||||
`CMPBR`/`CMPIBR` terminator, and `TMS34010ExpandCmpBr` (post-RA, before PEI
|
||||
and block placement) splits it into adjacent `CMP`/`CMPI` + `JA<cc>`.
|
||||
`isConditionalBranch`/`analyzeBranch`/remove/insert recognize the fused
|
||||
forms, so `-verify-machineinstrs` is clean. `m5-cmpbr-adjacent.ll` forces
|
||||
PHI copies and requires every compare to be immediately followed by its
|
||||
branch.
|
||||
- **`gc.statepoint`.** `StatepointLowering` asserted because this target does
|
||||
not build `CALLSEQ_START`/`CALLSEQ_END`. A GC strategy containing
|
||||
`llvm.experimental.gc.statepoint` and any call with operand bundles now get
|
||||
target diagnostics (`m5-reject-statepoint.ll`). Stackmaps/statepoints remain
|
||||
out of scope; the shadow-stack tests still pass.
|
||||
- **Object data and shift encoding.** Object-mode `.bss` is word-granular
|
||||
(the loader rejected termtris's 121,800-bit section; the native `.bss` is
|
||||
now 121,808 bits, identical to asm), odd `.data` is padded to a word
|
||||
(`m5-data-pad.ll`), and the object `SRAK`/`SRLK` forms encode `(-K) & 31`
|
||||
(`0x2f65`, not `0x2c05`) even though TableGen drops the negated field for
|
||||
the CodeGen definitions.
|
||||
|
||||
### Conformance tier
|
||||
|
||||
`scripts/tests/m5/conformance/run.sh` is the single compile/link/MAME/compare
|
||||
runner:
|
||||
|
||||
```
|
||||
SOURCE.c + shim.c + entry.asm
|
||||
-> tms-clang -> llc -filetype=asm or -filetype=obj
|
||||
-> GSPA/GSPLNK (native TI-COFF first; asm fallback only in exploratory mode;
|
||||
c-testsuite forces native and fails on fallback)
|
||||
-> pinned dwt loader in MAME
|
||||
-> status/output/magic block at 01B00000/01B00020/01B00040/01B10000
|
||||
-> compare with --expect/--expect-status/--expect-output-file
|
||||
```
|
||||
|
||||
Exit codes are 0 pass, 1 failure, 2 skip. `entry.asm` calls `main(1, argv)`,
|
||||
stores the A8 status, the output length and the `0x434F4E46` ("CONF") magic,
|
||||
and returns. `shim.c` implements `printf`/`putchar`/`puts` with the M4
|
||||
contiguous `&fmt` walk (no `va_list`), `exit`/`_exit`/`abort`, a 16 KiB output
|
||||
buffer and a small freestanding string/integer surface. `abort()` publishes
|
||||
status 134. `classify.sh` scans for floats, `long long`/`__int128`, bitfields,
|
||||
`va_list`/`va_start`/`va_arg`, `setjmp`/`longjmp`, signals, wide
|
||||
characters, float/long-long format specifiers and file I/O, and prints
|
||||
`feature: reason`; `manifest.tsv` documents the
|
||||
`test_id<TAB>suite<TAB>expected<TAB>features<TAB>skip_reason` row. A skip is
|
||||
reported, never counted as a pass. A missing completion magic is a failure.
|
||||
|
||||
Pins (`scripts/fetch-conformance.sh`, `vendor/conformance/PINS`):
|
||||
|
||||
- GCC 16.2.0 `gcc.c-torture/execute`,
|
||||
`https://ftp.gnu.org/gnu/gcc/gcc-16.2.0/gcc-16.2.0.tar.xz`, SHA-256
|
||||
`e6738e29597f733270731aa90600f37ffdc045079dfc27ec7e8192cc81085c3e`
|
||||
(1,692 `execute` tests);
|
||||
- c-testsuite `https://github.com/c-testsuite/c-testsuite` commit
|
||||
`5c7275656d751de0e68b2d340a95b5681858ed07` (220 `single-exec` tests).
|
||||
|
||||
Both corpora are materialized under the ignored `vendor/conformance` and are
|
||||
test-only; nothing is distributed (GCC is GPL and c-testsuite keeps its own
|
||||
per-test licenses). The GCC version is only the c-torture corpus source; the
|
||||
compiler under test is `tms-clang`. The three tiers:
|
||||
|
||||
- **LLVM `CodeGen/Generic`** (`scripts/tests/m5/generic.sh`) is compile-only:
|
||||
a 24-file curated allowlist must compile to non-empty asm, and the recursive
|
||||
sweep covers 197 `.ll` files (107 pass / 90 unsupported / 0 crash). Every
|
||||
unsupported row has a reason in `generic-expected-fail.txt`; an assertion,
|
||||
timeout, `SIGKILL` or any non-134 signal/undiagnosed failure is a crash and
|
||||
fails the gate. The sweep exposed the `gc.statepoint` assertion fixed above.
|
||||
`2010-11-04-BigByval.ll` needs a 900-second allowance (about 5m43s) and
|
||||
dominates the ~6–7 minute gate.
|
||||
- **GCC c-torture** (`scripts/tests/m5/c-torture.sh`) runs the curated 59-test
|
||||
integer/control/array/pointer list at `-O0` and `-O2`, requires `main` 0 and
|
||||
the completion magic, and ignores stdout. The gate result is
|
||||
`pass=59 skip=0 total=59` for each pass and takes about 16–17 minutes.
|
||||
`--scan` classifies the whole 1,692-test corpus (1,112 supported / 580
|
||||
skipped:
|
||||
212 long-long, 180 float, 112 bitfield, 36 varargs, 14 wide-char,
|
||||
11 file-io, 5 float-format, 3 setjmp, 2 signal, 2 int128, 2
|
||||
format-unsupported, 1 long-long-format). The MAME injection frames are
|
||||
300/600; the loader readback and magic remain required.
|
||||
- **c-testsuite** (`scripts/tests/m5/c-testsuite.sh`) runs 29 curated
|
||||
`single-exec` tests (17 silent status-0 and 12 formatted-output tests) and
|
||||
compares stdout byte-for-byte with `.c.expected`. The gate result is
|
||||
`pass=29 skip=191 total=220` and takes about 4–5 minutes. The skip file
|
||||
accounts for every other test: 159 `unselected` (outside the curated subset,
|
||||
not claimed to fail), 10 long-long, 9 float, 4 wide-char, 2 `runtime`
|
||||
(`00205`/`00216`: the M2 `.data` initializer gap), 2 libc (`calloc`,
|
||||
`sprintf`), 2 file-io, 2 bitfield, 1 long-long-format. The gate re-runs the
|
||||
classifier and fails on a stale reason; a listed classifier-rejected test is
|
||||
a failure, and an all-skip run fails.
|
||||
|
||||
The conformance shim captures stdout only; stderr-writing tests are skipped,
|
||||
not passed. That and the full suites are M6.
|
||||
|
||||
### M5 tests and evidence
|
||||
|
||||
The lit suite is 76 CodeGen tests (35 `m2-*`, 8 `m3-*`, 18 `m4-*`, 15
|
||||
`m5-*`) plus 4 MC tests. The M5 CodeGen tests are `m5-object-smoke`,
|
||||
`m5-jr`, `m5-mmtm`, `m5-alloca`, `m5-byval`, `m5-narrow-ret`, `m5-memintrin`,
|
||||
`m5-policy`, `m5-policy-i64`, `m5-reject-static-vararg`, `m5-private-long`,
|
||||
`m5-reject-statepoint`, `m5-sext-i16`, `m5-cmpbr-adjacent` and `m5-data-pad`.
|
||||
The MC tests are `encodings.s`, `disassembler.s`, `roundtrip.s` and
|
||||
`jr-range.s`.
|
||||
|
||||
The M5 project gates are `object-smoke.sh`, `native-link.sh`,
|
||||
`mc-roundtrip.sh`, `native.sh`, `jr.sh`, `mmtm.sh`, `robust.sh`, `policy.sh`,
|
||||
`private-long.sh`, `conformance/selftest.sh`, `generic.sh`, `c-torture.sh`
|
||||
and `c-testsuite.sh`, plus the unchanged M4/M3/M2 gates and
|
||||
`tools/tms-bitaddr/tests/run.sh`.
|
||||
|
||||
## M5 non-goals and the M6 boundary
|
||||
|
||||
M5 deliberately stops before:
|
||||
|
||||
- **Floats, `long long` and i64:** no float/double or `i64` arguments,
|
||||
returns, arithmetic or branches; the i33/`-replexitval` workaround stays
|
||||
until M6 removes it.
|
||||
- **General varargs:** no `va_list`, `va_start` or `va_arg`; only the
|
||||
contiguous `&last_fixed` pattern above. Non-contiguous/register varargs are
|
||||
M5.
|
||||
- **Bitfields and aggregates by value:** no structs/unions by value, `sret`,
|
||||
`byval`, bitfields, or aggregate returns. Struct *fields* through pointers
|
||||
are supported.
|
||||
- **Narrow returns:** `i8`/`i16` returns remain rejected; fixed `i8`/`i16`
|
||||
arguments are supported.
|
||||
- **Xlib beyond the tested calls:** the client uses the validated image,
|
||||
event, GC, window and error-handler entries. It does not implement the
|
||||
whole Xlib surface, `XGetImage`, or `XDestroyImage` on a non-heap image.
|
||||
- **`JR`/relaxation and deeper libc/runtime coverage:** M5 owns the
|
||||
code-size pass and broader `RTS.LIB` integration.
|
||||
contiguous `&last_fixed` pattern, which the conformance shim also uses.
|
||||
- **Bitfields and aggregates:** bitfields, `sret`, aggregate returns and
|
||||
general aggregate arguments remain unsupported; `byval` arguments and struct
|
||||
fields through pointers are supported.
|
||||
- **Debug info, the wider Xlib surface, stderr and the full suites:** no debug
|
||||
info; the client uses the validated Xlib entries; the conformance harness
|
||||
captures stdout only and runs curated subsets, not the full GCC/LLVM suites.
|
||||
- **Deeper libc/runtime coverage:** the conformance shim is freestanding; no
|
||||
new `RTS.LIB` entries.
|
||||
- **Native `.cinit` and public long names:** the object writer accepts only
|
||||
`.text`/`.data`/`.bss`, and public identifiers over 31 characters are
|
||||
truncated by GSPA.
|
||||
|
||||
The pinned termtris, GCC and c-testsuite trees are test-only and are not part
|
||||
of the compiler or project distribution (termtris is GPLv3; GCC is GPL;
|
||||
c-testsuite keeps its own per-test licenses).
|
||||
|
||||
## Build
|
||||
|
||||
@@ -577,15 +899,17 @@ cmake -S llvm -B build -G Ninja \
|
||||
-DLLVM_TARGETS_TO_BUILD="X86;TMS34010" \
|
||||
-DLLVM_INCLUDE_TESTS=ON -DLLVM_INCLUDE_EXAMPLES=OFF \
|
||||
-DLLVM_INCLUDE_BENCHMARKS=OFF
|
||||
ninja -C build llc clang opt llvm-config FileCheck count not
|
||||
ninja -C build llc llvm-mc clang opt llvm-config FileCheck count not
|
||||
```
|
||||
|
||||
`build/bin/llvm-lit` is generated by CMake configure. Run the full M2+M3+M4
|
||||
backend suite:
|
||||
`build/bin/llvm-lit` is generated by CMake configure. Run the full M2–M5
|
||||
backend and MC suite:
|
||||
|
||||
```sh
|
||||
build/bin/llvm-lit -v build/test/CodeGen/TMS34010 --filter='(m2|m3|m4)-'
|
||||
# -> 58 passed (32 m2-*, 8 m3-*, 18 m4-*)
|
||||
build/bin/llvm-lit -v build/test/CodeGen/TMS34010 --filter='(m2|m3|m4|m5)-'
|
||||
# -> 76 passed (35 m2-*, 8 m3-*, 18 m4-*, 15 m5-*)
|
||||
build/bin/llvm-lit -v build/test/MC/TMS34010
|
||||
# -> 4 passed (encodings, disassembler, round-trip, JR range)
|
||||
```
|
||||
|
||||
The M2 tests are `llvm/test/CodeGen/TMS34010/m2-*.ll`:
|
||||
@@ -612,7 +936,7 @@ The M3 tests are:
|
||||
(`JAUC`) and `RETS 2`.
|
||||
- `m3-if`: `CMP` + `JAGT` and `RETS 2`.
|
||||
- `m3-cmpi`: the constant-right compare becomes `CMPI` + `JALT`, at `-O0` and
|
||||
`-O2`.
|
||||
`-O2`; M5 pins the exact MOVI order with `CHECK`/`CHECK-NEXT`.
|
||||
- `m3-setcc`: the `SELECT_CC` 1/0 diamond (`CMP`/`JALT`/`MOVI 0`/`MOVI 1`).
|
||||
- `m3-unsigned`: `JALO`/`JAHI` for the unsigned conditions.
|
||||
- `m3-ops`: `SUB`/`AND`/`OR`/`XOR`/`SLL`/`SRL`/`SRA`, plus `NEG`/`NOT`/`ANDI`
|
||||
@@ -634,13 +958,27 @@ The M4 tests are:
|
||||
|
||||
The VT1200 project holds the harness, linker command file, COFF loader and
|
||||
MAME checks: <https://git.wut.ee/opencode.siil.arti.ee/tms-c-compiler>. From a
|
||||
project checkout that has this fork at `build/llvm-clean` (branch `m4`):
|
||||
project checkout that has this fork at `build/llvm-clean` (branch `m5`):
|
||||
|
||||
```sh
|
||||
LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=m4 ./scripts/build-llvm.sh
|
||||
ninja -C build/llvm-clean-build llc opt clang llvm-config FileCheck count not
|
||||
LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=m5 ./scripts/build-llvm.sh
|
||||
ninja -C build/llvm-clean-build llc llvm-mc opt clang llvm-config FileCheck count not
|
||||
build/llvm-clean-build/bin/llvm-lit -v build/llvm-clean/llvm/test/CodeGen/TMS34010 \
|
||||
--filter='(m2|m3|m4)-'
|
||||
--filter='(m2|m3|m4|m5)-'
|
||||
build/llvm-clean-build/bin/llvm-lit -v build/llvm-clean/llvm/test/MC/TMS34010
|
||||
scripts/tests/m5/object-smoke.sh # native .text == GSPA .text
|
||||
scripts/tests/m5/native-link.sh # GSPLNK link and loader/MAME
|
||||
scripts/tests/m5/mc-roundtrip.sh # 88 instructions through dis34
|
||||
scripts/tests/m5/native.sh # native xshow + termtris
|
||||
scripts/tests/m5/jr.sh # JR/relaxation and M3 MAME
|
||||
scripts/tests/m5/mmtm.sh # MMTM/MMFM and M2/M3 MAME
|
||||
scripts/tests/m5/robust.sh # alloca/byval/narrow/mem
|
||||
scripts/tests/m5/policy.sh
|
||||
scripts/tests/m5/private-long.sh
|
||||
scripts/tests/m5/conformance/selftest.sh
|
||||
scripts/tests/m5/generic.sh
|
||||
scripts/tests/m5/c-torture.sh
|
||||
scripts/tests/m5/c-testsuite.sh
|
||||
scripts/tests/m4/run.sh # XImage/event client in MAME
|
||||
scripts/tests/m4/dis-roundtrip.sh # linked .text decoded with dis34
|
||||
scripts/tests/m4/layout.sh # XImage/XEvent/error-event offsets
|
||||
@@ -655,17 +993,25 @@ scripts/tests/m4/varargs.sh
|
||||
tools/tms-bitaddr/tests/run.sh # GEP scaling/normalization
|
||||
scripts/tests/m3/run.sh # M3 regression
|
||||
scripts/tests/m3/dis-roundtrip.sh
|
||||
scripts/tests/m3/layout.sh
|
||||
scripts/tests/m3/diff/check.sh
|
||||
scripts/tests/m2/run.sh # M2 regression
|
||||
scripts/tests/m2/dis-roundtrip.sh
|
||||
scripts/tests/m2/diff/check.sh
|
||||
```
|
||||
|
||||
Expected: `m4 xshow: PASS`, `M4 dis34 round-trip: PASS`, `layout.sh: PASS`,
|
||||
`symbols.sh: PASS`, `m4 termtris: PASS`, `M4 diff: PASS`, the unsigned /
|
||||
private / mul / fnptr / varargs PASS lines, `tms-bitaddr: PASS`, and the M3/M2
|
||||
regression PASS lines. The MAME gates pin `VT1200_MONITOR=VR319`; the image
|
||||
gate requires the checker pixels, the real LK401 `q` (keycode 193, keysym
|
||||
`0x71`), focus `0x400001`, zero X errors and a loader status of 0. The
|
||||
differential check uses stored golden assembly from the 1987 TI compiler and
|
||||
does not run DOSBox; the MAME/dis34 gates do. The pinned termtris tree is
|
||||
Expected: `M5 object smoke: PASS`, `M5 native-link: PASS`, `M5 MC round-trip:
|
||||
PASS`, `M5 native: PASS`, `M5 JR: PASS`, `M5 MMTM: PASS`, `M5 robust: PASS`,
|
||||
`m5 policy: PASS`, `m5 private-long: PASS`, `conformance selftest: PASS`, the
|
||||
Generic/c-torture/c-testsuite PASS lines with their skip reasons, `m4 xshow:
|
||||
PASS`, `M4 dis34 round-trip: PASS`, `layout.sh: PASS`, `symbols.sh: PASS`,
|
||||
`m4 termtris: PASS`, `M4 diff: PASS`, the unsigned / private / mul / fnptr /
|
||||
varargs PASS lines, `tms-bitaddr: PASS`, and the M3/M2 regression PASS lines.
|
||||
MAME and conformance gates must run serially because `M4_SDK` is shared. The
|
||||
MAME gates pin `VT1200_MONITOR=VR319`; the image gate requires the checker
|
||||
pixels, the real LK401 `q` (keycode 193, keysym `0x71`), focus `0x400001`,
|
||||
zero X errors and a loader status of 0. The differential check uses stored
|
||||
golden assembly from the 1987 TI compiler and does not run DOSBox; the
|
||||
MAME/dis34 gates do. The pinned termtris, GCC and c-testsuite trees are
|
||||
test-only. The project README lists the prerequisites (DOSBox, mtools, the
|
||||
VT1200 SDK image, MAME).
|
||||
VT1200 SDK image, MAME and `vendor/conformance`).
|
||||
@@ -0,0 +1,17 @@
|
||||
add_llvm_component_library(LLVMTMS34010AsmParser
|
||||
../MCTargetDesc/TMS34010AsmParser.cpp
|
||||
|
||||
LINK_COMPONENTS
|
||||
CodeGenTypes
|
||||
MC
|
||||
MCParser
|
||||
TMS34010Desc
|
||||
TMS34010Info
|
||||
Support
|
||||
|
||||
DEPENDS
|
||||
TMS34010CommonTableGen
|
||||
|
||||
ADD_TO_COMPONENT
|
||||
TMS34010
|
||||
)
|
||||
@@ -2,9 +2,12 @@ add_llvm_component_group(TMS34010)
|
||||
|
||||
set(LLVM_TARGET_DEFINITIONS TMS34010.td)
|
||||
|
||||
tablegen(LLVM TMS34010GenAsmMatcher.inc -gen-asm-matcher)
|
||||
tablegen(LLVM TMS34010GenAsmWriter.inc -gen-asm-writer)
|
||||
tablegen(LLVM TMS34010GenDAGISel.inc -gen-dag-isel)
|
||||
tablegen(LLVM TMS34010GenDisassemblerTables.inc -gen-disassembler)
|
||||
tablegen(LLVM TMS34010GenInstrInfo.inc -gen-instr-info)
|
||||
tablegen(LLVM TMS34010GenMCCodeEmitter.inc -gen-emitter)
|
||||
tablegen(LLVM TMS34010GenRegisterInfo.inc -gen-register-info)
|
||||
tablegen(LLVM TMS34010GenSDNodeInfo.inc -gen-sd-node-info)
|
||||
tablegen(LLVM TMS34010GenSubtargetInfo.inc -gen-subtarget)
|
||||
@@ -46,3 +49,5 @@ add_llvm_target(TMS34010CodeGen
|
||||
|
||||
add_subdirectory(MCTargetDesc)
|
||||
add_subdirectory(TargetInfo)
|
||||
add_subdirectory(AsmParser)
|
||||
add_subdirectory(Disassembler)
|
||||
@@ -0,0 +1,15 @@
|
||||
add_llvm_component_library(LLVMTMS34010Disassembler
|
||||
../MCTargetDesc/TMS34010Disassembler.cpp
|
||||
|
||||
LINK_COMPONENTS
|
||||
MCDisassembler
|
||||
MC
|
||||
TMS34010Info
|
||||
Support
|
||||
|
||||
DEPENDS
|
||||
TMS34010CommonTableGen
|
||||
|
||||
ADD_TO_COMPONENT
|
||||
TMS34010
|
||||
)
|
||||
@@ -1,6 +1,18 @@
|
||||
# The assembler and disassembler sources live here next to the MC descriptions
|
||||
# but are compiled into the standard AsmParser/Disassembler component libraries
|
||||
# (see ../AsmParser and ../Disassembler), so they are intentionally not part of
|
||||
# this library.
|
||||
set(LLVM_OPTIONAL_SOURCES
|
||||
TMS34010AsmParser.cpp
|
||||
TMS34010Disassembler.cpp
|
||||
)
|
||||
|
||||
add_llvm_component_library(LLVMTMS34010Desc
|
||||
TMS34010InstPrinter.cpp
|
||||
TMS34010MCAsmBackend.cpp
|
||||
TMS34010MCAsmInfo.cpp
|
||||
TMS34010MCCodeEmitter.cpp
|
||||
TMS34010MCObjectWriter.cpp
|
||||
TMS34010MCTargetDesc.cpp
|
||||
|
||||
LINK_COMPONENTS
|
||||
|
||||
@@ -0,0 +1,507 @@
|
||||
//===-- TMS34010AsmParser.cpp - Parse TMS34010 assembly ---------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// GSPA 2.00 syntax is the source language. The parser intentionally emits a
|
||||
// flat token/register/immediate stream and lets TableGen's asm matcher match
|
||||
// the literal *, -, +, @ and parenthesis tokens that appear in the MC asm
|
||||
// strings. The only instruction with a non-regular grammar is MMTM/MMFM,
|
||||
// whose GSPA operand is a comma-separated register list that must be packed
|
||||
// into the 16-bit mask operand.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "TMS34010AsmParser.h"
|
||||
#include "TMS34010MCTargetDesc.h"
|
||||
#include "TargetInfo/TMS34010TargetInfo.h"
|
||||
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/MC/MCContext.h"
|
||||
#include "llvm/MC/MCExpr.h"
|
||||
#include "llvm/MC/MCInst.h"
|
||||
#include "llvm/MC/MCInstrInfo.h"
|
||||
#include "llvm/MC/MCParser/AsmLexer.h"
|
||||
#include "llvm/MC/MCParser/MCParsedAsmOperand.h"
|
||||
#include "llvm/MC/MCParser/MCTargetAsmParser.h"
|
||||
#include "llvm/MC/MCRegisterInfo.h"
|
||||
#include "llvm/MC/MCStreamer.h"
|
||||
#include "llvm/MC/MCSubtargetInfo.h"
|
||||
#include "llvm/MC/MCSymbol.h"
|
||||
#include "llvm/MC/TargetRegistry.h"
|
||||
#include "llvm/Support/Casting.h"
|
||||
#include "llvm/Support/Compiler.h"
|
||||
#include "llvm/Support/SMLoc.h"
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
namespace {
|
||||
|
||||
/// One parsed TMS34010 operand. The parser deliberately keeps punctuation as
|
||||
/// token operands so that a single Matcher table can describe all of GSPA's
|
||||
/// indirect forms.
|
||||
class TMS34010Operand : public MCParsedAsmOperand {
|
||||
enum KindTy { k_Token, k_Reg, k_Imm } Kind;
|
||||
|
||||
StringRef Token;
|
||||
MCRegister Reg = TMS34010::NoRegister;
|
||||
const MCExpr *Imm = nullptr;
|
||||
SMLoc Start, End;
|
||||
|
||||
public:
|
||||
TMS34010Operand(StringRef Tok, SMLoc S)
|
||||
: Kind(k_Token), Token(Tok), Start(S), End(S) {}
|
||||
TMS34010Operand(MCRegister R, SMLoc S, SMLoc E)
|
||||
: Kind(k_Reg), Reg(R), Start(S), End(E) {}
|
||||
TMS34010Operand(const MCExpr *V, SMLoc S, SMLoc E)
|
||||
: Kind(k_Imm), Imm(V), Start(S), End(E) {}
|
||||
|
||||
bool isToken() const override { return Kind == k_Token; }
|
||||
bool isReg() const override { return Kind == k_Reg; }
|
||||
bool isImm() const override { return Kind == k_Imm; }
|
||||
bool isMem() const override { return false; }
|
||||
|
||||
StringRef getToken() const {
|
||||
assert(Kind == k_Token && "not a token");
|
||||
return Token;
|
||||
}
|
||||
MCRegister getReg() const override {
|
||||
assert(Kind == k_Reg && "not a register");
|
||||
return Reg;
|
||||
}
|
||||
const MCExpr *getImm() const {
|
||||
assert(Kind == k_Imm && "not an immediate");
|
||||
return Imm;
|
||||
}
|
||||
|
||||
SMLoc getStartLoc() const override { return Start; }
|
||||
SMLoc getEndLoc() const override { return End; }
|
||||
|
||||
void addRegOperands(MCInst &Inst, unsigned N) const {
|
||||
assert(Kind == k_Reg && N == 1 && "invalid register operand");
|
||||
Inst.addOperand(MCOperand::createReg(Reg));
|
||||
}
|
||||
|
||||
void addImmOperands(MCInst &Inst, unsigned N) const {
|
||||
assert(Kind == k_Imm && N == 1 && "invalid immediate operand");
|
||||
int64_t Value;
|
||||
if (Imm->evaluateAsAbsolute(Value))
|
||||
Inst.addOperand(MCOperand::createImm(Value));
|
||||
else
|
||||
Inst.addOperand(MCOperand::createExpr(Imm));
|
||||
}
|
||||
|
||||
void print(raw_ostream &O, const MCAsmInfo &MAI) const override {
|
||||
switch (Kind) {
|
||||
case k_Token:
|
||||
O << "Token " << Token;
|
||||
break;
|
||||
case k_Reg:
|
||||
O << "Reg " << Reg.id();
|
||||
break;
|
||||
case k_Imm:
|
||||
O << "Imm ";
|
||||
MAI.printExpr(O, *Imm);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static std::unique_ptr<TMS34010Operand> CreateToken(StringRef Str, SMLoc S) {
|
||||
return std::make_unique<TMS34010Operand>(Str, S);
|
||||
}
|
||||
static std::unique_ptr<TMS34010Operand> CreateReg(MCRegister R, SMLoc S,
|
||||
SMLoc E) {
|
||||
return std::make_unique<TMS34010Operand>(R, S, E);
|
||||
}
|
||||
static std::unique_ptr<TMS34010Operand> CreateImm(const MCExpr *V, SMLoc S,
|
||||
SMLoc E) {
|
||||
return std::make_unique<TMS34010Operand>(V, S, E);
|
||||
}
|
||||
};
|
||||
|
||||
class TMS34010AsmParser : public MCTargetAsmParser {
|
||||
MCAsmParser &Parser;
|
||||
const MCRegisterInfo *MRI;
|
||||
|
||||
MCAsmParser &getParser() const { return Parser; }
|
||||
AsmLexer &getLexer() const { return Parser.getLexer(); }
|
||||
|
||||
bool parseOneOperand(OperandVector &Operands);
|
||||
bool parseIndirectOperand(OperandVector &Operands);
|
||||
bool parseRegisterListInstruction(StringRef Name, SMLoc NameLoc,
|
||||
OperandVector &Operands);
|
||||
|
||||
#define GET_ASSEMBLER_HEADER
|
||||
#include "TMS34010GenAsmMatcher.inc"
|
||||
|
||||
public:
|
||||
TMS34010AsmParser(const MCSubtargetInfo &STI, MCAsmParser &Parser,
|
||||
const MCInstrInfo &MII)
|
||||
: MCTargetAsmParser(STI, MII), Parser(Parser) {
|
||||
MCAsmParserExtension::Initialize(Parser);
|
||||
MRI = getContext().getRegisterInfo();
|
||||
setAvailableFeatures(ComputeAvailableFeatures(STI.getFeatureBits()));
|
||||
}
|
||||
|
||||
bool parseRegister(MCRegister &Reg, SMLoc &StartLoc,
|
||||
SMLoc &EndLoc) override;
|
||||
ParseStatus tryParseRegister(MCRegister &Reg, SMLoc &StartLoc,
|
||||
SMLoc &EndLoc) override;
|
||||
bool parseInstruction(ParseInstructionInfo &Info, StringRef Name,
|
||||
SMLoc NameLoc, OperandVector &Operands) override;
|
||||
bool matchAndEmitInstruction(SMLoc Loc, unsigned &Opcode,
|
||||
OperandVector &Operands, MCStreamer &Out,
|
||||
uint64_t &ErrorInfo,
|
||||
bool MatchingInlineAsm) override;
|
||||
ParseStatus parseDirective(AsmToken DirectiveID) override;
|
||||
};
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
// Defined by the generated matcher at the bottom of this file.
|
||||
static MCRegister MatchRegisterName(StringRef Name);
|
||||
static MCRegister MatchRegisterAltName(StringRef Name);
|
||||
|
||||
bool TMS34010AsmParser::parseRegister(MCRegister &Reg, SMLoc &StartLoc,
|
||||
SMLoc &EndLoc) {
|
||||
ParseStatus Res = tryParseRegister(Reg, StartLoc, EndLoc);
|
||||
if (Res.isSuccess())
|
||||
return false;
|
||||
if (Res.isNoMatch())
|
||||
return true;
|
||||
return Error(StartLoc, "invalid register name");
|
||||
}
|
||||
|
||||
ParseStatus TMS34010AsmParser::tryParseRegister(MCRegister &Reg,
|
||||
SMLoc &StartLoc,
|
||||
SMLoc &EndLoc) {
|
||||
if (getLexer().getKind() != AsmToken::Identifier)
|
||||
return ParseStatus::NoMatch;
|
||||
|
||||
// Register AsmNames are uppercase (A0, B15); GSPA's SP alias is lowercase.
|
||||
// Try the spelling as written, then both cases, so the assembler is
|
||||
// case-insensitive like GSPA without changing the printer's canonical names.
|
||||
StringRef Raw = getLexer().getTok().getIdentifier();
|
||||
auto Lookup = [&](StringRef Name) {
|
||||
Reg = MatchRegisterName(Name);
|
||||
if (Reg == TMS34010::NoRegister)
|
||||
Reg = MatchRegisterAltName(Name);
|
||||
};
|
||||
Lookup(Raw);
|
||||
if (Reg == TMS34010::NoRegister)
|
||||
Lookup(Raw.upper());
|
||||
if (Reg == TMS34010::NoRegister)
|
||||
Lookup(Raw.lower());
|
||||
if (Reg == TMS34010::NoRegister)
|
||||
return ParseStatus::NoMatch;
|
||||
|
||||
StartLoc = getLexer().getTok().getLoc();
|
||||
EndLoc = getLexer().getTok().getEndLoc();
|
||||
getLexer().Lex();
|
||||
return ParseStatus::Success;
|
||||
}
|
||||
|
||||
/// Parse a `*reg`, `*reg+`, `*reg(disp)` or `-*reg` operand after the leading
|
||||
/// `*` has been consumed. Any punctuation is retained in the operand stream
|
||||
/// because it is part of the TableGen asm string.
|
||||
bool TMS34010AsmParser::parseIndirectOperand(OperandVector &Operands) {
|
||||
SMLoc StarLoc = getLexer().getLoc();
|
||||
Operands.push_back(
|
||||
TMS34010Operand::CreateToken(getLexer().getTok().getString(), StarLoc));
|
||||
getLexer().Lex(); // eat '*'
|
||||
|
||||
MCRegister Reg;
|
||||
SMLoc StartLoc, EndLoc;
|
||||
if (parseRegister(Reg, StartLoc, EndLoc))
|
||||
return Error(StarLoc, "expected register after '*'");
|
||||
Operands.push_back(TMS34010Operand::CreateReg(Reg, StartLoc, EndLoc));
|
||||
|
||||
if (getLexer().is(AsmToken::Plus)) {
|
||||
SMLoc S = getLexer().getLoc();
|
||||
Operands.push_back(
|
||||
TMS34010Operand::CreateToken(getLexer().getTok().getString(), S));
|
||||
getLexer().Lex();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getLexer().is(AsmToken::LParen)) {
|
||||
SMLoc LS = getLexer().getLoc();
|
||||
Operands.push_back(
|
||||
TMS34010Operand::CreateToken(getLexer().getTok().getString(), LS));
|
||||
getLexer().Lex();
|
||||
|
||||
const MCExpr *Disp;
|
||||
SMLoc DispLoc = getLexer().getLoc();
|
||||
if (getParser().parseExpression(Disp))
|
||||
return Error(DispLoc, "expected displacement expression");
|
||||
Operands.push_back(
|
||||
TMS34010Operand::CreateImm(Disp, DispLoc, getLexer().getLoc()));
|
||||
|
||||
if (!getLexer().is(AsmToken::RParen))
|
||||
return Error(getLexer().getLoc(), "expected ')'");
|
||||
SMLoc RS = getLexer().getLoc();
|
||||
Operands.push_back(
|
||||
TMS34010Operand::CreateToken(getLexer().getTok().getString(), RS));
|
||||
getLexer().Lex();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TMS34010AsmParser::parseOneOperand(OperandVector &Operands) {
|
||||
switch (getLexer().getKind()) {
|
||||
case AsmToken::Star:
|
||||
return parseIndirectOperand(Operands);
|
||||
|
||||
case AsmToken::Minus: {
|
||||
// `-*reg` is a predecrement memory operand; `-imm` is an expression.
|
||||
AsmToken Minus = getLexer().getTok();
|
||||
getLexer().Lex();
|
||||
if (getLexer().is(AsmToken::Star)) {
|
||||
Operands.push_back(
|
||||
TMS34010Operand::CreateToken(Minus.getString(), Minus.getLoc()));
|
||||
return parseIndirectOperand(Operands);
|
||||
}
|
||||
// Put the minus back and let the expression parser apply its normal
|
||||
// precedence: ADDI -5+2 is (-5)+2, not -(5+2).
|
||||
getLexer().UnLex(Minus);
|
||||
const MCExpr *Value;
|
||||
SMLoc StartLoc = Minus.getLoc();
|
||||
if (getParser().parseExpression(Value))
|
||||
return Error(StartLoc, "expected expression after '-'");
|
||||
Operands.push_back(
|
||||
TMS34010Operand::CreateImm(Value, StartLoc, getLexer().getLoc()));
|
||||
return false;
|
||||
}
|
||||
|
||||
case AsmToken::At: {
|
||||
SMLoc S = getLexer().getLoc();
|
||||
Operands.push_back(
|
||||
TMS34010Operand::CreateToken(getLexer().getTok().getString(), S));
|
||||
getLexer().Lex();
|
||||
const MCExpr *Addr;
|
||||
SMLoc AddrLoc = getLexer().getLoc();
|
||||
if (getParser().parseExpression(Addr))
|
||||
return Error(AddrLoc, "expected absolute address expression");
|
||||
Operands.push_back(
|
||||
TMS34010Operand::CreateImm(Addr, AddrLoc, getLexer().getLoc()));
|
||||
return false;
|
||||
}
|
||||
|
||||
case AsmToken::Identifier: {
|
||||
MCRegister Reg;
|
||||
SMLoc StartLoc, EndLoc;
|
||||
ParseStatus Res = tryParseRegister(Reg, StartLoc, EndLoc);
|
||||
if (Res.isSuccess()) {
|
||||
Operands.push_back(TMS34010Operand::CreateReg(Reg, StartLoc, EndLoc));
|
||||
return false;
|
||||
}
|
||||
if (Res.isFailure())
|
||||
return Error(getLexer().getLoc(), "invalid register name");
|
||||
[[fallthrough]];
|
||||
}
|
||||
|
||||
case AsmToken::Integer:
|
||||
case AsmToken::Plus:
|
||||
case AsmToken::LParen:
|
||||
case AsmToken::Hash:
|
||||
default: {
|
||||
const MCExpr *Value;
|
||||
SMLoc StartLoc = getLexer().getLoc();
|
||||
if (getParser().parseExpression(Value))
|
||||
return Error(StartLoc, "expected expression operand");
|
||||
Operands.push_back(
|
||||
TMS34010Operand::CreateImm(Value, StartLoc, getLexer().getLoc()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TMS34010AsmParser::parseRegisterListInstruction(
|
||||
StringRef Name, SMLoc NameLoc, OperandVector &Operands) {
|
||||
Operands.push_back(TMS34010Operand::CreateToken(Name, NameLoc));
|
||||
|
||||
MCRegister Dst;
|
||||
SMLoc StartLoc, EndLoc;
|
||||
if (parseRegister(Dst, StartLoc, EndLoc))
|
||||
return Error(NameLoc, "expected a base register");
|
||||
Operands.push_back(TMS34010Operand::CreateReg(Dst, StartLoc, EndLoc));
|
||||
|
||||
if (!parseOptionalToken(AsmToken::Comma))
|
||||
return Error(getLexer().getLoc(), "expected a register list");
|
||||
|
||||
// The canonical form is a register list; a numeric mask is also accepted so
|
||||
// that the encoding can be pinned directly in tests.
|
||||
if (getLexer().is(AsmToken::Integer) || getLexer().is(AsmToken::Minus)) {
|
||||
const MCExpr *Mask;
|
||||
SMLoc MaskLoc = getLexer().getLoc();
|
||||
if (getParser().parseExpression(Mask))
|
||||
return Error(MaskLoc, "expected mask expression");
|
||||
Operands.push_back(
|
||||
TMS34010Operand::CreateImm(Mask, MaskLoc, getLexer().getLoc()));
|
||||
} else {
|
||||
uint16_t Mask = 0;
|
||||
unsigned File = MRI->getEncodingValue(Dst) & 0x10;
|
||||
bool Any = false;
|
||||
while (true) {
|
||||
MCRegister Reg;
|
||||
SMLoc RS, RE;
|
||||
if (parseRegister(Reg, RS, RE))
|
||||
return Error(getLexer().getLoc(), "expected register in list");
|
||||
unsigned Enc = MRI->getEncodingValue(Reg);
|
||||
if ((Enc & 0x10) != File)
|
||||
return Error(RS, "register list must use the same register file");
|
||||
unsigned Low = Enc & 0xF;
|
||||
if (Name.equals_insensitive("mmtm"))
|
||||
Mask |= static_cast<uint16_t>(1u << (15 - Low));
|
||||
else
|
||||
Mask |= static_cast<uint16_t>(1u << Low);
|
||||
Any = true;
|
||||
if (!parseOptionalToken(AsmToken::Comma))
|
||||
break;
|
||||
if (getLexer().is(AsmToken::EndOfStatement))
|
||||
break;
|
||||
}
|
||||
if (!Any)
|
||||
return Error(NameLoc, "empty register list");
|
||||
Operands.push_back(TMS34010Operand::CreateImm(
|
||||
MCConstantExpr::create(Mask, getContext()), NameLoc,
|
||||
getLexer().getLoc()));
|
||||
}
|
||||
|
||||
if (getLexer().isNot(AsmToken::EndOfStatement)) {
|
||||
SMLoc Loc = getLexer().getLoc();
|
||||
getParser().eatToEndOfStatement();
|
||||
return Error(Loc, "unexpected token after register list");
|
||||
}
|
||||
getParser().Lex();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TMS34010AsmParser::parseInstruction(ParseInstructionInfo &Info,
|
||||
StringRef Name, SMLoc NameLoc,
|
||||
OperandVector &Operands) {
|
||||
if (Name.equals_insensitive("mmtm") || Name.equals_insensitive("mmfm"))
|
||||
return parseRegisterListInstruction(Name, NameLoc, Operands);
|
||||
|
||||
Operands.push_back(TMS34010Operand::CreateToken(Name, NameLoc));
|
||||
|
||||
if (getLexer().is(AsmToken::EndOfStatement))
|
||||
return false;
|
||||
|
||||
while (true) {
|
||||
if (parseOneOperand(Operands))
|
||||
return true;
|
||||
if (!parseOptionalToken(AsmToken::Comma))
|
||||
break;
|
||||
}
|
||||
|
||||
if (getLexer().isNot(AsmToken::EndOfStatement)) {
|
||||
SMLoc Loc = getLexer().getLoc();
|
||||
getParser().eatToEndOfStatement();
|
||||
return Error(Loc, "unexpected token");
|
||||
}
|
||||
getParser().Lex();
|
||||
return false;
|
||||
}
|
||||
|
||||
ParseStatus TMS34010AsmParser::parseDirective(AsmToken DirectiveID) {
|
||||
return ParseStatus::NoMatch;
|
||||
}
|
||||
|
||||
static bool isShortJR(unsigned Opcode) {
|
||||
switch (Opcode) {
|
||||
case TMS34010::MC_JR:
|
||||
case TMS34010::MC_JRP:
|
||||
case TMS34010::MC_JRLS:
|
||||
case TMS34010::MC_JRHI:
|
||||
case TMS34010::MC_JRLT:
|
||||
case TMS34010::MC_JRGE:
|
||||
case TMS34010::MC_JRLE:
|
||||
case TMS34010::MC_JRGT:
|
||||
case TMS34010::MC_JRLO:
|
||||
case TMS34010::MC_JRHS:
|
||||
case TMS34010::MC_JREQ:
|
||||
case TMS34010::MC_JRNE:
|
||||
case TMS34010::MC_JRV:
|
||||
case TMS34010::MC_JRNV:
|
||||
case TMS34010::MC_JRN:
|
||||
case TMS34010::MC_JRNN:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool TMS34010AsmParser::matchAndEmitInstruction(
|
||||
SMLoc Loc, unsigned &Opcode, OperandVector &Operands, MCStreamer &Out,
|
||||
uint64_t &ErrorInfo, bool MatchingInlineAsm) {
|
||||
MCInst Inst;
|
||||
unsigned MatchResult =
|
||||
MatchInstructionImpl(Operands, Inst, ErrorInfo, MatchingInlineAsm);
|
||||
|
||||
switch (MatchResult) {
|
||||
case Match_Success:
|
||||
Inst.setLoc(Loc);
|
||||
|
||||
// The MC JR/JRcc encodings take the raw 8-bit word displacement. Zero
|
||||
// would be the 16-bit displacement form and 0x80 is the absolute-JA
|
||||
// marker, so neither belongs in the two-byte encoding.
|
||||
if (isShortJR(Inst.getOpcode()) && Inst.getNumOperands() == 1 &&
|
||||
Inst.getOperand(0).isImm()) {
|
||||
int64_t Disp = Inst.getOperand(0).getImm();
|
||||
if (Disp == 0 || Disp < -127 || Disp > 127)
|
||||
return Error(Loc, "8-bit JR displacement must be in [-127,-1] or "
|
||||
"[1,127]");
|
||||
}
|
||||
|
||||
// All same-file instructions share bit 4 of the instruction word. The
|
||||
// assembler must reject a pair in different files instead of silently
|
||||
// encoding the source register number in the destination file. The only
|
||||
// cross-file instruction is MOVE register-to-register.
|
||||
if (Inst.getOpcode() != TMS34010::MC_MOVE) {
|
||||
unsigned File = ~0u;
|
||||
for (const MCOperand &MO : Inst) {
|
||||
if (!MO.isReg())
|
||||
continue;
|
||||
unsigned F = MRI->getEncodingValue(MO.getReg()) & 0x10;
|
||||
if (File == ~0u)
|
||||
File = F;
|
||||
else if (File != F)
|
||||
return Error(Loc, "source and destination registers must be in the "
|
||||
"same register file");
|
||||
}
|
||||
}
|
||||
|
||||
Out.emitInstruction(Inst, *STI);
|
||||
return false;
|
||||
case Match_MnemonicFail:
|
||||
return Error(Loc, "invalid instruction mnemonic");
|
||||
case Match_InvalidOperand: {
|
||||
SMLoc ErrorLoc = Loc;
|
||||
if (ErrorInfo != ~0U) {
|
||||
if (ErrorInfo >= Operands.size())
|
||||
return Error(ErrorLoc, "too few operands for instruction");
|
||||
ErrorLoc = Operands[ErrorInfo]->getStartLoc();
|
||||
if (ErrorLoc == SMLoc())
|
||||
ErrorLoc = Loc;
|
||||
}
|
||||
return Error(ErrorLoc, "invalid operand for instruction");
|
||||
}
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
|
||||
LLVMInitializeTMS34010AsmParser() {
|
||||
RegisterMCAsmParser<TMS34010AsmParser> X(getTheTMS34010Target());
|
||||
}
|
||||
|
||||
#define GET_REGISTER_MATCHER
|
||||
#define GET_MATCHER_IMPLEMENTATION
|
||||
#include "TMS34010GenAsmMatcher.inc"
|
||||
@@ -0,0 +1,18 @@
|
||||
//===-- TMS34010AsmParser.h - TMS34010 assembly parser ----------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// The parser implementation is private to TMS34010AsmParser.cpp; it is
|
||||
// registered through LLVMInitializeTMS34010AsmParser in that file. This
|
||||
// header exists so the component follows the target layout.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010ASMPARSER_H
|
||||
#define LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010ASMPARSER_H
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,386 @@
|
||||
//===-- TMS34010Disassembler.cpp - TMS34010 disassembler --------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// TMS34010 instructions are one to three 16-bit words. The generated decoder
|
||||
// tables contain the fixed instruction bits; this file supplies the instruction
|
||||
// length, the register-file decoders and a few post-decode fixups for encodings
|
||||
// whose register file bit is shared between two operands.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "TMS34010MCTargetDesc.h"
|
||||
#include "TargetInfo/TMS34010TargetInfo.h"
|
||||
|
||||
#include "llvm/MC/MCContext.h"
|
||||
#include "llvm/MC/MCDecoder.h"
|
||||
#include "llvm/MC/MCDecoderOps.h"
|
||||
#include "llvm/MC/MCDisassembler/MCDisassembler.h"
|
||||
#include "llvm/MC/MCInst.h"
|
||||
#include "llvm/MC/MCSubtargetInfo.h"
|
||||
#include "llvm/MC/TargetRegistry.h"
|
||||
#include "llvm/Support/Compiler.h"
|
||||
#include "llvm/Support/Endian.h"
|
||||
|
||||
using namespace llvm;
|
||||
using namespace llvm::MCD;
|
||||
|
||||
#define DEBUG_TYPE "tms34010-disassembler"
|
||||
|
||||
typedef MCDisassembler::DecodeStatus DecodeStatus;
|
||||
|
||||
namespace {
|
||||
class TMS34010Disassembler : public MCDisassembler {
|
||||
public:
|
||||
TMS34010Disassembler(const MCSubtargetInfo &STI, MCContext &Ctx)
|
||||
: MCDisassembler(STI, Ctx) {}
|
||||
|
||||
DecodeStatus getInstruction(MCInst &MI, uint64_t &Size,
|
||||
ArrayRef<uint8_t> Bytes, uint64_t Address,
|
||||
raw_ostream &CStream) const override;
|
||||
};
|
||||
} // end anonymous namespace
|
||||
|
||||
static MCDisassembler *createTMS34010Disassembler(const Target &T,
|
||||
const MCSubtargetInfo &STI,
|
||||
MCContext &Ctx) {
|
||||
return new TMS34010Disassembler(STI, Ctx);
|
||||
}
|
||||
|
||||
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
|
||||
LLVMInitializeTMS34010Disassembler() {
|
||||
TargetRegistry::RegisterMCDisassembler(getTheTMS34010Target(),
|
||||
createTMS34010Disassembler);
|
||||
}
|
||||
|
||||
static const MCRegister GPRDecoderTable[] = {
|
||||
TMS34010::A0, TMS34010::A1, TMS34010::A2, TMS34010::A3,
|
||||
TMS34010::A4, TMS34010::A5, TMS34010::A6, TMS34010::A7,
|
||||
TMS34010::A8, TMS34010::A9, TMS34010::A10, TMS34010::A11,
|
||||
TMS34010::A12, TMS34010::A13, TMS34010::A14, TMS34010::A15,
|
||||
TMS34010::B0, TMS34010::B1, TMS34010::B2, TMS34010::B3,
|
||||
TMS34010::B4, TMS34010::B5, TMS34010::B6, TMS34010::B7,
|
||||
TMS34010::B8, TMS34010::B9, TMS34010::B10, TMS34010::B11,
|
||||
TMS34010::B12, TMS34010::B13, TMS34010::B14, TMS34010::B15};
|
||||
|
||||
static DecodeStatus DecodeGPRRegisterClass(MCInst &MI, uint64_t RegNo,
|
||||
uint64_t Address,
|
||||
const MCDisassembler *Decoder) {
|
||||
if (RegNo >= std::size(GPRDecoderTable))
|
||||
return MCDisassembler::Fail;
|
||||
MI.addOperand(MCOperand::createReg(GPRDecoderTable[RegNo]));
|
||||
return MCDisassembler::Success;
|
||||
}
|
||||
|
||||
static DecodeStatus DecodeSETFFs(MCInst &MI, uint64_t Field, uint64_t Address,
|
||||
const MCDisassembler *Decoder) {
|
||||
MI.addOperand(MCOperand::createImm(Field == 0 ? 32 : Field));
|
||||
return MCDisassembler::Success;
|
||||
}
|
||||
|
||||
static DecodeStatus DecodeInvImm(MCInst &MI, uint64_t Field, uint64_t Address,
|
||||
const MCDisassembler *Decoder) {
|
||||
MI.addOperand(
|
||||
MCOperand::createImm(static_cast<uint32_t>(~Field)));
|
||||
return MCDisassembler::Success;
|
||||
}
|
||||
|
||||
static DecodeStatus DecodeNegK(MCInst &MI, uint64_t Field, uint64_t Address,
|
||||
const MCDisassembler *Decoder) {
|
||||
// SRA/SRL store (-K) & 31; zero represents 32.
|
||||
unsigned K = Field == 0 ? 32 : 32 - Field;
|
||||
MI.addOperand(MCOperand::createImm(K));
|
||||
return MCDisassembler::Success;
|
||||
}
|
||||
|
||||
#include "TMS34010GenDisassemblerTables.inc"
|
||||
|
||||
/// Length of the instruction at the start of Bytes, in bytes. This mirrors
|
||||
/// the operands' fixed widths in the .td file; the generated decoder verifies
|
||||
/// the actual bits. 0 means the input is too short or the opcode is not a
|
||||
/// supported TMS34010 encoding.
|
||||
static unsigned instructionLength(uint16_t Op, ArrayRef<uint8_t> Bytes) {
|
||||
unsigned Len = 2;
|
||||
uint16_t Subop = Op & 0x01e0;
|
||||
|
||||
switch (Op & 0xfe00) {
|
||||
case 0x0000:
|
||||
break;
|
||||
case 0x0200:
|
||||
if (Subop == 0x0140)
|
||||
Len = 10; // MOVB @a1, @a2 (not currently decoded)
|
||||
break;
|
||||
case 0x0400:
|
||||
case 0x0600:
|
||||
if (Subop == 0x0180 || Subop == 0x01a0 || Subop == 0x01e0)
|
||||
Len = 6;
|
||||
else if (Subop == 0x01c0)
|
||||
Len = 10; // MOVE @a1, @a2 (not currently decoded)
|
||||
break;
|
||||
case 0x0800:
|
||||
if (Subop == 0x0180 || Subop == 0x01a0 || Subop == 0x01c0)
|
||||
Len = 4;
|
||||
else if (Subop == 0x01e0)
|
||||
Len = 6;
|
||||
break;
|
||||
case 0x0a00:
|
||||
if (Subop == 0x0100 || Subop == 0x0140 || Subop == 0x01e0)
|
||||
Len = 4;
|
||||
else if (Subop == 0x0120 || Subop == 0x0160 || Subop == 0x0180 ||
|
||||
Subop == 0x01a0 || Subop == 0x01c0)
|
||||
Len = 6;
|
||||
break;
|
||||
case 0x0c00:
|
||||
if (Subop == 0x0100 || Subop == 0x0140)
|
||||
Len = 6;
|
||||
else if (Subop == 0x0120 || Subop == 0x0180 || Subop == 0x01a0 ||
|
||||
Subop == 0x01c0)
|
||||
Len = 4;
|
||||
break;
|
||||
case 0x0e00:
|
||||
break;
|
||||
case 0x1000: case 0x1200: case 0x1400: case 0x1600:
|
||||
case 0x1800: case 0x1a00: case 0x1c00: case 0x1e00:
|
||||
case 0x2000: case 0x2200: case 0x2400: case 0x2600:
|
||||
case 0x2800: case 0x2a00: case 0x2c00: case 0x2e00:
|
||||
case 0x3000: case 0x3200: case 0x3800: case 0x3a00:
|
||||
case 0x3c00: case 0x3e00:
|
||||
case 0x4000: case 0x4200: case 0x4400: case 0x4600:
|
||||
case 0x4800: case 0x4a00: case 0x4c00: case 0x4e00:
|
||||
case 0x5000: case 0x5200: case 0x5400: case 0x5600:
|
||||
case 0x5800: case 0x5a00: case 0x5c00: case 0x5e00:
|
||||
case 0x6000: case 0x6200: case 0x6400: case 0x6600:
|
||||
case 0x6800: case 0x6a00: case 0x6c00: case 0x6e00:
|
||||
break;
|
||||
case 0x8000: case 0x8200: case 0x8400: case 0x8600:
|
||||
case 0x8800: case 0x8a00: case 0x8c00: case 0x8e00:
|
||||
case 0x9000: case 0x9200: case 0x9400: case 0x9600:
|
||||
case 0x9800: case 0x9a00: case 0x9c00:
|
||||
case 0xa000: case 0xa200: case 0xa400: case 0xa600:
|
||||
case 0xa800: case 0xaa00:
|
||||
break;
|
||||
case 0xac00: case 0xae00: case 0xb000: case 0xb200:
|
||||
case 0xb400: case 0xb600:
|
||||
Len = 4;
|
||||
break;
|
||||
case 0xb800: case 0xba00: case 0xbc00:
|
||||
Len = 6;
|
||||
break;
|
||||
case 0xc000: case 0xc200: case 0xc400: case 0xc600:
|
||||
case 0xc800: case 0xca00: case 0xcc00: case 0xce00: {
|
||||
uint8_t Lo8 = Op & 0xff;
|
||||
if (Lo8 == 0x80)
|
||||
Len = 6;
|
||||
else if (Lo8 == 0x00)
|
||||
Len = 4;
|
||||
break;
|
||||
}
|
||||
case 0xd000: case 0xd200:
|
||||
Len = 4;
|
||||
break;
|
||||
case 0xd400: case 0xd600:
|
||||
if (Subop == 0x0000)
|
||||
Len = 6;
|
||||
break;
|
||||
case 0xde00:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (Bytes.size() < Len)
|
||||
return 0;
|
||||
return Len;
|
||||
}
|
||||
|
||||
static bool isBRegister(MCRegister Reg) {
|
||||
return Reg >= TMS34010::B0 && Reg <= TMS34010::B15;
|
||||
}
|
||||
|
||||
/// Many TMS34010 encodings share bit 4 between the two register operands.
|
||||
/// TableGen decodes the four-bit source as an A register; recover the real
|
||||
/// file from the five-bit destination.
|
||||
static void fixSameFile(MCInst &MI, unsigned SrcIdx, unsigned DstIdx) {
|
||||
if (SrcIdx >= MI.getNumOperands() || DstIdx >= MI.getNumOperands())
|
||||
return;
|
||||
if (!MI.getOperand(SrcIdx).isReg() || !MI.getOperand(DstIdx).isReg())
|
||||
return;
|
||||
MCRegister Dst = MI.getOperand(DstIdx).getReg();
|
||||
if (!isBRegister(Dst))
|
||||
return;
|
||||
MCRegister Src = MI.getOperand(SrcIdx).getReg();
|
||||
unsigned Low = Src >= TMS34010::A0 ? Src - TMS34010::A0 : 0;
|
||||
if (Low > 15)
|
||||
return;
|
||||
MI.getOperand(SrcIdx).setReg(GPRDecoderTable[16 + Low]);
|
||||
}
|
||||
|
||||
/// MOVE register-to-register stores the source file in bit 4 and the
|
||||
/// cross-file bit in bit 9. The destination file is their xor.
|
||||
static void fixMoveRegisterFile(MCInst &MI, uint16_t Op) {
|
||||
if (MI.getNumOperands() < 2)
|
||||
return;
|
||||
unsigned SrcLow = (Op >> 5) & 0xf;
|
||||
unsigned DstLow = Op & 0xf;
|
||||
bool SrcB = (Op & 0x10) != 0;
|
||||
bool DstB = SrcB ^ ((Op & 0x0200) != 0);
|
||||
// MC_MOVE's operand order is (outs dst, ins src).
|
||||
MI.getOperand(0).setReg(GPRDecoderTable[DstLow + (DstB ? 16 : 0)]);
|
||||
MI.getOperand(1).setReg(GPRDecoderTable[SrcLow + (SrcB ? 16 : 0)]);
|
||||
}
|
||||
|
||||
DecodeStatus TMS34010Disassembler::getInstruction(MCInst &MI, uint64_t &Size,
|
||||
ArrayRef<uint8_t> Bytes,
|
||||
uint64_t Address,
|
||||
raw_ostream &CStream) const {
|
||||
if (Bytes.size() < 2) {
|
||||
Size = 0;
|
||||
return MCDisassembler::Fail;
|
||||
}
|
||||
|
||||
uint16_t Word0 = support::endian::read16le(Bytes.data());
|
||||
unsigned Len = instructionLength(Word0, Bytes);
|
||||
if (!Len) {
|
||||
Size = 0;
|
||||
return MCDisassembler::Fail;
|
||||
}
|
||||
|
||||
uint64_t Insn = Word0;
|
||||
if (Len >= 4)
|
||||
Insn |= static_cast<uint64_t>(
|
||||
support::endian::read16le(Bytes.data() + 2))
|
||||
<< 16;
|
||||
if (Len >= 6)
|
||||
Insn |= static_cast<uint64_t>(
|
||||
support::endian::read16le(Bytes.data() + 4))
|
||||
<< 32;
|
||||
|
||||
// The short ADDI/MOVI/CMPI/SUBI forms are selected by the emitter and are
|
||||
// not separate .td encodings (they would be ambiguous in the asm matcher),
|
||||
// so synthesise their MCInsts here.
|
||||
if (Len == 4) {
|
||||
uint16_t Base = Word0 & 0xffe0;
|
||||
if (Base == 0x0b00 || Base == 0x09c0 || Base == 0x0b40 ||
|
||||
Base == 0x0be0) {
|
||||
uint16_t Raw = support::endian::read16le(Bytes.data() + 2);
|
||||
unsigned Opc;
|
||||
int64_t Value;
|
||||
switch (Base) {
|
||||
case 0x0b00:
|
||||
Opc = TMS34010::MC_ADDI;
|
||||
Value = static_cast<int16_t>(Raw);
|
||||
break;
|
||||
case 0x09c0:
|
||||
Opc = TMS34010::MC_MOVI;
|
||||
Value = static_cast<int16_t>(Raw);
|
||||
break;
|
||||
case 0x0b40:
|
||||
Opc = TMS34010::MC_CMPI;
|
||||
Value = static_cast<uint16_t>(~Raw);
|
||||
break;
|
||||
default:
|
||||
Opc = TMS34010::MC_SUBI;
|
||||
Value = static_cast<uint16_t>(~Raw);
|
||||
break;
|
||||
}
|
||||
MI.clear();
|
||||
MI.setOpcode(Opc);
|
||||
MI.addOperand(MCOperand::createReg(GPRDecoderTable[Word0 & 0x1f]));
|
||||
MI.addOperand(MCOperand::createImm(Value));
|
||||
Size = 4;
|
||||
return MCDisassembler::Success;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t *Table = nullptr;
|
||||
switch (Len) {
|
||||
case 2:
|
||||
Table = DecoderTable16;
|
||||
break;
|
||||
case 4:
|
||||
Table = DecoderTable32;
|
||||
break;
|
||||
case 6:
|
||||
Table = DecoderTable48;
|
||||
break;
|
||||
default:
|
||||
Size = 0;
|
||||
return MCDisassembler::Fail;
|
||||
}
|
||||
|
||||
MI.clear();
|
||||
DecodeStatus Result =
|
||||
decodeInstruction(Table, MI, Insn, Address, this, STI);
|
||||
if (Result == MCDisassembler::Fail) {
|
||||
Size = 0;
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Repair the register-file bit for the encodings that share it.
|
||||
switch (MI.getOpcode()) {
|
||||
case TMS34010::MC_MOVE:
|
||||
fixMoveRegisterFile(MI, Word0);
|
||||
break;
|
||||
case TMS34010::MC_ADD:
|
||||
case TMS34010::MC_SUB:
|
||||
case TMS34010::MC_CMP:
|
||||
case TMS34010::MC_AND:
|
||||
case TMS34010::MC_OR:
|
||||
case TMS34010::MC_XOR:
|
||||
case TMS34010::MC_MPYS:
|
||||
case TMS34010::MC_MPYU:
|
||||
case TMS34010::MC_DIVS:
|
||||
case TMS34010::MC_DIVU:
|
||||
case TMS34010::MC_SLA:
|
||||
case TMS34010::MC_SLL:
|
||||
case TMS34010::MC_SRA:
|
||||
case TMS34010::MC_SRL:
|
||||
case TMS34010::MC_RL:
|
||||
case TMS34010::MC_MOVE_STORE:
|
||||
case TMS34010::MC_MOVE_STORE_POST:
|
||||
case TMS34010::MC_MOVE_STORE_PRE:
|
||||
case TMS34010::MC_MOVE_BOTH:
|
||||
case TMS34010::MC_MOVB_BOTH:
|
||||
case TMS34010::MC_MOVE_BOTH_POST:
|
||||
case TMS34010::MC_MOVE_BOTH_PRE:
|
||||
case TMS34010::MC_MOVE_STORE_DISP:
|
||||
case TMS34010::MC_MOVB_STORE:
|
||||
case TMS34010::MC_MOVB_STORE_DISP:
|
||||
fixSameFile(MI, 0, 1);
|
||||
break;
|
||||
case TMS34010::MC_MOVE_LOAD:
|
||||
case TMS34010::MC_MOVE_LOAD_POST:
|
||||
case TMS34010::MC_MOVE_LOAD_PRE:
|
||||
case TMS34010::MC_MOVE_LOAD_DISP:
|
||||
case TMS34010::MC_MOVB_LOAD:
|
||||
case TMS34010::MC_MOVB_LOAD_DISP:
|
||||
fixSameFile(MI, 1, 0);
|
||||
break;
|
||||
case TMS34010::MC_MOVE_BOTH_DISP:
|
||||
case TMS34010::MC_MOVE_DISP_POST:
|
||||
case TMS34010::MC_MOVB_BOTH_DISP:
|
||||
fixSameFile(MI, 0, 2);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// CLR and XOR share an encoding: XOR Rd, Rd is CLR. CLR is
|
||||
// isAsmParserOnly so the decoder table contains only XOR; rewrite it after
|
||||
// the register-file repair, otherwise B-file CLR cannot compare equal.
|
||||
if (MI.getOpcode() == TMS34010::MC_XOR && MI.getNumOperands() == 2 &&
|
||||
MI.getOperand(0).isReg() && MI.getOperand(1).isReg() &&
|
||||
MI.getOperand(0).getReg() == MI.getOperand(1).getReg()) {
|
||||
MCRegister Dst = MI.getOperand(1).getReg();
|
||||
MI.clear();
|
||||
MI.setOpcode(TMS34010::MC_CLR);
|
||||
MI.addOperand(MCOperand::createReg(Dst));
|
||||
}
|
||||
|
||||
Size = Len;
|
||||
return Result;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
//===-- TMS34010Disassembler.h - TMS34010 disassembler ----------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// The decoder is private to TMS34010Disassembler.cpp; it is registered through
|
||||
// LLVMInitializeTMS34010Disassembler in that file. This header exists so the
|
||||
// component follows the target layout.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010DISASSEMBLER_H
|
||||
#define LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010DISASSEMBLER_H
|
||||
|
||||
#endif
|
||||
@@ -7,15 +7,36 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "TMS34010InstPrinter.h"
|
||||
#include "TMS34010MCTargetDesc.h"
|
||||
#include "llvm/MC/MCAsmInfo.h"
|
||||
#include "llvm/MC/MCExpr.h"
|
||||
#include "llvm/MC/MCInst.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
#include "TMS34010GenAsmWriter.inc"
|
||||
|
||||
namespace {
|
||||
|
||||
const MCRegister GPRTable[] = {
|
||||
TMS34010::A0, TMS34010::A1, TMS34010::A2, TMS34010::A3,
|
||||
TMS34010::A4, TMS34010::A5, TMS34010::A6, TMS34010::A7,
|
||||
TMS34010::A8, TMS34010::A9, TMS34010::A10, TMS34010::A11,
|
||||
TMS34010::A12, TMS34010::A13, TMS34010::A14, TMS34010::A15,
|
||||
TMS34010::B0, TMS34010::B1, TMS34010::B2, TMS34010::B3,
|
||||
TMS34010::B4, TMS34010::B5, TMS34010::B6, TMS34010::B7,
|
||||
TMS34010::B8, TMS34010::B9, TMS34010::B10, TMS34010::B11,
|
||||
TMS34010::B12, TMS34010::B13, TMS34010::B14, TMS34010::B15};
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
void TMS34010InstPrinter::printRegName(raw_ostream &O, MCRegister Reg) {
|
||||
// GSPA spells register 15 of the A file "SP" and rejects "A15".
|
||||
if (Reg == TMS34010::A15) {
|
||||
O << "SP";
|
||||
return;
|
||||
}
|
||||
O << getRegisterName(Reg);
|
||||
}
|
||||
|
||||
@@ -23,6 +44,29 @@ void TMS34010InstPrinter::printInst(const MCInst *MI, uint64_t Address,
|
||||
StringRef Annot,
|
||||
const MCSubtargetInfo &STI,
|
||||
raw_ostream &O) {
|
||||
const unsigned Opcode = MI->getOpcode();
|
||||
if ((Opcode == TMS34010::MC_MMTM || Opcode == TMS34010::MC_MMFM) &&
|
||||
MI->getNumOperands() == 2 && MI->getOperand(0).isReg() &&
|
||||
MI->getOperand(1).isImm()) {
|
||||
O << "\t" << (Opcode == TMS34010::MC_MMTM ? "MMTM " : "MMFM ");
|
||||
printRegName(O, MI->getOperand(0).getReg());
|
||||
unsigned Mask = static_cast<unsigned>(MI->getOperand(1).getImm()) & 0xffff;
|
||||
unsigned File = (MI->getOperand(0).getReg() >= TMS34010::B0 &&
|
||||
MI->getOperand(0).getReg() <= TMS34010::B15)
|
||||
? 16
|
||||
: 0;
|
||||
for (unsigned I = 0; I < 16; ++I) {
|
||||
unsigned Bit = Opcode == TMS34010::MC_MMTM ? (Mask >> (15 - I)) & 1
|
||||
: (Mask >> I) & 1;
|
||||
if (!Bit)
|
||||
continue;
|
||||
O << ", ";
|
||||
printRegName(O, GPRTable[File + I]);
|
||||
}
|
||||
printAnnotation(O, Annot);
|
||||
return;
|
||||
}
|
||||
|
||||
printInstruction(MI, Address, O);
|
||||
printAnnotation(O, Annot);
|
||||
}
|
||||
@@ -31,7 +75,7 @@ void TMS34010InstPrinter::printOperand(const MCInst *MI, unsigned OpNo,
|
||||
raw_ostream &O) {
|
||||
const MCOperand &Op = MI->getOperand(OpNo);
|
||||
if (Op.isReg()) {
|
||||
O << getRegisterName(Op.getReg());
|
||||
printRegName(O, Op.getReg());
|
||||
} else if (Op.isImm()) {
|
||||
O << Op.getImm();
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
//===-- TMS34010MCAsmBackend.cpp - TMS34010 assembler backend -------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "TMS34010MCAsmBackend.h"
|
||||
#include "TMS34010MCObjectWriter.h"
|
||||
#include "TMS34010MCTargetDesc.h"
|
||||
|
||||
#include "llvm/ADT/Twine.h"
|
||||
#include "llvm/BinaryFormat/ELF.h"
|
||||
#include "llvm/MC/MCAssembler.h"
|
||||
#include "llvm/MC/MCFixup.h"
|
||||
#include "llvm/MC/MCInst.h"
|
||||
#include "llvm/MC/MCSection.h"
|
||||
#include "llvm/MC/MCSymbol.h"
|
||||
#include "llvm/MC/MCELFObjectWriter.h"
|
||||
#include "llvm/MC/MCValue.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
namespace {
|
||||
|
||||
/// `llvm-mc -triple=tms34010` defaults to the ELF object format (the triple
|
||||
/// has no OS component), while the CodeGen path uses the target's TI-COFF
|
||||
/// writer directly. This tiny ELF target writer keeps the generic llvm-mc
|
||||
/// object path self-consistent; the MC round-trip only needs the .text bytes.
|
||||
class TMS34010ELFObjectTargetWriter : public MCELFObjectTargetWriter {
|
||||
public:
|
||||
TMS34010ELFObjectTargetWriter()
|
||||
: MCELFObjectTargetWriter(/*Is64Bit=*/false, ELF::ELFOSABI_NONE,
|
||||
ELF::EM_NONE, /*HasRelocationAddend=*/false) {}
|
||||
|
||||
unsigned getRelocType(const MCFixup &Fixup, const MCValue &Target,
|
||||
bool IsPCRel) const override {
|
||||
// The MC tests use absolute numeric operands, so this path is not
|
||||
// exercised; TI-COFF relocations are handled by TMS34010COFFObjectWriter.
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
/// TI-COFF relocations store the addend in the relocated field and use bit
|
||||
/// addresses. A defined symbol contributes its section-relative bit offset;
|
||||
/// the expression constant is already a bit offset (tms-bitaddr --scale
|
||||
/// scaled address expressions, and --normalize keeps late GEP offsets in
|
||||
/// bits).
|
||||
uint64_t tms34010BitAddend(const MCAssembler &Asm, const MCValue &Target) {
|
||||
int64_t Addend = Target.getConstant();
|
||||
if (const MCSymbol *Add = Target.getAddSym())
|
||||
if (Add->isDefined() && Add->isInSection())
|
||||
Addend += static_cast<int64_t>(Asm.getSymbolOffset(*Add)) * 8;
|
||||
return static_cast<uint64_t>(Addend);
|
||||
}
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
TMS34010MCAsmBackend::TMS34010MCAsmBackend(const Target &T)
|
||||
: MCAsmBackend(llvm::endianness::little) {}
|
||||
|
||||
MCFixupKindInfo
|
||||
TMS34010MCAsmBackend::getFixupKindInfo(MCFixupKind Kind) const {
|
||||
if (Kind == TMS34010::FK_JR8)
|
||||
return {"FK_TMS34010_JR8", 0, 8, 0};
|
||||
// The built-in data fixups are the only other kinds the object path produces.
|
||||
return MCAsmBackend::getFixupKindInfo(Kind);
|
||||
}
|
||||
|
||||
/// Decode the backend's PC-relative byte value into the ISA's signed word
|
||||
/// displacement. The fixup sits on the instruction word (the displacement is
|
||||
/// bits 7-0, the low byte in memory), so the ISA's reference point (PC+16) is
|
||||
/// two bytes past it:
|
||||
/// disp = (target - (pc + 2)) / 2 == (Value - 2) / 2.
|
||||
static std::optional<int64_t> getJRDisplacement(uint64_t Value) {
|
||||
int64_t Delta = static_cast<int64_t>(Value) - 2;
|
||||
if (Delta % 2)
|
||||
return std::nullopt;
|
||||
return Delta / 2;
|
||||
}
|
||||
|
||||
bool TMS34010MCAsmBackend::mayNeedRelaxation(
|
||||
unsigned Opcode, ArrayRef<MCOperand> Operands,
|
||||
const MCSubtargetInfo &STI) const {
|
||||
// Only a symbolic target can be out of range. The assembler's JR/JRcc take a
|
||||
// numeric displacement and encode exactly what the source asked for.
|
||||
return TMS34010::isShortBranch(Opcode) && !Operands.empty() &&
|
||||
Operands[0].isExpr();
|
||||
}
|
||||
|
||||
bool TMS34010MCAsmBackend::fixupNeedsRelaxationAdvanced(
|
||||
const MCFragment &F, const MCFixup &Fixup, const MCValue &Target,
|
||||
uint64_t Value, bool Resolved) const {
|
||||
if (Fixup.getKind() != TMS34010::FK_JR8)
|
||||
return false;
|
||||
// A cross-section or undefined target cannot be resolved to a displacement.
|
||||
if (!Resolved)
|
||||
return true;
|
||||
std::optional<int64_t> Disp = getJRDisplacement(Value);
|
||||
// Zero is the 16-bit relative form and -128 is the absolute-JA marker, so
|
||||
// neither is encodable in one word. Anything else outside [-127,127] needs
|
||||
// the three-word absolute form.
|
||||
return !Disp || *Disp == 0 || *Disp < -127 || *Disp > 127;
|
||||
}
|
||||
|
||||
void TMS34010MCAsmBackend::relaxInstruction(
|
||||
MCInst &Inst, const MCSubtargetInfo &STI) const {
|
||||
unsigned Long = TMS34010::getLongBranchOpcode(Inst.getOpcode());
|
||||
if (!Long)
|
||||
llvm_unreachable("TMS34010: no wider form for this branch");
|
||||
Inst.setOpcode(Long);
|
||||
}
|
||||
|
||||
void TMS34010MCAsmBackend::applyFixup(const MCFragment &F,
|
||||
const MCFixup &Fixup,
|
||||
const MCValue &Target, uint8_t *Data,
|
||||
uint64_t Value, bool IsResolved) {
|
||||
if (Fixup.getKind() == TMS34010::FK_JR8) {
|
||||
if (!IsResolved)
|
||||
report_fatal_error(
|
||||
"TMS34010: an unresolved JR displacement must be relaxed");
|
||||
std::optional<int64_t> Disp = getJRDisplacement(Value);
|
||||
if (!Disp || *Disp == 0 || *Disp < -127 || *Disp > 127)
|
||||
report_fatal_error(
|
||||
"TMS34010: JR displacement is out of range after relaxation");
|
||||
Data[0] = static_cast<uint8_t>(static_cast<int8_t>(*Disp));
|
||||
return;
|
||||
}
|
||||
|
||||
if (Target.getSubSym())
|
||||
report_fatal_error(
|
||||
"TMS34010: symbol differences are not supported in TI-COFF objects");
|
||||
if (!IsResolved && Fixup.getKind() != FK_Data_4)
|
||||
report_fatal_error(
|
||||
"TMS34010: only 32-bit symbol relocations are supported");
|
||||
|
||||
// An unresolved symbol reference becomes an R-RELLONG relocation. The
|
||||
// relocated field holds the addend (a bit offset), as GSPA 2.00 does.
|
||||
if (!IsResolved)
|
||||
maybeAddReloc(F, Fixup, Target, Value, /*IsResolved=*/false);
|
||||
|
||||
uint64_t Field = IsResolved ? Value : tms34010BitAddend(*Asm, Target);
|
||||
switch (Fixup.getKind()) {
|
||||
case FK_Data_1:
|
||||
Data[0] = static_cast<uint8_t>(Field);
|
||||
break;
|
||||
case FK_Data_2:
|
||||
Data[0] = static_cast<uint8_t>(Field);
|
||||
Data[1] = static_cast<uint8_t>(Field >> 8);
|
||||
break;
|
||||
case FK_Data_4:
|
||||
Data[0] = static_cast<uint8_t>(Field);
|
||||
Data[1] = static_cast<uint8_t>(Field >> 8);
|
||||
Data[2] = static_cast<uint8_t>(Field >> 16);
|
||||
Data[3] = static_cast<uint8_t>(Field >> 24);
|
||||
break;
|
||||
default:
|
||||
report_fatal_error(Twine("TMS34010: unsupported fixup kind ") +
|
||||
Twine(Fixup.getKind()));
|
||||
}
|
||||
}
|
||||
|
||||
bool TMS34010MCAsmBackend::writeNopData(raw_ostream &OS, uint64_t Count,
|
||||
const MCSubtargetInfo *STI) const {
|
||||
if (Count % 2)
|
||||
return false;
|
||||
|
||||
// NOP is 0x0300, stored little-endian.
|
||||
for (uint64_t I = 0; I < Count; I += 2)
|
||||
OS << '\x00' << '\x03';
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<MCObjectTargetWriter>
|
||||
TMS34010MCAsmBackend::createObjectTargetWriter() const {
|
||||
return std::make_unique<TMS34010ELFObjectTargetWriter>();
|
||||
}
|
||||
|
||||
std::unique_ptr<MCObjectWriter>
|
||||
TMS34010MCAsmBackend::createObjectWriter(
|
||||
raw_pwrite_stream &OS, std::shared_ptr<TMS34010GlobalSymbols> Globals) const {
|
||||
return std::make_unique<TMS34010COFFObjectWriter>(OS, std::move(Globals));
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
//===-- TMS34010MCAsmBackend.h - TMS34010 assembler backend -----*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCASMBACKEND_H
|
||||
#define LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCASMBACKEND_H
|
||||
|
||||
#include "TMS34010MCObjectWriter.h"
|
||||
#include "llvm/MC/MCAsmBackend.h"
|
||||
|
||||
namespace llvm {
|
||||
|
||||
class MCObjectTargetWriter;
|
||||
class MCObjectWriter;
|
||||
class MCRegisterInfo;
|
||||
class MCSubtargetInfo;
|
||||
class MCTargetOptions;
|
||||
class MCInst;
|
||||
class MCOperand;
|
||||
class Target;
|
||||
class raw_pwrite_stream;
|
||||
|
||||
class TMS34010MCAsmBackend : public MCAsmBackend {
|
||||
public:
|
||||
TMS34010MCAsmBackend(const Target &T);
|
||||
|
||||
MCFixupKindInfo getFixupKindInfo(MCFixupKind Kind) const override;
|
||||
|
||||
void applyFixup(const MCFragment &F, const MCFixup &Fixup,
|
||||
const MCValue &Target, uint8_t *Data, uint64_t Value,
|
||||
bool IsResolved) override;
|
||||
|
||||
bool mayNeedRelaxation(unsigned Opcode, ArrayRef<MCOperand> Operands,
|
||||
const MCSubtargetInfo &STI) const override;
|
||||
|
||||
bool fixupNeedsRelaxationAdvanced(const MCFragment &F, const MCFixup &Fixup,
|
||||
const MCValue &Target, uint64_t Value,
|
||||
bool Resolved) const override;
|
||||
|
||||
void relaxInstruction(MCInst &Inst,
|
||||
const MCSubtargetInfo &STI) const override;
|
||||
|
||||
bool writeNopData(raw_ostream &OS, uint64_t Count,
|
||||
const MCSubtargetInfo *STI) const override;
|
||||
|
||||
std::unique_ptr<MCObjectTargetWriter> createObjectTargetWriter() const override;
|
||||
|
||||
/// Build the TI-COFF writer used by `llc -filetype=obj`. The generic
|
||||
/// MCAsmBackend::createObjectWriter only knows LLVM's built-in object
|
||||
/// formats, so the target machine calls this directly.
|
||||
std::unique_ptr<MCObjectWriter>
|
||||
createObjectWriter(raw_pwrite_stream &OS,
|
||||
std::shared_ptr<TMS34010GlobalSymbols> Globals) const;
|
||||
};
|
||||
|
||||
} // namespace llvm
|
||||
|
||||
#endif // LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCASMBACKEND_H
|
||||
@@ -19,7 +19,9 @@ TMS34010MCAsmInfo::TMS34010MCAsmInfo(const Triple &TT,
|
||||
CalleeSaveStackSlotSize = 4;
|
||||
|
||||
CommentString = ";";
|
||||
SeparatorString = ",";
|
||||
// A comma separates operands, not statements. GSPA statements end at the
|
||||
// newline, so the lexer must not turn a comma into EndOfStatement.
|
||||
SeparatorString = "\n";
|
||||
|
||||
// GSPA 2.00 identifiers accept only alphanumerics and `_`, so block labels
|
||||
// must be dot-free. Set both prefixes explicitly rather than relying on the
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
//===-- TMS34010MCCodeEmitter.cpp - TMS34010 code emitter -----------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// TMS34010 instructions are one to three 16-bit words, stored little-endian.
|
||||
// TableGen supplies the base opcode and operand placement; this file fixes the
|
||||
// two immediate forms GSPA 2.00 selects between.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "TMS34010MCCodeEmitter.h"
|
||||
#include "TMS34010MCTargetDesc.h"
|
||||
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/MC/MCContext.h"
|
||||
#include "llvm/MC/MCExpr.h"
|
||||
#include "llvm/MC/MCFixup.h"
|
||||
#include "llvm/MC/MCInst.h"
|
||||
#include "llvm/MC/MCInstrInfo.h"
|
||||
#include "llvm/MC/MCRegisterInfo.h"
|
||||
#include "llvm/MC/MCSubtargetInfo.h"
|
||||
#include "llvm/Support/EndianStream.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
#include "llvm/Support/MathExtras.h"
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
namespace {
|
||||
|
||||
class TMS34010MCCodeEmitter : public MCCodeEmitter {
|
||||
MCContext &Ctx;
|
||||
const MCInstrInfo &MCII;
|
||||
|
||||
/// TableGen'erated: the fixed opcode bits with every named operand merged
|
||||
/// into its field.
|
||||
uint64_t getBinaryCodeForInstr(const MCInst &MI,
|
||||
SmallVectorImpl<MCFixup> &Fixups,
|
||||
const MCSubtargetInfo &STI) const;
|
||||
|
||||
/// Return the encoding of one operand, recording a fixup when the operand
|
||||
/// is an unresolved expression.
|
||||
unsigned getMachineOpValue(const MCInst &MI, const MCOperand &MO,
|
||||
SmallVectorImpl<MCFixup> &Fixups,
|
||||
const MCSubtargetInfo &STI) const;
|
||||
|
||||
/// SETF encodes fs == 32 as zero.
|
||||
unsigned getSETFFsValue(const MCInst &MI, unsigned OpNo,
|
||||
SmallVectorImpl<MCFixup> &Fixups,
|
||||
const MCSubtargetInfo &STI) const;
|
||||
|
||||
/// CMPI/SUBI/ANDI store the one's complement of the immediate.
|
||||
unsigned getInvImmValue(const MCInst &MI, unsigned OpNo,
|
||||
SmallVectorImpl<MCFixup> &Fixups,
|
||||
const MCSubtargetInfo &STI) const;
|
||||
|
||||
/// SRA/SRL store (-K) & 31.
|
||||
unsigned getNegKValue(const MCInst &MI, unsigned OpNo,
|
||||
SmallVectorImpl<MCFixup> &Fixups,
|
||||
const MCSubtargetInfo &STI) const;
|
||||
|
||||
public:
|
||||
TMS34010MCCodeEmitter(MCContext &Ctx, const MCInstrInfo &MCII)
|
||||
: Ctx(Ctx), MCII(MCII) {}
|
||||
|
||||
void encodeInstruction(const MCInst &MI, SmallVectorImpl<char> &CB,
|
||||
SmallVectorImpl<MCFixup> &Fixups,
|
||||
const MCSubtargetInfo &STI) const override;
|
||||
};
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
unsigned TMS34010MCCodeEmitter::getMachineOpValue(
|
||||
const MCInst &MI, const MCOperand &MO, SmallVectorImpl<MCFixup> &Fixups,
|
||||
const MCSubtargetInfo &STI) const {
|
||||
if (MO.isReg())
|
||||
return Ctx.getRegisterInfo()->getEncodingValue(MO.getReg());
|
||||
|
||||
if (MO.isImm())
|
||||
return static_cast<unsigned>(MO.getImm());
|
||||
|
||||
if (MO.isExpr()) {
|
||||
// The instruction word carries a placeholder; encodeInstruction records
|
||||
// an FK_Data_4 fixup over the 32-bit field so the backend can write the
|
||||
// bit-address addend and the object writer can emit an R-RELLONG
|
||||
// relocation. The operand itself is encoded below as zero.
|
||||
return 0;
|
||||
}
|
||||
|
||||
llvm_unreachable("Unsupported TMS34010 operand");
|
||||
}
|
||||
|
||||
unsigned TMS34010MCCodeEmitter::getSETFFsValue(
|
||||
const MCInst &MI, unsigned OpNo, SmallVectorImpl<MCFixup> &Fixups,
|
||||
const MCSubtargetInfo &STI) const {
|
||||
const MCOperand &MO = MI.getOperand(OpNo);
|
||||
if (!MO.isImm())
|
||||
report_fatal_error("TMS34010: SETF field size must be a constant");
|
||||
int64_t Value = MO.getImm();
|
||||
// The ISA stores fs == 32 as zero; 0 is not a legal source spelling in
|
||||
// GSPA 2.00, which warns about it.
|
||||
if (Value == 32)
|
||||
return 0;
|
||||
if (Value < 0 || Value > 31)
|
||||
report_fatal_error("TMS34010: SETF field size must be 1..32");
|
||||
return static_cast<unsigned>(Value);
|
||||
}
|
||||
|
||||
unsigned TMS34010MCCodeEmitter::getInvImmValue(
|
||||
const MCInst &MI, unsigned OpNo, SmallVectorImpl<MCFixup> &Fixups,
|
||||
const MCSubtargetInfo &STI) const {
|
||||
const MCOperand &MO = MI.getOperand(OpNo);
|
||||
if (!MO.isImm())
|
||||
return 0;
|
||||
return static_cast<uint32_t>(~static_cast<uint64_t>(MO.getImm()));
|
||||
}
|
||||
|
||||
unsigned TMS34010MCCodeEmitter::getNegKValue(
|
||||
const MCInst &MI, unsigned OpNo, SmallVectorImpl<MCFixup> &Fixups,
|
||||
const MCSubtargetInfo &STI) const {
|
||||
const MCOperand &MO = MI.getOperand(OpNo);
|
||||
if (!MO.isImm())
|
||||
report_fatal_error("TMS34010: shift count must be a constant");
|
||||
return static_cast<unsigned>((-MO.getImm()) & 0x1F);
|
||||
}
|
||||
|
||||
static void writeWord(SmallVectorImpl<char> &CB, uint16_t Value) {
|
||||
support::endian::write<uint16_t>(CB, Value, llvm::endianness::little);
|
||||
}
|
||||
|
||||
void TMS34010MCCodeEmitter::encodeInstruction(
|
||||
const MCInst &MI, SmallVectorImpl<char> &CB,
|
||||
SmallVectorImpl<MCFixup> &Fixups, const MCSubtargetInfo &STI) const {
|
||||
const unsigned Opcode = MI.getOpcode();
|
||||
|
||||
// The one-word relative branches take a symbolic target in CodeGen. Their
|
||||
// operand is not an absolute address field: record an 8-bit PC-relative fixup
|
||||
// on the displacement byte; the backend subtracts PC+16 and converts the
|
||||
// result to the ISA's signed word displacement. A numeric operand (the
|
||||
// assembler's JR/JRcc) needs no fixup and falls through to the generated
|
||||
// encoder below.
|
||||
if (TMS34010::isShortBranch(Opcode) && MI.getNumOperands() == 1 &&
|
||||
MI.getOperand(0).isExpr()) {
|
||||
uint16_t Bits =
|
||||
static_cast<uint16_t>(getBinaryCodeForInstr(MI, Fixups, STI));
|
||||
writeWord(CB, Bits);
|
||||
Fixups.push_back(MCFixup::create(0, MI.getOperand(0).getExpr(),
|
||||
TMS34010::FK_JR8, /*PCRel=*/true));
|
||||
return;
|
||||
}
|
||||
|
||||
// SRAK/SRLK (the CodeGen instructions) store (-K) & 31 in bits 9..5, but
|
||||
// TableGen drops that negated field expression for these non-MC defs. The
|
||||
// MC_SRAK/MC_SRLK assembler defs use TMS34010NegK and are emitted by the
|
||||
// generated table. Encode the CodeGen form explicitly so `llc -filetype=obj`
|
||||
// matches the `llc -filetype=asm` text (GSPA) encoding.
|
||||
if (Opcode == TMS34010::SRAK || Opcode == TMS34010::SRLK) {
|
||||
unsigned Dst =
|
||||
Ctx.getRegisterInfo()->getEncodingValue(MI.getOperand(0).getReg());
|
||||
const MCOperand &MO = MI.getOperand(2);
|
||||
if (!MO.isImm())
|
||||
report_fatal_error("TMS34010: shift count must be a constant");
|
||||
uint16_t Bits =
|
||||
static_cast<uint16_t>(Opcode == TMS34010::SRAK ? 0x2800 : 0x2C00);
|
||||
Bits |= static_cast<uint16_t>(((-MO.getImm()) & 0x1F) << 5);
|
||||
Bits |= static_cast<uint16_t>(Dst & 0x1F);
|
||||
writeWord(CB, Bits);
|
||||
return;
|
||||
}
|
||||
|
||||
// MOVE register-to-register has a cross-file bit (bit 9) that is not a
|
||||
// straight operand field: it is src{4} xor dst{4}. TableGen's fixed-field
|
||||
// emitter cannot express that, so encode this one instruction manually.
|
||||
if (Opcode == TMS34010::MC_MOVE) {
|
||||
unsigned Dst =
|
||||
Ctx.getRegisterInfo()->getEncodingValue(MI.getOperand(0).getReg());
|
||||
unsigned Src =
|
||||
Ctx.getRegisterInfo()->getEncodingValue(MI.getOperand(1).getReg());
|
||||
uint16_t Bits = 0x4C00;
|
||||
Bits |= static_cast<uint16_t>(((Src >> 4) ^ (Dst >> 4)) << 9);
|
||||
Bits |= static_cast<uint16_t>((Src & 0xF) << 5);
|
||||
Bits |= static_cast<uint16_t>(Src & 0x10);
|
||||
Bits |= static_cast<uint16_t>(Dst & 0xF);
|
||||
writeWord(CB, Bits);
|
||||
return;
|
||||
}
|
||||
|
||||
// GSPA 2.00 encodes ADDI, MOVI, CMPI and SUBI with the short 16-bit
|
||||
// immediate form when the value fits in a signed 16-bit field, and with the
|
||||
// long 32-bit form otherwise. For ADDI, MOVI and CMPI the long encoding is
|
||||
// the .td encoding and the short form clears bit 5; SUBI has a separate
|
||||
// short opcode (0x0BE0) and stores the one's complement. A symbol operand
|
||||
// is always long and needs a relocation.
|
||||
bool ShortImm = false;
|
||||
unsigned ImmOp = 0;
|
||||
switch (Opcode) {
|
||||
case TMS34010::ADDI: ImmOp = 2; ShortImm = true; break;
|
||||
case TMS34010::ADDSTK: ImmOp = 0; ShortImm = true; break;
|
||||
case TMS34010::MOVI: ImmOp = 1; ShortImm = true; break;
|
||||
case TMS34010::CMPI: ImmOp = 0; ShortImm = true; break;
|
||||
case TMS34010::MC_ADDI: ImmOp = 1; ShortImm = true; break;
|
||||
case TMS34010::MC_SUBI: ImmOp = 1; ShortImm = true; break;
|
||||
case TMS34010::MC_CMPI: ImmOp = 1; ShortImm = true; break;
|
||||
case TMS34010::MC_MOVI: ImmOp = 1; ShortImm = true; break;
|
||||
case TMS34010::MC_ANDI:
|
||||
case TMS34010::MC_ORI:
|
||||
case TMS34010::MC_XORI:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (ShortImm) {
|
||||
const MCOperand &MO = MI.getOperand(ImmOp);
|
||||
if (MO.isImm() && isInt<16>(MO.getImm())) {
|
||||
if (Opcode == TMS34010::MC_SUBI) {
|
||||
// SUBI's short form is a different opcode word, not a bit in the long
|
||||
// form. Operand 0 is the destination (MC operand order is outs, ins).
|
||||
uint64_t Dst =
|
||||
Ctx.getRegisterInfo()->getEncodingValue(MI.getOperand(0).getReg());
|
||||
writeWord(CB, static_cast<uint16_t>(0x0BE0 | (Dst & 0x1F)));
|
||||
writeWord(CB, static_cast<uint16_t>(~MO.getImm()));
|
||||
return;
|
||||
}
|
||||
uint64_t Bits = getBinaryCodeForInstr(MI, Fixups, STI);
|
||||
writeWord(CB, static_cast<uint16_t>(Bits) & ~uint16_t{0x0020});
|
||||
bool Invert = Opcode == TMS34010::MC_CMPI || Opcode == TMS34010::CMPI;
|
||||
writeWord(CB, static_cast<uint16_t>(Invert ? ~MO.getImm()
|
||||
: MO.getImm()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t Bits = getBinaryCodeForInstr(MI, Fixups, STI);
|
||||
unsigned Size = MCII.get(Opcode).getSize();
|
||||
for (unsigned Word = 0; Word < Size / 2; ++Word) {
|
||||
writeWord(CB, static_cast<uint16_t>(Bits));
|
||||
Bits >>= 16;
|
||||
}
|
||||
|
||||
// Every TMS34010 absolute-address operand occupies the 32-bit word(s)
|
||||
// following the opcode. Record an FK_Data_4 fixup over that field; the
|
||||
// backend turns an unresolved symbol reference into a bit-addend TI-COFF
|
||||
// relocation. The operand index differs per instruction because TableGen
|
||||
// orders MCInst operands as outs then ins.
|
||||
int AddrOp = -1;
|
||||
switch (Opcode) {
|
||||
case TMS34010::MOVI:
|
||||
case TMS34010::MC_MOVI:
|
||||
// CMPI, SUBI and ANDI store the one's complement of the operand; a symbol
|
||||
// addend would have to be complemented too, which GSPA does not express.
|
||||
case TMS34010::MC_ADDI:
|
||||
case TMS34010::MC_ORI:
|
||||
case TMS34010::MC_XORI:
|
||||
AddrOp = 1;
|
||||
break;
|
||||
case TMS34010::MC_MOVE_ABS_STORE:
|
||||
case TMS34010::MC_MOVE_ABS_LOAD:
|
||||
case TMS34010::MC_MOVB_ABS_STORE:
|
||||
case TMS34010::MC_MOVB_ABS_LOAD:
|
||||
AddrOp = 1;
|
||||
break;
|
||||
case TMS34010::MC_MOVE_ABS_POST:
|
||||
case TMS34010::CALLA:
|
||||
case TMS34010::MC_CALLA:
|
||||
case TMS34010::JUncond:
|
||||
AddrOp = 0;
|
||||
break;
|
||||
default:
|
||||
if (MCII.get(Opcode).isBranch())
|
||||
AddrOp = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
bool SawExpr = false;
|
||||
if (AddrOp >= 0 && AddrOp < static_cast<int>(MI.getNumOperands())) {
|
||||
const MCOperand &MO = MI.getOperand(AddrOp);
|
||||
if (MO.isExpr()) {
|
||||
// The fixup covers the opcode word plus a 32-bit absolute field. A
|
||||
// future shorter branch must not silently acquire a misplaced
|
||||
// relocation.
|
||||
if (Size < 6)
|
||||
report_fatal_error(
|
||||
"TMS34010: symbol operand field is not a 32-bit absolute field");
|
||||
Fixups.push_back(MCFixup::create(2, MO.getExpr(), FK_Data_4));
|
||||
SawExpr = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Any other instruction with an unresolved operand would silently encode
|
||||
// zero; refuse instead of producing a wrong object. A 16-bit displacement
|
||||
// or a field-size operand must be resolved by the assembler.
|
||||
if (!SawExpr) {
|
||||
for (unsigned I = 0, E = MI.getNumOperands(); I != E; ++I)
|
||||
if (MI.getOperand(I).isExpr())
|
||||
report_fatal_error(
|
||||
"TMS34010: symbol operands are only supported for absolute "
|
||||
"address/immediate fields");
|
||||
}
|
||||
}
|
||||
|
||||
MCCodeEmitter *llvm::createTMS34010MCCodeEmitter(const MCInstrInfo &MCII,
|
||||
MCContext &Ctx) {
|
||||
return new TMS34010MCCodeEmitter(Ctx, MCII);
|
||||
}
|
||||
|
||||
#include "TMS34010GenMCCodeEmitter.inc"
|
||||
@@ -0,0 +1,25 @@
|
||||
//===-- TMS34010MCCodeEmitter.h - TMS34010 code emitter ---------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCCODEEMITTER_H
|
||||
#define LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCCODEEMITTER_H
|
||||
|
||||
#include "llvm/MC/MCCodeEmitter.h"
|
||||
|
||||
namespace llvm {
|
||||
|
||||
class MCContext;
|
||||
class MCInstrInfo;
|
||||
|
||||
/// Create the TMS34010 machine code emitter.
|
||||
MCCodeEmitter *createTMS34010MCCodeEmitter(const MCInstrInfo &MII,
|
||||
MCContext &Ctx);
|
||||
|
||||
} // namespace llvm
|
||||
|
||||
#endif // LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCCODEEMITTER_H
|
||||
@@ -0,0 +1,439 @@
|
||||
//===-- TMS34010MCObjectWriter.cpp - TI-COFF object writer ---------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// The TI-COFF dialect is the one GSPA 2.00 and GSPLNK 2.00 use (see
|
||||
// build/m3/m3.coff and vendor/vt1200/dwt/loader/coff.py):
|
||||
//
|
||||
// file header: machine 0x0090, optional-header size 0, flags 0x0100
|
||||
// section: name, paddr/vaddr 0, size in bits, raw/relocation file
|
||||
// offsets in bytes, flags 0x20 (.text) / 0x40 (.data) /
|
||||
// 0x80 (.bss)
|
||||
// symbol: section symbols first, class C_STAT (3) with one 18-byte aux
|
||||
// record; globals class C_EXT (2) with a bit-address value
|
||||
// relocation: 10 bytes {bit address, symbol index, type}; type 0x11 is
|
||||
// R-RELLONG and its field holds the bit-address addend
|
||||
//
|
||||
// A same-section reference uses symbol index 0xFFFF and the field holds the
|
||||
// section-relative bit offset, exactly as GSPA emits. A cross-section
|
||||
// reference uses the target section symbol and the field holds the target
|
||||
// symbol's section-relative bit offset. An undefined reference uses its
|
||||
// C_EXT symbol index and the field holds the expression constant.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "TMS34010MCObjectWriter.h"
|
||||
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/SmallString.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/ADT/Twine.h"
|
||||
#include "llvm/MC/MCAssembler.h"
|
||||
#include "llvm/MC/MCContext.h"
|
||||
#include "llvm/MC/MCFixup.h"
|
||||
#include "llvm/MC/MCObjectFileInfo.h"
|
||||
#include "llvm/MC/MCSection.h"
|
||||
#include "llvm/MC/MCSymbol.h"
|
||||
#include "llvm/MC/MCValue.h"
|
||||
#include "llvm/Support/EndianStream.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint16_t MachineTMS34010 = 0x0090;
|
||||
constexpr uint16_t FileFlagsAR32WR = 0x0100;
|
||||
constexpr uint16_t SectionFlagText = 0x20;
|
||||
constexpr uint16_t SectionFlagData = 0x40;
|
||||
constexpr uint16_t SectionFlagBss = 0x80;
|
||||
constexpr uint16_t RelocRellong = 0x11;
|
||||
constexpr uint8_t ClassExternal = 2; // C_EXT
|
||||
constexpr uint8_t ClassStatic = 3; // C_STAT (section symbol)
|
||||
|
||||
void append16(SmallVectorImpl<char> &V, uint16_t Value) {
|
||||
support::endian::write<uint16_t>(V, Value, llvm::endianness::little);
|
||||
}
|
||||
|
||||
void append32(SmallVectorImpl<char> &V, uint32_t Value) {
|
||||
support::endian::write<uint32_t>(V, Value, llvm::endianness::little);
|
||||
}
|
||||
|
||||
void write16(raw_pwrite_stream &OS, uint16_t Value) {
|
||||
support::endian::write<uint16_t>(OS, Value, llvm::endianness::little);
|
||||
}
|
||||
|
||||
void write32(raw_pwrite_stream &OS, uint32_t Value) {
|
||||
support::endian::write<uint32_t>(OS, Value, llvm::endianness::little);
|
||||
}
|
||||
|
||||
uint16_t flagsForSectionName(StringRef Name) {
|
||||
if (Name == ".text")
|
||||
return SectionFlagText;
|
||||
if (Name == ".data")
|
||||
return SectionFlagData;
|
||||
if (Name == ".bss")
|
||||
return SectionFlagBss;
|
||||
llvm_unreachable("not a TI-COFF section");
|
||||
}
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
bool TMS34010MCStreamer::emitSymbolAttribute(MCSymbol *Symbol,
|
||||
MCSymbolAttr Attribute) {
|
||||
switch (Attribute) {
|
||||
case MCSA_Global:
|
||||
case MCSA_Weak:
|
||||
case MCSA_WeakDefinition:
|
||||
Globals->insert(Symbol);
|
||||
break;
|
||||
case MCSA_Local:
|
||||
Globals->erase(Symbol);
|
||||
break;
|
||||
default:
|
||||
// Visibility and type attributes do not change GSPA's flat symbol table.
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void TMS34010MCStreamer::emitCommonSymbol(MCSymbol *Symbol, uint64_t Size,
|
||||
Align ByteAlignment) {
|
||||
// Common symbols are writable zero-initialized storage. GSPA has no .comm
|
||||
// directive, so place them in the virtual .bss section like an ordinary
|
||||
// zero-initialized definition. Every .bss allocation is word-granular:
|
||||
// GSPA and the VT1200 loader require a section size that is a whole number
|
||||
// of 16-bit words.
|
||||
unsigned AlignBytes = std::max<unsigned>(ByteAlignment.value(), 2);
|
||||
Globals->insert(Symbol);
|
||||
switchSection(getContext().getObjectFileInfo()->getBSSSection());
|
||||
emitValueToAlignment(Align(AlignBytes));
|
||||
emitLabel(Symbol);
|
||||
emitZeros(Size);
|
||||
emitValueToAlignment(Align(2));
|
||||
}
|
||||
|
||||
void TMS34010COFFObjectWriter::recordRelocation(const MCFragment &F,
|
||||
const MCFixup &Fixup,
|
||||
MCValue Target,
|
||||
uint64_t &FixedValue) {
|
||||
if (Target.getSubSym())
|
||||
report_fatal_error(
|
||||
"TMS34010: symbol differences are not supported in TI-COFF objects");
|
||||
|
||||
const MCSymbol *Symbol = Target.getAddSym();
|
||||
if (!Symbol)
|
||||
report_fatal_error("TMS34010: relocation without a symbol");
|
||||
|
||||
// The backend has already written the bit-address addend into the relocated
|
||||
// field; the writer only needs the target symbol and its section here.
|
||||
Relocations.push_back(
|
||||
{F.getParent(), Asm->getFragmentOffset(F) + Fixup.getOffset(), Symbol});
|
||||
}
|
||||
|
||||
uint64_t TMS34010COFFObjectWriter::writeObject() {
|
||||
const uint64_t Start = OS.tell();
|
||||
|
||||
struct SectionInfo {
|
||||
StringRef Name;
|
||||
const MCSection *Sec;
|
||||
uint16_t Flags;
|
||||
uint16_t SecNo;
|
||||
SmallString<256> Data;
|
||||
uint32_t RawPtr = 0;
|
||||
uint32_t RelPtr = 0;
|
||||
uint32_t SizeBits = 0;
|
||||
uint16_t NReloc = 0;
|
||||
};
|
||||
|
||||
// GSPA always writes .text, .data and .bss, in that order, even when a
|
||||
// section is empty. Match that so the linker sees the same shape.
|
||||
static constexpr StringLiteral Names[] = {".text", ".data", ".bss"};
|
||||
SmallVector<SectionInfo, 3> Sections;
|
||||
for (StringLiteral Name : Names) {
|
||||
const MCSection *Found = nullptr;
|
||||
for (const MCSection &Sec : *Asm)
|
||||
if (Sec.getName() == Name) {
|
||||
Found = &Sec;
|
||||
break;
|
||||
}
|
||||
Sections.push_back({Name, Found, flagsForSectionName(Name),
|
||||
static_cast<uint16_t>(Sections.size() + 1), {}, 0, 0,
|
||||
0, 0});
|
||||
}
|
||||
|
||||
// A registered non-empty section that is not one of the three TI-COFF
|
||||
// sections would be silently lost; fail loudly instead.
|
||||
for (const MCSection &Sec : *Asm) {
|
||||
bool Known = false;
|
||||
for (const SectionInfo &S : Sections)
|
||||
if (S.Name == Sec.getName())
|
||||
Known = true;
|
||||
if (!Known && Sec.begin() != Sec.end())
|
||||
report_fatal_error(Twine("TMS34010: section '") + Sec.getName() +
|
||||
"' has no TI-COFF representation");
|
||||
}
|
||||
|
||||
DenseMap<const MCSection *, unsigned> SectionIndex;
|
||||
for (const SectionInfo &S : Sections)
|
||||
if (S.Sec)
|
||||
SectionIndex[S.Sec] = S.SecNo - 1;
|
||||
|
||||
// Buffer each section's relocated bytes. .bss is virtual and has no file
|
||||
// bytes, so only its bit size is recorded.
|
||||
for (SectionInfo &S : Sections) {
|
||||
if (!S.Sec)
|
||||
continue;
|
||||
if (S.Flags == SectionFlagBss) {
|
||||
S.SizeBits = static_cast<uint32_t>(Asm->getSectionAddressSize(*S.Sec) * 8);
|
||||
} else {
|
||||
raw_svector_ostream DataOS(S.Data);
|
||||
Asm->writeSectionData(DataOS, S.Sec);
|
||||
S.SizeBits = static_cast<uint32_t>(S.Data.size() * 8);
|
||||
}
|
||||
}
|
||||
|
||||
const SectionInfo *Text = &Sections[0];
|
||||
if (!Text->Sec || Text->Data.empty())
|
||||
report_fatal_error("TMS34010: object has no .text contents");
|
||||
|
||||
// Group relocations by section and remember which undefined symbols they
|
||||
// reference. GSPA emits a C_EXT entry for every undefined symbol that a
|
||||
// relocation mentions.
|
||||
for (SectionInfo &S : Sections) {
|
||||
if (S.Flags == SectionFlagBss)
|
||||
continue;
|
||||
for (const PendingReloc &R : Relocations)
|
||||
if (R.Section == S.Sec)
|
||||
++S.NReloc;
|
||||
}
|
||||
|
||||
struct SymbolRecord {
|
||||
std::string Name;
|
||||
uint32_t Value;
|
||||
int16_t Section;
|
||||
uint8_t Class;
|
||||
uint8_t NumAux;
|
||||
uint32_t AuxSizeBits;
|
||||
uint16_t AuxNReloc;
|
||||
};
|
||||
|
||||
SmallVector<SymbolRecord, 16> Symbols;
|
||||
DenseMap<const MCSymbol *, uint32_t> SymbolIndex;
|
||||
|
||||
// Section symbols first: one record plus one aux record each. The aux
|
||||
// record repeats the section size in bits, as GSPA writes it.
|
||||
for (const SectionInfo &S : Sections)
|
||||
Symbols.push_back({S.Name.str(), 0, static_cast<int16_t>(S.SecNo),
|
||||
ClassStatic, 1, S.SizeBits, S.NReloc});
|
||||
|
||||
// Section symbols occupy two records each (record + aux). Globals follow
|
||||
// them and have no aux records, so record indices advance by one.
|
||||
uint32_t NextRecordIndex = 2u * static_cast<uint32_t>(Sections.size());
|
||||
|
||||
// Defined globals. Their values are section-relative bit addresses.
|
||||
for (const MCSymbol &Sym : Asm->symbols()) {
|
||||
if (Sym.isVariable() || Sym.isTemporary())
|
||||
continue;
|
||||
if (!Sym.isDefined())
|
||||
continue;
|
||||
if (!Globals->contains(&Sym))
|
||||
continue;
|
||||
StringRef Name = Sym.getName();
|
||||
if (Name.empty() || Name.starts_with("."))
|
||||
continue;
|
||||
if (!Sym.isInSection())
|
||||
report_fatal_error(Twine("TMS34010: absolute global '") + Name +
|
||||
"' has no TI-COFF representation");
|
||||
const MCSection &Sec = Sym.getSection();
|
||||
auto It = SectionIndex.find(&Sec);
|
||||
if (It == SectionIndex.end())
|
||||
report_fatal_error(Twine("TMS34010: global '") + Name +
|
||||
"' is in section '" + Sec.getName() +
|
||||
"' with no TI-COFF representation");
|
||||
SymbolIndex[&Sym] = NextRecordIndex++;
|
||||
Symbols.push_back({Name.str(),
|
||||
static_cast<uint32_t>(Asm->getSymbolOffset(Sym) * 8),
|
||||
static_cast<int16_t>(It->second + 1), ClassExternal, 0,
|
||||
0, 0});
|
||||
}
|
||||
|
||||
// Undefined symbols referenced by a relocation, in symbol-table order.
|
||||
DenseMap<const MCSymbol *, bool> UndefinedReferenced;
|
||||
for (const PendingReloc &R : Relocations)
|
||||
if (R.Symbol->isUndefined())
|
||||
UndefinedReferenced[R.Symbol] = true;
|
||||
|
||||
for (const MCSymbol &Sym : Asm->symbols()) {
|
||||
if (Sym.isVariable() || Sym.isTemporary())
|
||||
continue;
|
||||
if (!Sym.isUndefined() || !UndefinedReferenced.contains(&Sym))
|
||||
continue;
|
||||
StringRef Name = Sym.getName();
|
||||
if (Name.empty() || Name.starts_with("."))
|
||||
continue;
|
||||
SymbolIndex[&Sym] = NextRecordIndex++;
|
||||
Symbols.push_back({Name.str(), 0, 0, ClassExternal, 0, 0, 0});
|
||||
}
|
||||
|
||||
// Resolve relocation targets to symbol-table indices. A target section
|
||||
// symbol is always at record index 2 * (section number - 1) because each
|
||||
// section symbol is followed by exactly one aux record.
|
||||
struct FinalReloc {
|
||||
uint32_t AddrBits;
|
||||
uint32_t Symbol;
|
||||
};
|
||||
DenseMap<const MCSection *, SmallVector<FinalReloc, 4>> RelocsBySection;
|
||||
|
||||
for (const PendingReloc &R : Relocations) {
|
||||
uint32_t SymbolIndexValue;
|
||||
const MCSymbol *Target = R.Symbol;
|
||||
if (Target->isDefined() && Target->isInSection()) {
|
||||
const MCSection &TargetSec = Target->getSection();
|
||||
auto It = SectionIndex.find(&TargetSec);
|
||||
if (It == SectionIndex.end())
|
||||
report_fatal_error(Twine("TMS34010: relocation target '") +
|
||||
Target->getName() + "' is in section '" +
|
||||
TargetSec.getName() +
|
||||
"' with no TI-COFF representation");
|
||||
if (&TargetSec == R.Section) {
|
||||
SymbolIndexValue = 0xFFFF; // same section: section-relative addend
|
||||
} else {
|
||||
SymbolIndexValue = It->second * 2; // the target section symbol
|
||||
}
|
||||
} else if (Target->isUndefined()) {
|
||||
auto It = SymbolIndex.find(Target);
|
||||
if (It == SymbolIndex.end())
|
||||
report_fatal_error(Twine("TMS34010: relocation symbol '") +
|
||||
Target->getName() + "' has no symbol-table entry");
|
||||
SymbolIndexValue = It->second;
|
||||
} else {
|
||||
report_fatal_error(Twine("TMS34010: absolute relocation target '") +
|
||||
Target->getName() + "' has no TI-COFF representation");
|
||||
}
|
||||
|
||||
RelocsBySection[R.Section].push_back(
|
||||
{static_cast<uint32_t>(R.Offset * 8), SymbolIndexValue});
|
||||
}
|
||||
|
||||
// File layout: headers, raw section bytes, relocation blocks, symbols,
|
||||
// string table.
|
||||
uint32_t Cursor = 20 + 40 * static_cast<uint32_t>(Sections.size());
|
||||
for (SectionInfo &S : Sections) {
|
||||
if (S.Flags == SectionFlagBss) {
|
||||
S.RawPtr = 0;
|
||||
continue;
|
||||
}
|
||||
S.RawPtr = Cursor;
|
||||
Cursor += static_cast<uint32_t>(S.Data.size());
|
||||
}
|
||||
for (SectionInfo &S : Sections) {
|
||||
if (S.Flags == SectionFlagBss) {
|
||||
S.RelPtr = 0;
|
||||
continue;
|
||||
}
|
||||
S.RelPtr = Cursor;
|
||||
Cursor += 10u * S.NReloc;
|
||||
}
|
||||
const uint32_t SymPtr = Cursor;
|
||||
|
||||
// Build the 18-byte symbol records (plus aux records) and the string table.
|
||||
SmallString<256> SymbolTable;
|
||||
SmallString<256> StringTable;
|
||||
uint32_t NumSymbolEntries = 0;
|
||||
auto AddSymbol = [&](const SymbolRecord &S) {
|
||||
if (S.Name.size() <= 8) {
|
||||
SmallString<8> ShortName(S.Name);
|
||||
ShortName.resize(8, '\0');
|
||||
SymbolTable.append(ShortName.begin(), ShortName.end());
|
||||
} else {
|
||||
append32(SymbolTable, 0);
|
||||
append32(SymbolTable, static_cast<uint32_t>(4 + StringTable.size()));
|
||||
StringTable.append(S.Name.begin(), S.Name.end());
|
||||
StringTable.push_back('\0');
|
||||
}
|
||||
append32(SymbolTable, S.Value);
|
||||
append16(SymbolTable, static_cast<uint16_t>(S.Section));
|
||||
append16(SymbolTable, 0); // type
|
||||
SymbolTable.push_back(static_cast<char>(S.Class));
|
||||
SymbolTable.push_back(static_cast<char>(S.NumAux));
|
||||
++NumSymbolEntries;
|
||||
|
||||
if (S.NumAux) {
|
||||
// Section auxiliary record: bit size, relocation count, line count,
|
||||
// then zeroed checksum/number/selection fields.
|
||||
append32(SymbolTable, S.AuxSizeBits);
|
||||
append16(SymbolTable, S.AuxNReloc);
|
||||
append16(SymbolTable, 0);
|
||||
SymbolTable.append(10, '\0');
|
||||
NumSymbolEntries += S.NumAux;
|
||||
}
|
||||
};
|
||||
|
||||
for (const SymbolRecord &S : Symbols)
|
||||
AddSymbol(S);
|
||||
|
||||
// File header (20 bytes).
|
||||
write16(OS, MachineTMS34010);
|
||||
write16(OS, static_cast<uint16_t>(Sections.size()));
|
||||
write32(OS, 0); // TimeDateStamp: deterministic
|
||||
write32(OS, SymPtr);
|
||||
write32(OS, NumSymbolEntries);
|
||||
write16(OS, 0); // SizeOfOptionalHeader
|
||||
write16(OS, FileFlagsAR32WR);
|
||||
|
||||
// Section headers (40 bytes each).
|
||||
for (const SectionInfo &S : Sections) {
|
||||
SmallString<8> Name(S.Name);
|
||||
Name.resize(8, '\0');
|
||||
OS.write(Name.data(), Name.size());
|
||||
write32(OS, 0); // PhysicalAddress
|
||||
write32(OS, 0); // VirtualAddress
|
||||
write32(OS, S.SizeBits); // SizeOfRawData, in bits
|
||||
write32(OS, S.RawPtr); // PointerToRawData
|
||||
write32(OS, S.RelPtr); // PointerToRelocations
|
||||
write32(OS, 0); // PointerToLineNumbers
|
||||
write16(OS, S.NReloc);
|
||||
write16(OS, 0); // NumberOfLineNumbers
|
||||
write32(OS, S.Flags);
|
||||
}
|
||||
|
||||
// Raw data. .bss has no file bytes.
|
||||
for (const SectionInfo &S : Sections)
|
||||
if (S.Flags != SectionFlagBss)
|
||||
OS.write(S.Data.data(), S.Data.size());
|
||||
|
||||
// Relocations. The address is a bit address within the section. .bss has
|
||||
// no file bytes and must never carry a relocation; a future fixup in .bss is
|
||||
// a writer bug, not something to drop silently.
|
||||
for (const SectionInfo &S : Sections) {
|
||||
auto It = RelocsBySection.find(S.Sec);
|
||||
if (S.Flags == SectionFlagBss) {
|
||||
if (It != RelocsBySection.end() && !It->second.empty())
|
||||
report_fatal_error("TMS34010: .bss relocations are not supported");
|
||||
continue;
|
||||
}
|
||||
if (It == RelocsBySection.end())
|
||||
continue;
|
||||
for (const FinalReloc &R : It->second) {
|
||||
write32(OS, R.AddrBits);
|
||||
write32(OS, R.Symbol);
|
||||
write16(OS, RelocRellong);
|
||||
}
|
||||
}
|
||||
|
||||
// Symbol table and string table.
|
||||
OS.write(SymbolTable.data(), SymbolTable.size());
|
||||
write32(OS, static_cast<uint32_t>(4 + StringTable.size()));
|
||||
OS.write(StringTable.data(), StringTable.size());
|
||||
|
||||
return OS.tell() - Start;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
//===-- TMS34010MCObjectWriter.h - TI-COFF object writer -------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCOBJECTWRITER_H
|
||||
#define LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCOBJECTWRITER_H
|
||||
|
||||
#include "llvm/ADT/DenseSet.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/MC/MCAsmBackend.h"
|
||||
#include "llvm/MC/MCCodeEmitter.h"
|
||||
#include "llvm/MC/MCObjectStreamer.h"
|
||||
#include "llvm/MC/MCObjectWriter.h"
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace llvm {
|
||||
|
||||
class MCAsmBackend;
|
||||
class MCCodeEmitter;
|
||||
class MCContext;
|
||||
class MCFixup;
|
||||
class MCFragment;
|
||||
class MCSection;
|
||||
class MCSymbol;
|
||||
class MCValue;
|
||||
class raw_pwrite_stream;
|
||||
|
||||
/// Symbols that the assembler printer marked `.globl`. GSPA 2.00 only puts a
|
||||
/// defined symbol in the object symbol table when it is global; local labels
|
||||
/// are reached through their section symbol plus a bit offset.
|
||||
using TMS34010GlobalSymbols = DenseSet<const MCSymbol *>;
|
||||
|
||||
/// TI-COFF object writer: `.text`/`.data`/`.bss` sections, a symbol table with
|
||||
/// section symbols and external symbols, and R-RELLONG relocations whose
|
||||
/// relocated field is a bit-address addend. The conventions match GSPA 2.00:
|
||||
///
|
||||
/// file header: machine 0x0090, optional-header size 0, flags 0x0100
|
||||
/// section: size in bits, flags 0x20/0x40/0x80, paddr/vaddr 0
|
||||
/// symbol: section symbols C_STAT (3) with one aux record; globals
|
||||
/// C_EXT (2) with a bit value and section number
|
||||
/// relocation: 10 bytes, address in bits, type 0x11 (R-RELLONG)
|
||||
class TMS34010COFFObjectWriter : public MCObjectWriter {
|
||||
raw_pwrite_stream &OS;
|
||||
std::shared_ptr<TMS34010GlobalSymbols> Globals;
|
||||
|
||||
struct PendingReloc {
|
||||
const MCSection *Section;
|
||||
uint64_t Offset; // byte offset within the section
|
||||
const MCSymbol *Symbol;
|
||||
};
|
||||
SmallVector<PendingReloc, 16> Relocations;
|
||||
|
||||
public:
|
||||
TMS34010COFFObjectWriter(raw_pwrite_stream &OS,
|
||||
std::shared_ptr<TMS34010GlobalSymbols> Globals)
|
||||
: OS(OS), Globals(std::move(Globals)) {}
|
||||
|
||||
void recordRelocation(const MCFragment &F, const MCFixup &Fixup,
|
||||
MCValue Target, uint64_t &FixedValue) override;
|
||||
|
||||
uint64_t writeObject() override;
|
||||
};
|
||||
|
||||
/// The object streamer for the TI-COFF writer. It tracks `.globl` so the
|
||||
/// writer can distinguish global definitions from local labels, and it backs
|
||||
/// the `.bss` path with the target's virtual BSS section.
|
||||
class TMS34010MCStreamer : public MCObjectStreamer {
|
||||
std::shared_ptr<TMS34010GlobalSymbols> Globals;
|
||||
|
||||
public:
|
||||
TMS34010MCStreamer(MCContext &Ctx, std::unique_ptr<MCAsmBackend> TAB,
|
||||
std::unique_ptr<MCObjectWriter> OW,
|
||||
std::unique_ptr<MCCodeEmitter> Emitter,
|
||||
std::shared_ptr<TMS34010GlobalSymbols> Globals)
|
||||
: MCObjectStreamer(Ctx, std::move(TAB), std::move(OW), std::move(Emitter)),
|
||||
Globals(std::move(Globals)) {}
|
||||
|
||||
bool emitSymbolAttribute(MCSymbol *Symbol,
|
||||
MCSymbolAttr Attribute) override;
|
||||
|
||||
void emitCommonSymbol(MCSymbol *Symbol, uint64_t Size,
|
||||
Align ByteAlignment) override;
|
||||
};
|
||||
|
||||
} // namespace llvm
|
||||
|
||||
#endif // LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCOBJECTWRITER_H
|
||||
@@ -8,7 +8,9 @@
|
||||
|
||||
#include "TMS34010MCTargetDesc.h"
|
||||
#include "TMS34010InstPrinter.h"
|
||||
#include "TMS34010MCAsmBackend.h"
|
||||
#include "TMS34010MCAsmInfo.h"
|
||||
#include "TMS34010MCCodeEmitter.h"
|
||||
#include "TargetInfo/TMS34010TargetInfo.h"
|
||||
#include "llvm/MC/MCInstrInfo.h"
|
||||
#include "llvm/MC/MCRegisterInfo.h"
|
||||
@@ -27,6 +29,92 @@ using namespace llvm;
|
||||
#define GET_REGINFO_MC_DESC
|
||||
#include "TMS34010GenRegisterInfo.inc"
|
||||
|
||||
namespace llvm {
|
||||
namespace TMS34010 {
|
||||
|
||||
unsigned getLongBranchOpcode(unsigned Opcode) {
|
||||
switch (Opcode) {
|
||||
case JR: return JUncond;
|
||||
case JREQ: return JAEQ;
|
||||
case JRNE: return JANE;
|
||||
case JRLT: return JALT;
|
||||
case JRLE: return JALE;
|
||||
case JRGT: return JAGT;
|
||||
case JRGE: return JAGE;
|
||||
case JRLO: return JALO;
|
||||
case JRLS: return JALS;
|
||||
case JRHI: return JAHI;
|
||||
case JRHS: return JAHS;
|
||||
case JRP: return JAP;
|
||||
case JRN: return JAN;
|
||||
case JRV: return JAV;
|
||||
case JRNV: return JANV;
|
||||
case JRNN: return JANN;
|
||||
case MC_JR: return MC_JAUC;
|
||||
case MC_JREQ: return MC_JAEQ;
|
||||
case MC_JRNE: return MC_JANE;
|
||||
case MC_JRLT: return MC_JALT;
|
||||
case MC_JRLE: return MC_JALE;
|
||||
case MC_JRGT: return MC_JAGT;
|
||||
case MC_JRGE: return MC_JAGE;
|
||||
case MC_JRLO: return MC_JALO;
|
||||
case MC_JRLS: return MC_JALS;
|
||||
case MC_JRHI: return MC_JAHI;
|
||||
case MC_JRHS: return MC_JAHS;
|
||||
case MC_JRP: return MC_JAP;
|
||||
case MC_JRN: return MC_JAN;
|
||||
case MC_JRV: return MC_JAV;
|
||||
case MC_JRNV: return MC_JANV;
|
||||
case MC_JRNN: return MC_JANN;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned getShortBranchOpcode(unsigned Opcode) {
|
||||
switch (Opcode) {
|
||||
case JUncond: return JR;
|
||||
case JAEQ: return JREQ;
|
||||
case JANE: return JRNE;
|
||||
case JALT: return JRLT;
|
||||
case JALE: return JRLE;
|
||||
case JAGT: return JRGT;
|
||||
case JAGE: return JRGE;
|
||||
case JALO: return JRLO;
|
||||
case JALS: return JRLS;
|
||||
case JAHI: return JRHI;
|
||||
case JAHS: return JRHS;
|
||||
case JAP: return JRP;
|
||||
case JAN: return JRN;
|
||||
case JAV: return JRV;
|
||||
case JANV: return JRNV;
|
||||
case JANN: return JRNN;
|
||||
case MC_JAUC: return MC_JR;
|
||||
case MC_JAEQ: return MC_JREQ;
|
||||
case MC_JANE: return MC_JRNE;
|
||||
case MC_JALT: return MC_JRLT;
|
||||
case MC_JALE: return MC_JRLE;
|
||||
case MC_JAGT: return MC_JRGT;
|
||||
case MC_JAGE: return MC_JRGE;
|
||||
case MC_JALO: return MC_JRLO;
|
||||
case MC_JALS: return MC_JRLS;
|
||||
case MC_JAHI: return MC_JRHI;
|
||||
case MC_JAHS: return MC_JRHS;
|
||||
case MC_JAP: return MC_JRP;
|
||||
case MC_JAN: return MC_JRN;
|
||||
case MC_JAV: return MC_JRV;
|
||||
case MC_JANV: return MC_JRNV;
|
||||
case MC_JANN: return MC_JRNN;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool isShortBranch(unsigned Opcode) { return getLongBranchOpcode(Opcode) != 0; }
|
||||
|
||||
} // namespace TMS34010
|
||||
} // namespace llvm
|
||||
|
||||
static MCInstrInfo *createTMS34010MCInstrInfo() {
|
||||
MCInstrInfo *X = new MCInstrInfo();
|
||||
InitTMS34010MCInstrInfo(X);
|
||||
@@ -60,6 +148,13 @@ static MCInstPrinter *createTMS34010MCInstPrinter(const Triple &T,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static MCAsmBackend *createTMS34010MCAsmBackend(const Target &T,
|
||||
const MCSubtargetInfo &STI,
|
||||
const MCRegisterInfo &MRI,
|
||||
const MCTargetOptions &Options) {
|
||||
return new TMS34010MCAsmBackend(T);
|
||||
}
|
||||
|
||||
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
|
||||
LLVMInitializeTMS34010TargetMC() {
|
||||
Target &T = getTheTMS34010Target();
|
||||
@@ -69,10 +164,6 @@ LLVMInitializeTMS34010TargetMC() {
|
||||
TargetRegistry::RegisterMCRegInfo(T, createTMS34010MCRegisterInfo);
|
||||
TargetRegistry::RegisterMCSubtargetInfo(T, createTMS34010MCSubtargetInfo);
|
||||
TargetRegistry::RegisterMCInstPrinter(T, createTMS34010MCInstPrinter);
|
||||
TargetRegistry::RegisterMCCodeEmitter(T, createTMS34010MCCodeEmitter);
|
||||
TargetRegistry::RegisterMCAsmBackend(T, createTMS34010MCAsmBackend);
|
||||
}
|
||||
|
||||
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
|
||||
LLVMInitializeTMS34010AsmParser() {}
|
||||
|
||||
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
|
||||
LLVMInitializeTMS34010Disassembler() {}
|
||||
@@ -9,8 +9,34 @@
|
||||
#ifndef LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCTARGETDESC_H
|
||||
#define LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCTARGETDESC_H
|
||||
|
||||
#include "llvm/MC/MCFixup.h"
|
||||
|
||||
namespace llvm {
|
||||
class Target;
|
||||
|
||||
namespace TMS34010 {
|
||||
|
||||
/// Target fixup kinds. FK_JR8 covers the displacement byte of the one-word
|
||||
/// relative branches: a signed word count from PC+16, i.e.
|
||||
/// `disp = (target_bit - (pc_bit + 16)) / 16`. It is PC-relative in bytes;
|
||||
/// the backend converts the byte offset to the ISA's word displacement.
|
||||
enum FixupKind {
|
||||
FK_JR8 = FirstTargetFixupKind,
|
||||
LastTargetFixupKind,
|
||||
};
|
||||
|
||||
/// True for the one-word relative JR/JR<cc> opcodes (CodeGen and MC spellings).
|
||||
bool isShortBranch(unsigned Opcode);
|
||||
|
||||
/// The absolute JA* counterpart of a relative branch, or 0 when \p Opcode is
|
||||
/// not a relative branch.
|
||||
unsigned getLongBranchOpcode(unsigned Opcode);
|
||||
|
||||
/// The relative JR* counterpart of an absolute branch, or 0 when \p Opcode is
|
||||
/// not an absolute branch.
|
||||
unsigned getShortBranchOpcode(unsigned Opcode);
|
||||
|
||||
} // namespace TMS34010
|
||||
} // namespace llvm
|
||||
|
||||
#define GET_REGINFO_ENUM
|
||||
|
||||
@@ -27,7 +27,24 @@ def TMS34010AsmWriter : AsmWriter {
|
||||
bit isMCAsmWriter = 1;
|
||||
}
|
||||
|
||||
// The operand grammar uses * - + @ and parentheses as literal tokens in the
|
||||
// MC asm strings. The default tokenizer only isolates []*!, so add the
|
||||
// punctuation that follows (or precedes) an operand reference.
|
||||
def TMS34010AsmParserVariant : AsmParserVariant {
|
||||
int Variant = 0;
|
||||
string Name = "tms34010";
|
||||
string TokenizingCharacters = "[]*!()+";
|
||||
}
|
||||
|
||||
// GSPA spells A15 "SP"; emit the alternative-register matcher so the parser
|
||||
// accepts it. The canonical register name remains A15.
|
||||
def TMS34010AsmParser : AsmParser {
|
||||
let ShouldEmitMatchRegisterAltName = 1;
|
||||
}
|
||||
|
||||
def TMS34010 : Target {
|
||||
let InstructionSet = TMS34010InstrInfo;
|
||||
let AssemblyWriters = [TMS34010AsmWriter];
|
||||
let AssemblyParsers = [TMS34010AsmParser];
|
||||
let AssemblyParserVariants = [TMS34010AsmParserVariant];
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "llvm/IR/Instruction.h"
|
||||
#include "llvm/IR/Module.h"
|
||||
#include "llvm/IR/Operator.h"
|
||||
#include "llvm/MC/MCExpr.h"
|
||||
#include "llvm/MC/MCInst.h"
|
||||
#include "llvm/MC/MCStreamer.h"
|
||||
#include "llvm/MC/TargetRegistry.h"
|
||||
@@ -66,6 +67,13 @@ public:
|
||||
/// instead of `000h`, used for zero-initialized byte arrays.
|
||||
void emitByteLines(ArrayRef<uint64_t> Bytes, bool DecimalZeros = false);
|
||||
|
||||
/// Object-mode counterparts: emit real MC data and fixups so the TI-COFF
|
||||
/// writer can produce relocations instead of GSPA source text.
|
||||
void emitDataInitializerObject(const Constant *Init);
|
||||
void emitPointerInitializerObject(const Constant *Init);
|
||||
void emitPointerReferenceObject(const GlobalValue *GV, int64_t Addend);
|
||||
void emitByteLinesObject(ArrayRef<uint64_t> Bytes);
|
||||
|
||||
static char ID;
|
||||
};
|
||||
|
||||
@@ -343,6 +351,157 @@ void TMS34010AsmPrinter::emitDataInitializer(const Constant *Init) {
|
||||
reportUnsupportedDataInitializer();
|
||||
}
|
||||
|
||||
void TMS34010AsmPrinter::emitPointerReferenceObject(const GlobalValue *GV,
|
||||
int64_t Addend) {
|
||||
const MCExpr *Expr = MCSymbolRefExpr::create(getSymbol(GV), OutContext);
|
||||
if (Addend != 0)
|
||||
Expr = MCBinaryExpr::createAdd(
|
||||
Expr, MCConstantExpr::create(Addend, OutContext), OutContext);
|
||||
OutStreamer->emitValue(Expr, 4);
|
||||
}
|
||||
|
||||
void TMS34010AsmPrinter::emitPointerInitializerObject(const Constant *Init) {
|
||||
if (Init->isNullValue()) {
|
||||
OutStreamer->emitIntValue(0, 4);
|
||||
return;
|
||||
}
|
||||
|
||||
const Value *V = Init->stripPointerCasts();
|
||||
|
||||
if (const auto *GV = dyn_cast<GlobalValue>(V)) {
|
||||
emitPointerReferenceObject(GV, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto *GEP = dyn_cast<GEPOperator>(V)) {
|
||||
const auto *BaseGV =
|
||||
dyn_cast<GlobalValue>(GEP->getPointerOperand()->stripPointerCasts());
|
||||
if (!BaseGV || GEP->getNumOperands() != 2)
|
||||
reportUnsupportedDataInitializer();
|
||||
|
||||
const auto *Idx = dyn_cast<ConstantInt>(GEP->getOperand(1));
|
||||
if (!Idx)
|
||||
reportUnsupportedDataInitializer();
|
||||
|
||||
emitPointerReferenceObject(BaseGV, Idx->getSExtValue());
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto *CE = dyn_cast<ConstantExpr>(V)) {
|
||||
if (CE->getOpcode() == Instruction::IntToPtr) {
|
||||
const GlobalValue *BaseGV = nullptr;
|
||||
int64_t Addend = 0;
|
||||
if (decomposePointerInt(CE->getOperand(0), BaseGV, Addend)) {
|
||||
emitPointerReferenceObject(BaseGV, Addend);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reportUnsupportedDataInitializer();
|
||||
}
|
||||
|
||||
void TMS34010AsmPrinter::emitByteLinesObject(ArrayRef<uint64_t> Bytes) {
|
||||
SmallString<64> Data;
|
||||
for (uint64_t Byte : Bytes)
|
||||
Data.push_back(static_cast<char>(Byte & 0xFF));
|
||||
OutStreamer->emitBytes(Data);
|
||||
}
|
||||
|
||||
void TMS34010AsmPrinter::emitDataInitializerObject(const Constant *Init) {
|
||||
if (const auto *CI = dyn_cast<ConstantInt>(Init)) {
|
||||
unsigned Bits = CI->getType()->getIntegerBitWidth();
|
||||
uint64_t Value = CI->getZExtValue();
|
||||
if (Bits <= 8)
|
||||
OutStreamer->emitIntValue(Value & 0xFF, 1);
|
||||
else if (Bits <= 16)
|
||||
OutStreamer->emitIntValue(Value & 0xFFFF, 2);
|
||||
else if (Bits <= 32)
|
||||
OutStreamer->emitIntValue(Value & 0xFFFFFFFF, 4);
|
||||
else
|
||||
reportUnsupportedDataInitializer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Init->getType()->isPointerTy()) {
|
||||
emitPointerInitializerObject(Init);
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto *CDA = dyn_cast<ConstantDataArray>(Init)) {
|
||||
Type *ElemTy = CDA->getElementType();
|
||||
if (ElemTy->isIntegerTy(8)) {
|
||||
SmallVector<uint64_t, 32> Bytes;
|
||||
for (unsigned I = 0, E = CDA->getNumElements(); I != E; ++I)
|
||||
Bytes.push_back(CDA->getElementAsInteger(I) & 0xFF);
|
||||
emitByteLinesObject(Bytes);
|
||||
return;
|
||||
}
|
||||
if (ElemTy->isIntegerTy(16) || ElemTy->isIntegerTy(32) ||
|
||||
ElemTy->isPointerTy()) {
|
||||
for (unsigned I = 0, E = CDA->getNumElements(); I != E; ++I)
|
||||
emitDataInitializerObject(CDA->getElementAsConstant(I));
|
||||
return;
|
||||
}
|
||||
reportUnsupportedDataInitializer();
|
||||
}
|
||||
|
||||
if (const auto *CA = dyn_cast<ConstantArray>(Init)) {
|
||||
Type *ElemTy = CA->getType()->getElementType();
|
||||
if (ElemTy->isArrayTy()) {
|
||||
for (const Use &U : CA->operands())
|
||||
emitDataInitializerObject(cast<Constant>(U.get()));
|
||||
return;
|
||||
}
|
||||
if (ElemTy->isIntegerTy(8)) {
|
||||
SmallVector<uint64_t, 32> Bytes;
|
||||
for (const Use &U : CA->operands()) {
|
||||
const auto *CI = dyn_cast<ConstantInt>(U.get());
|
||||
if (!CI)
|
||||
reportUnsupportedDataInitializer();
|
||||
Bytes.push_back(CI->getZExtValue() & 0xFF);
|
||||
}
|
||||
emitByteLinesObject(Bytes);
|
||||
return;
|
||||
}
|
||||
if (ElemTy->isIntegerTy(16) || ElemTy->isIntegerTy(32) ||
|
||||
ElemTy->isPointerTy()) {
|
||||
for (const Use &U : CA->operands())
|
||||
emitDataInitializerObject(cast<Constant>(U.get()));
|
||||
return;
|
||||
}
|
||||
reportUnsupportedDataInitializer();
|
||||
}
|
||||
|
||||
if (isa<ConstantAggregateZero>(Init)) {
|
||||
Type *Ty = Init->getType();
|
||||
if (Ty->isIntegerTy()) {
|
||||
unsigned Bits = Ty->getIntegerBitWidth();
|
||||
if (Bits <= 8)
|
||||
OutStreamer->emitIntValue(0, 1);
|
||||
else if (Bits <= 16)
|
||||
OutStreamer->emitIntValue(0, 2);
|
||||
else if (Bits <= 32)
|
||||
OutStreamer->emitIntValue(0, 4);
|
||||
else
|
||||
reportUnsupportedDataInitializer();
|
||||
return;
|
||||
}
|
||||
if (Ty->isPointerTy()) {
|
||||
OutStreamer->emitIntValue(0, 4);
|
||||
return;
|
||||
}
|
||||
if (Ty->isArrayTy()) {
|
||||
uint64_t Size = getDataLayout().getTypeAllocSize(Ty).getFixedValue();
|
||||
OutStreamer->emitZeros(Size);
|
||||
return;
|
||||
}
|
||||
reportUnsupportedDataInitializer();
|
||||
}
|
||||
|
||||
reportUnsupportedDataInitializer();
|
||||
}
|
||||
|
||||
bool TMS34010AsmPrinter::doInitialization(Module &M) {
|
||||
AsmPrinter::doInitialization(M);
|
||||
|
||||
@@ -364,19 +523,43 @@ void TMS34010AsmPrinter::emitDataGlobal(const GlobalVariable *GV) {
|
||||
reportUnsupportedDataInitializer();
|
||||
|
||||
OutStreamer->switchSection(getObjFileLowering().getDataSection());
|
||||
const DataLayout &DL = GV->getParent()->getDataLayout();
|
||||
Align Alignment = getGVAlignment(GV, DL, GV->getAlign().valueOrOne());
|
||||
|
||||
if (!OutStreamer->hasRawTextSupport()) {
|
||||
// Object mode: emit real bytes and fixups. The alignment must precede the
|
||||
// label so the symbol value and the emitted data agree.
|
||||
if (Alignment.value() >= 2)
|
||||
OutStreamer->emitValueToAlignment(Alignment);
|
||||
if (!GV->hasLocalLinkage())
|
||||
OutStreamer->emitSymbolAttribute(getSymbol(GV), MCSA_Global);
|
||||
OutStreamer->emitLabel(getSymbol(GV));
|
||||
emitDataInitializerObject(Init);
|
||||
// TI-COFF sections are word-granular. An odd-length byte array (for
|
||||
// example a 7-byte string literal) would leave .data on an odd byte and
|
||||
// the VT1200 loader rejects the section (status 4). Pad each object to a
|
||||
// whole word; the padding belongs to the section, not the symbol.
|
||||
if (DL.getTypeAllocSize(Init->getType()).getFixedValue() % 2)
|
||||
OutStreamer->emitIntValue(0, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// GSPA implicitly aligns `.word`/`.long` to a 16-bit boundary but does not
|
||||
// move the label that precedes the directive. After an odd-length object
|
||||
// (for example the 5-byte "0::0" string) the label would point one byte
|
||||
// before its value. Emit the alignment explicitly so the label and the
|
||||
// emitted bytes agree.
|
||||
const DataLayout &DL = GV->getParent()->getDataLayout();
|
||||
Align Alignment = getGVAlignment(GV, DL, GV->getAlign().valueOrOne());
|
||||
if (Alignment.value() >= 2)
|
||||
OutStreamer->emitRawText("\t.even\n");
|
||||
if (!GV->hasLocalLinkage())
|
||||
OutStreamer->emitSymbolAttribute(getSymbol(GV), MCSA_Global);
|
||||
OutStreamer->emitRawText(getSymbol(GV)->getName() + ":\n");
|
||||
emitDataInitializer(Init);
|
||||
// Keep the object's .data a whole number of 16-bit words. An odd byte
|
||||
// array would otherwise leave the section odd; GSPLNK concatenates input
|
||||
// sections without realigning them, and the loader requires word sizes.
|
||||
if (DL.getTypeAllocSize(Init->getType()).getFixedValue() % 2)
|
||||
OutStreamer->emitRawText("\t.byte 0\n");
|
||||
}
|
||||
|
||||
void TMS34010AsmPrinter::emitGlobalVariable(const GlobalVariable *GV) {
|
||||
@@ -392,8 +575,27 @@ void TMS34010AsmPrinter::emitGlobalVariable(const GlobalVariable *GV) {
|
||||
// .bss line. Sizes and alignment in GSPA are in bits.
|
||||
const DataLayout &DL = GV->getParent()->getDataLayout();
|
||||
Align Alignment = getGVAlignment(GV, DL, GV->getAlign().valueOrOne());
|
||||
uint64_t SizeBits =
|
||||
DL.getTypeAllocSize(GV->getValueType()).getFixedValue() * 8;
|
||||
uint64_t SizeBytes =
|
||||
DL.getTypeAllocSize(GV->getValueType()).getFixedValue();
|
||||
|
||||
if (!OutStreamer->hasRawTextSupport()) {
|
||||
OutStreamer->switchSection(getObjFileLowering().getBSSSection());
|
||||
// A TI-COFF .bss allocation is word-granular. GSPA advances `.bss`
|
||||
// allocations to a 16-bit boundary (even when the object is a char or i1),
|
||||
// and the VT1200 loader rejects a section whose bit size is not a whole
|
||||
// number of words. Use the IR alignment but never less than two bytes,
|
||||
// then pad the allocation to a word.
|
||||
Align BSSAlign(std::max<unsigned>(Alignment.value(), 2));
|
||||
OutStreamer->emitValueToAlignment(BSSAlign);
|
||||
if (!GV->hasLocalLinkage())
|
||||
OutStreamer->emitSymbolAttribute(getSymbol(GV), MCSA_Global);
|
||||
OutStreamer->emitLabel(getSymbol(GV));
|
||||
OutStreamer->emitZeros(SizeBytes);
|
||||
OutStreamer->emitValueToAlignment(Align(2));
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t SizeBits = ((SizeBytes + 1) & ~1ULL) * 8;
|
||||
if (!GV->hasLocalLinkage())
|
||||
OutStreamer->emitSymbolAttribute(getSymbol(GV), MCSA_Global);
|
||||
OutStreamer->emitRawText(Twine("\t.bss\t") + getSymbol(GV)->getName() + "," +
|
||||
|
||||
@@ -13,22 +13,44 @@
|
||||
//
|
||||
// The prologue keeps the caller's FP and the used callee-saved registers on
|
||||
// the system stack, then points FP (A13) at the incoming program stack and
|
||||
// allocates the local frame above it. Each register is saved with its own
|
||||
// MOVE push, which is ABI-identical to one MMTM slot.
|
||||
// allocates the local frame above it. FP is one push and the callee-save set
|
||||
// is one MMTM register-list push; the matching epilogue is one MMFM followed
|
||||
// by the FP pop. MMTM walks the list low-to-high and predecrements SP for each
|
||||
// selected register, so the resulting slots are the same as one push per
|
||||
// register in ascending order.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "TMS34010FrameLowering.h"
|
||||
#include "TMS34010.h"
|
||||
#include "TMS34010InstrInfo.h"
|
||||
#include "TMS34010RegisterInfo.h"
|
||||
#include "TMS34010Subtarget.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/CodeGen/MachineFrameInfo.h"
|
||||
#include "llvm/CodeGen/MachineFunction.h"
|
||||
#include "llvm/CodeGen/MachineInstrBuilder.h"
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
namespace {
|
||||
|
||||
/// Encode the callee-save set as an MMTM/MMFM register list. The two
|
||||
/// directions number the A file from opposite ends: MMTM's mask has A0 in bit
|
||||
/// 15 and A15 in bit 0, MMFM's mask has A0 in bit 0. The list therefore
|
||||
/// remains ascending in both cases.
|
||||
uint16_t getCalleeSaveMask(const MachineFrameInfo &MFI,
|
||||
const TMS34010RegisterInfo &TRI, bool Restore) {
|
||||
uint16_t Mask = 0;
|
||||
for (const CalleeSavedInfo &CSI : MFI.getCalleeSavedInfo()) {
|
||||
unsigned Enc = TRI.getEncodingValue(CSI.getReg());
|
||||
assert(Enc < 16 && "the callee-save set is in the A register file");
|
||||
Mask |= static_cast<uint16_t>(1u << (Restore ? Enc : 15 - Enc));
|
||||
}
|
||||
return Mask;
|
||||
}
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
StackOffset
|
||||
TMS34010FrameLowering::getFrameIndexReference(const MachineFunction &MF, int FI,
|
||||
Register &FrameReg) const {
|
||||
@@ -54,15 +76,22 @@ void TMS34010FrameLowering::emitPrologue(MachineFunction &MF,
|
||||
DebugLoc DL = MBBI != MBB.end() ? MBBI->getDebugLoc() : DebugLoc();
|
||||
|
||||
// The field-size invariant comes first so that every frame access below is
|
||||
// a 32-bit access. FS0 = 16 keeps a later `short` load/store correct even
|
||||
// if the caller left it at 32.
|
||||
// a 32-bit access. FS0 = 16 sign-extending keeps a later `short` load/store
|
||||
// correct even if the caller left it at 32.
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::SETF16));
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::SETF32));
|
||||
|
||||
// Save the caller's frame pointer, then the used callee-saved registers.
|
||||
// Save the caller's frame pointer, then the used callee-saved registers in
|
||||
// one register-list push. The FP slot stays where it was: MMTM pushes the
|
||||
// list below it, exactly like one push per register.
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::PUSH32)).addReg(TMS34010::A13);
|
||||
for (const CalleeSavedInfo &CSI : MFI.getCalleeSavedInfo())
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::PUSH32)).addReg(CSI.getReg());
|
||||
const std::vector<CalleeSavedInfo> &CSI = MFI.getCalleeSavedInfo();
|
||||
if (!CSI.empty()) {
|
||||
const auto *TRI = STI.getRegisterInfo();
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::MMTM))
|
||||
.addReg(TMS34010::A15)
|
||||
.addImm(getCalleeSaveMask(MFI, *TRI, /*Restore=*/false));
|
||||
}
|
||||
|
||||
// FP = STK, then allocate the local frame.
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::SETFP));
|
||||
@@ -90,14 +119,27 @@ void TMS34010FrameLowering::emitEpilogue(MachineFunction &MF,
|
||||
// its old STK below the return address: SP + (NumSaved + 1) * 32.
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::LOADSTK))
|
||||
.addImm((NumSaved + 1) * 32);
|
||||
} else if (MFI.hasVarSizedObjects()) {
|
||||
// A no-argument function has no saved old STK to reload, and A14 points
|
||||
// somewhere above the fixed frame after a dynamic alloca. Rebuild it from
|
||||
// the frame pointer (FP + fixed frame size), discarding the dynamic area.
|
||||
int64_t FrameBits = MFI.getStackSize() * 8;
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::MOVE), TMS34010::A14)
|
||||
.addReg(TMS34010::A13);
|
||||
if (FrameBits)
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::ADDSTK)).addImm(FrameBits);
|
||||
} else {
|
||||
int64_t FrameBits = MFI.getStackSize() * 8;
|
||||
if (FrameBits)
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::ADDSTK)).addImm(-FrameBits);
|
||||
}
|
||||
|
||||
// Restore the callee-saved registers in reverse order, then the old FP.
|
||||
for (const CalleeSavedInfo &CI : llvm::reverse(CSI))
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::POP32), CI.getReg());
|
||||
// Restore the callee-saved register list in one MMFM, then the old FP.
|
||||
if (!CSI.empty()) {
|
||||
const auto *TRI = STI.getRegisterInfo();
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::MMFM))
|
||||
.addReg(TMS34010::A15)
|
||||
.addImm(getCalleeSaveMask(MFI, *TRI, /*Restore=*/true));
|
||||
}
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::POP32), TMS34010::A13);
|
||||
}
|
||||
@@ -20,8 +20,11 @@
|
||||
#include "llvm/CodeGen/TargetRegisterInfo.h"
|
||||
#include "llvm/IR/Function.h"
|
||||
#include "llvm/IR/GlobalValue.h"
|
||||
#include "llvm/IR/Intrinsics.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
#include "llvm/Support/MathExtras.h"
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
using namespace llvm;
|
||||
@@ -58,6 +61,17 @@ TMS34010TargetLowering::TMS34010TargetLowering(const TargetMachine &TM,
|
||||
setLoadExtAction(ISD::EXTLOAD, MVT::i32, MVT::i8, Custom);
|
||||
setLoadExtAction(ISD::EXTLOAD, MVT::i32, MVT::i16, Custom);
|
||||
|
||||
// Dynamic allocas grow the upward-growing program stack (A14). They are
|
||||
// lowered explicitly (LowerOperation) rather than by the generic expander:
|
||||
// the generic path derives the direction from the frame lowering, while the
|
||||
// TMS34010 frame keeps LLVM's "objects below FP" convention and moves A14
|
||||
// above the fixed frame. The explicit lowering also lets STACKSAVE/
|
||||
// STACKRESTORE use A14 as their pointer.
|
||||
setOperationAction(ISD::DYNAMIC_STACKALLOC, MVT::i32, Custom);
|
||||
setOperationAction(ISD::STACKSAVE, MVT::Other, Expand);
|
||||
setOperationAction(ISD::STACKRESTORE, MVT::Other, Expand);
|
||||
setStackPointerRegisterToSaveRestore(TMS34010::A14);
|
||||
|
||||
// Multiply and divide are single register-register instructions (MPYS/DIVS/
|
||||
// DIVU) with an odd tied destination; see TMS34010InstrInfo.td. The
|
||||
// remainder has no direct 32-bit form, so it is expanded from the quotient
|
||||
@@ -133,6 +147,24 @@ EVT TMS34010TargetLowering::getSetCCResultType(const DataLayout &DL,
|
||||
return MVT::i32;
|
||||
}
|
||||
|
||||
bool TMS34010TargetLowering::findOptimalMemOpLowering(
|
||||
LLVMContext &Context, std::vector<EVT> &MemOps, unsigned Limit,
|
||||
const MemOp &Op, unsigned DstAS, unsigned SrcAS,
|
||||
const AttributeList &FuncAttributes, EVT *LargestVT) const {
|
||||
(void)Context;
|
||||
(void)MemOps;
|
||||
(void)Limit;
|
||||
(void)Op;
|
||||
(void)DstAS;
|
||||
(void)SrcAS;
|
||||
(void)FuncAttributes;
|
||||
(void)LargestVT;
|
||||
// The generic expansion forms byte-offset loads/stores on the pointer
|
||||
// values, but TMS34010 pointers are bit addresses. TMS34010SelectionDAGInfo
|
||||
// emits the bit-scaled sequence instead.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TMS34010TargetLowering::allowsMisalignedMemoryAccesses(
|
||||
LLT VT, unsigned AddrSpace, Align Alignment,
|
||||
MachineMemOperand::Flags Flags, unsigned *Fast) const {
|
||||
@@ -198,28 +230,54 @@ SDValue TMS34010TargetLowering::LowerFormalArguments(
|
||||
false);
|
||||
}
|
||||
|
||||
// Every explicit argument is passed on the program stack. Argument i lives
|
||||
// one 32-bit slot below FP for each preceding argument, so its fixed object
|
||||
// is at byte offset -(i + 1) * 4 (bit offset -(i + 1) * 32 after frame
|
||||
// elimination).
|
||||
for (unsigned i = 0, e = Ins.size(); i != e; ++i) {
|
||||
const ISD::InputArg &In = Ins[i];
|
||||
// Every explicit argument is passed on the program stack. The caller pushes
|
||||
// the arguments last-first, so argument 0 occupies the topmost slot directly
|
||||
// below FP and argument i starts at FP minus the total size of arguments
|
||||
// 0..i. A plain argument occupies one 32-bit slot; a byval argument
|
||||
// occupies its object size rounded up to 16 bits (the TI rule) and its
|
||||
// incoming value is the address of the copy, not a load from that address.
|
||||
SmallVector<int64_t, 8> ArgOffsets(Ins.size(), 0);
|
||||
int64_t Offset = 0;
|
||||
for (unsigned I = 0; I != Ins.size(); ++I) {
|
||||
const ISD::InputArg &In = Ins[I];
|
||||
// `returned` is an optimization hint, not an ABI feature: the argument is
|
||||
// still passed in its normal stack slot, and the callee already returns
|
||||
// through the ABI's A8. The optimizer infers it for ordinary C such as
|
||||
// `int first(int a, int b) { return a; }`, so rejecting it made valid -O2
|
||||
// input uncompilable. The ABI-affecting flags stay rejected.
|
||||
if (In.Flags.isSRet() || In.Flags.isByVal() || In.Flags.isInReg() ||
|
||||
In.Flags.isNest())
|
||||
if (In.Flags.isSRet() || In.Flags.isInAlloca() ||
|
||||
In.Flags.isPreallocated() || In.Flags.isInReg() || In.Flags.isNest())
|
||||
report_fatal_error(
|
||||
"M2 supports only plain arguments (no sret/byval/inreg/nest)",
|
||||
"M2 supports only plain arguments "
|
||||
"(no sret/inalloca/preallocated/inreg/nest)",
|
||||
false);
|
||||
if (In.Flags.isByVal()) {
|
||||
uint64_t Size = In.Flags.getByValSize();
|
||||
if (!Size)
|
||||
report_fatal_error("TMS34010 byval argument has zero size", false);
|
||||
Offset += alignTo(Size, 2);
|
||||
} else {
|
||||
Offset += 4;
|
||||
}
|
||||
ArgOffsets[I] = -Offset;
|
||||
}
|
||||
|
||||
for (unsigned i = 0, e = Ins.size(); i != e; ++i) {
|
||||
const ISD::InputArg &In = Ins[i];
|
||||
if (In.VT != MVT::i32)
|
||||
report_fatal_error(
|
||||
"TMS34010 supports only integer (<=32 bit) and pointer arguments",
|
||||
false);
|
||||
|
||||
int FI = MFI.CreateFixedObject(4, -static_cast<int64_t>(i + 1) * 4, true);
|
||||
int64_t ArgOffset = ArgOffsets[i];
|
||||
if (In.Flags.isByVal()) {
|
||||
uint64_t Size = In.Flags.getByValSize();
|
||||
int FI = MFI.CreateFixedObject(Size, ArgOffset, true);
|
||||
InVals.push_back(DAG.getFrameIndex(FI, getPointerTy(DAG.getDataLayout())));
|
||||
continue;
|
||||
}
|
||||
|
||||
int FI = MFI.CreateFixedObject(4, ArgOffset, true);
|
||||
SDValue FIN = DAG.getFrameIndex(FI, getPointerTy(DAG.getDataLayout()));
|
||||
SDValue Load =
|
||||
DAG.getLoad(MVT::i32, DL, Chain, FIN,
|
||||
@@ -242,6 +300,27 @@ SDValue TMS34010TargetLowering::LowerCall(
|
||||
// the marker and lower an ordinary call rather than failing on it.
|
||||
CLI.IsTailCall = false;
|
||||
|
||||
// A statepoint call is lowered without CALLSEQ_START/CALLSEQ_END: this
|
||||
// target pushes arguments and calls directly. StatepointLowering expects
|
||||
// the standard call sequence and asserts on it, so reject the feature with
|
||||
// a diagnostic instead of letting the generic code crash. Stackmap and
|
||||
// patchpoint calls do not reach LowerCall, so only the statepoint intrinsic
|
||||
// is scanned for here. The scan is per call, but only functions with a GC
|
||||
// strategy pay for it; ordinary TMS34010 code has none. A statepoint in a
|
||||
// function without a GC strategy is invalid IR and fails earlier in
|
||||
// upstream GC lowering (Function::getGC), before target lowering runs.
|
||||
if (MF.getFunction().hasGC()) {
|
||||
for (const BasicBlock &BB : MF.getFunction())
|
||||
for (const Instruction &I : BB)
|
||||
if (const auto *CB = dyn_cast<CallBase>(&I))
|
||||
if (CB->getIntrinsicID() == Intrinsic::experimental_gc_statepoint)
|
||||
report_fatal_error(
|
||||
"TMS34010 does not support gc.statepoint calls", false);
|
||||
}
|
||||
if (CLI.CB && CLI.CB->hasOperandBundles())
|
||||
report_fatal_error(
|
||||
"TMS34010 does not support calls with operand bundles", false);
|
||||
|
||||
// A direct call names a global function. Anything else is an indirect call
|
||||
// through a 32-bit bit address (a function pointer, a plain integer, or the
|
||||
// inttoptr of an absolute ROM entry). External-symbol (asm label) callees
|
||||
@@ -267,17 +346,21 @@ SDValue TMS34010TargetLowering::LowerCall(
|
||||
// per variadic argument, which is exactly what compat.c's sprintf does.
|
||||
|
||||
// Check the original IR types: legalization may promote an i8 or split an
|
||||
// aggregate before the arguments reach Outs/OutVals.
|
||||
if (!CLI.RetTy->isVoidTy() && !CLI.RetTy->isIntegerTy(32) &&
|
||||
// aggregate before the arguments reach Outs/OutVals. A narrow return is
|
||||
// promoted to an i32 in A8; the caller's getCopyFromParts applies the
|
||||
// zeroext/signext assertion and truncation.
|
||||
if (!CLI.RetTy->isVoidTy() &&
|
||||
!(CLI.RetTy->isIntegerTy() && CLI.RetTy->getIntegerBitWidth() <= 32) &&
|
||||
!CLI.RetTy->isPointerTy())
|
||||
report_fatal_error("M2 supports only void/i32/pointer returns", false);
|
||||
report_fatal_error("M2 supports only void/integer(<=32)/pointer returns",
|
||||
false);
|
||||
|
||||
for (const TargetLowering::ArgListEntry &Arg : CLI.Args) {
|
||||
if (Arg.IsSRet || Arg.IsByVal || Arg.IsInReg || Arg.IsNest ||
|
||||
Arg.IsInAlloca || Arg.IsPreallocated)
|
||||
if (Arg.IsSRet || Arg.IsInReg || Arg.IsNest || Arg.IsInAlloca ||
|
||||
Arg.IsPreallocated)
|
||||
report_fatal_error(
|
||||
"M2 supports only plain call arguments "
|
||||
"(no sret/byval/inreg/nest/inalloca/preallocated)",
|
||||
"M2 supports only plain and byval call arguments "
|
||||
"(no sret/inreg/nest/inalloca/preallocated)",
|
||||
false);
|
||||
Type *Ty = Arg.OrigTy;
|
||||
if (!Ty->isPointerTy() &&
|
||||
@@ -306,9 +389,35 @@ SDValue TMS34010TargetLowering::LowerCall(
|
||||
0);
|
||||
|
||||
// Arguments are pushed last-first, so the first argument ends up at FP(-32).
|
||||
// Each push advances A14; the chain keeps the pushes in order.
|
||||
// Each push advances A14; the chain keeps the pushes in order. A byval
|
||||
// argument is copied into the argument area at the current top of the
|
||||
// program stack and the area is advanced by its slot size. The callee gets
|
||||
// the address of that copy.
|
||||
for (unsigned I = CLI.Outs.size(); I != 0; --I) {
|
||||
SDValue Arg = CLI.OutVals[I - 1];
|
||||
const ISD::OutputArg &Out = CLI.Outs[I - 1];
|
||||
if (Out.Flags.isByVal()) {
|
||||
uint64_t Size = Out.Flags.getByValSize();
|
||||
if (!Size)
|
||||
report_fatal_error("TMS34010 byval argument has zero size", false);
|
||||
uint64_t SlotSize = alignTo(Size, 2);
|
||||
|
||||
SDValue SP = DAG.getCopyFromReg(Chain, DL, TMS34010::A14, MVT::i32);
|
||||
Chain = SP.getValue(1);
|
||||
Chain = DAG.getMemcpy(
|
||||
Chain, DL, SP, Arg, DAG.getConstant(Size, DL, MVT::i32), Align(2),
|
||||
Align(2), /*isVolatile=*/false, /*AlwaysInline=*/true,
|
||||
/*CI=*/nullptr, std::nullopt,
|
||||
MachinePointerInfo::getUnknownStack(MF),
|
||||
MachinePointerInfo::getUnknownStack(MF));
|
||||
// ADDSTK and the frame adjustments are in bits; SlotSize is in bytes.
|
||||
SDValue SlotVal =
|
||||
DAG.getSignedTargetConstant(SlotSize * 8, DL, MVT::i32);
|
||||
Chain = SDValue(
|
||||
DAG.getMachineNode(TMS34010::ADDSTK, DL, MVT::Other, SlotVal, Chain),
|
||||
0);
|
||||
continue;
|
||||
}
|
||||
Chain = SDValue(
|
||||
DAG.getMachineNode(TMS34010::PUSHARG, DL, MVT::Other, Arg, Chain), 0);
|
||||
}
|
||||
@@ -364,7 +473,8 @@ SDValue TMS34010TargetLowering::LowerCall(
|
||||
DAG.getMachineNode(CallOpc, DL, MVT::Other, Target, RegMask, Chain), 0);
|
||||
|
||||
// ROM/Xlib code clobbers the field-size state, so re-establish both fields
|
||||
// before any further access: FS0 = 16 for `short` and FS1 = 32 for words.
|
||||
// before any further access: FS0 = 16 sign-extending for `short` and FS1 = 32
|
||||
// for words.
|
||||
Chain = SDValue(
|
||||
DAG.getMachineNode(TMS34010::SETF16, DL, MVT::Other, Chain), 0);
|
||||
Chain = SDValue(
|
||||
@@ -386,10 +496,14 @@ bool TMS34010TargetLowering::CanLowerReturn(
|
||||
const Type *RetTy) const {
|
||||
// Returning false here makes SelectionDAG demote the return value to an
|
||||
// sret pointer, silently changing the signature to something M2 cannot
|
||||
// lower either. Reject unsupported returns loudly instead.
|
||||
if (RetTy->isVoidTy() || RetTy->isIntegerTy(32) || RetTy->isPointerTy())
|
||||
// lower either. Reject unsupported returns loudly instead. Narrow integer
|
||||
// returns are promoted to i32 in A8 and the zeroext/signext attribute is
|
||||
// applied by the caller's getCopyFromParts, so i8/i16 are accepted.
|
||||
if (RetTy->isVoidTy() || RetTy->isPointerTy() ||
|
||||
(RetTy->isIntegerTy() && RetTy->getIntegerBitWidth() <= 32))
|
||||
return true;
|
||||
report_fatal_error("M2 supports only void/i32/pointer returns", false);
|
||||
report_fatal_error("M2 supports only void/integer(<=32)/pointer returns",
|
||||
false);
|
||||
}
|
||||
|
||||
SDValue TMS34010TargetLowering::LowerReturn(
|
||||
@@ -403,11 +517,22 @@ SDValue TMS34010TargetLowering::LowerReturn(
|
||||
(void)IsVarArg;
|
||||
|
||||
// A scalar result is returned in A8. A8 is an implicit use of RETS, so the
|
||||
// copy stays live through the terminator.
|
||||
// copy stays live through the terminator. The type legalizer has promoted a
|
||||
// narrow integer return to i32 and applied zeroext/signext already; if an
|
||||
// i8/i16 ever reaches here, extend it explicitly according to the return
|
||||
// attribute so A8 still holds the ABI-promoted value.
|
||||
if (!Outs.empty()) {
|
||||
if (Outs.size() != 1 || Outs[0].VT != MVT::i32)
|
||||
report_fatal_error("M2 supports only void/i32/pointer returns", false);
|
||||
Chain = DAG.getCopyToReg(Chain, DL, TMS34010::A8, OutVals[0]);
|
||||
if (Outs.size() != 1 || !Outs[0].VT.isInteger() ||
|
||||
Outs[0].VT.getSizeInBits() > 32)
|
||||
report_fatal_error("M2 supports only void/integer(<=32)/pointer returns",
|
||||
false);
|
||||
SDValue Val = OutVals[0];
|
||||
if (Val.getValueType() != MVT::i32) {
|
||||
unsigned ExtOpc = Outs[0].Flags.isSExt() ? ISD::SIGN_EXTEND
|
||||
: ISD::ZERO_EXTEND;
|
||||
Val = DAG.getNode(ExtOpc, DL, MVT::i32, Val);
|
||||
}
|
||||
Chain = DAG.getCopyToReg(Chain, DL, TMS34010::A8, Val);
|
||||
}
|
||||
|
||||
// RETS must pop the caller's argument words: 2 when the function has
|
||||
@@ -457,14 +582,12 @@ static unsigned getBranchOpcode(ISD::CondCode CC) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit CMP/CMPI for `LHS - RHS` followed by the conditional branch that
|
||||
/// Emit a CMPBR/CMPIBR pseudo for `LHS - RHS` and the conditional branch that
|
||||
/// implements `CC`, targeting \p Dest. The flags always represent
|
||||
/// `LHS - RHS`, so CMP takes RHS as its source and LHS as its destination.
|
||||
///
|
||||
/// The compare produces a Glue result that is passed to the branch. Glued
|
||||
/// DAG nodes are scheduled adjacently, so no flag-writing instruction can be
|
||||
/// placed between the compare and the branch (the branch would otherwise test
|
||||
/// the flags of that instruction instead).
|
||||
/// `LHS - RHS`, so the compare takes RHS as its source and LHS as its
|
||||
/// destination. The pseudo stays a single terminator until the post-regalloc
|
||||
/// pass splits it into adjacent CMP/CMPI and Jcc instructions, so no
|
||||
/// flag-writing instruction can be placed between them.
|
||||
static SDValue emitCmpAndBranch(SDValue Chain, SDValue LHS, SDValue RHS,
|
||||
ISD::CondCode CC, SDValue Dest,
|
||||
SelectionDAG &DAG, const SDLoc &DL) {
|
||||
@@ -485,26 +608,24 @@ static SDValue emitCmpAndBranch(SDValue Chain, SDValue LHS, SDValue RHS,
|
||||
}
|
||||
|
||||
unsigned BranchOpc = getBranchOpcode(CC);
|
||||
SDValue Cmp;
|
||||
SDValue Br = DAG.getTargetConstant(BranchOpc, DL, MVT::i32);
|
||||
|
||||
// Keep the compare and branch as one terminator until the post-regalloc
|
||||
// pass. The ST flags live in a physical register and a MOVE between the
|
||||
// pair would overwrite them; gluing CMP to Jcc is not enough because a PHI
|
||||
// copy can be inserted between the two during register allocation or
|
||||
// scheduling.
|
||||
if (const auto *C = dyn_cast<ConstantSDNode>(RHS)) {
|
||||
// Signed form: a 32-bit immediate with the top bit set (or a negative
|
||||
// constant) must truncate to i32, not sign-extend to uint64_t.
|
||||
SDValue Imm =
|
||||
DAG.getSignedTargetConstant(C->getSExtValue(), DL, MVT::i32);
|
||||
Cmp = SDValue(DAG.getMachineNode(TMS34010::CMPI, DL, MVT::Other,
|
||||
MVT::Glue, Imm, LHS, Chain),
|
||||
0);
|
||||
} else {
|
||||
Cmp = SDValue(DAG.getMachineNode(TMS34010::CMP, DL, MVT::Other, MVT::Glue,
|
||||
RHS, LHS, Chain),
|
||||
0);
|
||||
return SDValue(DAG.getMachineNode(TMS34010::CMPIBR, DL, MVT::Other,
|
||||
{Dest, LHS, Imm, Br, Chain}),
|
||||
0);
|
||||
}
|
||||
|
||||
// Results: 0 = chain, 1 = glue. The branch takes the compare's chain and,
|
||||
// as its final operand, the glue that pins it directly after the compare.
|
||||
SDValue Glue = Cmp.getValue(1);
|
||||
return SDValue(DAG.getMachineNode(BranchOpc, DL, MVT::Other, Dest,
|
||||
Cmp.getValue(0), Glue),
|
||||
return SDValue(DAG.getMachineNode(TMS34010::CMPBR, DL, MVT::Other,
|
||||
{Dest, LHS, RHS, Br, Chain}),
|
||||
0);
|
||||
}
|
||||
|
||||
@@ -582,6 +703,47 @@ SDValue TMS34010TargetLowering::LowerSELECT_CC(SDValue Op,
|
||||
SDValue TMS34010TargetLowering::LowerOperation(SDValue Op,
|
||||
SelectionDAG &DAG) const {
|
||||
switch (Op.getOpcode()) {
|
||||
case ISD::DYNAMIC_STACKALLOC: {
|
||||
// The TMS34010 program stack grows upward: A14 points one past the fixed
|
||||
// frame, so the new object starts at A14 and A14 is advanced past it. The
|
||||
// returned pointer is the old (possibly alignment-adjusted) A14. A call
|
||||
// saves this A14 on the system stack and restores it, so the dynamic area
|
||||
// stays live across calls; the epilogue restores A14 from FP, discarding
|
||||
// the whole dynamic area.
|
||||
SDLoc DL(Op);
|
||||
SDValue Chain = Op.getOperand(0);
|
||||
SDValue Size = Op.getOperand(1);
|
||||
Align Alignment = cast<ConstantSDNode>(Op.getOperand(2))->getAlignValue();
|
||||
|
||||
// SelectionDAGBuilder::visitAlloca gives a byte count, but A14 and every
|
||||
// other TMS34010 pointer are bit addresses. Keep the alignment test in
|
||||
// bytes, but convert both the size and the alignment mask to bits.
|
||||
assert(Size.getValueType() == MVT::i32 &&
|
||||
"dynamic alloca size is an i32 byte count");
|
||||
SDValue SizeBits = DAG.getNode(ISD::SHL, DL, MVT::i32, Size,
|
||||
DAG.getConstant(3, DL, MVT::i32));
|
||||
assert(SizeBits.getOpcode() == ISD::SHL &&
|
||||
isa<ConstantSDNode>(SizeBits.getOperand(1)) &&
|
||||
cast<ConstantSDNode>(SizeBits.getOperand(1))->getZExtValue() == 3 &&
|
||||
"dynamic alloca size must be scaled by 3 bits per byte");
|
||||
|
||||
SDValue SP = DAG.getCopyFromReg(Chain, DL, TMS34010::A14, MVT::i32);
|
||||
Chain = SP.getValue(1);
|
||||
SDValue Ptr = SP;
|
||||
if (Alignment > Align(2)) {
|
||||
uint64_t AlignBits = Alignment.value() * 8;
|
||||
SDValue Bias = DAG.getConstant(AlignBits - 1, DL, MVT::i32);
|
||||
SDValue Mask =
|
||||
DAG.getSignedConstant(-static_cast<int64_t>(AlignBits), DL,
|
||||
MVT::i32);
|
||||
Ptr = DAG.getNode(ISD::ADD, DL, MVT::i32, Ptr, Bias);
|
||||
Ptr = DAG.getNode(ISD::AND, DL, MVT::i32, Ptr, Mask);
|
||||
}
|
||||
SDValue NewSP = DAG.getNode(ISD::ADD, DL, MVT::i32, Ptr, SizeBits);
|
||||
Chain = DAG.getCopyToReg(Chain, DL, TMS34010::A14, NewSP);
|
||||
SDValue Ops[] = {Ptr, Chain};
|
||||
return DAG.getMergeValues(Ops, DL);
|
||||
}
|
||||
case ISD::LOAD:
|
||||
// A zero-extending (or any-extending) narrow load becomes the legal
|
||||
// sign-extending load plus an ANDI with the width mask. Any extension may
|
||||
@@ -693,7 +855,7 @@ MachineBasicBlock *TMS34010TargetLowering::emitSelectCC(
|
||||
MachineOperand RHSMO = MI.getOperand(4);
|
||||
unsigned BranchOpc = MI.getOperand(5).getImm();
|
||||
|
||||
// Build: ThisMBB: CMP; Jcc TrueMBB
|
||||
// Build: ThisMBB: CMPBR/CMPIBR TrueMBB
|
||||
// IfFalseMBB: false value; JAUC SinkMBB
|
||||
// TrueMBB: true value
|
||||
// SinkMBB: PHI
|
||||
@@ -718,15 +880,18 @@ MachineBasicBlock *TMS34010TargetLowering::emitSelectCC(
|
||||
|
||||
MachineBasicBlock::iterator InsertPt = MI;
|
||||
if (RHSMO.isImm()) {
|
||||
BuildMI(*ThisMBB, InsertPt, DL, TII.get(TMS34010::CMPI))
|
||||
.addImm(RHSMO.getImm())
|
||||
.add(LHSMO);
|
||||
} else {
|
||||
BuildMI(*ThisMBB, InsertPt, DL, TII.get(TMS34010::CMP))
|
||||
BuildMI(*ThisMBB, InsertPt, DL, TII.get(TMS34010::CMPIBR))
|
||||
.addMBB(TrueMBB)
|
||||
.add(LHSMO)
|
||||
.add(RHSMO)
|
||||
.add(LHSMO);
|
||||
.addImm(BranchOpc);
|
||||
} else {
|
||||
BuildMI(*ThisMBB, InsertPt, DL, TII.get(TMS34010::CMPBR))
|
||||
.addMBB(TrueMBB)
|
||||
.add(LHSMO)
|
||||
.add(RHSMO)
|
||||
.addImm(BranchOpc);
|
||||
}
|
||||
BuildMI(*ThisMBB, InsertPt, DL, TII.get(BranchOpc)).addMBB(TrueMBB);
|
||||
|
||||
Register TrueReg = MRI.createVirtualRegister(RC);
|
||||
Register FalseReg = MRI.createVirtualRegister(RC);
|
||||
|
||||
@@ -66,6 +66,14 @@ public:
|
||||
EVT getSetCCResultType(const DataLayout &DL, LLVMContext &Context,
|
||||
EVT VT) const override;
|
||||
|
||||
// Pointers are bit addresses; the generic memory-op expansion uses byte
|
||||
// offsets. Refuse it so TMS34010SelectionDAGInfo emits the bit-scaled copy.
|
||||
bool findOptimalMemOpLowering(LLVMContext &Context, std::vector<EVT> &MemOps,
|
||||
unsigned Limit, const MemOp &Op, unsigned DstAS,
|
||||
unsigned SrcAS,
|
||||
const AttributeList &FuncAttributes,
|
||||
EVT *LargestVT = nullptr) const override;
|
||||
|
||||
private:
|
||||
SDValue LowerBRCOND(SDValue Op, SelectionDAG &DAG) const;
|
||||
SDValue LowerBR_CC(SDValue Op, SelectionDAG &DAG) const;
|
||||
|
||||
@@ -7,12 +7,17 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "TMS34010InstrInfo.h"
|
||||
#include "MCTargetDesc/TMS34010MCTargetDesc.h"
|
||||
#include "TMS34010.h"
|
||||
#include "TMS34010Subtarget.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/CodeGen/MachineFunction.h"
|
||||
#include "llvm/CodeGen/MachineFunctionPass.h"
|
||||
#include "llvm/CodeGen/MachineInstrBuilder.h"
|
||||
#include "llvm/CodeGen/MachineRegisterInfo.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
#include "llvm/Support/MathExtras.h"
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
@@ -22,7 +27,9 @@ using namespace llvm;
|
||||
TMS34010InstrInfo::TMS34010InstrInfo(const TMS34010Subtarget &STI)
|
||||
: TMS34010GenInstrInfo(STI, RI), RI() {}
|
||||
|
||||
/// True for every conditional absolute branch defined in the .td file.
|
||||
/// True for every conditional branch defined in the .td file, absolute or
|
||||
/// relative. Instruction selection emits the absolute form; the pre-emit
|
||||
/// branch-relaxation pass may rewrite a local one to the relative form.
|
||||
static bool isConditionalBranch(unsigned Opcode) {
|
||||
switch (Opcode) {
|
||||
case TMS34010::JAEQ:
|
||||
@@ -40,12 +47,56 @@ static bool isConditionalBranch(unsigned Opcode) {
|
||||
case TMS34010::JAV:
|
||||
case TMS34010::JANV:
|
||||
case TMS34010::JANN:
|
||||
case TMS34010::JREQ:
|
||||
case TMS34010::JRNE:
|
||||
case TMS34010::JRLT:
|
||||
case TMS34010::JRLE:
|
||||
case TMS34010::JRGT:
|
||||
case TMS34010::JRGE:
|
||||
case TMS34010::JRLO:
|
||||
case TMS34010::JRLS:
|
||||
case TMS34010::JRHI:
|
||||
case TMS34010::JRHS:
|
||||
case TMS34010::JRP:
|
||||
case TMS34010::JRN:
|
||||
case TMS34010::JRV:
|
||||
case TMS34010::JRNV:
|
||||
case TMS34010::JRNN:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// True for the unconditional branch in either form.
|
||||
static bool isUnconditionalBranch(unsigned Opcode) {
|
||||
return Opcode == TMS34010::JUncond || Opcode == TMS34010::JR;
|
||||
}
|
||||
|
||||
/// True for the fused compare-and-branch pseudo. This instruction stays a
|
||||
/// terminator until TMS34010ExpandCmpBr splits it after register allocation.
|
||||
static bool isFusedConditionalBranch(unsigned Opcode) {
|
||||
return Opcode == TMS34010::CMPBR || Opcode == TMS34010::CMPIBR;
|
||||
}
|
||||
|
||||
/// Return the real conditional-branch opcode carried by a CMPBR/CMPIBR.
|
||||
static unsigned getFusedBranchOpcode(const MachineInstr &MI) {
|
||||
return MI.getOperand(3).getImm();
|
||||
}
|
||||
|
||||
/// Describe a fused compare-and-branch as a conditional branch for
|
||||
/// analyzeBranch. Cond[0] is the real JAcc opcode (what a normal conditional
|
||||
/// branch reports); the remaining operands let insertBranch rebuild the fused
|
||||
/// pseudo if a pass removes and re-inserts the branch before
|
||||
/// TMS34010ExpandCmpBr runs.
|
||||
static void appendFusedCond(SmallVectorImpl<MachineOperand> &Cond,
|
||||
const MachineInstr &MI) {
|
||||
Cond.push_back(MachineOperand::CreateImm(getFusedBranchOpcode(MI)));
|
||||
Cond.push_back(MachineOperand::CreateImm(MI.getOpcode()));
|
||||
Cond.push_back(MI.getOperand(1));
|
||||
Cond.push_back(MI.getOperand(2));
|
||||
}
|
||||
|
||||
bool TMS34010InstrInfo::analyzeBranch(MachineBasicBlock &MBB,
|
||||
MachineBasicBlock *&TBB,
|
||||
MachineBasicBlock *&FBB,
|
||||
@@ -61,13 +112,13 @@ bool TMS34010InstrInfo::analyzeBranch(MachineBasicBlock &MBB,
|
||||
return false; // Empty block: it just falls through.
|
||||
|
||||
// A single unconditional branch.
|
||||
if (I->getOpcode() == TMS34010::JUncond) {
|
||||
if (isUnconditionalBranch(I->getOpcode())) {
|
||||
TBB = I->getOperand(0).getMBB();
|
||||
|
||||
// A conditional branch immediately before the unconditional branch is the
|
||||
// two-way form: the conditional target is TBB and the unconditional target
|
||||
// is FBB. The compare is fused into the conditional branch, so Cond just
|
||||
// records its opcode.
|
||||
// is FBB. A fused compare-and-branch contributes the real JAcc opcode to
|
||||
// Cond plus the operands needed to rebuild the pseudo.
|
||||
if (I != MBB.begin()) {
|
||||
MachineBasicBlock::iterator P = std::prev(I);
|
||||
while (P != MBB.begin() && P->isDebugInstr())
|
||||
@@ -76,6 +127,11 @@ bool TMS34010InstrInfo::analyzeBranch(MachineBasicBlock &MBB,
|
||||
FBB = TBB;
|
||||
TBB = P->getOperand(0).getMBB();
|
||||
Cond.push_back(MachineOperand::CreateImm(P->getOpcode()));
|
||||
} else if (!P->isDebugInstr() &&
|
||||
isFusedConditionalBranch(P->getOpcode())) {
|
||||
FBB = TBB;
|
||||
TBB = P->getOperand(0).getMBB();
|
||||
appendFusedCond(Cond, *P);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -88,6 +144,13 @@ bool TMS34010InstrInfo::analyzeBranch(MachineBasicBlock &MBB,
|
||||
return false;
|
||||
}
|
||||
|
||||
// A fused compare-and-branch before TMS34010ExpandCmpBr splits it.
|
||||
if (isFusedConditionalBranch(I->getOpcode())) {
|
||||
TBB = I->getOperand(0).getMBB();
|
||||
appendFusedCond(Cond, *I);
|
||||
return false;
|
||||
}
|
||||
|
||||
// RETS and any other terminator cannot be analyzed.
|
||||
return true;
|
||||
}
|
||||
@@ -104,7 +167,9 @@ unsigned TMS34010InstrInfo::removeBranch(MachineBasicBlock &MBB,
|
||||
if (I->isDebugInstr())
|
||||
continue;
|
||||
if (I->getOpcode() != TMS34010::JUncond &&
|
||||
!isConditionalBranch(I->getOpcode()))
|
||||
I->getOpcode() != TMS34010::JR &&
|
||||
!isConditionalBranch(I->getOpcode()) &&
|
||||
!isFusedConditionalBranch(I->getOpcode()))
|
||||
break;
|
||||
|
||||
if (BytesRemoved)
|
||||
@@ -141,7 +206,42 @@ unsigned TMS34010InstrInfo::insertBranch(MachineBasicBlock &MBB,
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Cond[0] is the conditional branch opcode; the compare is fused into it.
|
||||
// A fused compare-and-branch is rebuilt as the same pseudo. Cond[0] is the
|
||||
// real JAcc opcode; Cond[1..3] are the pseudo opcode, LHS and RHS/immediate.
|
||||
if (Cond.size() == 4) {
|
||||
unsigned BranchOpc = Cond[0].getImm();
|
||||
unsigned PseudoOpc = Cond[1].getImm();
|
||||
assert(isConditionalBranch(BranchOpc) &&
|
||||
"expected a real conditional branch opcode");
|
||||
assert(isFusedConditionalBranch(PseudoOpc) &&
|
||||
"expected a fused compare-and-branch pseudo");
|
||||
MachineOperand LHS = Cond[2];
|
||||
MachineOperand RHS = Cond[3];
|
||||
// The operands were captured from an instruction that has been removed;
|
||||
// do not carry stale kill flags into the rebuilt pseudo.
|
||||
if (LHS.isReg())
|
||||
LHS.setIsKill(false);
|
||||
if (RHS.isReg())
|
||||
RHS.setIsKill(false);
|
||||
auto &MI = *BuildMI(&MBB, DL, get(PseudoOpc))
|
||||
.addMBB(TBB)
|
||||
.add(LHS)
|
||||
.add(RHS)
|
||||
.addImm(BranchOpc);
|
||||
unsigned Count = 1;
|
||||
if (BytesAdded)
|
||||
*BytesAdded += getInstSizeInBytes(MI);
|
||||
if (FBB) {
|
||||
auto &Uncond = *BuildMI(&MBB, DL, get(TMS34010::JUncond)).addMBB(FBB);
|
||||
if (BytesAdded)
|
||||
*BytesAdded += getInstSizeInBytes(Uncond);
|
||||
++Count;
|
||||
}
|
||||
return Count;
|
||||
}
|
||||
|
||||
// Cond[0] is the conditional branch opcode; the compare is a separate CMP
|
||||
// emitted by TMS34010ExpandCmpBr immediately before it.
|
||||
assert(Cond.size() == 1 && "TMS34010 branch conditions have one component");
|
||||
unsigned Opcode = Cond[0].getImm();
|
||||
assert(isConditionalBranch(Opcode) && "expected a conditional branch");
|
||||
@@ -229,3 +329,211 @@ bool TMS34010InstrInfo::expandPostRAPseudo(MachineInstr &MI) const {
|
||||
MI.eraseFromParent();
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/// Split a compare-and-branch pseudo into adjacent CMP/CMPI and JAcc.
|
||||
///
|
||||
/// The pseudo exists because ST is a physical register and the register-to-
|
||||
/// register MOVE (like almost every ALU instruction) writes it. A PHI copy
|
||||
/// inserted before register allocation can therefore land between a separate
|
||||
/// CMP and Jcc, and the branch would test the copy's flags. Keeping the pair
|
||||
/// as one terminator through scheduling and register allocation avoids that;
|
||||
/// this pass expands it after register allocation, so nothing can be inserted
|
||||
/// between the two real instructions.
|
||||
class TMS34010ExpandCmpBr : public MachineFunctionPass {
|
||||
public:
|
||||
static char ID;
|
||||
|
||||
TMS34010ExpandCmpBr() : MachineFunctionPass(ID) {}
|
||||
|
||||
bool runOnMachineFunction(MachineFunction &MF) override;
|
||||
};
|
||||
|
||||
char TMS34010ExpandCmpBr::ID = 0;
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
bool TMS34010ExpandCmpBr::runOnMachineFunction(MachineFunction &MF) {
|
||||
const TMS34010InstrInfo &TII =
|
||||
*MF.getSubtarget<TMS34010Subtarget>().getInstrInfo();
|
||||
bool Changed = false;
|
||||
|
||||
for (MachineBasicBlock &MBB : MF) {
|
||||
for (MachineInstr &MI : llvm::make_early_inc_range(MBB)) {
|
||||
const unsigned Opc = MI.getOpcode();
|
||||
if (Opc != TMS34010::CMPBR && Opc != TMS34010::CMPIBR)
|
||||
continue;
|
||||
|
||||
DebugLoc DL = MI.getDebugLoc();
|
||||
const MachineOperand Dest = MI.getOperand(0);
|
||||
const MachineOperand LHS = MI.getOperand(1);
|
||||
const MachineOperand RHS = MI.getOperand(2);
|
||||
const unsigned BranchOpc = MI.getOperand(3).getImm();
|
||||
|
||||
if (Opc == TMS34010::CMPIBR)
|
||||
BuildMI(MBB, MI, DL, TII.get(TMS34010::CMPI))
|
||||
.addImm(RHS.getImm())
|
||||
.add(LHS);
|
||||
else
|
||||
BuildMI(MBB, MI, DL, TII.get(TMS34010::CMP)).add(RHS).add(LHS);
|
||||
BuildMI(MBB, MI, DL, TII.get(BranchOpc)).add(Dest);
|
||||
MI.eraseFromParent();
|
||||
Changed = true;
|
||||
}
|
||||
}
|
||||
return Changed;
|
||||
}
|
||||
|
||||
FunctionPass *llvm::createTMS34010ExpandCmpBrPass() {
|
||||
return new TMS34010ExpandCmpBr();
|
||||
}
|
||||
|
||||
namespace {
|
||||
/// Rewrite in-range absolute branches to the one-word relative form.
|
||||
///
|
||||
/// The pass runs after prologue/epilogue insertion and block placement, when
|
||||
/// instruction sizes and block order are final. It starts by assuming the
|
||||
/// smallest possible layout (every local branch in the one-word form) and then
|
||||
/// grows back to the absolute form every branch whose displacement does not
|
||||
/// fit. Growing an instruction can only push a branch further out of range,
|
||||
/// never bring one back in, so the layout converges and the final encoding is
|
||||
/// self-consistent.
|
||||
///
|
||||
/// Zero is not an encodable displacement (the ISA reserves it for the 16-bit
|
||||
/// relative form), so a branch to the instruction immediately after it keeps
|
||||
/// the absolute form.
|
||||
class TMS34010BranchRelaxation : public MachineFunctionPass {
|
||||
public:
|
||||
static char ID;
|
||||
|
||||
TMS34010BranchRelaxation() : MachineFunctionPass(ID) {}
|
||||
|
||||
bool runOnMachineFunction(MachineFunction &MF) override;
|
||||
};
|
||||
|
||||
char TMS34010BranchRelaxation::ID = 0;
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
unsigned TMS34010InstrInfo::getInstSizeInBytes(const MachineInstr &MI) const {
|
||||
// ADDI, MOVI, CMPI, SUBI and ADDSTK are emitted in the 16-bit immediate form
|
||||
// when the value fits a signed 16-bit field. The .td describes only the long
|
||||
// form (the generic encoder uses MCII.get(Opcode).getSize() for it), so the
|
||||
// machine-level layout must account for the 4-byte form explicitly. The
|
||||
// operand indices mirror TMS34010MCCodeEmitter::encodeInstruction.
|
||||
unsigned ImmOp;
|
||||
switch (MI.getOpcode()) {
|
||||
case TMS34010::ADDI:
|
||||
ImmOp = 2;
|
||||
break;
|
||||
case TMS34010::ADDSTK:
|
||||
case TMS34010::CMPI:
|
||||
ImmOp = 0;
|
||||
break;
|
||||
case TMS34010::MOVI:
|
||||
case TMS34010::MC_ADDI:
|
||||
case TMS34010::MC_SUBI:
|
||||
case TMS34010::MC_CMPI:
|
||||
case TMS34010::MC_MOVI:
|
||||
ImmOp = 1;
|
||||
break;
|
||||
default:
|
||||
return MI.getDesc().getSize();
|
||||
}
|
||||
if (ImmOp >= MI.getNumOperands() || !MI.getOperand(ImmOp).isImm())
|
||||
return MI.getDesc().getSize();
|
||||
return isInt<16>(MI.getOperand(ImmOp).getImm()) ? 4 : 6;
|
||||
}
|
||||
|
||||
bool TMS34010BranchRelaxation::runOnMachineFunction(MachineFunction &MF) {
|
||||
const TMS34010InstrInfo &TII =
|
||||
*MF.getSubtarget<TMS34010Subtarget>().getInstrInfo();
|
||||
|
||||
// A non-meta instruction with no size would make the layout meaningless.
|
||||
// Keep every branch absolute instead of guessing (and instead of aborting on
|
||||
// a function this pass cannot measure).
|
||||
for (const MachineBasicBlock &MBB : MF)
|
||||
for (const MachineInstr &MI : MBB)
|
||||
if (!MI.isDebugInstr() && !MI.isMetaInstruction() &&
|
||||
TII.getInstSizeInBytes(MI) == 0)
|
||||
return false;
|
||||
|
||||
// Tentatively select the one-word form for every branch whose target is a
|
||||
// block in this function. The layout below then grows every branch that
|
||||
// does not fit. Starting from the smallest possible layout is what makes the
|
||||
// iteration monotone: growing an instruction can only push a branch further
|
||||
// out of range, never bring one back in, so the loop terminates and the
|
||||
// result is self-consistent.
|
||||
bool EverChanged = false;
|
||||
for (MachineBasicBlock &MBB : MF)
|
||||
for (MachineInstr &MI : MBB) {
|
||||
if (MI.isDebugInstr() || MI.isMetaInstruction())
|
||||
continue;
|
||||
unsigned ShortOpc = TMS34010::getShortBranchOpcode(MI.getOpcode());
|
||||
if (!ShortOpc || MI.getNumOperands() == 0 || !MI.getOperand(0).isMBB())
|
||||
continue;
|
||||
MI.setDesc(TII.get(ShortOpc));
|
||||
EverChanged = true;
|
||||
}
|
||||
|
||||
bool Changed = true;
|
||||
while (Changed) {
|
||||
Changed = false;
|
||||
|
||||
// Byte offset of every block from the start of the function.
|
||||
DenseMap<const MachineBasicBlock *, int64_t> BlockOffset;
|
||||
int64_t Offset = 0;
|
||||
for (const MachineBasicBlock &MBB : MF) {
|
||||
BlockOffset[&MBB] = Offset;
|
||||
for (const MachineInstr &MI : MBB) {
|
||||
if (MI.isDebugInstr() || MI.isMetaInstruction())
|
||||
continue;
|
||||
unsigned Size = TII.getInstSizeInBytes(MI);
|
||||
assert(Size && "unsized instruction survived the pre-scan");
|
||||
Offset += Size;
|
||||
}
|
||||
}
|
||||
|
||||
for (MachineBasicBlock &MBB : MF) {
|
||||
int64_t InstrOffset = BlockOffset.lookup(&MBB);
|
||||
for (MachineInstr &MI : MBB) {
|
||||
if (MI.isDebugInstr() || MI.isMetaInstruction())
|
||||
continue;
|
||||
|
||||
unsigned Size = TII.getInstSizeInBytes(MI);
|
||||
assert(Size && "unsized instruction survived the pre-scan");
|
||||
|
||||
unsigned LongOpc = TMS34010::getLongBranchOpcode(MI.getOpcode());
|
||||
if (LongOpc && MI.getNumOperands() != 0 &&
|
||||
MI.getOperand(0).isMBB()) {
|
||||
const MachineBasicBlock *Target = MI.getOperand(0).getMBB();
|
||||
auto It = BlockOffset.find(Target);
|
||||
bool Fits = false;
|
||||
if (It != BlockOffset.end()) {
|
||||
// One-word instruction: PC after it is InstrOffset + 2 bytes, and
|
||||
// the ISA displacement counts 16-bit words from PC+16.
|
||||
int64_t Delta = It->second - (InstrOffset + 2);
|
||||
int64_t Disp = Delta / 2;
|
||||
// Zero is the 16-bit relative form, so it is not encodable here.
|
||||
Fits = Delta % 2 == 0 && Disp != 0 && Disp >= -127 && Disp <= 127;
|
||||
}
|
||||
if (!Fits) {
|
||||
MI.setDesc(TII.get(LongOpc));
|
||||
Changed = true;
|
||||
EverChanged = true;
|
||||
Size = TII.getInstSizeInBytes(MI);
|
||||
}
|
||||
}
|
||||
|
||||
InstrOffset += Size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return EverChanged;
|
||||
}
|
||||
|
||||
FunctionPass *llvm::createTMS34010BranchRelaxationPass() {
|
||||
return new TMS34010BranchRelaxation();
|
||||
}
|
||||
@@ -59,11 +59,21 @@ public:
|
||||
const DebugLoc &DL,
|
||||
int *BytesAdded = nullptr) const override;
|
||||
|
||||
unsigned getInstSizeInBytes(const MachineInstr &MI) const override {
|
||||
return MI.getDesc().getSize();
|
||||
}
|
||||
unsigned getInstSizeInBytes(const MachineInstr &MI) const override;
|
||||
};
|
||||
|
||||
/// Shrink local absolute branches to the one-word relative form when the final
|
||||
/// machine layout proves the displacement fits. Runs after prologue/epilogue
|
||||
/// insertion and block placement; the MC backend repeats the range check for
|
||||
/// symbolic operands as a safety net.
|
||||
FunctionPass *createTMS34010BranchRelaxationPass();
|
||||
|
||||
/// Split the compare-and-branch pseudo (CMPBR/CMPIBR) into adjacent CMP/CMPI
|
||||
/// and JAcc instructions. This runs after register allocation (post-RA), when
|
||||
/// the PHI copies and the pre-RA schedule are final, so no copy can land
|
||||
/// between the compare and the branch and overwrite the ST flags.
|
||||
FunctionPass *createTMS34010ExpandCmpBrPass();
|
||||
|
||||
} // namespace llvm
|
||||
|
||||
#endif
|
||||
@@ -28,6 +28,13 @@ class TMS34010Inst<dag outs, dag ins, string asmstr, list<dag> pattern>
|
||||
let AsmString = asmstr;
|
||||
let Pattern = pattern;
|
||||
let Size = 2;
|
||||
// The instruction-selection definitions below are not part of the MC
|
||||
// instruction set: the assembler and disassembler use the explicit MC
|
||||
// descriptions at the end of this file, which have the same encodings but
|
||||
// accept both register files and expose only the operands that GSPA prints.
|
||||
// Keeping the two sets separate also avoids an ambiguity in the asm matcher
|
||||
// between a tied two-address instruction and its textual form.
|
||||
let isCodeGenOnly = 1;
|
||||
}
|
||||
|
||||
// ADD Rs, Rd: 0100 000S SSSR DDDD
|
||||
@@ -82,8 +89,10 @@ def SUB : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, ARegs:$val),
|
||||
}
|
||||
|
||||
// CMP Rs, Rd: 0100 100S SSSR DDDD (0x4800). Sets the status register from
|
||||
// Rd - Rs. This is emitted directly from LowerBR_CC/LowerBRCOND and by the
|
||||
// SELECTCC expansion, so it has no pattern.
|
||||
// Rd - Rs. Instruction selection emits the fused CMPBR/CMPIBR pseudo;
|
||||
// TMS34010ExpandCmpBr expands it to CMP/CMPI immediately followed by JAcc
|
||||
// after register allocation. The SELECTCC expansion also uses the pseudo.
|
||||
// CMP has no pattern.
|
||||
def CMP : TMS34010Inst<(outs), (ins ARegs:$src, ARegs:$dst),
|
||||
"CMP $src, $dst", []> {
|
||||
let isCompare = 1;
|
||||
@@ -96,10 +105,18 @@ def CMP : TMS34010Inst<(outs), (ins ARegs:$src, ARegs:$dst),
|
||||
let Inst{3-0} = dst{3-0};
|
||||
}
|
||||
|
||||
// CMPI/ANDI store the one's complement of the immediate. The operand has an
|
||||
// encoder/decoder pair in the MC layer; using it for the selection definitions
|
||||
// too means the generated emitter writes real operand bits instead of silently
|
||||
// dropping a !xor field assignment.
|
||||
def TMS34010InvImmOp : Operand<i32> {
|
||||
let EncoderMethod = "getInvImmValue";
|
||||
let DecoderMethod = "DecodeInvImm";
|
||||
}
|
||||
|
||||
// CMPI imm32, Rd: 0000 1011 011R DDDD followed by the 32-bit immediate
|
||||
// (0x0B60). The status register is set from Rd - imm. Like ANDI, the
|
||||
// immediate is stored one's-complemented.
|
||||
def CMPI : TMS34010Inst<(outs), (ins i32imm:$imm, ARegs:$dst),
|
||||
// (0x0B60). The status register is set from Rd - imm.
|
||||
def CMPI : TMS34010Inst<(outs), (ins TMS34010InvImmOp:$imm, ARegs:$dst),
|
||||
"CMPI $imm, $dst", []> {
|
||||
let isCompare = 1;
|
||||
let Defs = [ST];
|
||||
@@ -108,8 +125,8 @@ def CMPI : TMS34010Inst<(outs), (ins i32imm:$imm, ARegs:$dst),
|
||||
bits<32> imm;
|
||||
let Inst{15-5} = 0b00001011011;
|
||||
let Inst{4-0} = dst;
|
||||
let Inst{31-16} = !xor(imm{15-0}, 0xFFFF);
|
||||
let Inst{47-32} = !xor(imm{31-16}, 0xFFFF);
|
||||
let Inst{31-16} = imm{15-0};
|
||||
let Inst{47-32} = imm{31-16};
|
||||
}
|
||||
|
||||
// AND Rs, Rd: 0101 000S SSSR DDDD (0x5000).
|
||||
@@ -339,7 +356,8 @@ def SRLK : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, i32imm:$imm),
|
||||
// ANDI imm32, Rd: 0000 1011 100R DDDD followed by the immediate (0x0B80). The
|
||||
// machine stores the one's complement of the operand and the CPU ANDs with it;
|
||||
// the assembler hides that from the GSPA text.
|
||||
def ANDI : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, i32imm:$imm),
|
||||
def ANDI : TMS34010Inst<(outs ARegs:$dst),
|
||||
(ins ARegs:$src, TMS34010InvImmOp:$imm),
|
||||
"ANDI $imm, $dst",
|
||||
[(set ARegs:$dst, (and ARegs:$src, imm:$imm))]> {
|
||||
let Constraints = "$src = $dst";
|
||||
@@ -349,8 +367,8 @@ def ANDI : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, i32imm:$imm),
|
||||
bits<32> imm;
|
||||
let Inst{15-5} = 0b00001011100;
|
||||
let Inst{4-0} = dst;
|
||||
let Inst{31-16} = !xor(imm{15-0}, 0xFFFF);
|
||||
let Inst{47-32} = !xor(imm{31-16}, 0xFFFF);
|
||||
let Inst{31-16} = imm{15-0};
|
||||
let Inst{47-32} = imm{31-16};
|
||||
}
|
||||
|
||||
// MOVI imm32, Rd: 0000 1001 111R DDDD followed by the 32-bit immediate.
|
||||
@@ -499,11 +517,20 @@ def RETS : TMS34010Inst<(outs), (ins i32imm:$n), "RETS $n", []> {
|
||||
// Push/pop on the system stack (A15/SP). The CPU predecrements and
|
||||
// postincrements by one 32-bit slot, so each is ABI-identical to one MMTM/MMFM
|
||||
// slot. MMTM/MMFM is a later code-size optimisation.
|
||||
//
|
||||
// The encodings are the MOVE store/load predecrement/postincrement forms:
|
||||
// MOVE Rs, -*SP, 1 = 1010 001S SSSF 1111 (0xA200 base)
|
||||
// MOVE *SP+, Rd, 1 = 1001 0111 111F DDDD (0x97E0 base)
|
||||
def PUSH32 : TMS34010Inst<(outs), (ins ARegsAll:$src),
|
||||
"MOVE $src, -*SP, 1", []> {
|
||||
let mayStore = 1;
|
||||
let Uses = [A15];
|
||||
let Defs = [A15, ST];
|
||||
bits<5> src;
|
||||
let Inst{15-9} = 0b1010001;
|
||||
let Inst{8-5} = src{3-0};
|
||||
let Inst{4} = src{4};
|
||||
let Inst{3-0} = 0b1111;
|
||||
}
|
||||
|
||||
def POP32 : TMS34010Inst<(outs ARegsAll:$dst), (ins),
|
||||
@@ -511,57 +538,133 @@ def POP32 : TMS34010Inst<(outs ARegsAll:$dst), (ins),
|
||||
let mayLoad = 1;
|
||||
let Uses = [A15];
|
||||
let Defs = [A15, ST];
|
||||
bits<5> dst;
|
||||
let Inst{15-5} = 0b10010111111;
|
||||
let Inst{4-0} = dst;
|
||||
}
|
||||
|
||||
// Save/restore a register list on the system stack in one instruction. The
|
||||
// frame lowering emits one MMTM for the callee-save set and one MMFM for the
|
||||
// restore, so the code has the same system-stack slots as the per-register
|
||||
// MOVE pushes but four bytes per list instead of two bytes per register.
|
||||
//
|
||||
// These are CodeGen instructions; MCInstLower maps them to MC_MMTM/MC_MMFM so
|
||||
// the assembler printer and the encoder use the register-list form. The list
|
||||
// operand is the raw 16-bit encoded mask: MMTM numbers the A file from the MSB
|
||||
// (A0 is bit 15, A15 is bit 0) while MMFM numbers it from the LSB (A0 is bit
|
||||
// 0). The frame lowering computes the matching mask for each direction.
|
||||
def MMTM : TMS34010Inst<(outs), (ins ARegsAll:$dst, i32imm:$mask),
|
||||
"MMTM $dst, $mask", []> {
|
||||
let mayStore = 1;
|
||||
let Uses = [A15];
|
||||
let Defs = [A15, ST];
|
||||
let Size = 4;
|
||||
bits<5> dst;
|
||||
bits<16> mask;
|
||||
let Inst{15-5} = 0b00001001100;
|
||||
let Inst{4-0} = dst;
|
||||
let Inst{31-16} = mask;
|
||||
}
|
||||
|
||||
def MMFM : TMS34010Inst<(outs), (ins ARegsAll:$dst, i32imm:$mask),
|
||||
"MMFM $dst, $mask", []> {
|
||||
let mayLoad = 1;
|
||||
let Uses = [A15];
|
||||
let Defs = [A15, ST];
|
||||
let Size = 4;
|
||||
bits<5> dst;
|
||||
bits<16> mask;
|
||||
let Inst{15-5} = 0b00001001101;
|
||||
let Inst{4-0} = dst;
|
||||
let Inst{31-16} = mask;
|
||||
}
|
||||
|
||||
// Push one 32-bit argument on the program stack (A14/STK).
|
||||
// MOVE Rs, *A14+, 1 = 1001 001S SSSF 1110 (0x9200 base)
|
||||
def PUSHARG : TMS34010Inst<(outs), (ins ARegs:$src),
|
||||
"MOVE $src, *A14+, 1", []> {
|
||||
let mayStore = 1;
|
||||
let Uses = [A14];
|
||||
let Defs = [A14, ST];
|
||||
bits<5> src;
|
||||
let Inst{15-9} = 0b1001001;
|
||||
let Inst{8-5} = src{3-0};
|
||||
let Inst{4} = src{4};
|
||||
let Inst{3-0} = 0b1110;
|
||||
}
|
||||
|
||||
// FP = STK, and the program-stack frame adjustment (negative to deallocate).
|
||||
// MOVE A14, A13 = 0100 1101 1100 1101 (0x4DCD).
|
||||
def SETFP : TMS34010Inst<(outs), (ins), "MOVE A14, A13", []> {
|
||||
let Uses = [A14];
|
||||
let Defs = [A13, ST];
|
||||
let Inst{15-0} = 0x4DCD;
|
||||
}
|
||||
|
||||
// The long-immediate ADDI form (bit 5 set, 32-bit immediate). The MC emitter
|
||||
// emits the 16-bit form (0x0B00 base, one immediate word) when the value fits
|
||||
// in a signed 16-bit field, matching GSPA 2.00.
|
||||
def ADDSTK : TMS34010Inst<(outs), (ins i32imm:$imm), "ADDI $imm, A14", []> {
|
||||
let Uses = [A14];
|
||||
let Defs = [A14, ST];
|
||||
let Size = 6;
|
||||
bits<32> imm;
|
||||
let Inst{15-5} = 0b00001011001;
|
||||
let Inst{4-0} = 0b01110;
|
||||
let Inst{31-16} = imm{15-0};
|
||||
let Inst{47-32} = imm{31-16};
|
||||
}
|
||||
|
||||
// Materialise a frame address; the base is the reserved FP, so this is not
|
||||
// the allocatable-register MOVE pattern.
|
||||
// MOVE A13, Rd = 0100 1101 101D DDDD (0x4DA0 base)
|
||||
def MOVEFP : TMS34010Inst<(outs ARegs:$dst), (ins), "MOVE A13, $dst", []> {
|
||||
let Uses = [A13];
|
||||
let Defs = [ST];
|
||||
bits<5> dst;
|
||||
let Inst{15-10} = 0b010011;
|
||||
let Inst{9-5} = 0b01101;
|
||||
let Inst{4-0} = dst;
|
||||
}
|
||||
|
||||
// Read the caller's saved old STK from the system stack into A14. The
|
||||
// displacement form is two 16-bit words.
|
||||
// MOVE *SP(off), A14, 1 = 1011 0111 1110 1110 (0xB7EE) + 16-bit disp.
|
||||
def LOADSTK : TMS34010Inst<(outs), (ins i32imm:$off),
|
||||
"MOVE *SP($off), A14, 1", []> {
|
||||
let mayLoad = 1;
|
||||
let Size = 4;
|
||||
let Uses = [A15];
|
||||
let Defs = [A14, ST];
|
||||
bits<16> off;
|
||||
let Inst{15-9} = 0b1011011;
|
||||
let Inst{8-5} = 0b1111;
|
||||
let Inst{4} = 0;
|
||||
let Inst{3-0} = 0b1110;
|
||||
let Inst{31-16} = off;
|
||||
}
|
||||
|
||||
// The ABI's field-size pair. FS0 must be 16 for `short` accesses and FS1
|
||||
// must be 32 for words, so the prologue and every call re-establish both.
|
||||
// ROM code may leave either field at a different width.
|
||||
def SETF16 : TMS34010Inst<(outs), (ins), "SETF 16, 0, 0", []> {
|
||||
// The ABI's field-size pair. FS0 must be 16 sign-extending for `short`
|
||||
// accesses and FS1 must be 32 for words, so the prologue and every call
|
||||
// re-establish both. ROM code may leave either field at a different width.
|
||||
//
|
||||
// FS0's field-extension bit (fe) must be 1: a 16-bit load sign-extends, which
|
||||
// is what the SEXTLOAD/zext-mask lowering below assumes. With fe=0 the
|
||||
// hardware zero-extends, so a negative `short` would enter the 32-bit DAG as a
|
||||
// large positive value.
|
||||
def SETF16 : TMS34010Inst<(outs), (ins), "SETF 16, 1, 0", []> {
|
||||
let hasSideEffects = 1;
|
||||
// SETF fs=16, fe=1, field=0: 0000 0101 0111 0000.
|
||||
let Inst{15-0} = 0x0570;
|
||||
}
|
||||
|
||||
def SETF32 : TMS34010Inst<(outs), (ins), "SETF 32, 0, 1", []> {
|
||||
let hasSideEffects = 1;
|
||||
// SETF fs=32, fe=0, field=1: 0000 0111 0100 0000.
|
||||
let Inst{15-0} = 0x0740;
|
||||
}
|
||||
|
||||
// Direct ROM/local call; the target is a symbol or bit address. CALLA is the
|
||||
// opcode word plus a 32-bit absolute target, three words.
|
||||
// CALLA is the opcode word plus a 32-bit absolute target, three words.
|
||||
def CALLA : TMS34010Inst<(outs), (ins i32imm:$target), "CALLA $target", []> {
|
||||
let isCall = 1;
|
||||
let Size = 6;
|
||||
@@ -569,6 +672,10 @@ def CALLA : TMS34010Inst<(outs), (ins i32imm:$target), "CALLA $target", []> {
|
||||
// saved STK by the callee's epilogue, so the call both uses and defines it.
|
||||
let Uses = [A14];
|
||||
let Defs = [A8, A14, ST];
|
||||
bits<32> target;
|
||||
let Inst{15-0} = 0x0D5F;
|
||||
let Inst{31-16} = target{15-0};
|
||||
let Inst{47-32} = target{31-16};
|
||||
}
|
||||
|
||||
// Indirect call through a register: CALL Rs is 0000 1001 001R DDDD (0x0920),
|
||||
@@ -601,6 +708,10 @@ def JUncond : TMS34010Inst<(outs), (ins brtarget:$target), "JAUC $target",
|
||||
let isTerminator = 1;
|
||||
let isBarrier = 1;
|
||||
let Size = 6;
|
||||
bits<32> target;
|
||||
let Inst{15-0} = 0xC080;
|
||||
let Inst{31-16} = target{15-0};
|
||||
let Inst{47-32} = target{31-16};
|
||||
}
|
||||
|
||||
// Conditional absolute branches. The JAcc opcode is 1100 cccc 1000 0000
|
||||
@@ -608,18 +719,53 @@ def JUncond : TMS34010Inst<(outs), (ins brtarget:$target), "JAUC $target",
|
||||
// uc/p/ls/hi/lt/ge/le/gt/c/nc/eq/ne/v/nv/n/nn = 0..15. A conditional branch
|
||||
// falls through when the condition is false, so it is a terminator and a
|
||||
// branch but not a barrier. The condition flags live in ST.
|
||||
// The lowering glues the compare to the branch: the DAG branch node carries a
|
||||
// trailing glue operand, which forces the CMP/CMPI to be emitted immediately
|
||||
// before the branch. The variadic slot holds that glue; it is not printed.
|
||||
// Instruction selection emits CMPBR/CMPIBR so the compare and branch stay one
|
||||
// terminator through scheduling and register allocation. TMS34010ExpandCmpBr
|
||||
// expands the pseudo after register allocation into CMP/CMPI immediately
|
||||
// followed by one of these branches. The variadic slot is retained for the
|
||||
// generic branch helpers; it is not printed.
|
||||
class TMS34010Jcc<bits<4> cc, string asmstr>
|
||||
: TMS34010Inst<(outs), (ins brtarget:$target, variable_ops), asmstr, []> {
|
||||
let isBranch = 1;
|
||||
let isTerminator = 1;
|
||||
let Uses = [ST];
|
||||
let Size = 6;
|
||||
bits<32> target;
|
||||
let Inst{15-12} = 0b1100;
|
||||
let Inst{11-8} = cc;
|
||||
let Inst{7-0} = 0b10000000;
|
||||
let Inst{31-16} = target{15-0};
|
||||
let Inst{47-32} = target{31-16};
|
||||
}
|
||||
|
||||
// Compare-and-branch pseudo. The target's ST flags are a physical register
|
||||
// and the register-to-register MOVE (like almost every ALU op) writes ST, so
|
||||
// a PHI copy or scheduling move can land between a CMP and its Jcc. Keeping
|
||||
// the pair as one terminator through scheduling and register allocation and
|
||||
// splitting it in the post-regalloc pass makes that impossible. The expansion
|
||||
// emits CMP/CMPI immediately followed by the branch named by $br.
|
||||
def CMPBR : TMS34010Inst<(outs),
|
||||
(ins brtarget:$target, ARegs:$lhs, ARegs:$rhs,
|
||||
i32imm:$br, variable_ops),
|
||||
"# CMPBR", []> {
|
||||
let isPseudo = 1;
|
||||
let isBranch = 1;
|
||||
let isTerminator = 1;
|
||||
let Defs = [ST];
|
||||
// Approximate size before expansion (CMP + absolute JAcc). The real
|
||||
// CMP/CMPI and branch sizes are laid out after TMS34010ExpandCmpBr splits it.
|
||||
let Size = 8;
|
||||
}
|
||||
|
||||
def CMPIBR : TMS34010Inst<(outs),
|
||||
(ins brtarget:$target, ARegs:$lhs, i32imm:$imm,
|
||||
i32imm:$br, variable_ops),
|
||||
"# CMPIBR", []> {
|
||||
let isPseudo = 1;
|
||||
let isBranch = 1;
|
||||
let isTerminator = 1;
|
||||
let Defs = [ST];
|
||||
let Size = 8;
|
||||
}
|
||||
|
||||
def JAEQ : TMS34010Jcc<0xA, "JAEQ $target">; // equal (Z)
|
||||
@@ -638,6 +784,51 @@ def JAV : TMS34010Jcc<0xC, "JAV $target">; // overflow
|
||||
def JANV : TMS34010Jcc<0xD, "JANV $target">; // no overflow
|
||||
def JANN : TMS34010Jcc<0xF, "JANN $target">; // nonnegative
|
||||
|
||||
// One-word relative branches. The low byte is a signed *word* displacement
|
||||
// from PC+16: dis34 and the ISA reference decode the target as
|
||||
// `pc + 16 + (disp << 4)` in bit addresses, so `disp = (target - (pc + 16)) / 16`
|
||||
// and the legal values are [-127,-1] or [1,127]. Zero would select the 16-bit
|
||||
// relative form and 0x80 is the absolute-JA marker, so the encoder must never
|
||||
// emit either.
|
||||
//
|
||||
// These definitions are not selected directly: LowerBR_CC keeps the absolute
|
||||
// JA* form, and TMS34010BranchRelaxation rewrites a local branch to the short
|
||||
// form once the final block layout proves the displacement fits. The MC
|
||||
// backend repeats the range check for symbolic operands and widens an
|
||||
// out-of-range JR* back to JA*. GSPA accepts a label operand here and spells
|
||||
// the unconditional form JRUC.
|
||||
class TMS34010Jrc<bits<4> cc, string asmstr>
|
||||
: TMS34010Inst<(outs), (ins brtarget:$target, variable_ops), asmstr, []> {
|
||||
let isBranch = 1;
|
||||
let isTerminator = 1;
|
||||
let Uses = [ST];
|
||||
let Size = 2;
|
||||
bits<8> target;
|
||||
let Inst{15-12} = 0b1100;
|
||||
let Inst{11-8} = cc;
|
||||
let Inst{7-0} = target;
|
||||
}
|
||||
|
||||
def JR : TMS34010Jrc<0x0, "JRUC $target"> { // unconditional
|
||||
let isBarrier = 1;
|
||||
let Uses = [];
|
||||
}
|
||||
def JREQ : TMS34010Jrc<0xA, "JREQ $target">; // equal (Z)
|
||||
def JRNE : TMS34010Jrc<0xB, "JRNE $target">; // not equal (~Z)
|
||||
def JRLT : TMS34010Jrc<0x4, "JRLT $target">; // signed less than
|
||||
def JRLE : TMS34010Jrc<0x6, "JRLE $target">; // signed less or equal
|
||||
def JRGT : TMS34010Jrc<0x7, "JRGT $target">; // signed greater than
|
||||
def JRGE : TMS34010Jrc<0x5, "JRGE $target">; // signed greater or equal
|
||||
def JRLO : TMS34010Jrc<0x8, "JRLO $target">; // unsigned lower (carry)
|
||||
def JRLS : TMS34010Jrc<0x2, "JRLS $target">; // unsigned lower or same
|
||||
def JRHI : TMS34010Jrc<0x3, "JRHI $target">; // unsigned higher
|
||||
def JRHS : TMS34010Jrc<0x9, "JRHS $target">; // unsigned higher or same
|
||||
def JRP : TMS34010Jrc<0x1, "JRP $target">; // positive
|
||||
def JRN : TMS34010Jrc<0xE, "JRN $target">; // negative
|
||||
def JRV : TMS34010Jrc<0xC, "JRV $target">; // overflow
|
||||
def JRNV : TMS34010Jrc<0xD, "JRNV $target">; // no overflow
|
||||
def JRNN : TMS34010Jrc<0xF, "JRNN $target">; // nonnegative
|
||||
|
||||
// SELECTCC is expanded after instruction selection into a compare plus a
|
||||
// two-way diamond; it never reaches the assembly printer. The true/false
|
||||
// operands may be registers or immediates, so they use an unconstrained
|
||||
@@ -682,3 +873,599 @@ def SEXT16 : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src), "# SEXT16",
|
||||
def NOP : TMS34010Inst<(outs), (ins), "NOP", []> {
|
||||
let Inst{15-0} = 0x0300;
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// MC instruction descriptions
|
||||
//
|
||||
// These are the assembler/disassembler/encoder instructions. They are
|
||||
// deliberately separate from the selection definitions above: every operand
|
||||
// that GSPA prints appears in the asm string, both register files are accepted,
|
||||
// and the hidden tied two-address operand is gone. Encodings are identical to
|
||||
// the selection definitions for the A file. The module-level selection
|
||||
// definitions are isCodeGenOnly, so TableGen's asm matcher and disassembler
|
||||
// only see this table.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
class TMS34010MCInst<dag outs, dag ins, string asmstr>
|
||||
: Instruction {
|
||||
field bits<48> Inst;
|
||||
let Namespace = "TMS34010";
|
||||
let OutOperandList = outs;
|
||||
let InOperandList = ins;
|
||||
let AsmString = asmstr;
|
||||
let Pattern = [];
|
||||
let Size = 2;
|
||||
}
|
||||
|
||||
// Register-to-register ALU group: bits 15-9 hold the opcode, bits 8-5 the low
|
||||
// four bits of the source, and bits 4-0 the whole destination (bit 4 is the
|
||||
// shared register-file bit). GSPA 2.00 only encodes same-file operands.
|
||||
class TMS34010RR<bits<7> op, string mnem>
|
||||
: TMS34010MCInst<(outs), (ins GPR:$src, GPR:$dst),
|
||||
mnem # " $src, $dst"> {
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
let Inst{15-9} = op;
|
||||
let Inst{8-5} = src{3-0};
|
||||
let Inst{4-0} = dst;
|
||||
}
|
||||
|
||||
def MC_ADD : TMS34010RR<0b0100000, "ADD">;
|
||||
def MC_SUB : TMS34010RR<0b0100010, "SUB">;
|
||||
def MC_CMP : TMS34010RR<0b0100100, "CMP">;
|
||||
def MC_AND : TMS34010RR<0b0101000, "AND">;
|
||||
def MC_OR : TMS34010RR<0b0101010, "OR">;
|
||||
def MC_XOR : TMS34010RR<0b0101011, "XOR">;
|
||||
def MC_MPYS : TMS34010RR<0b0101110, "MPYS">;
|
||||
def MC_MPYU : TMS34010RR<0b0101111, "MPYU">;
|
||||
def MC_DIVS : TMS34010RR<0b0101100, "DIVS">;
|
||||
def MC_DIVU : TMS34010RR<0b0101101, "DIVU">;
|
||||
def MC_SLA : TMS34010RR<0b0110000, "SLA">;
|
||||
def MC_SLL : TMS34010RR<0b0110001, "SLL">;
|
||||
def MC_SRA : TMS34010RR<0b0110010, "SRA">;
|
||||
def MC_SRL : TMS34010RR<0b0110011, "SRL">;
|
||||
def MC_RL : TMS34010RR<0b0110100, "RL">;
|
||||
|
||||
// Immediate shifts. SLL/SLA/RL store K directly; SRA/SRL store (-K)&31,
|
||||
// so those use an operand with a small encoder/decoder pair.
|
||||
def TMS34010NegK : Operand<i32> {
|
||||
let EncoderMethod = "getNegKValue";
|
||||
let DecoderMethod = "DecodeNegK";
|
||||
}
|
||||
|
||||
class TMS34010KShift<bits<6> op, string mnem>
|
||||
: TMS34010MCInst<(outs GPR:$dst), (ins i32imm:$k),
|
||||
mnem # " $k, $dst"> {
|
||||
bits<5> dst;
|
||||
bits<5> k;
|
||||
let Inst{15-10} = op;
|
||||
let Inst{9-5} = k;
|
||||
let Inst{4-0} = dst;
|
||||
}
|
||||
|
||||
class TMS34010NKShift<bits<6> op, string mnem>
|
||||
: TMS34010MCInst<(outs GPR:$dst), (ins TMS34010NegK:$k),
|
||||
mnem # " $k, $dst"> {
|
||||
bits<5> dst;
|
||||
bits<5> k;
|
||||
let Inst{15-10} = op;
|
||||
let Inst{9-5} = k;
|
||||
let Inst{4-0} = dst;
|
||||
}
|
||||
|
||||
def MC_SLAK : TMS34010KShift<0b001000, "SLA">;
|
||||
def MC_SLLK : TMS34010KShift<0b001001, "SLL">;
|
||||
def MC_SRAK : TMS34010NKShift<0b001010, "SRA">;
|
||||
def MC_SRLK : TMS34010NKShift<0b001011, "SRL">;
|
||||
def MC_RLK : TMS34010KShift<0b001100, "RL">;
|
||||
|
||||
class TMS34010Unary<bits<11> op, string mnem>
|
||||
: TMS34010MCInst<(outs), (ins GPR:$dst), mnem # " $dst"> {
|
||||
bits<5> dst;
|
||||
let Inst{15-5} = op;
|
||||
let Inst{4-0} = dst;
|
||||
}
|
||||
|
||||
def MC_NEG : TMS34010Unary<0b00000011101, "NEG">;
|
||||
def MC_NOT : TMS34010Unary<0b00000011111, "NOT">;
|
||||
|
||||
// CLR and XOR share the 0x5600 encoding when the two registers are equal.
|
||||
// Keep CLR out of the generated decoder table; the disassembler turns an XOR
|
||||
// with equal operands into CLR.
|
||||
def MC_CLR : TMS34010MCInst<(outs), (ins GPR:$dst), "CLR $dst"> {
|
||||
let isAsmParserOnly = 1;
|
||||
bits<5> dst;
|
||||
let Inst{15-9} = 0b0101011;
|
||||
let Inst{8-5} = dst{3-0};
|
||||
let Inst{4-0} = dst;
|
||||
}
|
||||
|
||||
// Immediate group. The long form is the .td encoding; TMS34010MCCodeEmitter
|
||||
// selects the short word form for ADDI, MOVI, CMPI and SUBI when the value
|
||||
// fits a signed 16-bit field, exactly as GSPA 2.00 does. CMPI, SUBI and ANDI
|
||||
// store the one's complement of the operand.
|
||||
class TMS34010Imm<bits<11> op, string mnem>
|
||||
: TMS34010MCInst<(outs GPR:$dst), (ins i32imm:$imm),
|
||||
mnem # " $imm, $dst"> {
|
||||
bits<5> dst;
|
||||
bits<32> imm;
|
||||
let Inst{15-5} = op;
|
||||
let Inst{4-0} = dst;
|
||||
let Inst{31-16} = imm{15-0};
|
||||
let Inst{47-32} = imm{31-16};
|
||||
let Size = 6;
|
||||
}
|
||||
|
||||
class TMS34010InvImm<bits<11> op, string mnem>
|
||||
: TMS34010MCInst<(outs GPR:$dst), (ins TMS34010InvImmOp:$imm),
|
||||
mnem # " $imm, $dst"> {
|
||||
bits<5> dst;
|
||||
bits<32> imm;
|
||||
let Inst{15-5} = op;
|
||||
let Inst{4-0} = dst;
|
||||
let Inst{31-16} = imm{15-0};
|
||||
let Inst{47-32} = imm{31-16};
|
||||
let Size = 6;
|
||||
}
|
||||
|
||||
def MC_ADDI : TMS34010Imm<0b00001011001, "ADDI">;
|
||||
def MC_SUBI : TMS34010InvImm<0b00001101000, "SUBI">;
|
||||
def MC_CMPI : TMS34010InvImm<0b00001011011, "CMPI">;
|
||||
def MC_ANDI : TMS34010InvImm<0b00001011100, "ANDI">;
|
||||
def MC_ORI : TMS34010Imm<0b00001011101, "ORI">;
|
||||
def MC_XORI : TMS34010Imm<0b00001011110, "XORI">;
|
||||
def MC_MOVI : TMS34010Imm<0b00001001111, "MOVI">;
|
||||
|
||||
// MOVE register-to-register. Bit 9 is set when the destination file differs
|
||||
// from the source file; bit 4 is always the source file.
|
||||
def MC_MOVE : TMS34010MCInst<(outs GPR:$dst), (ins GPR:$src),
|
||||
"MOVE $src, $dst"> {
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
let Inst{15-10} = 0b010011;
|
||||
let Inst{9} = !xor(src{4}, dst{4});
|
||||
let Inst{8-5} = src{3-0};
|
||||
let Inst{4} = src{4};
|
||||
let Inst{3-0} = dst{3-0};
|
||||
}
|
||||
|
||||
// Register-indirect MOVE forms. Bit 9 selects the field size (FS0/FS1) for
|
||||
// MOVE; bit 4 is the shared register-file bit and comes from the destination
|
||||
// operand. The source operand's low four bits live in bits 8-5.
|
||||
class TMS34010MoveStore<bits<6> op, string asmstr>
|
||||
: TMS34010MCInst<(outs), (ins GPR:$src, GPR:$dst, i32imm:$fsz),
|
||||
asmstr> {
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
bits<1> fsz;
|
||||
let Inst{15-10} = op;
|
||||
let Inst{9} = fsz;
|
||||
let Inst{8-5} = src{3-0};
|
||||
let Inst{4-0} = dst;
|
||||
}
|
||||
|
||||
class TMS34010MoveLoad<bits<6> op, string asmstr>
|
||||
: TMS34010MCInst<(outs GPR:$dst), (ins GPR:$src, i32imm:$fsz),
|
||||
asmstr> {
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
bits<1> fsz;
|
||||
let Inst{15-10} = op;
|
||||
let Inst{9} = fsz;
|
||||
let Inst{8-5} = src{3-0};
|
||||
let Inst{4-0} = dst;
|
||||
}
|
||||
|
||||
def MC_MOVE_STORE : TMS34010MoveStore<0b100000, "MOVE $src, *$dst, $fsz">;
|
||||
def MC_MOVE_LOAD : TMS34010MoveLoad<0b100001, "MOVE *$src, $dst, $fsz">;
|
||||
def MC_MOVE_STORE_POST : TMS34010MoveStore<0b100100, "MOVE $src, *$dst+, $fsz">;
|
||||
def MC_MOVE_LOAD_POST : TMS34010MoveLoad<0b100101, "MOVE *$src+, $dst, $fsz">;
|
||||
def MC_MOVE_STORE_PRE : TMS34010MoveStore<0b101000, "MOVE $src, -*$dst, $fsz">;
|
||||
def MC_MOVE_LOAD_PRE : TMS34010MoveLoad<0b101001, "MOVE -*$src, $dst, $fsz">;
|
||||
|
||||
// Register-to-register through memory (both operands indirect).
|
||||
class TMS34010MoveBoth<bits<6> op, string asmstr>
|
||||
: TMS34010MCInst<(outs), (ins GPR:$src, GPR:$dst, i32imm:$fsz),
|
||||
asmstr> {
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
bits<1> fsz;
|
||||
let Inst{15-10} = op;
|
||||
let Inst{9} = fsz;
|
||||
let Inst{8-5} = src{3-0};
|
||||
let Inst{4-0} = dst;
|
||||
}
|
||||
|
||||
def MC_MOVE_BOTH : TMS34010MoveBoth<0b100010, "MOVE *$src, *$dst, $fsz">;
|
||||
def MC_MOVE_BOTH_POST : TMS34010MoveBoth<0b100110, "MOVE *$src+, *$dst+, $fsz">;
|
||||
def MC_MOVE_BOTH_PRE : TMS34010MoveBoth<0b101010, "MOVE -*$src, -*$dst, $fsz">;
|
||||
|
||||
// Register + 16-bit displacement forms.
|
||||
class TMS34010MoveStoreDisp<bits<6> op, string asmstr>
|
||||
: TMS34010MCInst<(outs),
|
||||
(ins GPR:$src, GPR:$dst, i32imm:$disp, i32imm:$fsz),
|
||||
asmstr> {
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
bits<16> disp;
|
||||
bits<1> fsz;
|
||||
let Inst{15-10} = op;
|
||||
let Inst{9} = fsz;
|
||||
let Inst{8-5} = src{3-0};
|
||||
let Inst{4-0} = dst;
|
||||
let Inst{31-16} = disp;
|
||||
let Size = 4;
|
||||
}
|
||||
|
||||
class TMS34010MoveLoadDisp<bits<6> op, string asmstr>
|
||||
: TMS34010MCInst<(outs GPR:$dst),
|
||||
(ins GPR:$src, i32imm:$disp, i32imm:$fsz),
|
||||
asmstr> {
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
bits<16> disp;
|
||||
bits<1> fsz;
|
||||
let Inst{15-10} = op;
|
||||
let Inst{9} = fsz;
|
||||
let Inst{8-5} = src{3-0};
|
||||
let Inst{4-0} = dst;
|
||||
let Inst{31-16} = disp;
|
||||
let Size = 4;
|
||||
}
|
||||
|
||||
class TMS34010MoveBothDisp<bits<6> op, string asmstr>
|
||||
: TMS34010MCInst<(outs),
|
||||
(ins GPR:$src, i32imm:$disp1, GPR:$dst, i32imm:$disp2,
|
||||
i32imm:$fsz),
|
||||
asmstr> {
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
bits<16> disp1;
|
||||
bits<16> disp2;
|
||||
bits<1> fsz;
|
||||
let Inst{15-10} = op;
|
||||
let Inst{9} = fsz;
|
||||
let Inst{8-5} = src{3-0};
|
||||
let Inst{4-0} = dst;
|
||||
let Inst{31-16} = disp1;
|
||||
let Inst{47-32} = disp2;
|
||||
let Size = 6;
|
||||
}
|
||||
|
||||
def MC_MOVE_STORE_DISP : TMS34010MoveStoreDisp<0b101100,
|
||||
"MOVE $src, *$dst($disp), $fsz">;
|
||||
def MC_MOVE_LOAD_DISP : TMS34010MoveLoadDisp<0b101101,
|
||||
"MOVE *$src($disp), $dst, $fsz">;
|
||||
def MC_MOVE_BOTH_DISP : TMS34010MoveBothDisp<0b101110,
|
||||
"MOVE *$src($disp1), *$dst($disp2), $fsz">;
|
||||
|
||||
// A displacement-indirect source with a post-increment destination.
|
||||
def MC_MOVE_DISP_POST : TMS34010MCInst<(outs),
|
||||
(ins GPR:$src, i32imm:$disp, GPR:$dst,
|
||||
i32imm:$fsz),
|
||||
"MOVE *$src($disp), *$dst+, $fsz"> {
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
bits<16> disp;
|
||||
bits<1> fsz;
|
||||
let Inst{15-10} = 0b110100;
|
||||
let Inst{9} = fsz;
|
||||
let Inst{8-5} = src{3-0};
|
||||
let Inst{4-0} = dst;
|
||||
let Inst{31-16} = disp;
|
||||
let Size = 4;
|
||||
}
|
||||
|
||||
// Absolute-address MOVE forms. The address occupies the 32-bit word after
|
||||
// the opcode; an unresolved symbol becomes an R-RELLONG relocation.
|
||||
class TMS34010MoveAbsStore<bits<6> op, bits<4> subop>
|
||||
: TMS34010MCInst<(outs), (ins GPR:$src, i32imm:$addr, i32imm:$fsz),
|
||||
"MOVE $src, @$addr, $fsz"> {
|
||||
bits<5> src;
|
||||
bits<32> addr;
|
||||
bits<1> fsz;
|
||||
let Inst{15-10} = op;
|
||||
let Inst{9} = fsz;
|
||||
let Inst{8-5} = subop;
|
||||
let Inst{4-0} = src;
|
||||
let Inst{31-16} = addr{15-0};
|
||||
let Inst{47-32} = addr{31-16};
|
||||
let Size = 6;
|
||||
}
|
||||
|
||||
class TMS34010MoveAbsLoad<bits<6> op, bits<4> subop>
|
||||
: TMS34010MCInst<(outs GPR:$dst), (ins i32imm:$addr, i32imm:$fsz),
|
||||
"MOVE @$addr, $dst, $fsz"> {
|
||||
bits<5> dst;
|
||||
bits<32> addr;
|
||||
bits<1> fsz;
|
||||
let Inst{15-10} = op;
|
||||
let Inst{9} = fsz;
|
||||
let Inst{8-5} = subop;
|
||||
let Inst{4-0} = dst;
|
||||
let Inst{31-16} = addr{15-0};
|
||||
let Inst{47-32} = addr{31-16};
|
||||
let Size = 6;
|
||||
}
|
||||
|
||||
def MC_MOVE_ABS_STORE : TMS34010MoveAbsStore<0b000001, 0b1100>;
|
||||
def MC_MOVE_ABS_LOAD : TMS34010MoveAbsLoad<0b000001, 0b1101>;
|
||||
|
||||
def MC_MOVE_ABS_POST : TMS34010MCInst<(outs),
|
||||
(ins i32imm:$addr, GPR:$dst,
|
||||
i32imm:$fsz),
|
||||
"MOVE @$addr, *$dst+, $fsz"> {
|
||||
bits<5> dst;
|
||||
bits<32> addr;
|
||||
bits<1> fsz;
|
||||
let Inst{15-10} = 0b110101;
|
||||
let Inst{9} = fsz;
|
||||
let Inst{4-0} = dst;
|
||||
let Inst{31-16} = addr{15-0};
|
||||
let Inst{47-32} = addr{31-16};
|
||||
let Size = 6;
|
||||
}
|
||||
|
||||
// MOVB forms. These have no field-size operand: bit 9 is part of the opcode,
|
||||
// except for the absolute forms where it selects load vs. store.
|
||||
class TMS34010MovbStore<bits<6> op, bit load, string asmstr>
|
||||
: TMS34010MCInst<(outs), (ins GPR:$src, GPR:$dst), asmstr> {
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
let Inst{15-10} = op;
|
||||
let Inst{9} = load;
|
||||
let Inst{8-5} = src{3-0};
|
||||
let Inst{4-0} = dst;
|
||||
}
|
||||
|
||||
class TMS34010MovbLoad<bits<6> op, bit load, string asmstr>
|
||||
: TMS34010MCInst<(outs GPR:$dst), (ins GPR:$src), asmstr> {
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
let Inst{15-10} = op;
|
||||
let Inst{9} = load;
|
||||
let Inst{8-5} = src{3-0};
|
||||
let Inst{4-0} = dst;
|
||||
}
|
||||
|
||||
def MC_MOVB_STORE : TMS34010MovbStore<0b100011, 0, "MOVB $src, *$dst">;
|
||||
def MC_MOVB_LOAD : TMS34010MovbLoad<0b100011, 1, "MOVB *$src, $dst">;
|
||||
def MC_MOVB_BOTH : TMS34010MCInst<(outs), (ins GPR:$src, GPR:$dst),
|
||||
"MOVB *$src, *$dst"> {
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
let Inst{15-10} = 0b100111;
|
||||
let Inst{9} = 0;
|
||||
let Inst{8-5} = src{3-0};
|
||||
let Inst{4-0} = dst;
|
||||
}
|
||||
|
||||
def MC_MOVB_STORE_DISP : TMS34010MCInst<(outs),
|
||||
(ins GPR:$src, GPR:$dst,
|
||||
i32imm:$disp),
|
||||
"MOVB $src, *$dst($disp)"> {
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
bits<16> disp;
|
||||
let Inst{15-10} = 0b101011;
|
||||
let Inst{9} = 0;
|
||||
let Inst{8-5} = src{3-0};
|
||||
let Inst{4-0} = dst;
|
||||
let Inst{31-16} = disp;
|
||||
let Size = 4;
|
||||
}
|
||||
|
||||
def MC_MOVB_LOAD_DISP : TMS34010MCInst<(outs GPR:$dst),
|
||||
(ins GPR:$src, i32imm:$disp),
|
||||
"MOVB *$src($disp), $dst"> {
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
bits<16> disp;
|
||||
let Inst{15-10} = 0b101011;
|
||||
let Inst{9} = 1;
|
||||
let Inst{8-5} = src{3-0};
|
||||
let Inst{4-0} = dst;
|
||||
let Inst{31-16} = disp;
|
||||
let Size = 4;
|
||||
}
|
||||
|
||||
def MC_MOVB_BOTH_DISP : TMS34010MCInst<(outs),
|
||||
(ins GPR:$src, i32imm:$disp1,
|
||||
GPR:$dst, i32imm:$disp2),
|
||||
"MOVB *$src($disp1), *$dst($disp2)"> {
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
bits<16> disp1;
|
||||
bits<16> disp2;
|
||||
let Inst{15-10} = 0b101111;
|
||||
let Inst{9} = 0;
|
||||
let Inst{8-5} = src{3-0};
|
||||
let Inst{4-0} = dst;
|
||||
let Inst{31-16} = disp1;
|
||||
let Inst{47-32} = disp2;
|
||||
let Size = 6;
|
||||
}
|
||||
|
||||
// Absolute MOVB. 0x05E0/0x07E0; bit 9 selects the load form.
|
||||
class TMS34010MovbAbsStore<bits<4> subop, bit load>
|
||||
: TMS34010MCInst<(outs), (ins GPR:$src, i32imm:$addr),
|
||||
"MOVB $src, @$addr"> {
|
||||
bits<5> src;
|
||||
bits<32> addr;
|
||||
let Inst{15-10} = 0b000001;
|
||||
let Inst{9} = load;
|
||||
let Inst{8-5} = subop;
|
||||
let Inst{4-0} = src;
|
||||
let Inst{31-16} = addr{15-0};
|
||||
let Inst{47-32} = addr{31-16};
|
||||
let Size = 6;
|
||||
}
|
||||
|
||||
class TMS34010MovbAbsLoad<bits<4> subop, bit load>
|
||||
: TMS34010MCInst<(outs GPR:$dst), (ins i32imm:$addr),
|
||||
"MOVB @$addr, $dst"> {
|
||||
bits<5> dst;
|
||||
bits<32> addr;
|
||||
let Inst{15-10} = 0b000001;
|
||||
let Inst{9} = load;
|
||||
let Inst{8-5} = subop;
|
||||
let Inst{4-0} = dst;
|
||||
let Inst{31-16} = addr{15-0};
|
||||
let Inst{47-32} = addr{31-16};
|
||||
let Size = 6;
|
||||
}
|
||||
|
||||
def MC_MOVB_ABS_STORE : TMS34010MovbAbsStore<0b1111, 0>;
|
||||
def MC_MOVB_ABS_LOAD : TMS34010MovbAbsLoad<0b1111, 1>;
|
||||
|
||||
// Calls, branches and returns.
|
||||
def MC_CALL : TMS34010MCInst<(outs), (ins GPR:$target), "CALL $target"> {
|
||||
bits<5> target;
|
||||
let Inst{15-5} = 0b00001001001;
|
||||
let Inst{4-0} = target;
|
||||
}
|
||||
|
||||
def MC_CALLA : TMS34010MCInst<(outs), (ins i32imm:$target),
|
||||
"CALLA $target"> {
|
||||
bits<32> target;
|
||||
let Inst{15-0} = 0x0D5F;
|
||||
let Inst{31-16} = target{15-0};
|
||||
let Inst{47-32} = target{31-16};
|
||||
let Size = 6;
|
||||
}
|
||||
|
||||
class TMS34010MCJcc<bits<4> cc, string mnem>
|
||||
: TMS34010MCInst<(outs), (ins i32imm:$target),
|
||||
mnem # " $target"> {
|
||||
let isBranch = 1;
|
||||
bits<32> target;
|
||||
let Inst{15-12} = 0b1100;
|
||||
let Inst{11-8} = cc;
|
||||
let Inst{7-0} = 0b10000000;
|
||||
let Inst{31-16} = target{15-0};
|
||||
let Inst{47-32} = target{31-16};
|
||||
let Size = 6;
|
||||
}
|
||||
|
||||
def MC_JAUC : TMS34010MCJcc<0x0, "JAUC">;
|
||||
def MC_JAEQ : TMS34010MCJcc<0xA, "JAEQ">;
|
||||
def MC_JANE : TMS34010MCJcc<0xB, "JANE">;
|
||||
def MC_JALT : TMS34010MCJcc<0x4, "JALT">;
|
||||
def MC_JALE : TMS34010MCJcc<0x6, "JALE">;
|
||||
def MC_JAGT : TMS34010MCJcc<0x7, "JAGT">;
|
||||
def MC_JAGE : TMS34010MCJcc<0x5, "JAGE">;
|
||||
def MC_JALO : TMS34010MCJcc<0x8, "JALO">;
|
||||
def MC_JALS : TMS34010MCJcc<0x2, "JALS">;
|
||||
def MC_JAHI : TMS34010MCJcc<0x3, "JAHI">;
|
||||
def MC_JAHS : TMS34010MCJcc<0x9, "JAHS">;
|
||||
def MC_JAP : TMS34010MCJcc<0x1, "JAP">;
|
||||
def MC_JAN : TMS34010MCJcc<0xE, "JAN">;
|
||||
def MC_JAV : TMS34010MCJcc<0xC, "JAV">;
|
||||
def MC_JANV : TMS34010MCJcc<0xD, "JANV">;
|
||||
def MC_JANN : TMS34010MCJcc<0xF, "JANN">;
|
||||
|
||||
// Short relative branches. The operand is the raw 8-bit word displacement
|
||||
// from PC+16; Task 5 supplies target/relaxation logic. GSPA spells the
|
||||
// unconditional form JRUC, so an alias accepts it.
|
||||
class TMS34010JR<bits<4> cc, string mnem>
|
||||
: TMS34010MCInst<(outs), (ins i32imm:$disp), mnem # " $disp"> {
|
||||
let isBranch = 1;
|
||||
bits<8> disp;
|
||||
let Inst{15-12} = 0b1100;
|
||||
let Inst{11-8} = cc;
|
||||
let Inst{7-0} = disp;
|
||||
}
|
||||
|
||||
def MC_JR : TMS34010JR<0x0, "JR">;
|
||||
def MC_JREQ : TMS34010JR<0xA, "JREQ">;
|
||||
def MC_JRNE : TMS34010JR<0xB, "JRNE">;
|
||||
def MC_JRLT : TMS34010JR<0x4, "JRLT">;
|
||||
def MC_JRLE : TMS34010JR<0x6, "JRLE">;
|
||||
def MC_JRGT : TMS34010JR<0x7, "JRGT">;
|
||||
def MC_JRGE : TMS34010JR<0x5, "JRGE">;
|
||||
def MC_JRLO : TMS34010JR<0x8, "JRLO">;
|
||||
def MC_JRLS : TMS34010JR<0x2, "JRLS">;
|
||||
def MC_JRHI : TMS34010JR<0x3, "JRHI">;
|
||||
def MC_JRHS : TMS34010JR<0x9, "JRHS">;
|
||||
def MC_JRP : TMS34010JR<0x1, "JRP">;
|
||||
def MC_JRN : TMS34010JR<0xE, "JRN">;
|
||||
def MC_JRV : TMS34010JR<0xC, "JRV">;
|
||||
def MC_JRNV : TMS34010JR<0xD, "JRNV">;
|
||||
def MC_JRNN : TMS34010JR<0xF, "JRNN">;
|
||||
|
||||
def MC_RETS : TMS34010MCInst<(outs), (ins i32imm:$n), "RETS $n"> {
|
||||
bits<5> n;
|
||||
let Inst{15-5} = 0b00001001011;
|
||||
let Inst{4-0} = n;
|
||||
}
|
||||
|
||||
def MC_MMTM : TMS34010MCInst<(outs), (ins GPR:$dst, i32imm:$mask),
|
||||
"MMTM $dst, $mask"> {
|
||||
bits<5> dst;
|
||||
bits<16> mask;
|
||||
let Inst{15-5} = 0b00001001100;
|
||||
let Inst{4-0} = dst;
|
||||
let Inst{31-16} = mask;
|
||||
let Size = 4;
|
||||
}
|
||||
|
||||
def MC_MMFM : TMS34010MCInst<(outs), (ins GPR:$dst, i32imm:$mask),
|
||||
"MMFM $dst, $mask"> {
|
||||
bits<5> dst;
|
||||
bits<16> mask;
|
||||
let Inst{15-5} = 0b00001001101;
|
||||
let Inst{4-0} = dst;
|
||||
let Inst{31-16} = mask;
|
||||
let Size = 4;
|
||||
}
|
||||
|
||||
// Field extension. GSPA's field operand is 0 or 1 and is encoded in bit 9.
|
||||
class TMS34010Ext<bit zext, string mnem>
|
||||
: TMS34010MCInst<(outs), (ins GPR:$dst, i32imm:$fsz),
|
||||
mnem # " $dst, $fsz"> {
|
||||
bits<5> dst;
|
||||
bits<1> fsz;
|
||||
let Inst{15-10} = 0b000001;
|
||||
let Inst{9} = fsz;
|
||||
let Inst{8-6} = 0b100;
|
||||
let Inst{5} = zext;
|
||||
let Inst{4-0} = dst;
|
||||
}
|
||||
|
||||
def MC_SEXT : TMS34010Ext<0, "SEXT">;
|
||||
def MC_ZEXT : TMS34010Ext<1, "ZEXT">;
|
||||
|
||||
// SETF fs, fe, field. fs == 32 is encoded as zero, so the operand has a
|
||||
// small custom encoder/decoder pair rather than the generic immediate one.
|
||||
def TMS34010SETFFs : Operand<i32> {
|
||||
let EncoderMethod = "getSETFFsValue";
|
||||
let DecoderMethod = "DecodeSETFFs";
|
||||
}
|
||||
|
||||
def MC_SETF : TMS34010MCInst<(outs),
|
||||
(ins TMS34010SETFFs:$fs, i32imm:$fe,
|
||||
i32imm:$fsz),
|
||||
"SETF $fs, $fe, $fsz"> {
|
||||
bits<5> fs;
|
||||
bits<1> fe;
|
||||
bits<1> fsz;
|
||||
let Inst{15-10} = 0b000001;
|
||||
let Inst{9} = fsz;
|
||||
let Inst{8-6} = 0b101;
|
||||
let Inst{5} = fe;
|
||||
let Inst{4-0} = fs;
|
||||
}
|
||||
|
||||
def MC_NOP : TMS34010MCInst<(outs), (ins), "NOP"> {
|
||||
let Inst{15-0} = 0x0300;
|
||||
}
|
||||
|
||||
// GSPA spellings that differ from the canonical printer spelling.
|
||||
def : InstAlias<"JRUC $disp", (MC_JR i32imm:$disp)>;
|
||||
def : InstAlias<"RETS", (MC_RETS 0)>;
|
||||
// dis34's condition-name aliases for the carry/borrow conditions. GSPA
|
||||
// accepts the JA/JR forms above; accept the dis34 spellings as well.
|
||||
def : InstAlias<"JAC $target", (MC_JALO i32imm:$target)>;
|
||||
def : InstAlias<"JANC $target", (MC_JAHS i32imm:$target)>;
|
||||
def : InstAlias<"JRC $disp", (MC_JRLO i32imm:$disp)>;
|
||||
def : InstAlias<"JRNC $disp", (MC_JRHS i32imm:$disp)>;
|
||||
@@ -7,6 +7,7 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "TMS34010MCInstLower.h"
|
||||
#include "TMS34010.h"
|
||||
#include "llvm/CodeGen/AsmPrinter.h"
|
||||
#include "llvm/CodeGen/MachineBasicBlock.h"
|
||||
#include "llvm/CodeGen/MachineInstr.h"
|
||||
@@ -44,7 +45,16 @@ MCOperand TMS34010MCInstLower::LowerSymbolOperand(const MachineOperand &MO,
|
||||
}
|
||||
|
||||
void TMS34010MCInstLower::Lower(const MachineInstr *MI, MCInst &OutMI) const {
|
||||
OutMI.setOpcode(MI->getOpcode());
|
||||
unsigned Opcode = MI->getOpcode();
|
||||
|
||||
// The frame lowering uses CodeGen MMTM/MMFM instructions; the assembler and
|
||||
// encoder use the MC descriptions, whose operands and encoding are the same.
|
||||
// Remap here so the printer emits the register-list spelling.
|
||||
if (Opcode == TMS34010::MMTM)
|
||||
Opcode = TMS34010::MC_MMTM;
|
||||
else if (Opcode == TMS34010::MMFM)
|
||||
Opcode = TMS34010::MC_MMFM;
|
||||
OutMI.setOpcode(Opcode);
|
||||
|
||||
for (const MachineOperand &MO : MI->operands()) {
|
||||
MCOperand MCOp;
|
||||
|
||||
@@ -32,7 +32,13 @@ def A11 : TMS34010Reg<11, "A11">;
|
||||
def A12 : TMS34010Reg<12, "A12">;
|
||||
def A13 : TMS34010Reg<13, "A13">;
|
||||
def A14 : TMS34010Reg<14, "A14">;
|
||||
def A15 : TMS34010Reg<15, "A15">;
|
||||
// GSPA spells register 15 of the A file "SP"; it rejects "A15". The encoder
|
||||
// and the fixed SP forms use the same register number, so an AltName is enough
|
||||
// for the assembler. The printer canonicalises A15 to SP in
|
||||
// TMS34010InstPrinter.
|
||||
def A15 : TMS34010Reg<15, "A15"> {
|
||||
let AltNames = ["sp"];
|
||||
}
|
||||
|
||||
// The status register is not a general-purpose register: CMP/CMPI define it and
|
||||
// the conditional branches use it. It is deliberately outside every register
|
||||
@@ -63,6 +69,18 @@ def ARegs : RegisterClass<"TMS34010", [i32], 32,
|
||||
(add A0, A1, A2, A3, A4, A5, A6, A7,
|
||||
A9, A10, A11, A12)>;
|
||||
|
||||
// Every register in both files. This class is used only by the MC instruction
|
||||
// descriptions: the assembler must accept the B file even though the M4
|
||||
// allocator and ABI do not use it. It is never allocatable, so no code path
|
||||
// can select a B register from a DAG pattern.
|
||||
def GPR : RegisterClass<"TMS34010", [i32], 32,
|
||||
(add A0, A1, A2, A3, A4, A5, A6, A7,
|
||||
A8, A9, A10, A11, A12, A13, A14, A15,
|
||||
B0, B1, B2, B3, B4, B5, B6, B7,
|
||||
B8, B9, B10, B11, B12, B13, B14, B15)> {
|
||||
let isAllocatable = 0;
|
||||
}
|
||||
|
||||
// MPYS/MPYU/DIVS/DIVU treat an even Rd as the first of a register pair: an
|
||||
// even multiply writes Rd:Rd+1, and an even divide reads a 64-bit Rd:Rd+1
|
||||
// dividend and writes the quotient to Rd and the remainder to Rd+1. The
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "TMS34010SelectionDAGInfo.h"
|
||||
#include "llvm/CodeGen/SelectionDAG.h"
|
||||
|
||||
#define GET_SDNODE_DESC
|
||||
#include "TMS34010GenSDNodeInfo.inc"
|
||||
@@ -17,3 +18,122 @@ TMS34010SelectionDAGInfo::TMS34010SelectionDAGInfo()
|
||||
: SelectionDAGGenTargetInfo(TMS34010GenSDNodeInfo) {}
|
||||
|
||||
TMS34010SelectionDAGInfo::~TMS34010SelectionDAGInfo() = default;
|
||||
|
||||
/// The largest known-size IR memory operation that is expanded inline. Larger
|
||||
/// constants are normally already converted to loops by
|
||||
/// PreISelIntrinsicLowering, whose default `getMaxMemIntrinsicInlineSizeThreshold`
|
||||
/// is also 64. Keep this bound in sync with that threshold: if PreISel is
|
||||
/// configured to inline larger constants, this hook would see them and (for a
|
||||
/// non-AlwaysInline copy) return SDValue(), which falls into the libcall path
|
||||
/// that this target cannot lower.
|
||||
static constexpr uint64_t MaxInlineMemOpBytes = 64;
|
||||
|
||||
/// Byte address arithmetic on this target is bit address arithmetic: a
|
||||
/// one-byte step is eight in the pointer value. The generic memcpy/memset
|
||||
/// expansion adds byte offsets, so the target expands the known-size copies
|
||||
/// itself with bit-scaled word and byte accesses.
|
||||
static SDValue emitMemCopy(SelectionDAG &DAG, const SDLoc &DL, SDValue Chain,
|
||||
SDValue Dst, SDValue Src, uint64_t Size,
|
||||
bool IsVolatile, MachinePointerInfo DstPtrInfo,
|
||||
MachinePointerInfo SrcPtrInfo) {
|
||||
EVT PtrVT = Dst.getValueType();
|
||||
MachineMemOperand::Flags MMOFlags =
|
||||
IsVolatile ? MachineMemOperand::MOVolatile : MachineMemOperand::MONone;
|
||||
|
||||
uint64_t I = 0;
|
||||
// 32-bit accesses are the widest legal scalar memory operation and are
|
||||
// unaligned-safe on the TMS34010.
|
||||
for (; I + 4 <= Size; I += 4) {
|
||||
SDValue Off = DAG.getConstant(I * 8, DL, MVT::i32);
|
||||
SDValue SrcAddr = DAG.getNode(ISD::ADD, DL, PtrVT, Src, Off);
|
||||
SDValue DstAddr = DAG.getNode(ISD::ADD, DL, PtrVT, Dst, Off);
|
||||
SDValue Load = DAG.getLoad(MVT::i32, DL, Chain, SrcAddr,
|
||||
SrcPtrInfo.getWithOffset(I), Align(1), MMOFlags);
|
||||
Chain = DAG.getStore(Load.getValue(1), DL, Load, DstAddr,
|
||||
DstPtrInfo.getWithOffset(I), Align(1), MMOFlags);
|
||||
}
|
||||
for (; I < Size; ++I) {
|
||||
SDValue Off = DAG.getConstant(I * 8, DL, MVT::i32);
|
||||
SDValue SrcAddr = DAG.getNode(ISD::ADD, DL, PtrVT, Src, Off);
|
||||
SDValue DstAddr = DAG.getNode(ISD::ADD, DL, PtrVT, Dst, Off);
|
||||
SDValue Load = DAG.getLoad(MVT::i8, DL, Chain, SrcAddr,
|
||||
SrcPtrInfo.getWithOffset(I), Align(1), MMOFlags);
|
||||
Chain = DAG.getStore(Load.getValue(1), DL, Load, DstAddr,
|
||||
DstPtrInfo.getWithOffset(I), Align(1), MMOFlags);
|
||||
}
|
||||
return Chain;
|
||||
}
|
||||
|
||||
SDValue TMS34010SelectionDAGInfo::EmitTargetCodeForMemcpy(
|
||||
SelectionDAG &DAG, const SDLoc &DL, SDValue Chain, SDValue Dst, SDValue Src,
|
||||
SDValue Size, Align DstAlign, Align SrcAlign, bool IsVolatile,
|
||||
bool AlwaysInline, MachinePointerInfo DstPtrInfo,
|
||||
MachinePointerInfo SrcPtrInfo) const {
|
||||
(void)DstAlign;
|
||||
(void)SrcAlign;
|
||||
auto *ConstantSize = dyn_cast<ConstantSDNode>(Size);
|
||||
if (!ConstantSize)
|
||||
return SDValue();
|
||||
uint64_t Bytes = ConstantSize->getZExtValue();
|
||||
if (!Bytes)
|
||||
return Chain;
|
||||
// A non-inline constant this large falls back to the libcall path; byval
|
||||
// copies are AlwaysInline and stay exact (they must not call with A14 as
|
||||
// both the destination and the argument area). The bound is coupled to
|
||||
// PreISel's inline threshold; see MaxInlineMemOpBytes.
|
||||
if (Bytes > MaxInlineMemOpBytes && !AlwaysInline)
|
||||
return SDValue();
|
||||
return emitMemCopy(DAG, DL, Chain, Dst, Src, Bytes, IsVolatile, DstPtrInfo,
|
||||
SrcPtrInfo);
|
||||
}
|
||||
|
||||
SDValue TMS34010SelectionDAGInfo::EmitTargetCodeForMemset(
|
||||
SelectionDAG &DAG, const SDLoc &DL, SDValue Chain, SDValue Dst, SDValue Src,
|
||||
SDValue Size, Align Alignment, bool IsVolatile, bool AlwaysInline,
|
||||
MachinePointerInfo DstPtrInfo) const {
|
||||
(void)Alignment;
|
||||
auto *ConstantSize = dyn_cast<ConstantSDNode>(Size);
|
||||
if (!ConstantSize)
|
||||
return SDValue();
|
||||
uint64_t Bytes = ConstantSize->getZExtValue();
|
||||
if (!Bytes)
|
||||
return Chain;
|
||||
if (Bytes > MaxInlineMemOpBytes && !AlwaysInline)
|
||||
return SDValue();
|
||||
|
||||
EVT PtrVT = Dst.getValueType();
|
||||
MachineMemOperand::Flags MMOFlags =
|
||||
IsVolatile ? MachineMemOperand::MOVolatile : MachineMemOperand::MONone;
|
||||
|
||||
// Replicate the byte value into a 32-bit word. Src may have been promoted
|
||||
// past i8 by legalization, so mask the low byte first.
|
||||
SDValue Byte = DAG.getAnyExtOrTrunc(Src, DL, MVT::i32);
|
||||
Byte = DAG.getNode(ISD::AND, DL, MVT::i32, Byte,
|
||||
DAG.getConstant(0xFF, DL, MVT::i32));
|
||||
SDValue Shift8 = DAG.getConstant(8, DL, MVT::i32);
|
||||
SDValue Shift16 = DAG.getConstant(16, DL, MVT::i32);
|
||||
SDValue Shift24 = DAG.getConstant(24, DL, MVT::i32);
|
||||
SDValue Word = Byte;
|
||||
Word = DAG.getNode(ISD::OR, DL, MVT::i32, Word,
|
||||
DAG.getNode(ISD::SHL, DL, MVT::i32, Byte, Shift8));
|
||||
Word = DAG.getNode(ISD::OR, DL, MVT::i32, Word,
|
||||
DAG.getNode(ISD::SHL, DL, MVT::i32, Byte, Shift16));
|
||||
Word = DAG.getNode(ISD::OR, DL, MVT::i32, Word,
|
||||
DAG.getNode(ISD::SHL, DL, MVT::i32, Byte, Shift24));
|
||||
SDValue Byte8 = DAG.getAnyExtOrTrunc(Src, DL, MVT::i8);
|
||||
|
||||
uint64_t I = 0;
|
||||
for (; I + 4 <= Bytes; I += 4) {
|
||||
SDValue Off = DAG.getConstant(I * 8, DL, MVT::i32);
|
||||
SDValue DstAddr = DAG.getNode(ISD::ADD, DL, PtrVT, Dst, Off);
|
||||
Chain = DAG.getStore(Chain, DL, Word, DstAddr,
|
||||
DstPtrInfo.getWithOffset(I), Align(1), MMOFlags);
|
||||
}
|
||||
for (; I < Bytes; ++I) {
|
||||
SDValue Off = DAG.getConstant(I * 8, DL, MVT::i32);
|
||||
SDValue DstAddr = DAG.getNode(ISD::ADD, DL, PtrVT, Dst, Off);
|
||||
Chain = DAG.getStore(Chain, DL, Byte8, DstAddr,
|
||||
DstPtrInfo.getWithOffset(I), Align(1), MMOFlags);
|
||||
}
|
||||
return Chain;
|
||||
}
|
||||
@@ -21,6 +21,22 @@ public:
|
||||
TMS34010SelectionDAGInfo();
|
||||
|
||||
~TMS34010SelectionDAGInfo() override;
|
||||
|
||||
// The TMS34010 pointer type is a *bit* address, while LLVM's generic
|
||||
// memcpy/memset expansion adds byte offsets. Emitting the copies here keeps
|
||||
// every offset in bits: four bytes advance the address by 32.
|
||||
SDValue EmitTargetCodeForMemcpy(SelectionDAG &DAG, const SDLoc &DL,
|
||||
SDValue Chain, SDValue Dst, SDValue Src,
|
||||
SDValue Size, Align DstAlign, Align SrcAlign,
|
||||
bool IsVolatile, bool AlwaysInline,
|
||||
MachinePointerInfo DstPtrInfo,
|
||||
MachinePointerInfo SrcPtrInfo) const override;
|
||||
|
||||
SDValue EmitTargetCodeForMemset(SelectionDAG &DAG, const SDLoc &DL,
|
||||
SDValue Chain, SDValue Dst, SDValue Src,
|
||||
SDValue Size, Align Alignment,
|
||||
bool IsVolatile, bool AlwaysInline,
|
||||
MachinePointerInfo DstPtrInfo) const override;
|
||||
};
|
||||
|
||||
} // namespace llvm
|
||||
|
||||
@@ -7,26 +7,39 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "TMS34010TargetMachine.h"
|
||||
#include "MCTargetDesc/TMS34010MCAsmBackend.h"
|
||||
#include "MCTargetDesc/TMS34010MCObjectWriter.h"
|
||||
#include "TMS34010.h"
|
||||
#include "TargetInfo/TMS34010TargetInfo.h"
|
||||
#include "llvm/ADT/SmallPtrSet.h"
|
||||
#include "llvm/ADT/SmallString.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/StringExtras.h"
|
||||
#include "llvm/Analysis/TargetTransformInfo.h"
|
||||
#include "llvm/CodeGen/TargetLoweringObjectFileImpl.h"
|
||||
#include "llvm/CodeGen/TargetPassConfig.h"
|
||||
#include "llvm/IR/CFG.h"
|
||||
#include "llvm/IR/Function.h"
|
||||
#include "llvm/IR/GlobalValue.h"
|
||||
#include "llvm/IR/IRBuilder.h"
|
||||
#include "llvm/IR/Instructions.h"
|
||||
#include "llvm/IR/IntrinsicInst.h"
|
||||
#include "llvm/IR/Mangler.h"
|
||||
#include "llvm/IR/Module.h"
|
||||
#include "llvm/MC/MCCodeEmitter.h"
|
||||
#include "llvm/MC/MCContext.h"
|
||||
#include "llvm/MC/MCStreamer.h"
|
||||
#include "llvm/MC/MCSubtargetInfo.h"
|
||||
#include "llvm/MC/MCSymbol.h"
|
||||
#include "llvm/MC/TargetRegistry.h"
|
||||
#include "llvm/Pass.h"
|
||||
#include "llvm/Support/Compiler.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
#include "llvm/Target/TargetOptions.h"
|
||||
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
|
||||
#include "llvm/Transforms/Utils/LowerMemIntrinsics.h"
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
using namespace llvm;
|
||||
@@ -77,23 +90,55 @@ static void sanitizeGSPASymbolName(SmallVectorImpl<char> &Name) {
|
||||
// llc gives a file input the file name as the module identifier (or the IR's
|
||||
// own source_filename) and "<stdin>" for standard input, so the token is
|
||||
// deterministic for a given input and stable for lit's stdin RUN lines.
|
||||
//
|
||||
// GSPA 2.00 keeps only 31 identifier characters and silently truncates the
|
||||
// rest. The token itself is `_` plus eight hex digits, so a long private base
|
||||
// name would push the token past the limit and reintroduce the collision. Cap
|
||||
// the base at 22 characters; if it is longer, fold the full name into another
|
||||
// eight-hex token (13-char prefix + `_` + 8 hex = 22) so the module token is
|
||||
// always the last field of a 31-character label.
|
||||
//
|
||||
// Only local symbols are capped. A *public* name longer than 31 characters is
|
||||
// still silently truncated by GSPA; that remains a known limitation outside
|
||||
// the private-label scope of this task.
|
||||
static constexpr unsigned MaxGSPASymbolLength = 31;
|
||||
static constexpr unsigned HexTokenLength = 9; // '_' + 8 hex digits
|
||||
|
||||
static unsigned hashName(StringRef S) {
|
||||
// Custom 64-bit string hash (FNV-style, not canonical FNV-1a), folded to
|
||||
// 32 bits. Eight uppercase hex digits stay GSPA-safe.
|
||||
uint64_t Hash = 1469598103934665603ULL;
|
||||
for (unsigned char C : S) {
|
||||
Hash ^= C;
|
||||
Hash *= 1099511628211ULL;
|
||||
}
|
||||
return static_cast<unsigned>(Hash ^ (Hash >> 32));
|
||||
}
|
||||
|
||||
static void appendHexToken(SmallVectorImpl<char> &Name, unsigned Token) {
|
||||
Name.push_back('_');
|
||||
for (int Shift = 28; Shift >= 0; Shift -= 4)
|
||||
Name.push_back(hexdigit((Token >> Shift) & 0xF));
|
||||
}
|
||||
|
||||
static void appendModuleToken(SmallVectorImpl<char> &Name,
|
||||
const GlobalValue *GV) {
|
||||
const Module *M = GV->getParent();
|
||||
StringRef ID = M ? M->getModuleIdentifier() : StringRef();
|
||||
|
||||
// Custom 64-bit string hash (FNV-style, not canonical FNV-1a), folded to
|
||||
// 32 bits. Eight uppercase hex digits stay GSPA-safe and
|
||||
// are short enough not to stress the 1987 assembler's identifier limits.
|
||||
uint64_t Hash = 1469598103934665603ULL;
|
||||
for (unsigned char C : ID) {
|
||||
Hash ^= C;
|
||||
Hash *= 1099511628211ULL;
|
||||
unsigned Token = hashName(ID);
|
||||
constexpr unsigned MaxBaseLength =
|
||||
MaxGSPASymbolLength - HexTokenLength;
|
||||
if (Name.size() > MaxBaseLength) {
|
||||
unsigned NameToken = hashName(StringRef(Name.data(), Name.size()));
|
||||
constexpr unsigned Keep =
|
||||
MaxBaseLength - HexTokenLength;
|
||||
SmallString<64> Short;
|
||||
Short.append(Name.begin(), Name.begin() + Keep);
|
||||
appendHexToken(Short, NameToken);
|
||||
Name.assign(Short.begin(), Short.end());
|
||||
}
|
||||
unsigned Token = static_cast<unsigned>(Hash ^ (Hash >> 32));
|
||||
Name.push_back('_');
|
||||
for (int Shift = 28; Shift >= 0; Shift -= 4)
|
||||
Name.push_back(hexdigit((Token >> Shift) & 0xF));
|
||||
appendHexToken(Name, Token);
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -163,6 +208,171 @@ public:
|
||||
|
||||
char TMS34010SplitCriticalEdges::ID = 0;
|
||||
|
||||
/// PreISelIntrinsicLowering expands a runtime-length or larger-than-threshold
|
||||
/// constant memcpy/memset/memmove into IR loops built from i8 GEPs. Those
|
||||
/// GEPs carry byte offsets, but TMS34010 pointers are bit addresses (the
|
||||
/// tms-clang pipeline normalizes earlier GEPs before llc). Scale only the
|
||||
/// GEPs in the known expansion / memmove loop blocks; every GEP in the input
|
||||
/// module is already a bit offset by contract.
|
||||
class TMS34010ScaleMemExpansionGEPs : public FunctionPass {
|
||||
public:
|
||||
static char ID;
|
||||
TMS34010ScaleMemExpansionGEPs() : FunctionPass(ID) {}
|
||||
|
||||
bool runOnFunction(Function &F) override {
|
||||
bool Changed = false;
|
||||
for (BasicBlock &BB : F) {
|
||||
StringRef Name = BB.getName();
|
||||
// The exact LowerMemIntrinsics block names. Both the runtime ("dynamic")
|
||||
// and the >threshold constant ("static") expansions must be scaled.
|
||||
// memmove has its own loop/middle/residual blocks; do NOT match
|
||||
// memmove_done (the continuation block keeps the user's already-bit-
|
||||
// offset GEPs) or the copy_backwards/copy_forward selection blocks.
|
||||
// LLVM uniquifies repeated block names with a numeric suffix, so match
|
||||
// the prefixes rather than exact names: a function with two memmoves
|
||||
// has e.g. memmove_bwd_main_loop7 next to memmove_bwd_main_loop.
|
||||
bool IsMemMoveLoop = Name.starts_with("memmove_") &&
|
||||
!Name.starts_with("memmove_done") &&
|
||||
!Name.starts_with("memmove_copy_");
|
||||
if (!Name.starts_with("dynamic-memcpy-expansion-") &&
|
||||
!Name.starts_with("static-memcpy-expansion-") &&
|
||||
!Name.starts_with("dynamic-memset-expansion-") &&
|
||||
!Name.starts_with("static-memset-expansion-") && !IsMemMoveLoop)
|
||||
continue;
|
||||
SmallVector<GetElementPtrInst *, 4> GEPs;
|
||||
for (Instruction &I : BB)
|
||||
if (auto *GEP = dyn_cast<GetElementPtrInst>(&I))
|
||||
GEPs.push_back(GEP);
|
||||
for (GetElementPtrInst *GEP : GEPs)
|
||||
Changed |= scaleGEP(GEP);
|
||||
}
|
||||
return Changed;
|
||||
}
|
||||
|
||||
private:
|
||||
static bool scaleGEP(GetElementPtrInst *GEP) {
|
||||
// The LowerMemIntrinsics form always indexes i8 with one index.
|
||||
if (GEP->getNumOperands() != 2 ||
|
||||
!GEP->getSourceElementType()->isIntegerTy(8))
|
||||
return false;
|
||||
|
||||
IRBuilder<> B(GEP);
|
||||
Value *Idx = B.CreateSExtOrTrunc(GEP->getOperand(1), B.getInt32Ty(),
|
||||
"bit.idx");
|
||||
Value *Base =
|
||||
B.CreatePtrToInt(GEP->getPointerOperand(), B.getInt32Ty(), "bit.base");
|
||||
Value *Off = B.CreateShl(Idx, 3, "bit.off");
|
||||
Value *Addr = B.CreateAdd(Base, Off, "bit.addr");
|
||||
Value *NewPtr = B.CreateIntToPtr(Addr, GEP->getType(), "bit.ptr");
|
||||
GEP->replaceAllUsesWith(NewPtr);
|
||||
GEP->eraseFromParent();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
char TMS34010ScaleMemExpansionGEPs::ID = 0;
|
||||
|
||||
/// PreISelIntrinsicLowering expands only memmove intrinsics whose size is not a
|
||||
/// compile-time constant (or is above its threshold). A small constant
|
||||
/// memmove is left for SelectionDAG, where this target has no memmove hook and
|
||||
/// the fallback libcall is rejected by LowerCall. Expand every remaining
|
||||
/// memmove into the same overlap-safe forward/backward loops used by the
|
||||
/// dynamic form; the GEP scaling pass below handles their byte offsets.
|
||||
class TMS34010LowerMemMove : public FunctionPass {
|
||||
public:
|
||||
static char ID;
|
||||
TMS34010LowerMemMove() : FunctionPass(ID) {}
|
||||
|
||||
bool runOnFunction(Function &F) override {
|
||||
const TargetTransformInfo &TTI =
|
||||
getAnalysis<TargetTransformInfoWrapperPass>().getTTI(F);
|
||||
SmallVector<MemMoveInst *, 4> Work;
|
||||
for (BasicBlock &BB : F)
|
||||
for (Instruction &I : BB)
|
||||
if (auto *MM = dyn_cast<MemMoveInst>(&I))
|
||||
Work.push_back(MM);
|
||||
bool Changed = false;
|
||||
for (MemMoveInst *MM : Work) {
|
||||
if (expandMemMoveAsLoop(MM, TTI)) {
|
||||
MM->eraseFromParent();
|
||||
Changed = true;
|
||||
}
|
||||
}
|
||||
return Changed;
|
||||
}
|
||||
|
||||
void getAnalysisUsage(AnalysisUsage &AU) const override {
|
||||
AU.addRequired<TargetTransformInfoWrapperPass>();
|
||||
}
|
||||
};
|
||||
|
||||
char TMS34010LowerMemMove::ID = 0;
|
||||
|
||||
/// Reject IR shapes that are outside the supported M5 pipeline, so the raw
|
||||
/// pipeline gets an actionable diagnostic instead of a generic SelectionDAG
|
||||
/// failure or an accidental reliance on an unsupported wide-multiply path.
|
||||
///
|
||||
/// - An i33 multiply (exactly 33 bits) is what IndVarSimplify's closed-form
|
||||
/// exit-value replacement produces (n*(n+1)/2). tms-clang keeps the loop
|
||||
/// with -replexitval=never; an opt run that omits it reaches llc as
|
||||
/// `mul i33` and gets a diagnostic naming that driver policy.
|
||||
/// - A 64-bit (or wider) multiply is the ordinary i64 path, not the i33
|
||||
/// closed-form shape. It is reserved for M6, and its diagnostic must not
|
||||
/// point at -replexitval=never (that policy is already applied by tms-clang
|
||||
/// and does nothing for i64).
|
||||
/// - A local (static) variadic definition loses its `...` to
|
||||
/// DeadArgumentElimination at -O2 because nothing calls llvm.va_start. The
|
||||
/// M4 contiguous-stack walk still reads those slots, so reject it with a
|
||||
/// remedy. -O0 keeps the M4 varargs gate working, and external definitions
|
||||
/// (sprintf) are unaffected.
|
||||
class TMS34010RejectUnsupportedIR : public FunctionPass {
|
||||
public:
|
||||
static char ID;
|
||||
|
||||
explicit TMS34010RejectUnsupportedIR(CodeGenOptLevel OL)
|
||||
: FunctionPass(ID), OptLevel(OL) {}
|
||||
|
||||
bool runOnFunction(Function &F) override {
|
||||
for (BasicBlock &BB : F)
|
||||
for (Instruction &I : BB)
|
||||
if (auto *BO = dyn_cast<BinaryOperator>(&I))
|
||||
if (BO->getOpcode() == Instruction::Mul)
|
||||
if (auto *IT = dyn_cast<IntegerType>(BO->getType())) {
|
||||
unsigned Width = IT->getBitWidth();
|
||||
if (Width == 33)
|
||||
report_fatal_error(
|
||||
"TMS34010: i33 multiply (umul_lohi shape) reached code "
|
||||
"generation from the raw opt pipeline; tms-clang avoids "
|
||||
"this closed-form loop exit value with -replexitval=never. "
|
||||
"Add that flag to opt; the closed-form path is reserved "
|
||||
"for i64/M6.",
|
||||
false);
|
||||
if (Width > 33)
|
||||
report_fatal_error(
|
||||
"TMS34010: 64-bit multiply reached code generation; the "
|
||||
"i64 path is reserved for M6. M5 supports 32-bit "
|
||||
"multiplication only.",
|
||||
false);
|
||||
}
|
||||
|
||||
if (OptLevel != CodeGenOptLevel::None && F.isVarArg() &&
|
||||
F.hasLocalLinkage() && !F.isDeclaration())
|
||||
report_fatal_error(
|
||||
Twine("TMS34010: local variadic definition '") + F.getName() +
|
||||
"' is not accepted at a non-O0 llc level; general varargs/"
|
||||
"va_list are an M6 feature. M5 supports only the contiguous-"
|
||||
"stack form anchored at an i32/pointer last fixed argument; "
|
||||
"compile with -O0 for that M4-compatible subset.",
|
||||
false);
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
CodeGenOptLevel OptLevel;
|
||||
};
|
||||
|
||||
char TMS34010RejectUnsupportedIR::ID = 0;
|
||||
|
||||
class TMS34010PassConfig : public TargetPassConfig {
|
||||
public:
|
||||
TMS34010PassConfig(TMS34010TargetMachine &TM, PassManagerBase &PM)
|
||||
@@ -176,6 +386,14 @@ public:
|
||||
|
||||
void addIRPasses() override {
|
||||
TargetPassConfig::addIRPasses();
|
||||
addPass(new TMS34010RejectUnsupportedIR(
|
||||
getTMS34010TargetMachine().getOptLevel()));
|
||||
// PreISelIntrinsicLowering ran before this hook and may have introduced
|
||||
// byte-offset GEPs in the runtime-size mem intrinsic loops. Small constant
|
||||
// memmove intrinsics are not expanded by it; expand them here so they use
|
||||
// the same overlap-safe loops and the scaling pass below.
|
||||
addPass(new TMS34010LowerMemMove());
|
||||
addPass(new TMS34010ScaleMemExpansionGEPs());
|
||||
addPass(new TMS34010SplitCriticalEdges());
|
||||
}
|
||||
|
||||
@@ -186,6 +404,17 @@ public:
|
||||
// This is a machine-level choice only: llc still skips the IR pipeline at
|
||||
// -O0, so late GEPs keep their bit offsets.
|
||||
void addFastRegAlloc() override { addOptimizedRegAlloc(); }
|
||||
|
||||
// Branch relaxation is a pre-emit pass: it runs after prologue/epilogue
|
||||
// insertion, pseudo expansion and block placement, when the final layout
|
||||
// decides which local branches fit the one-word relative form.
|
||||
void addPostRegAlloc() override {
|
||||
addPass(createTMS34010ExpandCmpBrPass());
|
||||
}
|
||||
|
||||
void addPreEmitPass() override {
|
||||
addPass(createTMS34010BranchRelaxationPass());
|
||||
}
|
||||
};
|
||||
|
||||
} // end anonymous namespace
|
||||
@@ -213,6 +442,45 @@ TMS34010TargetMachine::createPassConfig(PassManagerBase &PM) {
|
||||
return new TMS34010PassConfig(*this, PM);
|
||||
}
|
||||
|
||||
Expected<std::unique_ptr<MCStreamer>> TMS34010TargetMachine::createMCStreamer(
|
||||
raw_pwrite_stream &Out, raw_pwrite_stream *DwoOut, CodeGenFileType FileType,
|
||||
MCContext &Ctx) {
|
||||
if (FileType != CodeGenFileType::ObjectFile)
|
||||
return CodeGenTargetMachineImpl::createMCStreamer(Out, DwoOut, FileType,
|
||||
Ctx);
|
||||
|
||||
if (DwoOut)
|
||||
return make_error<StringError>(
|
||||
"TMS34010 does not support split DWARF objects",
|
||||
inconvertibleErrorCode());
|
||||
|
||||
std::unique_ptr<MCCodeEmitter> MCE(
|
||||
getTarget().createMCCodeEmitter(*getMCInstrInfo(), Ctx));
|
||||
if (!MCE)
|
||||
return make_error<StringError>("createMCCodeEmitter failed",
|
||||
inconvertibleErrorCode());
|
||||
|
||||
std::unique_ptr<MCAsmBackend> MAB(getTarget().createMCAsmBackend(
|
||||
getMCSubtargetInfo(), getMCRegisterInfo(), Options.MCOptions));
|
||||
if (!MAB)
|
||||
return make_error<StringError>("createMCAsmBackend failed",
|
||||
inconvertibleErrorCode());
|
||||
|
||||
// TI COFF is not one of LLVM's built-in object formats, so build the writer
|
||||
// and the generic object streamer directly instead of going through
|
||||
// MCAsmBackend::createObjectWriter's format switch. The streamer records
|
||||
// `.globl` symbols in a set shared with the writer, which uses it to tell
|
||||
// global definitions from local labels.
|
||||
auto Globals = std::make_shared<TMS34010GlobalSymbols>();
|
||||
auto *TMSMAB = static_cast<TMS34010MCAsmBackend *>(MAB.get());
|
||||
std::unique_ptr<MCObjectWriter> OW =
|
||||
TMSMAB->createObjectWriter(Out, Globals);
|
||||
|
||||
return std::make_unique<TMS34010MCStreamer>(Ctx, std::move(MAB),
|
||||
std::move(OW), std::move(MCE),
|
||||
std::move(Globals));
|
||||
}
|
||||
|
||||
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
|
||||
LLVMInitializeTMS34010Target() {
|
||||
RegisterTargetMachine<TMS34010TargetMachine> X(getTheTMS34010Target());
|
||||
|
||||
@@ -32,6 +32,10 @@ public:
|
||||
|
||||
TargetPassConfig *createPassConfig(PassManagerBase &PM) override;
|
||||
|
||||
Expected<std::unique_ptr<MCStreamer>>
|
||||
createMCStreamer(raw_pwrite_stream &Out, raw_pwrite_stream *DwoOut,
|
||||
CodeGenFileType FileType, MCContext &Ctx) override;
|
||||
|
||||
TargetLoweringObjectFile *getObjFileLowering() const override {
|
||||
return TLOF.get();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,6 @@ entry:
|
||||
; CHECK-LABEL: _call0:
|
||||
; CHECK-NOT: MOVE A14, -*SP, 1
|
||||
; CHECK: CALLA _callee
|
||||
; CHECK: SETF 16, 0, 0
|
||||
; CHECK: SETF 16, 1, 0
|
||||
; CHECK-NEXT: SETF 32, 0, 1
|
||||
; CHECK: RETS 0
|
||||
@@ -9,10 +9,10 @@ entry:
|
||||
}
|
||||
; CHECK-LABEL: _keep:
|
||||
; CHECK: MOVE A13, -*SP, 1
|
||||
; CHECK: MOVE A{{[0-9]+}}, -*SP, 1
|
||||
; CHECK-NEXT: MMTM SP, A0, A1
|
||||
; CHECK: CALLA _callee
|
||||
; CHECK: SETF 32, 0, 1
|
||||
; CHECK: MOVE *SP({{[0-9]+}}), A14, 1
|
||||
; CHECK: MOVE *SP+, A{{[0-9]+}}, 1
|
||||
; CHECK: MOVE *SP+, A13, 1
|
||||
; CHECK: RETS 2
|
||||
; CHECK: MOVE *SP(128), A14, 1
|
||||
; CHECK-NEXT: MMFM SP, A0, A1
|
||||
; CHECK-NEXT: MOVE *SP+, A13, 1
|
||||
; CHECK-NEXT: RETS 2
|
||||
@@ -10,9 +10,9 @@ entry:
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _caller:
|
||||
; CHECK: SETF 16, 0, 0
|
||||
; CHECK: SETF 16, 1, 0
|
||||
; CHECK-NEXT: SETF 32, 0, 1
|
||||
; CHECK: CALLA _callee
|
||||
; CHECK-NEXT: SETF 16, 0, 0
|
||||
; CHECK-NEXT: SETF 16, 1, 0
|
||||
; CHECK-NEXT: SETF 32, 0, 1
|
||||
; CHECK: RETS 2
|
||||
@@ -27,6 +27,6 @@ entry:
|
||||
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 0
|
||||
; CHECK-LABEL: _store_after_call:
|
||||
; CHECK: CALLA _callee
|
||||
; CHECK-NEXT: SETF 16, 0, 0
|
||||
; CHECK-NEXT: SETF 16, 1, 0
|
||||
; CHECK-NEXT: SETF 32, 0, 1
|
||||
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 0
|
||||
@@ -0,0 +1,9 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
;
|
||||
; Floating-point returns are outside the M5 integer ABI.
|
||||
; CHECK: M2 supports only void/integer(<=32)/pointer returns
|
||||
define float @retf() {
|
||||
entry:
|
||||
ret float 1.0
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
;
|
||||
; i64 returns stay outside the M5 ABI even though i8/i16 returns are now
|
||||
; accepted; CanLowerReturn must reject the original IR type before the type
|
||||
; legalizer can split it into two i32 return pieces.
|
||||
; CHECK: M2 supports only void/integer(<=32)/pointer returns
|
||||
define i64 @ret64() {
|
||||
entry:
|
||||
ret i64 7
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; CHECK: M2 supports only void/i32/pointer returns
|
||||
define i8 @retc() {
|
||||
entry:
|
||||
ret i8 7
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
;
|
||||
; The inalloca convention has no TMS34010 lowering; reject the formal flag
|
||||
; rather than treating the pointer as an ordinary argument.
|
||||
; CHECK: M2 supports only plain arguments (no sret/inalloca/preallocated/inreg/nest)
|
||||
define void @inalloca_arg(ptr inalloca(i32) %p) {
|
||||
entry:
|
||||
store i32 0, ptr %p
|
||||
ret void
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
;
|
||||
; The preallocated convention has no TMS34010 lowering; reject the formal flag
|
||||
; rather than treating the pointer as an ordinary argument.
|
||||
; CHECK: M2 supports only plain arguments (no sret/inalloca/preallocated/inreg/nest)
|
||||
define void @preallocated_arg(ptr preallocated(i32) %p) {
|
||||
entry:
|
||||
store i32 0, ptr %p
|
||||
ret void
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
;
|
||||
; M2 has no sret convention. The pointer attribute must be rejected even
|
||||
; though its legalized type is a plain i32.
|
||||
; CHECK: M2 supports only plain arguments (no sret/byval/inreg/nest)
|
||||
; CHECK: M2 supports only plain arguments (no sret/inalloca/preallocated/inreg/nest)
|
||||
define void @sret_arg(ptr sret(i32) %out) {
|
||||
entry:
|
||||
store i32 0, ptr %out
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
; CanLowerReturn must reject an unsupported return type itself. Returning
|
||||
; false would make SelectionDAG demote the aggregate to an sret pointer and
|
||||
; silently change the function signature.
|
||||
; CHECK: M2 supports only void/i32/pointer returns
|
||||
; CHECK: M2 supports only void/integer(<=32)/pointer returns
|
||||
%struct.S = type { i32, i32 }
|
||||
|
||||
define %struct.S @structret(i32 %a) {
|
||||
|
||||
@@ -12,5 +12,5 @@ loop:
|
||||
; CHECK-LABEL: _spin:
|
||||
; CHECK-NOT: .LBB
|
||||
; CHECK: [[L:LBB[0-9_]+]]:
|
||||
; CHECK: JAUC [[L]]
|
||||
; CHECK: J{{A|R}}UC [[L]]
|
||||
; CHECK-NOT: .LBB
|
||||
@@ -1,5 +1,5 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s --check-prefix=O0
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s --check-prefix=O2
|
||||
define i32 @lt5(i32 %a) {
|
||||
entry:
|
||||
%c = icmp slt i32 %a, 5
|
||||
@@ -7,10 +7,17 @@ entry:
|
||||
ret i32 %r
|
||||
}
|
||||
; A small constant on the right becomes CMPI, and the compare must stay
|
||||
; adjacent to the branch: nothing may write the flags in between.
|
||||
; CHECK-LABEL: _lt5:
|
||||
; CHECK: CMPI 5
|
||||
; CHECK-NEXT: JALT
|
||||
; CHECK-DAG: MOVI 0
|
||||
; CHECK-DAG: MOVI 1
|
||||
; CHECK-DAG: RETS 2
|
||||
; adjacent to the branch: nothing may write the flags in between. The two
|
||||
; materialized values are pinned in their emitted order, but the surrounding
|
||||
; frame/return layout is deliberately not pinned so this test anchors only
|
||||
; the compare, branch, and MOVI order.
|
||||
; O0-LABEL: _lt5:
|
||||
; O0: CMPI 5
|
||||
; O0-NEXT: J{{A|R}}LT
|
||||
; O0: MOVI 0
|
||||
; O0: MOVI 1
|
||||
; O2-LABEL: _lt5:
|
||||
; O2: CMPI 5
|
||||
; O2-NEXT: J{{A|R}}LT
|
||||
; O2: MOVI 1
|
||||
; O2: MOVI 0
|
||||
@@ -7,5 +7,5 @@ entry:
|
||||
}
|
||||
; CHECK-LABEL: _max:
|
||||
; CHECK: CMP
|
||||
; CHECK-NEXT: JAGT
|
||||
; CHECK-NEXT: J{{A|R}}GT
|
||||
; CHECK: RETS 2
|
||||
@@ -1,4 +1,4 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
; RUN: llc -march=tms34010 -O0 -verify-machineinstrs -filetype=asm < %s | FileCheck %s
|
||||
define i32 @sumto(i32 %n) {
|
||||
entry:
|
||||
br label %loop
|
||||
@@ -14,6 +14,6 @@ exit:
|
||||
}
|
||||
; CHECK-LABEL: _sumto:
|
||||
; CHECK: CMP
|
||||
; CHECK-NEXT: JALE
|
||||
; CHECK: JAUC
|
||||
; CHECK-NEXT: J{{A|R}}LE
|
||||
; CHECK: J{{A|R}}UC
|
||||
; CHECK: RETS 2
|
||||
@@ -7,7 +7,7 @@ entry:
|
||||
}
|
||||
; CHECK-LABEL: _lt:
|
||||
; CHECK: CMP
|
||||
; CHECK-NEXT: JALT
|
||||
; CHECK-NEXT: J{{A|R}}LT
|
||||
; CHECK: MOVI 0
|
||||
; CHECK: MOVI 1
|
||||
; CHECK: RETS 2
|
||||
@@ -13,7 +13,7 @@ entry:
|
||||
}
|
||||
; CHECK-LABEL: _ult:
|
||||
; CHECK: CMP
|
||||
; CHECK-NEXT: JALO
|
||||
; CHECK-NEXT: J{{A|R}}LO
|
||||
; CHECK-LABEL: _ugt:
|
||||
; CHECK: CMP
|
||||
; CHECK-NEXT: JAHI
|
||||
; CHECK-NEXT: J{{A|R}}HI
|
||||
@@ -64,31 +64,31 @@ entry:
|
||||
}
|
||||
; CHECK-LABEL: _slt:
|
||||
; CHECK: CMP
|
||||
; CHECK-NEXT: JALT
|
||||
; CHECK-NEXT: J{{A|R}}LT
|
||||
; CHECK-LABEL: _sle:
|
||||
; CHECK: CMP
|
||||
; CHECK-NEXT: JALE
|
||||
; CHECK-NEXT: J{{A|R}}LE
|
||||
; CHECK-LABEL: _sgt:
|
||||
; CHECK: CMP
|
||||
; CHECK-NEXT: JAGT
|
||||
; CHECK-NEXT: J{{A|R}}GT
|
||||
; CHECK-LABEL: _sge:
|
||||
; CHECK: CMP
|
||||
; CHECK-NEXT: JAGE
|
||||
; CHECK-NEXT: J{{A|R}}GE
|
||||
; CHECK-LABEL: _ult:
|
||||
; CHECK: CMP
|
||||
; CHECK-NEXT: JALO
|
||||
; CHECK-NEXT: J{{A|R}}LO
|
||||
; CHECK-LABEL: _ule:
|
||||
; CHECK: CMP
|
||||
; CHECK-NEXT: JALS
|
||||
; CHECK-NEXT: J{{A|R}}LS
|
||||
; CHECK-LABEL: _ugt:
|
||||
; CHECK: CMP
|
||||
; CHECK-NEXT: JAHI
|
||||
; CHECK-NEXT: J{{A|R}}HI
|
||||
; CHECK-LABEL: _uge:
|
||||
; CHECK: CMP
|
||||
; CHECK-NEXT: JAHS
|
||||
; CHECK-NEXT: J{{A|R}}HS
|
||||
; CHECK-LABEL: _eq:
|
||||
; CHECK: CMP
|
||||
; CHECK-NEXT: JAEQ
|
||||
; CHECK-NEXT: J{{A|R}}EQ
|
||||
; CHECK-LABEL: _ne:
|
||||
; CHECK: CMP
|
||||
; CHECK-NEXT: JANE
|
||||
; CHECK-NEXT: J{{A|R}}NE
|
||||
@@ -31,12 +31,12 @@ good:
|
||||
|
||||
; CHECK-LABEL: _ult_high_bit:
|
||||
; CHECK: CMPI -2147483647
|
||||
; CHECK-NEXT: JALO
|
||||
; CHECK-NEXT: J{{A|R}}LO
|
||||
|
||||
; CHECK-LABEL: _eq_all_ones_select:
|
||||
; CHECK: CMPI -1
|
||||
; CHECK-NEXT: JAEQ
|
||||
; CHECK-NEXT: J{{A|R}}EQ
|
||||
|
||||
; CHECK-LABEL: _xg_range:
|
||||
; CHECK: CMPI -33554177
|
||||
; CHECK-NEXT: JAHI
|
||||
; CHECK-NEXT: J{{A|R}}HI
|
||||
@@ -21,6 +21,6 @@ default: ret i32 0
|
||||
}
|
||||
; CHECK-LABEL: _sw:
|
||||
; CHECK: CMPI 2, A{{[0-9]+}}
|
||||
; CHECK: JAGT
|
||||
; CHECK: J{{A|R}}GT
|
||||
; CHECK-NOT: .long
|
||||
; CHECK: RETS
|
||||
@@ -0,0 +1,117 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=obj < %s -o %t.o
|
||||
;
|
||||
; Dynamic alloca. The TMS34010 program stack (A14/STK) grows upward, so a
|
||||
; runtime-sized object starts at the current A14 (rounded up to the alloca's
|
||||
; alignment) and A14 is advanced past it. The fixed frame stays between FP
|
||||
; (A13) and the old A14, so FP-relative locals/arguments are unaffected. The
|
||||
; request is 32-bit, so the byte count is 4*n, the A14 advance is (4*n)<<3
|
||||
; bits, and the 4-byte alloca alignment is the bit mask -32.
|
||||
|
||||
declare i32 @leaf()
|
||||
declare ptr @llvm.stacksave()
|
||||
declare void @llvm.stackrestore(ptr)
|
||||
|
||||
define i32 @alloca_sum(i32 %n) {
|
||||
entry:
|
||||
%a = alloca i32, i32 %n, align 4
|
||||
br label %loop
|
||||
loop:
|
||||
%i = phi i32 [ 0, %entry ], [ %i1, %loop ]
|
||||
%acc = phi i32 [ 0, %entry ], [ %acc1, %loop ]
|
||||
%p = getelementptr i32, ptr %a, i32 %i
|
||||
store volatile i32 %i, ptr %p
|
||||
%v = load volatile i32, ptr %p
|
||||
%acc1 = add i32 %acc, %v
|
||||
%i1 = add i32 %i, 1
|
||||
%c = icmp ult i32 %i1, %n
|
||||
br i1 %c, label %loop, label %done
|
||||
done:
|
||||
%q = getelementptr i32, ptr %a, i32 %n
|
||||
%last = getelementptr i32, ptr %q, i32 -1
|
||||
%lv = load i32, ptr %last
|
||||
%r = add i32 %acc1, %lv
|
||||
%z = call i32 @leaf()
|
||||
%s = add i32 %r, %z
|
||||
ret i32 %s
|
||||
}
|
||||
; The allocation reads A14, rounds it to the 32-bit (4-byte) boundary and
|
||||
; advances A14 by the byte size shifted to bits. The call to @leaf must not
|
||||
; disturb the allocation: the caller path saves the current A14 on the system
|
||||
; stack and the callee's epilogue restores it.
|
||||
; CHECK-LABEL: _alloca_sum:
|
||||
; CHECK: SLL 5, A{{[0-9]+}}
|
||||
; CHECK: MOVE A14, A{{[0-9]+}}
|
||||
; CHECK: ADDI 31, A{{[0-9]+}}
|
||||
; CHECK: ANDI -32, A{{[0-9]+}}
|
||||
; CHECK: ADD A{{[0-9]+}}, A{{[0-9]+}}
|
||||
; CHECK-NEXT: MOVE A{{[0-9]+}}, A14
|
||||
; CHECK: CALLA _leaf
|
||||
; CHECK: MOVE *SP({{[0-9]+}}), A14, 1
|
||||
; CHECK: RETS 2
|
||||
|
||||
@count = external global i32
|
||||
|
||||
; A dynamic alloca in a no-argument function has no saved old-STK slot to
|
||||
; reload. The epilogue must rebuild A14 from FP + fixed frame size so the
|
||||
; dynamic area is discarded; the old ADDI -frame,A14 would instead leave A14
|
||||
; above the allocation.
|
||||
define i32 @alloca_noarg() {
|
||||
entry:
|
||||
%n = load i32, ptr @count
|
||||
%a = alloca i32, i32 %n, align 4
|
||||
%i = add i32 %n, -1
|
||||
%p = getelementptr i32, ptr %a, i32 %i
|
||||
store volatile i32 7, ptr %p
|
||||
%v = load volatile i32, ptr %p
|
||||
ret i32 %v
|
||||
}
|
||||
; CHECK-LABEL: _alloca_noarg:
|
||||
; CHECK: MOVE A14, A{{[0-9]+}}
|
||||
; CHECK: ADDI 31, A{{[0-9]+}}
|
||||
; CHECK: ANDI -32, A{{[0-9]+}}
|
||||
; CHECK: SLL 5, A{{[0-9]+}}
|
||||
; CHECK: ADD A{{[0-9]+}}, A{{[0-9]+}}
|
||||
; CHECK: ADD A{{[0-9]+}}, A{{[0-9]+}}
|
||||
; CHECK-NEXT: MOVE A{{[0-9]+}}, A14
|
||||
; CHECK: MOVE A13, A14
|
||||
; CHECK: RETS 0
|
||||
|
||||
; A byte-sized alloca still has its size rounded up to the 2-byte stack
|
||||
; alignment ((n+1) & ~1) before the byte->bit shift; align 1 itself adds no
|
||||
; pointer alignment mask.
|
||||
define i32 @alloca_bytes(i32 %n) {
|
||||
entry:
|
||||
%a = alloca i8, i32 %n, align 1
|
||||
store volatile i8 5, ptr %a
|
||||
%v = load volatile i8, ptr %a
|
||||
%z = zext i8 %v to i32
|
||||
ret i32 %z
|
||||
}
|
||||
; CHECK-LABEL: _alloca_bytes:
|
||||
; CHECK: MOVE A14, A{{[0-9]+}}
|
||||
; CHECK: ADDI 1, A{{[0-9]+}}
|
||||
; CHECK: ANDI 536870910, A{{[0-9]+}}
|
||||
; CHECK: SLL 3, A{{[0-9]+}}
|
||||
; CHECK: ADD A{{[0-9]+}}, A{{[0-9]+}}
|
||||
; CHECK-NEXT: MOVE A{{[0-9]+}}, A14
|
||||
; CHECK: RETS 2
|
||||
|
||||
; stacksave/stackrestore use A14 as the saved stack pointer.
|
||||
define i32 @alloca_saved(i32 %n) {
|
||||
entry:
|
||||
%saved = call ptr @llvm.stacksave()
|
||||
%a = alloca i32, i32 %n, align 4
|
||||
store volatile i32 9, ptr %a
|
||||
%v = load volatile i32, ptr %a
|
||||
call void @llvm.stackrestore(ptr %saved)
|
||||
ret i32 %v
|
||||
}
|
||||
; CHECK-LABEL: _alloca_saved:
|
||||
; CHECK: SLL 5, A{{[0-9]+}}
|
||||
; CHECK: MOVE A14, A{{[0-9]+}}
|
||||
; CHECK: ADDI 31, A{{[0-9]+}}
|
||||
; CHECK: ANDI -32, A{{[0-9]+}}
|
||||
; CHECK: ADD A{{[0-9]+}}, A{{[0-9]+}}
|
||||
; CHECK-NEXT: MOVE A{{[0-9]+}}, A14
|
||||
; CHECK: RETS 2
|
||||
@@ -0,0 +1,100 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=obj < %s -o %t.o
|
||||
;
|
||||
; byval arguments. The caller copies the object into the program-stack
|
||||
; argument area (the current A14) and advances A14 by the object size rounded
|
||||
; up to a 32-bit argument slot. The callee's incoming value is the address of
|
||||
; that copy; the address is a fixed object at FP - (sum of the sizes of
|
||||
; arguments 0..i), preserving the M4 argument order and 32-bit slots.
|
||||
;
|
||||
; In @byval_head the byval pair (8 bytes) is argument 0 and the scalar is
|
||||
; argument 1, so the pair copy starts at FP(-64) bits and the scalar slot at
|
||||
; FP(-96) bits.
|
||||
|
||||
%pair = type { i32, i32 }
|
||||
|
||||
define i32 @byval_head(ptr byval(%pair) align 4 %p, i32 %k) {
|
||||
entry:
|
||||
%a = load i32, ptr %p
|
||||
%q = getelementptr %pair, ptr %p, i32 0, i32 1
|
||||
%b = load i32, ptr %q
|
||||
%m = mul i32 %a, 3
|
||||
%r = add i32 %m, %b
|
||||
%s = add i32 %r, %k
|
||||
ret i32 %s
|
||||
}
|
||||
; The callee reads the private copy: the pointer is FP(-64) and the second
|
||||
; field is four bytes above it. The scalar argument remains at FP(-96).
|
||||
; CHECK-LABEL: _byval_head:
|
||||
; CHECK: MOVE A13, A{{[0-9]+}}
|
||||
; CHECK-NEXT: ADDI -64, A{{[0-9]+}}
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
|
||||
; CHECK: ADDI 4, A{{[0-9]+}}
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
|
||||
; CHECK: ADDI -96, A{{[0-9]+}}
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
|
||||
; CHECK: RETS 2
|
||||
|
||||
define i32 @byval_head_caller(i32 %x, i32 %y, i32 %k) {
|
||||
entry:
|
||||
%s = alloca %pair, align 4
|
||||
%f0 = getelementptr %pair, ptr %s, i32 0, i32 0
|
||||
store i32 %x, ptr %f0
|
||||
%f1 = getelementptr %pair, ptr %s, i32 0, i32 1
|
||||
store i32 %y, ptr %f1
|
||||
%r = call i32 @byval_head(ptr byval(%pair) align 4 %s, i32 %k)
|
||||
ret i32 %r
|
||||
}
|
||||
; The caller first pushes the scalar (argument 1), then copies the two words of
|
||||
; the byval object to the current A14 and advances A14 by 8.
|
||||
; CHECK-LABEL: _byval_head_caller:
|
||||
; CHECK: MOVE A14, -*SP, 1
|
||||
; CHECK: MOVE A{{[0-9]+}}, *A14+, 1
|
||||
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 1
|
||||
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 1
|
||||
; CHECK: ADDI 64, A14
|
||||
; CHECK: CALLA _byval_head
|
||||
; CHECK: MOVE *SP({{[0-9]+}}), A14, 1
|
||||
; CHECK: RETS 2
|
||||
|
||||
; A 6-byte byval object distinguishes the TI 16-bit slot rounding from a
|
||||
; 32-bit extension: the object occupies 6 bytes, so the scalar argument starts
|
||||
; at FP(-80) and the caller advances A14 by 6*8 = 48 bits.
|
||||
%six = type { i32, i16 }
|
||||
|
||||
define i32 @byval_six(ptr byval(%six) align 2 %p, i32 %k) {
|
||||
entry:
|
||||
%a = load i32, ptr %p
|
||||
%q = getelementptr %six, ptr %p, i32 0, i32 1
|
||||
%b = load i16, ptr %q
|
||||
%bz = zext i16 %b to i32
|
||||
%m = mul i32 %a, 3
|
||||
%r = add i32 %m, %bz
|
||||
%s = add i32 %r, %k
|
||||
ret i32 %s
|
||||
}
|
||||
; CHECK-LABEL: _byval_six:
|
||||
; CHECK: MOVE A13, A{{[0-9]+}}
|
||||
; CHECK-NEXT: ADDI -48, A{{[0-9]+}}
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 0
|
||||
; CHECK: ADDI -80, A{{[0-9]+}}
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
|
||||
; CHECK: RETS 2
|
||||
|
||||
define i32 @byval_six_caller(i32 %x, i32 %y, i32 %k) {
|
||||
entry:
|
||||
%s = alloca %six, align 2
|
||||
store i32 %x, ptr %s
|
||||
%q = getelementptr %six, ptr %s, i32 0, i32 1
|
||||
%t = trunc i32 %y to i16
|
||||
store i16 %t, ptr %q
|
||||
%r = call i32 @byval_six(ptr byval(%six) align 2 %s, i32 %k)
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _byval_six_caller:
|
||||
; CHECK: MOVE A14, -*SP, 1
|
||||
; CHECK: MOVE A{{[0-9]+}}, *A14+, 1
|
||||
; CHECK: ADDI 48, A14
|
||||
; CHECK: CALLA _byval_six
|
||||
; CHECK: RETS 2
|
||||
@@ -0,0 +1,51 @@
|
||||
; RUN: llc -march=tms34010 -O0 -verify-machineinstrs -filetype=asm < %s | FileCheck %s
|
||||
; RUN: llc -march=tms34010 -O2 -verify-machineinstrs -filetype=asm < %s | FileCheck %s
|
||||
;
|
||||
; The TMS34010 ST flags live in a physical register, and a register-to-register
|
||||
; MOVE (like almost every ALU instruction) writes ST. A PHI copy inserted for
|
||||
; the loop back edge must therefore never land between CMP and its conditional
|
||||
; branch. The compare/branch pair stays a single CMPBR/CMPIBR pseudo through
|
||||
; scheduling and register allocation and is split only after RA; this test
|
||||
; keeps several live loop values so the old separate CMP+Jcc form inserted a
|
||||
; copy (c-torture 20000422-1 O2 then branched on the copy's flags and hung).
|
||||
|
||||
@ops = global [13 x i32] zeroinitializer
|
||||
|
||||
define i32 @sort_step(i32 %n, i32 %i0) {
|
||||
entry:
|
||||
br label %outer
|
||||
|
||||
outer:
|
||||
%i = phi i32 [ %i0, %entry ]
|
||||
%j = phi i32 [ %n, %entry ]
|
||||
%keep = phi i32 [ 1, %entry ]
|
||||
%cond = icmp sgt i32 %j, %i
|
||||
br i1 %cond, label %body, label %exit
|
||||
|
||||
body:
|
||||
%idx = phi i32 [ %j, %outer ], [ %idxnext, %body ]
|
||||
%slot = getelementptr i32, ptr @ops, i32 %idx
|
||||
%v = load i32, ptr %slot, align 2
|
||||
%cmp = icmp slt i32 %v, %keep
|
||||
%sel = select i1 %cmp, i32 %v, i32 %keep
|
||||
%idxnext = add nsw i32 %idx, -1
|
||||
%jnext = add nsw i32 %j, -1
|
||||
%inext = add nuw nsw i32 %i, 1
|
||||
%nextkeep = add nuw nsw i32 %keep, %sel
|
||||
%done = icmp sgt i32 %idxnext, %i
|
||||
br i1 %done, label %body, label %exit
|
||||
|
||||
exit:
|
||||
%r = phi i32 [ 0, %outer ], [ %nextkeep, %body ]
|
||||
ret i32 %r
|
||||
}
|
||||
|
||||
; CHECK-LABEL: _sort_step:
|
||||
; The loop-back-edge compare is followed immediately by its branch; a copy of
|
||||
; the PHI values has to stay on one side of the pair.
|
||||
; CHECK: CMP
|
||||
; CHECK-NEXT: JR{{.*}} LBB
|
||||
; CHECK: CMP
|
||||
; CHECK-NEXT: JR{{.*}} LBB
|
||||
; CHECK: CMP
|
||||
; CHECK-NEXT: JR{{.*}} LBB
|
||||
@@ -0,0 +1,22 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=obj < %s -o %t.o
|
||||
; RUN: python3 %S/m5-data-pad.py %t.o
|
||||
;
|
||||
; TI-COFF sections are word-granular and the VT1200 loader rejects a section
|
||||
; whose bit size is not a whole number of words. An odd-length byte array
|
||||
; must be padded, and a char .bss object must allocate a whole word.
|
||||
@odd = private unnamed_addr constant [3 x i8] c"ab\00", align 1
|
||||
@cb = global i8 7
|
||||
@zc = global i8 0
|
||||
define ptr @p() {
|
||||
entry:
|
||||
ret ptr @odd
|
||||
}
|
||||
; CHECK: .data
|
||||
; CHECK-LABEL: {{__.*odd.*}}:
|
||||
; CHECK: .byte 061h, 062h, 000h
|
||||
; CHECK: .byte 0
|
||||
; CHECK-LABEL: _cb:
|
||||
; CHECK: .byte 007h
|
||||
; CHECK: .byte 0
|
||||
; CHECK: .bss _zc,16,8
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check the TI-COFF section layout emitted for m5-data-pad.ll.
|
||||
|
||||
The IR has a 3-byte string (odd), a 1-byte .data char, and a 1-byte .bss
|
||||
char. The target must pad the initialized data and the .bss allocation to
|
||||
whole 16-bit words, because the VT1200 loader rejects a section whose size is
|
||||
not a whole number of words.
|
||||
"""
|
||||
|
||||
import struct
|
||||
import sys
|
||||
|
||||
path = sys.argv[1]
|
||||
data = open(path, "rb").read()
|
||||
machine, nscns, _time, _symptr, _nsyms, opthdr, _flags = struct.unpack_from(
|
||||
"<HHIIIHH", data, 0)
|
||||
if machine != 0x0090:
|
||||
sys.exit(f"{path}: machine {machine:#06x}, want 0x0090")
|
||||
if nscns != 3:
|
||||
sys.exit(f"{path}: {nscns} sections, want 3 (.text/.data/.bss)")
|
||||
|
||||
base = 20 + opthdr
|
||||
sections = {}
|
||||
for i in range(nscns):
|
||||
off = base + i * 40
|
||||
name = data[off:off + 8].split(b"\0", 1)[0].decode("latin1")
|
||||
size, ptr = struct.unpack_from("<II", data, off + 16)
|
||||
sections[name] = (size, ptr)
|
||||
|
||||
for name in (".text", ".data", ".bss"):
|
||||
if name not in sections:
|
||||
sys.exit(f"{path}: missing {name}")
|
||||
|
||||
data_size, data_ptr = sections[".data"]
|
||||
if data_size != 48: # 3-byte string + pad + 1-byte char + pad
|
||||
sys.exit(f"{path}: .data size {data_size} bits, want 48")
|
||||
raw = data[data_ptr:data_ptr + data_size // 8]
|
||||
if raw != bytes([0x61, 0x62, 0x00, 0x00, 0x07, 0x00]):
|
||||
sys.exit(f"{path}: .data bytes {raw.hex()}, want 616200000700")
|
||||
|
||||
bss_size, _bss_ptr = sections[".bss"]
|
||||
if bss_size != 16:
|
||||
sys.exit(f"{path}: .bss size {bss_size} bits, want 16")
|
||||
|
||||
print("m5-data-pad: .data=6 bytes (padded), .bss=2 bytes (whole word)")
|
||||
@@ -0,0 +1,348 @@
|
||||
; RUN: llc -march=tms34010 -O0 -verify-machineinstrs -filetype=asm < %s -o %t.s
|
||||
; RUN: FileCheck %s < %t.s
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=obj < %s -o %t.o
|
||||
; RUN: test -s %t.o
|
||||
|
||||
; M5 branch relaxation: a local branch whose displacement fits the ISA's 8-bit
|
||||
; word displacement uses the one-word relative JR/JR<cc> form, and a branch
|
||||
; that cannot reach in one word keeps the three-word absolute JAUC/JA<cc> form.
|
||||
; Both file types must agree; scripts/tests/m5/jr.sh checks the linked M3 image
|
||||
; in MAME and the actual .text sizes.
|
||||
|
||||
; The loop body is tiny, so both branches are in range: the conditional exit
|
||||
; is a JRLT and the back edge is a JRUC.
|
||||
define i32 @short_loop(i32 %n) {
|
||||
entry:
|
||||
br label %loop
|
||||
loop:
|
||||
%i = phi i32 [ 0, %entry ], [ %inc, %loop ]
|
||||
%s = phi i32 [ 0, %entry ], [ %s2, %loop ]
|
||||
%s2 = add i32 %s, %i
|
||||
%inc = add i32 %i, 1
|
||||
%c = icmp slt i32 %inc, %n
|
||||
br i1 %c, label %loop, label %done
|
||||
done:
|
||||
ret i32 %s2
|
||||
}
|
||||
|
||||
; CHECK-LABEL: _short_loop:
|
||||
; CHECK: CMP
|
||||
; CHECK-NEXT: JRLT
|
||||
; CHECK: JRUC
|
||||
; CHECK-NOT: JALT
|
||||
|
||||
; The 24 load/add/store triples in %body each cost 8 bytes: the ADDI uses the
|
||||
; 16-bit immediate form (4 bytes), not the .td long form (6 bytes). The exact
|
||||
; layout puts %exit 113 words away, so this branch must be in range and use
|
||||
; JREQ; measuring the long form instead would count 137 words and leave JAEQ.
|
||||
; This pins getInstSizeInBytes to the size the encoder really produces.
|
||||
define i32 @exact_relax(i32 %a) {
|
||||
entry:
|
||||
%s = alloca i32
|
||||
store i32 0, ptr %s
|
||||
%c = icmp eq i32 %a, 0
|
||||
br i1 %c, label %exit, label %body
|
||||
body:
|
||||
%v0 = load i32, ptr %s
|
||||
%w0 = add i32 %v0, 1
|
||||
store i32 %w0, ptr %s
|
||||
%v1 = load i32, ptr %s
|
||||
%w1 = add i32 %v1, 1
|
||||
store i32 %w1, ptr %s
|
||||
%v2 = load i32, ptr %s
|
||||
%w2 = add i32 %v2, 1
|
||||
store i32 %w2, ptr %s
|
||||
%v3 = load i32, ptr %s
|
||||
%w3 = add i32 %v3, 1
|
||||
store i32 %w3, ptr %s
|
||||
%v4 = load i32, ptr %s
|
||||
%w4 = add i32 %v4, 1
|
||||
store i32 %w4, ptr %s
|
||||
%v5 = load i32, ptr %s
|
||||
%w5 = add i32 %v5, 1
|
||||
store i32 %w5, ptr %s
|
||||
%v6 = load i32, ptr %s
|
||||
%w6 = add i32 %v6, 1
|
||||
store i32 %w6, ptr %s
|
||||
%v7 = load i32, ptr %s
|
||||
%w7 = add i32 %v7, 1
|
||||
store i32 %w7, ptr %s
|
||||
%v8 = load i32, ptr %s
|
||||
%w8 = add i32 %v8, 1
|
||||
store i32 %w8, ptr %s
|
||||
%v9 = load i32, ptr %s
|
||||
%w9 = add i32 %v9, 1
|
||||
store i32 %w9, ptr %s
|
||||
%v10 = load i32, ptr %s
|
||||
%w10 = add i32 %v10, 1
|
||||
store i32 %w10, ptr %s
|
||||
%v11 = load i32, ptr %s
|
||||
%w11 = add i32 %v11, 1
|
||||
store i32 %w11, ptr %s
|
||||
%v12 = load i32, ptr %s
|
||||
%w12 = add i32 %v12, 1
|
||||
store i32 %w12, ptr %s
|
||||
%v13 = load i32, ptr %s
|
||||
%w13 = add i32 %v13, 1
|
||||
store i32 %w13, ptr %s
|
||||
%v14 = load i32, ptr %s
|
||||
%w14 = add i32 %v14, 1
|
||||
store i32 %w14, ptr %s
|
||||
%v15 = load i32, ptr %s
|
||||
%w15 = add i32 %v15, 1
|
||||
store i32 %w15, ptr %s
|
||||
%v16 = load i32, ptr %s
|
||||
%w16 = add i32 %v16, 1
|
||||
store i32 %w16, ptr %s
|
||||
%v17 = load i32, ptr %s
|
||||
%w17 = add i32 %v17, 1
|
||||
store i32 %w17, ptr %s
|
||||
%v18 = load i32, ptr %s
|
||||
%w18 = add i32 %v18, 1
|
||||
store i32 %w18, ptr %s
|
||||
%v19 = load i32, ptr %s
|
||||
%w19 = add i32 %v19, 1
|
||||
store i32 %w19, ptr %s
|
||||
%v20 = load i32, ptr %s
|
||||
%w20 = add i32 %v20, 1
|
||||
store i32 %w20, ptr %s
|
||||
%v21 = load i32, ptr %s
|
||||
%w21 = add i32 %v21, 1
|
||||
store i32 %w21, ptr %s
|
||||
%v22 = load i32, ptr %s
|
||||
%w22 = add i32 %v22, 1
|
||||
store i32 %w22, ptr %s
|
||||
%v23 = load i32, ptr %s
|
||||
%w23 = add i32 %v23, 1
|
||||
store i32 %w23, ptr %s
|
||||
br label %exit
|
||||
exit:
|
||||
%r = load i32, ptr %s
|
||||
ret i32 %r
|
||||
}
|
||||
|
||||
; CHECK-LABEL: _exact_relax:
|
||||
; CHECK: CMPI 0
|
||||
; CHECK-NEXT: JREQ
|
||||
; CHECK-NOT: JAEQ
|
||||
|
||||
; A forward conditional branch over 200 volatile loads is far out of the
|
||||
; signed 8-bit word range (the body is well over 127 words), so it keeps the
|
||||
; absolute JAEQ form. The unconditional branch to the fallthrough block is
|
||||
; zero displacement, which the one-word form cannot encode, so it is JAUC too.
|
||||
@g = global i32 0
|
||||
define i32 @far_branch(i32 %a) {
|
||||
entry:
|
||||
%c = icmp eq i32 %a, 0
|
||||
br i1 %c, label %far, label %near
|
||||
near:
|
||||
%v0 = load volatile i32, ptr @g
|
||||
%v1 = load volatile i32, ptr @g
|
||||
%v2 = load volatile i32, ptr @g
|
||||
%v3 = load volatile i32, ptr @g
|
||||
%v4 = load volatile i32, ptr @g
|
||||
%v5 = load volatile i32, ptr @g
|
||||
%v6 = load volatile i32, ptr @g
|
||||
%v7 = load volatile i32, ptr @g
|
||||
%v8 = load volatile i32, ptr @g
|
||||
%v9 = load volatile i32, ptr @g
|
||||
%v10 = load volatile i32, ptr @g
|
||||
%v11 = load volatile i32, ptr @g
|
||||
%v12 = load volatile i32, ptr @g
|
||||
%v13 = load volatile i32, ptr @g
|
||||
%v14 = load volatile i32, ptr @g
|
||||
%v15 = load volatile i32, ptr @g
|
||||
%v16 = load volatile i32, ptr @g
|
||||
%v17 = load volatile i32, ptr @g
|
||||
%v18 = load volatile i32, ptr @g
|
||||
%v19 = load volatile i32, ptr @g
|
||||
%v20 = load volatile i32, ptr @g
|
||||
%v21 = load volatile i32, ptr @g
|
||||
%v22 = load volatile i32, ptr @g
|
||||
%v23 = load volatile i32, ptr @g
|
||||
%v24 = load volatile i32, ptr @g
|
||||
%v25 = load volatile i32, ptr @g
|
||||
%v26 = load volatile i32, ptr @g
|
||||
%v27 = load volatile i32, ptr @g
|
||||
%v28 = load volatile i32, ptr @g
|
||||
%v29 = load volatile i32, ptr @g
|
||||
%v30 = load volatile i32, ptr @g
|
||||
%v31 = load volatile i32, ptr @g
|
||||
%v32 = load volatile i32, ptr @g
|
||||
%v33 = load volatile i32, ptr @g
|
||||
%v34 = load volatile i32, ptr @g
|
||||
%v35 = load volatile i32, ptr @g
|
||||
%v36 = load volatile i32, ptr @g
|
||||
%v37 = load volatile i32, ptr @g
|
||||
%v38 = load volatile i32, ptr @g
|
||||
%v39 = load volatile i32, ptr @g
|
||||
%v40 = load volatile i32, ptr @g
|
||||
%v41 = load volatile i32, ptr @g
|
||||
%v42 = load volatile i32, ptr @g
|
||||
%v43 = load volatile i32, ptr @g
|
||||
%v44 = load volatile i32, ptr @g
|
||||
%v45 = load volatile i32, ptr @g
|
||||
%v46 = load volatile i32, ptr @g
|
||||
%v47 = load volatile i32, ptr @g
|
||||
%v48 = load volatile i32, ptr @g
|
||||
%v49 = load volatile i32, ptr @g
|
||||
%v50 = load volatile i32, ptr @g
|
||||
%v51 = load volatile i32, ptr @g
|
||||
%v52 = load volatile i32, ptr @g
|
||||
%v53 = load volatile i32, ptr @g
|
||||
%v54 = load volatile i32, ptr @g
|
||||
%v55 = load volatile i32, ptr @g
|
||||
%v56 = load volatile i32, ptr @g
|
||||
%v57 = load volatile i32, ptr @g
|
||||
%v58 = load volatile i32, ptr @g
|
||||
%v59 = load volatile i32, ptr @g
|
||||
%v60 = load volatile i32, ptr @g
|
||||
%v61 = load volatile i32, ptr @g
|
||||
%v62 = load volatile i32, ptr @g
|
||||
%v63 = load volatile i32, ptr @g
|
||||
%v64 = load volatile i32, ptr @g
|
||||
%v65 = load volatile i32, ptr @g
|
||||
%v66 = load volatile i32, ptr @g
|
||||
%v67 = load volatile i32, ptr @g
|
||||
%v68 = load volatile i32, ptr @g
|
||||
%v69 = load volatile i32, ptr @g
|
||||
%v70 = load volatile i32, ptr @g
|
||||
%v71 = load volatile i32, ptr @g
|
||||
%v72 = load volatile i32, ptr @g
|
||||
%v73 = load volatile i32, ptr @g
|
||||
%v74 = load volatile i32, ptr @g
|
||||
%v75 = load volatile i32, ptr @g
|
||||
%v76 = load volatile i32, ptr @g
|
||||
%v77 = load volatile i32, ptr @g
|
||||
%v78 = load volatile i32, ptr @g
|
||||
%v79 = load volatile i32, ptr @g
|
||||
%v80 = load volatile i32, ptr @g
|
||||
%v81 = load volatile i32, ptr @g
|
||||
%v82 = load volatile i32, ptr @g
|
||||
%v83 = load volatile i32, ptr @g
|
||||
%v84 = load volatile i32, ptr @g
|
||||
%v85 = load volatile i32, ptr @g
|
||||
%v86 = load volatile i32, ptr @g
|
||||
%v87 = load volatile i32, ptr @g
|
||||
%v88 = load volatile i32, ptr @g
|
||||
%v89 = load volatile i32, ptr @g
|
||||
%v90 = load volatile i32, ptr @g
|
||||
%v91 = load volatile i32, ptr @g
|
||||
%v92 = load volatile i32, ptr @g
|
||||
%v93 = load volatile i32, ptr @g
|
||||
%v94 = load volatile i32, ptr @g
|
||||
%v95 = load volatile i32, ptr @g
|
||||
%v96 = load volatile i32, ptr @g
|
||||
%v97 = load volatile i32, ptr @g
|
||||
%v98 = load volatile i32, ptr @g
|
||||
%v99 = load volatile i32, ptr @g
|
||||
%v100 = load volatile i32, ptr @g
|
||||
%v101 = load volatile i32, ptr @g
|
||||
%v102 = load volatile i32, ptr @g
|
||||
%v103 = load volatile i32, ptr @g
|
||||
%v104 = load volatile i32, ptr @g
|
||||
%v105 = load volatile i32, ptr @g
|
||||
%v106 = load volatile i32, ptr @g
|
||||
%v107 = load volatile i32, ptr @g
|
||||
%v108 = load volatile i32, ptr @g
|
||||
%v109 = load volatile i32, ptr @g
|
||||
%v110 = load volatile i32, ptr @g
|
||||
%v111 = load volatile i32, ptr @g
|
||||
%v112 = load volatile i32, ptr @g
|
||||
%v113 = load volatile i32, ptr @g
|
||||
%v114 = load volatile i32, ptr @g
|
||||
%v115 = load volatile i32, ptr @g
|
||||
%v116 = load volatile i32, ptr @g
|
||||
%v117 = load volatile i32, ptr @g
|
||||
%v118 = load volatile i32, ptr @g
|
||||
%v119 = load volatile i32, ptr @g
|
||||
%v120 = load volatile i32, ptr @g
|
||||
%v121 = load volatile i32, ptr @g
|
||||
%v122 = load volatile i32, ptr @g
|
||||
%v123 = load volatile i32, ptr @g
|
||||
%v124 = load volatile i32, ptr @g
|
||||
%v125 = load volatile i32, ptr @g
|
||||
%v126 = load volatile i32, ptr @g
|
||||
%v127 = load volatile i32, ptr @g
|
||||
%v128 = load volatile i32, ptr @g
|
||||
%v129 = load volatile i32, ptr @g
|
||||
%v130 = load volatile i32, ptr @g
|
||||
%v131 = load volatile i32, ptr @g
|
||||
%v132 = load volatile i32, ptr @g
|
||||
%v133 = load volatile i32, ptr @g
|
||||
%v134 = load volatile i32, ptr @g
|
||||
%v135 = load volatile i32, ptr @g
|
||||
%v136 = load volatile i32, ptr @g
|
||||
%v137 = load volatile i32, ptr @g
|
||||
%v138 = load volatile i32, ptr @g
|
||||
%v139 = load volatile i32, ptr @g
|
||||
%v140 = load volatile i32, ptr @g
|
||||
%v141 = load volatile i32, ptr @g
|
||||
%v142 = load volatile i32, ptr @g
|
||||
%v143 = load volatile i32, ptr @g
|
||||
%v144 = load volatile i32, ptr @g
|
||||
%v145 = load volatile i32, ptr @g
|
||||
%v146 = load volatile i32, ptr @g
|
||||
%v147 = load volatile i32, ptr @g
|
||||
%v148 = load volatile i32, ptr @g
|
||||
%v149 = load volatile i32, ptr @g
|
||||
%v150 = load volatile i32, ptr @g
|
||||
%v151 = load volatile i32, ptr @g
|
||||
%v152 = load volatile i32, ptr @g
|
||||
%v153 = load volatile i32, ptr @g
|
||||
%v154 = load volatile i32, ptr @g
|
||||
%v155 = load volatile i32, ptr @g
|
||||
%v156 = load volatile i32, ptr @g
|
||||
%v157 = load volatile i32, ptr @g
|
||||
%v158 = load volatile i32, ptr @g
|
||||
%v159 = load volatile i32, ptr @g
|
||||
%v160 = load volatile i32, ptr @g
|
||||
%v161 = load volatile i32, ptr @g
|
||||
%v162 = load volatile i32, ptr @g
|
||||
%v163 = load volatile i32, ptr @g
|
||||
%v164 = load volatile i32, ptr @g
|
||||
%v165 = load volatile i32, ptr @g
|
||||
%v166 = load volatile i32, ptr @g
|
||||
%v167 = load volatile i32, ptr @g
|
||||
%v168 = load volatile i32, ptr @g
|
||||
%v169 = load volatile i32, ptr @g
|
||||
%v170 = load volatile i32, ptr @g
|
||||
%v171 = load volatile i32, ptr @g
|
||||
%v172 = load volatile i32, ptr @g
|
||||
%v173 = load volatile i32, ptr @g
|
||||
%v174 = load volatile i32, ptr @g
|
||||
%v175 = load volatile i32, ptr @g
|
||||
%v176 = load volatile i32, ptr @g
|
||||
%v177 = load volatile i32, ptr @g
|
||||
%v178 = load volatile i32, ptr @g
|
||||
%v179 = load volatile i32, ptr @g
|
||||
%v180 = load volatile i32, ptr @g
|
||||
%v181 = load volatile i32, ptr @g
|
||||
%v182 = load volatile i32, ptr @g
|
||||
%v183 = load volatile i32, ptr @g
|
||||
%v184 = load volatile i32, ptr @g
|
||||
%v185 = load volatile i32, ptr @g
|
||||
%v186 = load volatile i32, ptr @g
|
||||
%v187 = load volatile i32, ptr @g
|
||||
%v188 = load volatile i32, ptr @g
|
||||
%v189 = load volatile i32, ptr @g
|
||||
%v190 = load volatile i32, ptr @g
|
||||
%v191 = load volatile i32, ptr @g
|
||||
%v192 = load volatile i32, ptr @g
|
||||
%v193 = load volatile i32, ptr @g
|
||||
%v194 = load volatile i32, ptr @g
|
||||
%v195 = load volatile i32, ptr @g
|
||||
%v196 = load volatile i32, ptr @g
|
||||
%v197 = load volatile i32, ptr @g
|
||||
%v198 = load volatile i32, ptr @g
|
||||
%v199 = load volatile i32, ptr @g
|
||||
ret i32 1
|
||||
far:
|
||||
ret i32 7
|
||||
}
|
||||
|
||||
; CHECK-LABEL: _far_branch:
|
||||
; CHECK: CMPI 0
|
||||
; CHECK-NEXT: JAEQ
|
||||
; CHECK-NEXT: JAUC
|
||||
; CHECK-NOT: JREQ
|
||||
@@ -0,0 +1,184 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=obj < %s -o %t.o
|
||||
;
|
||||
; llvm.memcpy/llvm.memset are lowered to target code: a known size becomes
|
||||
; bit-scaled word (FS1=32) copies/stores, and a runtime size becomes an inline
|
||||
; byte loop whose GEP offsets are scaled from bytes to bits. No libcall to
|
||||
; _memcpy/_memset is emitted, so the same machine instructions feed both the
|
||||
; assembly and the native object path. This test pins that behavior for M5;
|
||||
; the ROM/rts.lib entry points are not required (the inline loop is the
|
||||
; validated fallback for arbitrary lengths).
|
||||
|
||||
declare void @llvm.memcpy.p0.p0.i32(ptr, ptr, i32, i1 immarg)
|
||||
declare void @llvm.memset.p0.i32(ptr, i8, i32, i1 immarg)
|
||||
declare void @llvm.memmove.p0.p0.i32(ptr, ptr, i32, i1 immarg)
|
||||
|
||||
define void @copy_known(ptr %d, ptr %s) {
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr %d, ptr %s, i32 16, i1 false)
|
||||
ret void
|
||||
}
|
||||
; Four 32-bit load/store pairs, no call. The word offsets are 4, 8 and 12
|
||||
; bytes * 8 = 32, 64 and 96 bits: the generic expansion would use the byte
|
||||
; values directly and address the wrong place.
|
||||
; CHECK-LABEL: _copy_known:
|
||||
; CHECK-DAG: ADDI 32, A{{[0-9]+}}
|
||||
; CHECK-DAG: ADDI 64, A{{[0-9]+}}
|
||||
; CHECK-DAG: ADDI 96, A{{[0-9]+}}
|
||||
; CHECK: MOVE {{A[0-9]+}}, *A{{[0-9]+}}, 1
|
||||
; CHECK-NOT: CALLA
|
||||
; CHECK: RETS 2
|
||||
|
||||
define void @fill_known(ptr %d) {
|
||||
call void @llvm.memset.p0.i32(ptr %d, i8 90, i32 8, i1 false)
|
||||
ret void
|
||||
}
|
||||
; The byte value is replicated into a word (0x5A5A5A5A) and stored twice; the
|
||||
; second store is four bytes (32 bits) above the first.
|
||||
; CHECK-LABEL: _fill_known:
|
||||
; CHECK: MOVI 1515870810, A{{[0-9]+}}
|
||||
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 1
|
||||
; CHECK: ADDI 32, A{{[0-9]+}}
|
||||
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 1
|
||||
; CHECK-NOT: CALLA
|
||||
; CHECK: RETS 2
|
||||
|
||||
define void @copy_dynamic(ptr %d, ptr %s, i32 %n) {
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr %d, ptr %s, i32 %n, i1 false)
|
||||
ret void
|
||||
}
|
||||
; Runtime length: a byte load/store loop, skipped when n == 0.
|
||||
; CHECK-LABEL: _copy_dynamic:
|
||||
; CHECK: CMPI 0, A{{[0-9]+}}
|
||||
; CHECK: SLL 3, A{{[0-9]+}}
|
||||
; CHECK: MOVB *A{{[0-9]+}}, A{{[0-9]+}}
|
||||
; CHECK: MOVB A{{[0-9]+}}, *A{{[0-9]+}}
|
||||
; CHECK: JRLO
|
||||
; CHECK-NOT: CALLA
|
||||
; CHECK: RETS 2
|
||||
|
||||
define void @fill_dynamic(ptr %d, i32 %n) {
|
||||
call void @llvm.memset.p0.i32(ptr %d, i8 90, i32 %n, i1 false)
|
||||
ret void
|
||||
}
|
||||
; CHECK-LABEL: _fill_dynamic:
|
||||
; CHECK: CMPI 0, A{{[0-9]+}}
|
||||
; CHECK: SLL 3, A{{[0-9]+}}
|
||||
; CHECK: MOVB A{{[0-9]+}}, *A{{[0-9]+}}
|
||||
; CHECK: JRLO
|
||||
; CHECK-NOT: CALLA
|
||||
; CHECK: RETS 2
|
||||
|
||||
; Runtime-size memmove is expanded by LowerMemIntrinsics into its own
|
||||
; memmove_* forward/backward loops. Those blocks are in the scaling pass's
|
||||
; prefix set too; every address step must be 8 bits per byte.
|
||||
define void @move_dynamic(ptr %d, ptr %s, i32 %n) {
|
||||
call void @llvm.memmove.p0.p0.i32(ptr %d, ptr %s, i32 %n, i1 false)
|
||||
ret void
|
||||
}
|
||||
; CHECK-LABEL: _move_dynamic:
|
||||
; CHECK: SLL 3, A{{[0-9]+}}
|
||||
; CHECK: MOVB
|
||||
; CHECK: JR{{[A-Z]*}} LBB{{[0-9]+}}_{{[0-9]+}}
|
||||
; CHECK-NOT: CALLA
|
||||
; CHECK: RETS 2
|
||||
|
||||
; Constants above PreISel's 64-byte inline threshold are expanded by
|
||||
; LowerMemIntrinsics into static-* loops, not the target hook. Those blocks
|
||||
; must be in the scaling pass's prefix set too; without them the loop would
|
||||
; add 1 bit per byte instead of 8.
|
||||
define void @copy_100(ptr %d, ptr %s) {
|
||||
call void @llvm.memcpy.p0.p0.i32(ptr %d, ptr %s, i32 100, i1 false)
|
||||
ret void
|
||||
}
|
||||
; CHECK-LABEL: _copy_100:
|
||||
; CHECK: SLL 3, A{{[0-9]+}}
|
||||
; CHECK: ADD A{{[0-9]+}}, A{{[0-9]+}}
|
||||
; CHECK: MOVB *A{{[0-9]+}}, A{{[0-9]+}}
|
||||
; CHECK: MOVB A{{[0-9]+}}, *A{{[0-9]+}}
|
||||
; CHECK: ADDI 1, A{{[0-9]+}}
|
||||
; CHECK: CMPI 100, A{{[0-9]+}}
|
||||
; CHECK: JRLO
|
||||
; CHECK-NOT: CALLA
|
||||
; CHECK: RETS 2
|
||||
|
||||
define void @fill_100(ptr %d) {
|
||||
call void @llvm.memset.p0.i32(ptr %d, i8 90, i32 100, i1 false)
|
||||
ret void
|
||||
}
|
||||
; CHECK-LABEL: _fill_100:
|
||||
; CHECK: MOVI 90, A{{[0-9]+}}
|
||||
; CHECK: SLL 3, A{{[0-9]+}}
|
||||
; CHECK: ADD A{{[0-9]+}}, A{{[0-9]+}}
|
||||
; CHECK: MOVB A{{[0-9]+}}, *A{{[0-9]+}}
|
||||
; CHECK: ADDI 1, A{{[0-9]+}}
|
||||
; CHECK: CMPI 100, A{{[0-9]+}}
|
||||
; CHECK: JRLO
|
||||
; CHECK-NOT: CALLA
|
||||
; CHECK: RETS 2
|
||||
|
||||
; The memmove_done continuation is not a lowerer loop block: GEPs after the
|
||||
; memmove are already bit offsets by contract and must not be scaled a second
|
||||
; time. The 24-bit GEP must stay ADDI 24, not become ADDI 192 (24*8).
|
||||
define i32 @move_then_gep(ptr %d, ptr %s, i32 %n) {
|
||||
call void @llvm.memmove.p0.p0.i32(ptr %d, ptr %s, i32 %n, i1 false)
|
||||
%p = getelementptr i8, ptr %d, i32 24
|
||||
%v = load i32, ptr %p
|
||||
ret i32 %v
|
||||
}
|
||||
; CHECK-LABEL: _move_then_gep:
|
||||
; CHECK: SLL 3, A{{[0-9]+}}
|
||||
; CHECK: ADDI 24, A{{[0-9]+}}
|
||||
; CHECK-NOT: ADDI 192
|
||||
; CHECK: RETS 2
|
||||
|
||||
; Two memmove expansions in one function get uniquified block names
|
||||
; (memmove_bwd_main_loop7, memmove_fwd_main_loop10, memmove_done6, ...).
|
||||
; The loop prefixes must still be scaled, while neither continuation block may
|
||||
; scale its user GEP. The two GEPs are already 24-bit and 48-bit offsets.
|
||||
define i32 @two_moves(ptr %d, ptr %s, i32 %n) {
|
||||
call void @llvm.memmove.p0.p0.i32(ptr %d, ptr %s, i32 %n, i1 false)
|
||||
%p1 = getelementptr i8, ptr %d, i32 24
|
||||
%v1 = load i32, ptr %p1
|
||||
call void @llvm.memmove.p0.p0.i32(ptr %d, ptr %s, i32 %n, i1 false)
|
||||
%p2 = getelementptr i8, ptr %d, i32 48
|
||||
%v2 = load i32, ptr %p2
|
||||
%r = add i32 %v1, %v2
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _two_moves:
|
||||
; CHECK: SLL 3, A{{[0-9]+}}
|
||||
; CHECK: ADDI 8, A{{[0-9]+}}
|
||||
; CHECK: ADDI 24, A{{[0-9]+}}
|
||||
; CHECK: SLL 3, A{{[0-9]+}}
|
||||
; CHECK: ADDI 8, A{{[0-9]+}}
|
||||
; CHECK: ADDI 48, A{{[0-9]+}}
|
||||
; CHECK-NOT: ADDI 192
|
||||
; CHECK-NOT: ADDI 384
|
||||
; CHECK: RETS 2
|
||||
|
||||
; Constant memmove <= 64 bytes is not expanded by PreISel; the target pass
|
||||
; expands it as the same overlap-safe forward/backward loops, with bit-scaled
|
||||
; addresses. These cases used to reach the rejected libcall path.
|
||||
define void @move_10(ptr %d, ptr %s) {
|
||||
call void @llvm.memmove.p0.p0.i32(ptr %d, ptr %s, i32 10, i1 false)
|
||||
ret void
|
||||
}
|
||||
; CHECK-LABEL: _move_10:
|
||||
; CHECK: MOVI 10, A{{[0-9]+}}
|
||||
; CHECK: SLL 3, A{{[0-9]+}}
|
||||
; CHECK: MOVB *A{{[0-9]+}}, A{{[0-9]+}}
|
||||
; CHECK: MOVB A{{[0-9]+}}, *A{{[0-9]+}}
|
||||
; CHECK-NOT: CALLA
|
||||
; CHECK: RETS 2
|
||||
|
||||
define void @move_64(ptr %d, ptr %s) {
|
||||
call void @llvm.memmove.p0.p0.i32(ptr %d, ptr %s, i32 64, i1 false)
|
||||
ret void
|
||||
}
|
||||
; CHECK-LABEL: _move_64:
|
||||
; CHECK: MOVI 64, A{{[0-9]+}}
|
||||
; CHECK: SLL 3, A{{[0-9]+}}
|
||||
; CHECK: MOVB *A{{[0-9]+}}, A{{[0-9]+}}
|
||||
; CHECK: MOVB A{{[0-9]+}}, *A{{[0-9]+}}
|
||||
; CHECK-NOT: CALLA
|
||||
; CHECK: RETS 2
|
||||
@@ -0,0 +1,54 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=obj < %s -o %t.o
|
||||
;
|
||||
; M5 register-list frame saves. The callee keeps four values live across the
|
||||
; call to @leaf. The odd multiply destinations force A1/A3/A5 and the call
|
||||
; argument forces A0, so the callee-save set is A0, A1, A3, A5. The prologue
|
||||
; saves it with one MMTM and the epilogue restores it with one MMFM; the old
|
||||
; per-register MOVE pushes are gone, but the ABI-visible system-stack slots
|
||||
; are unchanged: A13 (the caller's FP) below the return PC, the four callee
|
||||
; registers below A13, and the old STK at SP + (1 + 4 + 1) * 32 = SP(192).
|
||||
|
||||
declare i32 @leaf(i32)
|
||||
|
||||
define i32 @mmtm_callee(i32 %a, i32 %b, i32 %c, i32 %d) {
|
||||
entry:
|
||||
%m1 = mul i32 %a, %b
|
||||
%m2 = mul i32 %c, %d
|
||||
%m3 = mul i32 %m2, %a
|
||||
%z = call i32 @leaf(i32 %m1)
|
||||
%r = add i32 %m2, %m3
|
||||
%s = add i32 %r, %z
|
||||
ret i32 %s
|
||||
}
|
||||
; CHECK-LABEL: _mmtm_callee:
|
||||
; CHECK: SETF 32, 0, 1
|
||||
; CHECK-NEXT: MOVE A13, -*SP, 1
|
||||
; CHECK-NEXT: MMTM SP, A0, A1, A3, A5
|
||||
; CHECK-NEXT: MOVE A14, A13
|
||||
; CHECK: CALLA _leaf
|
||||
; CHECK: SETF 32, 0, 1
|
||||
; CHECK: MOVE *SP(192), A14, 1
|
||||
; CHECK-NEXT: MMFM SP, A0, A1, A3, A5
|
||||
; CHECK-NEXT: MOVE *SP+, A13, 1
|
||||
; CHECK-NEXT: RETS 2
|
||||
|
||||
; The caller keeps two loaded values live across the call, so it too needs the
|
||||
; register-list save and the same old-STK/RETS 2 ABI.
|
||||
define i32 @mmtm_caller(ptr %p, i32 %a, i32 %b) {
|
||||
entry:
|
||||
%x = load i32, ptr %p
|
||||
%m = mul i32 %a, %b
|
||||
%z = call i32 @mmtm_callee(i32 %x, i32 %m, i32 %a, i32 %b)
|
||||
%r = add i32 %x, %z
|
||||
%s = add i32 %r, %m
|
||||
ret i32 %s
|
||||
}
|
||||
; CHECK-LABEL: _mmtm_caller:
|
||||
; CHECK: MOVE A13, -*SP, 1
|
||||
; CHECK-NEXT: MMTM SP,
|
||||
; CHECK: MOVE A14, -*SP, 1
|
||||
; CHECK: CALLA _mmtm_callee
|
||||
; CHECK: MMFM SP,
|
||||
; CHECK: MOVE *SP+, A13, 1
|
||||
; CHECK-NEXT: RETS 2
|
||||
@@ -0,0 +1,82 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=obj < %s -o %t.o
|
||||
;
|
||||
; i8/i16 returns travel in A8 as a 32-bit ABI value. The callee applies the
|
||||
; declared zeroext/signext attribute before the copy to A8; a plain (noext)
|
||||
; narrow return leaves the promoted value alone and the caller applies the
|
||||
; truncation/extension requested by the IR. i64, float and aggregate returns
|
||||
; stay rejected (the aggregate case is pinned by m2-reject-structret.ll; the
|
||||
; other wide/FP returns use the same CanLowerReturn diagnostic).
|
||||
|
||||
define zeroext i8 @ret8(i32 %x) noinline {
|
||||
%t = trunc i32 %x to i8
|
||||
ret i8 %t
|
||||
}
|
||||
; The 8-bit load sign-extends, so zeroext clears the high bits before A8.
|
||||
; CHECK-LABEL: _ret8:
|
||||
; CHECK: MOVB *A{{[0-9]+}}, A{{[0-9]+}}
|
||||
; CHECK: ANDI 255, A{{[0-9]+}}
|
||||
; CHECK: MOVE A{{[0-9]+}}, A8
|
||||
; CHECK: RETS 2
|
||||
|
||||
define signext i16 @ret16(i32 %x) noinline {
|
||||
%t = trunc i32 %x to i16
|
||||
ret i16 %t
|
||||
}
|
||||
; The FS0 16-bit load already sign-extends, which is the signext attribute.
|
||||
; CHECK-LABEL: _ret16:
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 0
|
||||
; CHECK: MOVE A{{[0-9]+}}, A8
|
||||
; CHECK: RETS 2
|
||||
|
||||
define i32 @narrow_caller(i32 %x) {
|
||||
%a = call zeroext i8 @ret8(i32 %x)
|
||||
%b = call signext i16 @ret16(i32 %x)
|
||||
%az = zext i8 %a to i32
|
||||
%bs = sext i16 %b to i32
|
||||
%r = add i32 %az, %bs
|
||||
ret i32 %r
|
||||
}
|
||||
; The attribute already guarantees the extension, so the caller consumes A8
|
||||
; directly.
|
||||
; CHECK-LABEL: _narrow_caller:
|
||||
; CHECK: CALLA _ret8
|
||||
; CHECK: MOVE A8, A{{[0-9]+}}
|
||||
; CHECK: CALLA _ret16
|
||||
; CHECK: MOVE A8, A{{[0-9]+}}
|
||||
; CHECK: ADD A{{[0-9]+}}, A{{[0-9]+}}
|
||||
; CHECK: RETS 2
|
||||
|
||||
define i8 @ret8_plain(i32 %x) noinline {
|
||||
%t = trunc i32 %x to i8
|
||||
ret i8 %t
|
||||
}
|
||||
; CHECK-LABEL: _ret8_plain:
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
|
||||
; CHECK: MOVE A{{[0-9]+}}, A8
|
||||
; CHECK: RETS 2
|
||||
|
||||
define i32 @plain_caller(i32 %x) {
|
||||
%a = call i8 @ret8_plain(i32 %x)
|
||||
%az = zext i8 %a to i32
|
||||
ret i32 %az
|
||||
}
|
||||
; Without zeroext the caller must mask the returned byte itself.
|
||||
; CHECK-LABEL: _plain_caller:
|
||||
; CHECK: CALLA _ret8_plain
|
||||
; CHECK: ANDI 255, A{{[0-9]+}}
|
||||
; CHECK: MOVE A{{[0-9]+}}, A8
|
||||
; CHECK: RETS 2
|
||||
|
||||
define i32 @plain_caller_s(i32 %x) {
|
||||
%a = call i8 @ret8_plain(i32 %x)
|
||||
%az = sext i8 %a to i32
|
||||
ret i32 %az
|
||||
}
|
||||
; The sign-extending caller uses the verified shift pair.
|
||||
; CHECK-LABEL: _plain_caller_s:
|
||||
; CHECK: CALLA _ret8_plain
|
||||
; CHECK: SLL 24, A{{[0-9]+}}
|
||||
; CHECK: SRA 24, A{{[0-9]+}}
|
||||
; CHECK: MOVE A{{[0-9]+}}, A8
|
||||
; CHECK: RETS 2
|
||||
@@ -0,0 +1,18 @@
|
||||
; RUN: llc -march=tms34010 -filetype=obj < %s -o %t.o
|
||||
; RUN: test -s %t.o
|
||||
; RUN: llc -march=tms34010 -filetype=asm < %s -o %t.s
|
||||
; RUN: grep -q 'RETS 2' %t.s
|
||||
; RUN: grep -q 'MOVI 305419896' %t.s
|
||||
; RUN: grep -q 'MOVI 42' %t.s
|
||||
define i32 @f(i32 %a) {
|
||||
%r = add i32 %a, 1
|
||||
ret i32 %r
|
||||
}
|
||||
; Long (32-bit) MOVI immediate form.
|
||||
define i32 @movi_long() {
|
||||
ret i32 305419896
|
||||
}
|
||||
; Short (16-bit) MOVI immediate form.
|
||||
define i32 @movi_short() {
|
||||
ret i32 42
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s --check-prefix=I64
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s --check-prefix=I64
|
||||
;
|
||||
; A 64-bit multiply is the ordinary i64 path, not IndVarSimplify's i33
|
||||
; closed-form exit value. Its diagnostic must name the i64/M6 boundary and
|
||||
; must NOT tell the user to add -replexitval=never: tms-clang already passes
|
||||
; that policy and it does nothing for i64 (it only disables the i33 exit-value
|
||||
; rewrite). This pins the distinct remediation the Task 8 review found.
|
||||
define i32 @wide64(i32 %a, i32 %b) {
|
||||
entry:
|
||||
%ae = zext i32 %a to i64
|
||||
%be = zext i32 %b to i64
|
||||
%m = mul i64 %ae, %be
|
||||
%r = trunc i64 %m to i32
|
||||
ret i32 %r
|
||||
}
|
||||
|
||||
; I64: LLVM ERROR: TMS34010: 64-bit multiply reached code generation
|
||||
; I64-SAME: reserved for M6
|
||||
; I64-NOT: -replexitval=never
|
||||
@@ -0,0 +1,25 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s --check-prefix=I33
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s --check-prefix=I33
|
||||
;
|
||||
; M5 keeps the tms-clang `-replexitval=never` driver policy until i64/M6: this
|
||||
; target has no i64, and IndVarSimplify's closed-form exit value is an i33
|
||||
; multiply. A raw `opt -passes='default<O2>'` without the policy reaches that
|
||||
; shape. llc must reject it with a clear, actionable TMS34010 diagnostic, not
|
||||
; a SelectionDAG "Cannot select" or an assertion, and the message must name
|
||||
; the driver policy that avoids it.
|
||||
;
|
||||
; The shape below is exactly what IndVarSimplify produces for sumto():
|
||||
; zext i32 -> i33, add 1, mul, lshr 1, trunc i32.
|
||||
define i32 @sumto_closed_form(i32 %n) {
|
||||
entry:
|
||||
%a = zext i32 %n to i33
|
||||
%b = add i33 %a, 1
|
||||
%c = mul i33 %a, %b
|
||||
%d = lshr i33 %c, 1
|
||||
%e = trunc i33 %d to i32
|
||||
ret i32 %e
|
||||
}
|
||||
|
||||
; I33: LLVM ERROR: TMS34010: i33 multiply (umul_lohi shape) reached code generation
|
||||
; I33-SAME: -replexitval=never
|
||||
; I33-NOT: Cannot select
|
||||
@@ -0,0 +1,24 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm %s -o %t-a.s
|
||||
; RUN: cp %s %t-b.ll
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm %t-b.ll -o %t-b.s
|
||||
; RUN: grep -oE '__2ELthis_is__[0-9A-F]{8}_[0-9A-F]{8}' %t-a.s > %t-a.sym
|
||||
; RUN: grep -oE '__2ELthis_is__[0-9A-F]{8}_[0-9A-F]{8}' %t-b.s > %t-b.sym
|
||||
; RUN: test -s %t-a.sym
|
||||
; RUN: test -s %t-b.sym
|
||||
; RUN: FileCheck %s --check-prefix=SYM < %t-a.s
|
||||
; RUN: not diff %t-a.sym %t-b.sym
|
||||
; RUN: awk '{ if (length($0) != 31) exit 1 }' %t-a.sym
|
||||
; RUN: awk '{ if (length($0) != 31) exit 1 }' %t-b.sym
|
||||
;
|
||||
; GSPA 2.00 keeps only 31 identifier characters. The M4 module token is
|
||||
; `_` plus eight hex digits; if it is appended to a long private name the
|
||||
; assembler truncates it away and two translation units collide again. M5
|
||||
; caps the sanitized base and folds the full name into an eight-hex token, so
|
||||
; the module token always stays at the end and the whole label is 31 chars.
|
||||
@this_is_a_very_long_private_name = private constant [5 x i8] c"same\00", align 1
|
||||
|
||||
define ptr @get_long_private() {
|
||||
ret ptr @this_is_a_very_long_private_name
|
||||
}
|
||||
|
||||
; SYM: __2ELthis_is__{{[0-9A-F]+}}_{{[0-9A-F]+}}:
|
||||
@@ -0,0 +1,21 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
;
|
||||
; The TMS34010 call lowering pushes arguments and emits CALLA directly; it
|
||||
; never builds CALLSEQ_START/CALLSEQ_END. StatepointLowering expects that
|
||||
; sequence and asserts (`CallEnd->getOpcode() == ISD::CALLSEQ_END`) when it is
|
||||
; missing. Reject the feature at the target boundary with a diagnostic
|
||||
; instead of letting the generic code crash.
|
||||
;
|
||||
; The CodeGen/Generic test overloaded-intrinsic-name.ll exposed this.
|
||||
; CHECK: TMS34010 does not support gc.statepoint calls
|
||||
|
||||
define ptr addrspace(1) @test(ptr addrspace(1) %v) gc "statepoint-example" {
|
||||
entry:
|
||||
%tok = call token (i64, i32, ptr, i32, i32, ...) @llvm.experimental.gc.statepoint.p0(i64 0, i32 0, ptr elementtype(i1 ()) @return_i1, i32 0, i32 0, i32 0, i32 0) ["gc-live"(ptr addrspace(1) %v)]
|
||||
%v-new = call ptr addrspace(1) @llvm.experimental.gc.relocate.p1(token %tok, i32 0, i32 0)
|
||||
ret ptr addrspace(1) %v-new
|
||||
}
|
||||
|
||||
declare zeroext i1 @return_i1()
|
||||
declare token @llvm.experimental.gc.statepoint.p0(i64, i32, ptr, i32, i32, ...)
|
||||
declare ptr addrspace(1) @llvm.experimental.gc.relocate.p1(token, i32, i32)
|
||||
@@ -0,0 +1,29 @@
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s --check-prefix=REJECT
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s --check-prefix=ALLOW
|
||||
;
|
||||
; A `static` variadic definition is local, and DeadArgumentElimination removes
|
||||
; its `...` when no `llvm.va_start` is present because it assumes the extra
|
||||
; arguments are dead. The M4 contiguous-stack walk in the function body still
|
||||
; reads those slots, so the optimized result silently miscompiles. The
|
||||
; authoritative guard is tms-clang's pre-opt scan; this llc guard pins the
|
||||
; non-O0 pipeline boundary with accurate wording (llc cannot see the post-DAE
|
||||
; shape). -O0 still supports the M4 varargs pattern, and external variadic
|
||||
; definitions (sprintf) are unaffected.
|
||||
define internal i32 @static_vararg(i32 %a, ...) {
|
||||
entry:
|
||||
ret i32 %a
|
||||
}
|
||||
|
||||
define i32 @caller(i32 %x) {
|
||||
entry:
|
||||
%r = call i32 (i32, ...) @static_vararg(i32 %x, i32 7)
|
||||
ret i32 %r
|
||||
}
|
||||
|
||||
; REJECT: LLVM ERROR: TMS34010: local variadic definition 'static_vararg'
|
||||
; REJECT-SAME: general varargs/va_list are an M6 feature
|
||||
; REJECT-SAME: compile with -O0
|
||||
; ALLOW-LABEL: _static_vararg_{{[0-9A-F]+}}:
|
||||
; ALLOW: RETS 2
|
||||
; ALLOW-LABEL: _caller:
|
||||
; ALLOW: CALLA _static_vararg_{{[0-9A-F]+}}
|
||||
@@ -0,0 +1,52 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
;
|
||||
; A 16-bit load must sign-extend into the 32-bit DAG. FS0's field-extension
|
||||
; bit (the `fe` operand of SETF) controls that: the ABI used to emit
|
||||
; `SETF 16, 0, 0`, which the hardware zero-extends, so a negative `short`
|
||||
; entered arithmetic as a large positive i32. c-torture 20030128-1,
|
||||
; 920721-1 and 20000412-1 exposed the bug through short division, a short
|
||||
; argument spill, and a short index respectively.
|
||||
;
|
||||
; `SETF 16, 1, 0` is the sign-extending FS0 configuration. The SEXTLOAD and
|
||||
; the zext-mask lowering (m4-zext.ll) both assume it.
|
||||
|
||||
@neg = global i16 -32768
|
||||
|
||||
define i32 @load_neg(ptr %p) {
|
||||
%v = load i16, ptr %p
|
||||
%r = sext i16 %v to i32
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _load_neg:
|
||||
; CHECK: SETF 16, 1, 0
|
||||
; CHECK: SETF 32, 0, 1
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 0
|
||||
; CHECK-NOT: SLL 16
|
||||
; CHECK: RETS 2
|
||||
|
||||
define i32 @load_global() {
|
||||
%v = load i16, ptr @neg
|
||||
%r = sext i16 %v to i32
|
||||
ret i32 %r
|
||||
}
|
||||
; The same sign-extending FS0 load for a global `short` (the c-torture shape).
|
||||
; CHECK-LABEL: _load_global:
|
||||
; CHECK: SETF 16, 1, 0
|
||||
; CHECK: MOVI _neg, A{{[0-9]+}}
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 0
|
||||
; CHECK-NOT: ANDI 65535
|
||||
; CHECK: RETS 0
|
||||
|
||||
define i32 @divide_neg(ptr %p) {
|
||||
%v = load i16, ptr %p
|
||||
%s = sext i16 %v to i32
|
||||
%r = sdiv i32 50, %s
|
||||
ret i32 %r
|
||||
}
|
||||
; The sign-extending load feeds DIVS directly; no shift pair or mask is needed.
|
||||
; CHECK-LABEL: _divide_neg:
|
||||
; CHECK: SETF 16, 1, 0
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 0
|
||||
; CHECK-NOT: ANDI 65535
|
||||
; CHECK: DIVS
|
||||
; CHECK: RETS 2
|
||||
@@ -0,0 +1,167 @@
|
||||
# RUN: llvm-mc -triple=tms34010 -disassemble < %s | FileCheck %s
|
||||
#
|
||||
# The inverse of the .td encodings, including the short immediate forms that
|
||||
# the emitter selects and the CLR/XOR shared encoding. Bytes are the GSPA
|
||||
# listing bytes in memory order.
|
||||
|
||||
# CHECK: ADD A1, A2
|
||||
0x22 0x40
|
||||
# CHECK: SUB A1, A2
|
||||
0x22 0x44
|
||||
# CHECK: CMP A1, A2
|
||||
0x22 0x48
|
||||
# CHECK: AND A1, A2
|
||||
0x22 0x50
|
||||
# CHECK: OR A1, A2
|
||||
0x22 0x54
|
||||
# CHECK: XOR A1, A2
|
||||
0x22 0x56
|
||||
# CHECK: CLR A0
|
||||
0x00 0x56
|
||||
# CHECK: CLR B0
|
||||
0x10 0x56
|
||||
|
||||
# CHECK: ADDI 5, A0
|
||||
0x00 0x0b 0x05 0x00
|
||||
# CHECK: ADDI -32, A0
|
||||
0x00 0x0b 0xe0 0xff
|
||||
# CHECK: ADDI -3, A0
|
||||
0x00 0x0b 0xfd 0xff
|
||||
# CHECK: MOVI 5, A0
|
||||
0xc0 0x09 0x05 0x00
|
||||
# CHECK: CMPI 5, A0
|
||||
0x40 0x0b 0xfa 0xff
|
||||
# CHECK: SUBI 5, A0
|
||||
0xe0 0x0b 0xfa 0xff
|
||||
# CHECK: CMPI 100000, A0
|
||||
0x60 0x0b 0x5f 0x79 0xfe 0xff
|
||||
# CHECK: SUBI 100000, A0
|
||||
0x00 0x0d 0x5f 0x79 0xfe 0xff
|
||||
# CHECK: ADDI 100000, A0
|
||||
0x20 0x0b 0xa0 0x86 0x01 0x00
|
||||
# CHECK: MOVI 100000, A0
|
||||
0xe0 0x09 0xa0 0x86 0x01 0x00
|
||||
# CHECK: ANDI 5, A0
|
||||
0x80 0x0b 0xfa 0xff 0xff 0xff
|
||||
|
||||
# CHECK: SLL 3, A0
|
||||
0x60 0x24
|
||||
# CHECK: SRA 3, A0
|
||||
0xa0 0x2b
|
||||
# CHECK: SRL 3, A0
|
||||
0xa0 0x2f
|
||||
# CHECK: SLL A1, A2
|
||||
0x22 0x62
|
||||
# CHECK: SRA A1, A2
|
||||
0x22 0x64
|
||||
# CHECK: SRL A1, A2
|
||||
0x22 0x66
|
||||
# CHECK: SLA 3, A0
|
||||
0x60 0x20
|
||||
# CHECK: SLA A1, A2
|
||||
0x22 0x60
|
||||
# CHECK: RL 3, A0
|
||||
0x60 0x30
|
||||
# CHECK: RL A1, A2
|
||||
0x22 0x68
|
||||
|
||||
# CHECK: CLR A0
|
||||
0x00 0x56
|
||||
# CHECK: NEG A0
|
||||
0xa0 0x03
|
||||
# CHECK: NOT A0
|
||||
0xe0 0x03
|
||||
|
||||
# CHECK: MOVE A0, A1
|
||||
0x01 0x4c
|
||||
# CHECK: MOVE B0, B1
|
||||
0x11 0x4c
|
||||
# CHECK: MOVE A0, B1
|
||||
0x01 0x4e
|
||||
# CHECK: MOVE B0, A1
|
||||
0x11 0x4e
|
||||
# CHECK: MOVE B0, *B1, 1
|
||||
0x11 0x82
|
||||
# CHECK: MOVE *B1, B0, 1
|
||||
0x30 0x86
|
||||
# CHECK: MOVE A0, *A1+, 1
|
||||
0x01 0x92
|
||||
# CHECK: MOVE *A1+, A0, 1
|
||||
0x20 0x96
|
||||
# CHECK: MOVE A0, -*A1, 1
|
||||
0x01 0xa2
|
||||
# CHECK: MOVE -*A1, A0, 1
|
||||
0x20 0xa6
|
||||
# CHECK: MOVE A0, *A1(16), 1
|
||||
0x01 0xb2 0x10 0x00
|
||||
# CHECK: MOVE *A1(16), A0, 1
|
||||
0x20 0xb6 0x10 0x00
|
||||
# CHECK: MOVE *A1(16), *A2(32), 1
|
||||
0x22 0xba 0x10 0x00 0x20 0x00
|
||||
# CHECK: MOVE *A1(16), *A2+, 1
|
||||
0x22 0xd2 0x10 0x00
|
||||
# CHECK: MOVB A0, *A1
|
||||
0x01 0x8c
|
||||
# CHECK: MOVB *A1, A0
|
||||
0x20 0x8e
|
||||
# CHECK: MOVB *A1, *A2
|
||||
0x22 0x9c
|
||||
# CHECK: MOVB A0, *A1(16)
|
||||
0x01 0xac 0x10 0x00
|
||||
# CHECK: MOVB *A1(16), A0
|
||||
0x20 0xae 0x10 0x00
|
||||
# CHECK: MOVB *A1(16), *A2(32)
|
||||
0x22 0xbc 0x10 0x00 0x20 0x00
|
||||
|
||||
# CHECK: MOVE A0, @4660, 1
|
||||
0x80 0x07 0x34 0x12 0x00 0x00
|
||||
# CHECK: MOVE @4660, A0, 1
|
||||
0xa0 0x07 0x34 0x12 0x00 0x00
|
||||
# CHECK: MOVB A0, @4660
|
||||
0xe0 0x05 0x34 0x12 0x00 0x00
|
||||
# CHECK: MOVB @4660, A0
|
||||
0xe0 0x07 0x34 0x12 0x00 0x00
|
||||
|
||||
# CHECK: CALL A0
|
||||
0x20 0x09
|
||||
# CHECK: CALL B0
|
||||
0x30 0x09
|
||||
# CHECK: CALLA 4660
|
||||
0x5f 0x0d 0x34 0x12 0x00 0x00
|
||||
# CHECK: JAUC 4660
|
||||
0x80 0xc0 0x34 0x12 0x00 0x00
|
||||
# CHECK: JAEQ 4660
|
||||
0x80 0xca 0x34 0x12 0x00 0x00
|
||||
# CHECK: JR 3
|
||||
0x03 0xc0
|
||||
# CHECK: JREQ 3
|
||||
0x03 0xca
|
||||
|
||||
# CHECK: RETS 0
|
||||
0x60 0x09
|
||||
# CHECK: RETS 2
|
||||
0x62 0x09
|
||||
# CHECK: SETF 16, 0, 0
|
||||
0x50 0x05
|
||||
# CHECK: SETF 32, 0, 1
|
||||
0x40 0x07
|
||||
# CHECK: SETF 16, 1, 1
|
||||
0x70 0x07
|
||||
# CHECK: MMTM SP, A0, A1
|
||||
0x8f 0x09 0x00 0xc0
|
||||
# CHECK: MMFM SP, A0, A1
|
||||
0xaf 0x09 0x03 0x00
|
||||
# CHECK: MMTM B15, B0, B1
|
||||
0x9f 0x09 0x00 0xc0
|
||||
# CHECK: MMFM B15, B0, B1
|
||||
0xbf 0x09 0x03 0x00
|
||||
# CHECK: NOP
|
||||
0x00 0x03
|
||||
# CHECK: SEXT A0, 0
|
||||
0x00 0x05
|
||||
# CHECK: ZEXT A0, 0
|
||||
0x20 0x05
|
||||
# CHECK: SEXT A0, 1
|
||||
0x00 0x07
|
||||
# CHECK: ZEXT A0, 1
|
||||
0x20 0x07
|
||||
@@ -0,0 +1,283 @@
|
||||
# RUN: llvm-mc -triple=tms34010 -show-encoding %s | FileCheck %s
|
||||
#
|
||||
# GSPA 2.00 / dis34 are the encoding oracle. Every expected byte pair below is
|
||||
# the little-endian 16-bit instruction word from a GSPA listing, not an LLVM
|
||||
# round-trip of the encoder. Multi-word instructions list their words in
|
||||
# memory order (opcode word first, then the immediate/address words).
|
||||
|
||||
# CHECK: ADD A1, A2{{.*}}encoding: [0x22,0x40]
|
||||
ADD A1, A2
|
||||
# CHECK: ADD B1, B2{{.*}}encoding: [0x32,0x40]
|
||||
ADD B1, B2
|
||||
|
||||
# CHECK: SUB A1, A2{{.*}}encoding: [0x22,0x44]
|
||||
SUB A1, A2
|
||||
# CHECK: CMP A1, A2{{.*}}encoding: [0x22,0x48]
|
||||
CMP A1, A2
|
||||
# CHECK: AND A1, A2{{.*}}encoding: [0x22,0x50]
|
||||
AND A1, A2
|
||||
# CHECK: OR A1, A2{{.*}}encoding: [0x22,0x54]
|
||||
OR A1, A2
|
||||
# CHECK: XOR A1, A2{{.*}}encoding: [0x22,0x56]
|
||||
XOR A1, A2
|
||||
|
||||
# CHECK: ADDI 5, A0{{.*}}encoding: [0x00,0x0b,0x05,0x00]
|
||||
ADDI 5, A0
|
||||
# CHECK: ADDI 100000, A0{{.*}}encoding: [0x20,0x0b,0xa0,0x86,0x01,0x00]
|
||||
ADDI 100000, A0
|
||||
# CHECK: ADDI -32, A0{{.*}}encoding: [0x00,0x0b,0xe0,0xff]
|
||||
ADDI -32, A0
|
||||
# Unary minus binds tighter than '+': this is (-5)+2 = -3.
|
||||
# CHECK: ADDI -3, A0{{.*}}encoding: [0x00,0x0b,0xfd,0xff]
|
||||
ADDI -5+2, A0
|
||||
# CHECK: SUBI 5, A0{{.*}}encoding: [0xe0,0x0b,0xfa,0xff]
|
||||
SUBI 5, A0
|
||||
# CHECK: SUBI 100000, A0{{.*}}encoding: [0x00,0x0d,0x5f,0x79,0xfe,0xff]
|
||||
SUBI 100000, A0
|
||||
# CHECK: CMPI 5, A0{{.*}}encoding: [0x40,0x0b,0xfa,0xff]
|
||||
CMPI 5, A0
|
||||
# CHECK: CMPI 100000, A0{{.*}}encoding: [0x60,0x0b,0x5f,0x79,0xfe,0xff]
|
||||
CMPI 100000, A0
|
||||
# CHECK: ANDI 5, A0{{.*}}encoding: [0x80,0x0b,0xfa,0xff,0xff,0xff]
|
||||
ANDI 5, A0
|
||||
# CHECK: ANDI 100000, A0{{.*}}encoding: [0x80,0x0b,0x5f,0x79,0xfe,0xff]
|
||||
ANDI 100000, A0
|
||||
# CHECK: ORI 5, A0{{.*}}encoding: [0xa0,0x0b,0x05,0x00,0x00,0x00]
|
||||
ORI 5, A0
|
||||
# CHECK: ORI 100000, A0{{.*}}encoding: [0xa0,0x0b,0xa0,0x86,0x01,0x00]
|
||||
ORI 100000, A0
|
||||
# CHECK: XORI 5, A0{{.*}}encoding: [0xc0,0x0b,0x05,0x00,0x00,0x00]
|
||||
XORI 5, A0
|
||||
# CHECK: XORI 100000, A0{{.*}}encoding: [0xc0,0x0b,0xa0,0x86,0x01,0x00]
|
||||
XORI 100000, A0
|
||||
|
||||
# CHECK: CLR A0{{.*}}encoding: [0x00,0x56]
|
||||
CLR A0
|
||||
# CHECK: NEG A0{{.*}}encoding: [0xa0,0x03]
|
||||
NEG A0
|
||||
# CHECK: NOT A0{{.*}}encoding: [0xe0,0x03]
|
||||
NOT A0
|
||||
|
||||
# CHECK: SLL 3, A0{{.*}}encoding: [0x60,0x24]
|
||||
SLL 3, A0
|
||||
# CHECK: SLL A1, A2{{.*}}encoding: [0x22,0x62]
|
||||
SLL A1, A2
|
||||
# CHECK: SRA 3, A0{{.*}}encoding: [0xa0,0x2b]
|
||||
SRA 3, A0
|
||||
# CHECK: SRA A1, A2{{.*}}encoding: [0x22,0x64]
|
||||
SRA A1, A2
|
||||
# CHECK: SRL 3, A0{{.*}}encoding: [0xa0,0x2f]
|
||||
SRL 3, A0
|
||||
# CHECK: SRL A1, A2{{.*}}encoding: [0x22,0x66]
|
||||
SRL A1, A2
|
||||
# CHECK: SLA 3, A0{{.*}}encoding: [0x60,0x20]
|
||||
SLA 3, A0
|
||||
# CHECK: SLA A1, A2{{.*}}encoding: [0x22,0x60]
|
||||
SLA A1, A2
|
||||
# CHECK: RL 3, A0{{.*}}encoding: [0x60,0x30]
|
||||
RL 3, A0
|
||||
# CHECK: RL A1, A2{{.*}}encoding: [0x22,0x68]
|
||||
RL A1, A2
|
||||
|
||||
# CHECK: MPYS A1, A2{{.*}}encoding: [0x22,0x5c]
|
||||
MPYS A1, A2
|
||||
# CHECK: MPYU A1, A2{{.*}}encoding: [0x22,0x5e]
|
||||
MPYU A1, A2
|
||||
# CHECK: DIVS A1, A2{{.*}}encoding: [0x22,0x58]
|
||||
DIVS A1, A2
|
||||
# CHECK: DIVU A1, A2{{.*}}encoding: [0x22,0x5a]
|
||||
DIVU A1, A2
|
||||
|
||||
# CHECK: MOVI 5, A0{{.*}}encoding: [0xc0,0x09,0x05,0x00]
|
||||
MOVI 5, A0
|
||||
# CHECK: MOVI 100000, A0{{.*}}encoding: [0xe0,0x09,0xa0,0x86,0x01,0x00]
|
||||
MOVI 100000, A0
|
||||
|
||||
# CHECK: MOVE A0, A1{{.*}}encoding: [0x01,0x4c]
|
||||
MOVE A0, A1
|
||||
# CHECK: MOVE B0, B1{{.*}}encoding: [0x11,0x4c]
|
||||
MOVE B0, B1
|
||||
# CHECK: MOVE A0, B1{{.*}}encoding: [0x01,0x4e]
|
||||
MOVE A0, B1
|
||||
# CHECK: MOVE B0, A1{{.*}}encoding: [0x11,0x4e]
|
||||
MOVE B0, A1
|
||||
|
||||
# CHECK: MOVE A0, *A1, 0{{.*}}encoding: [0x01,0x80]
|
||||
MOVE A0, *A1, 0
|
||||
# CHECK: MOVE A0, *A1, 1{{.*}}encoding: [0x01,0x82]
|
||||
MOVE A0, *A1, 1
|
||||
# CHECK: MOVE *A1, A0, 0{{.*}}encoding: [0x20,0x84]
|
||||
MOVE *A1, A0, 0
|
||||
# CHECK: MOVE *A1, A0, 1{{.*}}encoding: [0x20,0x86]
|
||||
MOVE *A1, A0, 1
|
||||
# CHECK: MOVE *A0, *A1, 0{{.*}}encoding: [0x01,0x88]
|
||||
MOVE *A0, *A1, 0
|
||||
# CHECK: MOVE *A0, *A1, 1{{.*}}encoding: [0x01,0x8a]
|
||||
MOVE *A0, *A1, 1
|
||||
# CHECK: MOVE A0, *A1+, 1{{.*}}encoding: [0x01,0x92]
|
||||
MOVE A0, *A1+, 1
|
||||
# CHECK: MOVE *A1+, A0, 1{{.*}}encoding: [0x20,0x96]
|
||||
MOVE *A1+, A0, 1
|
||||
# CHECK: MOVE A0, -*A1, 1{{.*}}encoding: [0x01,0xa2]
|
||||
MOVE A0, -*A1, 1
|
||||
# CHECK: MOVE -*A1, A0, 1{{.*}}encoding: [0x20,0xa6]
|
||||
MOVE -*A1, A0, 1
|
||||
# CHECK: MOVE A0, *A1(16), 1{{.*}}encoding: [0x01,0xb2,0x10,0x00]
|
||||
MOVE A0, *A1(16), 1
|
||||
# CHECK: MOVE *A1(16), A0, 1{{.*}}encoding: [0x20,0xb6,0x10,0x00]
|
||||
MOVE *A1(16), A0, 1
|
||||
# CHECK: MOVE *A1(16), *A2(32), 1{{.*}}encoding: [0x22,0xba,0x10,0x00,0x20,0x00]
|
||||
MOVE *A1(16), *A2(32), 1
|
||||
# CHECK: MOVE *A1(16), *A2+, 1{{.*}}encoding: [0x22,0xd2,0x10,0x00]
|
||||
MOVE *A1(16), *A2+, 1
|
||||
# CHECK: MOVE @4660, *A1+, 1{{.*}}encoding: [0x01,0xd6,0x34,0x12,0x00,0x00]
|
||||
MOVE @0x1234, *A1+, 1
|
||||
|
||||
# CHECK: MOVB A0, *A1{{.*}}encoding: [0x01,0x8c]
|
||||
MOVB A0, *A1
|
||||
# CHECK: MOVB *A1, A0{{.*}}encoding: [0x20,0x8e]
|
||||
MOVB *A1, A0
|
||||
# CHECK: MOVB *A1, *A2{{.*}}encoding: [0x22,0x9c]
|
||||
MOVB *A1, *A2
|
||||
# CHECK: MOVB A0, *A1(16){{.*}}encoding: [0x01,0xac,0x10,0x00]
|
||||
MOVB A0, *A1(16)
|
||||
# CHECK: MOVB *A1(16), A0{{.*}}encoding: [0x20,0xae,0x10,0x00]
|
||||
MOVB *A1(16), A0
|
||||
# CHECK: MOVB *A1(16), *A2(32){{.*}}encoding: [0x22,0xbc,0x10,0x00,0x20,0x00]
|
||||
MOVB *A1(16), *A2(32)
|
||||
|
||||
# CHECK: MOVE A0, @4660, 1{{.*}}encoding: [0x80,0x07,0x34,0x12,0x00,0x00]
|
||||
MOVE A0, @0x1234, 1
|
||||
# CHECK: MOVE @4660, A0, 1{{.*}}encoding: [0xa0,0x07,0x34,0x12,0x00,0x00]
|
||||
MOVE @0x1234, A0, 1
|
||||
# CHECK: MOVB A0, @4660{{.*}}encoding: [0xe0,0x05,0x34,0x12,0x00,0x00]
|
||||
MOVB A0, @0x1234
|
||||
# CHECK: MOVB @4660, A0{{.*}}encoding: [0xe0,0x07,0x34,0x12,0x00,0x00]
|
||||
MOVB @0x1234, A0
|
||||
|
||||
# CHECK: CALL A0{{.*}}encoding: [0x20,0x09]
|
||||
CALL A0
|
||||
# CHECK: CALL B0{{.*}}encoding: [0x30,0x09]
|
||||
CALL B0
|
||||
# CHECK: CALLA 4660{{.*}}encoding: [0x5f,0x0d,0x34,0x12,0x00,0x00]
|
||||
CALLA 0x1234
|
||||
# CHECK: JAUC 4660{{.*}}encoding: [0x80,0xc0,0x34,0x12,0x00,0x00]
|
||||
JAUC 0x1234
|
||||
# CHECK: JAEQ 4660{{.*}}encoding: [0x80,0xca,0x34,0x12,0x00,0x00]
|
||||
JAEQ 0x1234
|
||||
# CHECK: JANE 4660{{.*}}encoding: [0x80,0xcb,0x34,0x12,0x00,0x00]
|
||||
JANE 0x1234
|
||||
# CHECK: JALT 4660{{.*}}encoding: [0x80,0xc4,0x34,0x12,0x00,0x00]
|
||||
JALT 0x1234
|
||||
# CHECK: JALE 4660{{.*}}encoding: [0x80,0xc6,0x34,0x12,0x00,0x00]
|
||||
JALE 0x1234
|
||||
# CHECK: JAGT 4660{{.*}}encoding: [0x80,0xc7,0x34,0x12,0x00,0x00]
|
||||
JAGT 0x1234
|
||||
# CHECK: JAGE 4660{{.*}}encoding: [0x80,0xc5,0x34,0x12,0x00,0x00]
|
||||
JAGE 0x1234
|
||||
# CHECK: JALO 4660{{.*}}encoding: [0x80,0xc8,0x34,0x12,0x00,0x00]
|
||||
JALO 0x1234
|
||||
# CHECK: JALS 4660{{.*}}encoding: [0x80,0xc2,0x34,0x12,0x00,0x00]
|
||||
JALS 0x1234
|
||||
# CHECK: JAHI 4660{{.*}}encoding: [0x80,0xc3,0x34,0x12,0x00,0x00]
|
||||
JAHI 0x1234
|
||||
# CHECK: JAHS 4660{{.*}}encoding: [0x80,0xc9,0x34,0x12,0x00,0x00]
|
||||
JAHS 0x1234
|
||||
# CHECK: JAP 4660{{.*}}encoding: [0x80,0xc1,0x34,0x12,0x00,0x00]
|
||||
JAP 0x1234
|
||||
# CHECK: JAN 4660{{.*}}encoding: [0x80,0xce,0x34,0x12,0x00,0x00]
|
||||
JAN 0x1234
|
||||
# CHECK: JAV 4660{{.*}}encoding: [0x80,0xcc,0x34,0x12,0x00,0x00]
|
||||
JAV 0x1234
|
||||
# CHECK: JANV 4660{{.*}}encoding: [0x80,0xcd,0x34,0x12,0x00,0x00]
|
||||
JANV 0x1234
|
||||
# CHECK: JANN 4660{{.*}}encoding: [0x80,0xcf,0x34,0x12,0x00,0x00]
|
||||
JANN 0x1234
|
||||
|
||||
# CHECK: RETS 0{{.*}}encoding: [0x60,0x09]
|
||||
RETS 0
|
||||
# CHECK: RETS 0{{.*}}encoding: [0x60,0x09]
|
||||
RETS
|
||||
# CHECK: RETS 2{{.*}}encoding: [0x62,0x09]
|
||||
RETS 2
|
||||
|
||||
# CHECK: SETF 16, 0, 0{{.*}}encoding: [0x50,0x05]
|
||||
SETF 16, 0, 0
|
||||
# CHECK: SETF 16, 1, 0{{.*}}encoding: [0x70,0x05]
|
||||
SETF 16, 1, 0
|
||||
# CHECK: SETF 32, 0, 1{{.*}}encoding: [0x40,0x07]
|
||||
SETF 32, 0, 1
|
||||
# CHECK: SETF 16, 1, 1{{.*}}encoding: [0x70,0x07]
|
||||
SETF 16, 1, 1
|
||||
|
||||
# CHECK: MMTM SP, A0, A1{{.*}}encoding: [0x8f,0x09,0x00,0xc0]
|
||||
MMTM SP, A0, A1
|
||||
# CHECK: MMFM SP, A0, A1{{.*}}encoding: [0xaf,0x09,0x03,0x00]
|
||||
MMFM SP, A0, A1
|
||||
# CHECK: MMTM B15, B0, B1{{.*}}encoding: [0x9f,0x09,0x00,0xc0]
|
||||
MMTM B15, B0, B1
|
||||
# CHECK: MMFM B15, B0, B1{{.*}}encoding: [0xbf,0x09,0x03,0x00]
|
||||
MMFM B15, B0, B1
|
||||
|
||||
# CHECK: NOP{{.*}}encoding: [0x00,0x03]
|
||||
NOP
|
||||
|
||||
# CHECK: SEXT A0, 0{{.*}}encoding: [0x00,0x05]
|
||||
SEXT A0, 0
|
||||
# CHECK: ZEXT A0, 0{{.*}}encoding: [0x20,0x05]
|
||||
ZEXT A0, 0
|
||||
# CHECK: SEXT A0, 1{{.*}}encoding: [0x00,0x07]
|
||||
SEXT A0, 1
|
||||
# CHECK: ZEXT A0, 1{{.*}}encoding: [0x20,0x07]
|
||||
ZEXT A0, 1
|
||||
|
||||
# GSPA's unconditional short branch spelling is JRUC (it rejects a bare JR).
|
||||
# The operand is the raw 8-bit word displacement from PC+16; all 16 condition
|
||||
# codes are pinned. Task 5 owns branch targets and relaxation.
|
||||
# CHECK: JR 3{{.*}}encoding: [0x03,0xc0]
|
||||
JRUC 3
|
||||
# CHECK: JRP 3{{.*}}encoding: [0x03,0xc1]
|
||||
JRP 3
|
||||
# CHECK: JRLS 3{{.*}}encoding: [0x03,0xc2]
|
||||
JRLS 3
|
||||
# CHECK: JRHI 3{{.*}}encoding: [0x03,0xc3]
|
||||
JRHI 3
|
||||
# CHECK: JRLT 3{{.*}}encoding: [0x03,0xc4]
|
||||
JRLT 3
|
||||
# CHECK: JRGE 3{{.*}}encoding: [0x03,0xc5]
|
||||
JRGE 3
|
||||
# CHECK: JRLE 3{{.*}}encoding: [0x03,0xc6]
|
||||
JRLE 3
|
||||
# CHECK: JRGT 3{{.*}}encoding: [0x03,0xc7]
|
||||
JRGT 3
|
||||
# CHECK: JRLO 3{{.*}}encoding: [0x03,0xc8]
|
||||
JRLO 3
|
||||
# CHECK: JRHS 3{{.*}}encoding: [0x03,0xc9]
|
||||
JRHS 3
|
||||
# CHECK: JREQ 3{{.*}}encoding: [0x03,0xca]
|
||||
JREQ 3
|
||||
# CHECK: JRNE 3{{.*}}encoding: [0x03,0xcb]
|
||||
JRNE 3
|
||||
# CHECK: JRV 3{{.*}}encoding: [0x03,0xcc]
|
||||
JRV 3
|
||||
# CHECK: JRNV 3{{.*}}encoding: [0x03,0xcd]
|
||||
JRNV 3
|
||||
# CHECK: JRN 3{{.*}}encoding: [0x03,0xce]
|
||||
JRN 3
|
||||
# CHECK: JRNN 3{{.*}}encoding: [0x03,0xcf]
|
||||
JRNN 3
|
||||
# Endpoints of the legal 8-bit displacement range.
|
||||
# CHECK: JR -1{{.*}}encoding: [0xff,0xc0]
|
||||
JR -1
|
||||
# CHECK: JR 127{{.*}}encoding: [0x7f,0xc0]
|
||||
JR 127
|
||||
|
||||
# dis34's condition aliases are accepted by the parser and print canonically.
|
||||
# CHECK: JALO 4660{{.*}}encoding: [0x80,0xc8,0x34,0x12,0x00,0x00]
|
||||
JAC 0x1234
|
||||
# CHECK: JAHS 4660{{.*}}encoding: [0x80,0xc9,0x34,0x12,0x00,0x00]
|
||||
JANC 0x1234
|
||||
# CHECK: JRLO 3{{.*}}encoding: [0x03,0xc8]
|
||||
JRC 3
|
||||
# CHECK: JRHS 3{{.*}}encoding: [0x03,0xc9]
|
||||
JRNC 3
|
||||
@@ -0,0 +1,6 @@
|
||||
# RUN: not llvm-mc -triple=tms34010 -show-encoding %s 2>&1 | FileCheck %s
|
||||
#
|
||||
# The 8-bit JR form has no zero displacement (zero is the 16-bit form) and no
|
||||
# 0x80 low byte (0x80 is the absolute-JA marker).
|
||||
# CHECK: 8-bit JR displacement must be in
|
||||
JR 0
|
||||
@@ -0,0 +1,2 @@
|
||||
if not "TMS34010" in config.root.targets:
|
||||
config.unsupported = True
|
||||
@@ -0,0 +1,98 @@
|
||||
# RUN: llvm-mc -triple=tms34010 -filetype=obj %s -o %t.o
|
||||
# RUN: test -s %t.o
|
||||
# Source for scripts/tests/m5/mc-roundtrip.sh.
|
||||
#
|
||||
# This file deliberately contains only instructions whose GSPA spelling and
|
||||
# dis34 spelling agree after case/number normalisation. The unconditional
|
||||
# short branch is exercised by encodings.s: GSPA accepts only the JRUC alias
|
||||
# and its operand is a word displacement, while dis34 prints an absolute
|
||||
# target, so it cannot be compared textually here.
|
||||
|
||||
ADD A1, A2
|
||||
SUB A1, A2
|
||||
CMP A1, A2
|
||||
AND A1, A2
|
||||
OR A1, A2
|
||||
XOR A1, A2
|
||||
ADDI 5, A0
|
||||
ADDI 100000, A0
|
||||
SUBI 5, A0
|
||||
SUBI 100000, A0
|
||||
CMPI 5, A0
|
||||
ANDI 100000, A0
|
||||
ORI 100000, A0
|
||||
XORI 100000, A0
|
||||
CLR A0
|
||||
NEG A0
|
||||
NOT A0
|
||||
SLL 3, A0
|
||||
SLL A1, A2
|
||||
SRA 3, A0
|
||||
SRL A1, A2
|
||||
MPYS A1, A2
|
||||
MPYU A1, A2
|
||||
DIVS A1, A2
|
||||
DIVU A1, A2
|
||||
MOVI 5, A0
|
||||
MOVI 100000, A0
|
||||
MOVE A0, A1
|
||||
MOVE B0, B1
|
||||
MOVE A0, B1
|
||||
MOVE B0, A1
|
||||
MOVE A0, *A1, 0
|
||||
MOVE A0, *A1, 1
|
||||
MOVE *A1, A0, 0
|
||||
MOVE *A1, A0, 1
|
||||
MOVE *A0, *A1, 0
|
||||
MOVE *A0, *A1, 1
|
||||
MOVE A0, *A1+, 1
|
||||
MOVE *A1+, A0, 1
|
||||
MOVE A0, -*A1, 1
|
||||
MOVE -*A1, A0, 1
|
||||
MOVE A0, *A1(16), 1
|
||||
MOVE *A1(16), A0, 1
|
||||
MOVE *A1(16), *A2(32), 1
|
||||
MOVE *A1(16), *A2+, 1
|
||||
MOVE @0x1234, *A1+, 1
|
||||
MOVB A0, *A1
|
||||
MOVB *A1, A0
|
||||
MOVB *A1, *A2
|
||||
MOVB A0, *A1(16)
|
||||
MOVB *A1(16), A0
|
||||
MOVB *A1(16), *A2(32)
|
||||
MOVE A0, @0x1234, 1
|
||||
MOVE @0x1234, A0, 1
|
||||
MOVB A0, @0x1234
|
||||
CALL A0
|
||||
CALL B0
|
||||
CALLA 0x1234
|
||||
JAUC 0x1234
|
||||
JAEQ 0x1234
|
||||
JANE 0x1234
|
||||
JALT 0x1234
|
||||
JALE 0x1234
|
||||
JAGT 0x1234
|
||||
JAGE 0x1234
|
||||
JALO 0x1234
|
||||
JALS 0x1234
|
||||
JAHI 0x1234
|
||||
JAHS 0x1234
|
||||
JAP 0x1234
|
||||
JAN 0x1234
|
||||
JAV 0x1234
|
||||
JANV 0x1234
|
||||
JANN 0x1234
|
||||
RETS 0
|
||||
RETS 2
|
||||
SETF 16, 0, 0
|
||||
SETF 32, 0, 1
|
||||
SETF 16, 1, 1
|
||||
MMTM SP, A0, A1
|
||||
MMFM SP, A0, A1
|
||||
MMTM B15, B0, B1
|
||||
MMFM B15, B0, B1
|
||||
NOP
|
||||
SEXT A0, 0
|
||||
ZEXT A0, 0
|
||||
SEXT A0, 1
|
||||
ZEXT A0, 1
|
||||
Reference in new issue
Block a user