Checkpoint.

This commit is contained in:
Scott Duensing 2026-04-25 17:07:28 -05:00
commit 873eab4922
68 changed files with 6998 additions and 0 deletions

11
.gitignore vendored Normal file
View file

@ -0,0 +1,11 @@
# Ephemeral: regenerable via setup.sh
tools/
.cache/
# Claude Code tool state
.claude/
# Editor / OS
*.swp
*.swo
.DS_Store

478
LLVM_65816_DESIGN.md Normal file
View file

@ -0,0 +1,478 @@
# LLVM 65816 Backend for Apple IIgs
## Project Design and Handoff Document
---
## 1. Project Goal
Build an optimized Clang/LLVM C compiler backend targeting the WDC 65816
processor, specifically for Apple IIgs development. The backend must produce
genuinely optimized output — not just correct code, but tight, cycle-efficient
65816 assembly that takes full advantage of the architecture.
ORCA/C is the existing open source option and its code generation quality is
poor. Calypsi is the commercial alternative with good output quality but is
closed source. This project aims to produce an open source backend that matches
or exceeds Calypsi's output quality.
---
## 2. Background and Context
### 2.1 The 65816 Architecture
The WDC W65C816S is a 16-bit microprocessor used in the Apple IIgs (and SNES).
Key characteristics relevant to code generation:
- **Dynamic register width:** The A (accumulator), X, and Y registers can be
either 8-bit or 16-bit depending on the M and X bits in the processor status
register. Mode switching is done via REP (Reset bits) and SEP (Set bits)
instructions. REP #$20 sets 16-bit accumulator mode; SEP #$20 sets 8-bit.
REP #$10 sets 16-bit index registers; SEP #$10 sets 8-bit.
- **Direct page:** A relocatable 256-byte window in bank 0 RAM, similar to
zero page on the 6502 but moveable. The Direct Page Register (DP) holds the
base address. Direct page addressing saves one byte per instruction and one
cycle over absolute addressing. Highly valuable for hot variables.
- **24-bit address space:** Addresses are bank:offset. The Data Bank Register
(DBR) implicitly provides the bank for 16-bit absolute data accesses. The
Program Bank Register (PBR) provides the bank for code. Long (24-bit)
addressing is available for cross-bank access.
- **Stack:** 16-bit stack pointer in bank 0. Stack-relative addressing is
available but expensive.
- **Native vs. emulation mode:** At reset the CPU starts in emulation mode
(behaves like 65C02). Native mode (16-bit capable) is entered by clearing
the emulation bit. All IIgs native code runs in native mode.
### 2.2 Why This Is Hard for LLVM
LLVM's register allocator assumes fixed-width registers. The 65816's dynamic
register widths are a fundamental mismatch. Key problems:
1. **REP/SEP scheduling:** Optimal code minimizes mode switches since each
REP/SEP costs 3 cycles. The backend must decide register width per
region of code, coalescing regions that use the same width to minimize
transitions. This is a global dataflow problem.
2. **Direct page as a register file:** LLVM has no model for a relocatable
zero page. Hot variables should be allocated to direct page offsets, but
GS/OS (the IIgs operating system) reserves certain direct page locations
for its own use. The allocator must know which regions are safe.
3. **Bank register management:** DBR affects all 16-bit absolute data accesses.
The backend must ensure DBR is set correctly for cross-bank data access.
4. **24-bit address space in 32-bit ELF:** LLVM's ELF format uses 32-bit
addresses. The llvm-mos project has already defined an ELF extension for
MOS processors that handles this correctly (ELFCLASS32 with unused high
bits zeroed).
5. **GS/OS calling conventions:** Apple IIgs toolbox calls use a stack-based
parameter protocol different from any standard calling convention LLVM
knows. Custom lowering is required for toolbox calls.
### 2.3 Existing Work
- **llvm-mos:** An open source LLVM fork providing first-class support for
MOS Technology 65xx processors. The 6502 backend is production quality.
The 65816 assembler (llvm-mc) has partial support — direct page assumed
at 0x0000, 24-bit long addressing supported, 16-bit immediates supported.
The **compiler backend** (C to 65816) is incomplete. Issues #32 and #321
in the llvm-mos GitHub track this work. The core blocker is REP/SEP
register width management.
- **jeremysrand/llvm-65816:** A separate LLVM backend attempt specifically
targeting Apple IIgs, aiming to output Merlin 32-compatible assembly.
The repo explicitly states "Don't even try to use it yet" and appears
stalled.
- **WorldsApartDevTeam/65816-c:** Claims C11 compliance for 65C816. Only
2 stars, 28 commits, no releases. Very early stage.
- **Calypsi:** Commercial closed-source compiler, version 5.16 released
April 15, 2026. Best available 65816 C compiler. Free for hobby use.
Use its output as a quality benchmark.
- **ORCA/C:** Open source, runs on-machine or via emulator. Mature but
generates poor code. The baseline we need to beat significantly.
### 2.4 llvm-mos as the Foundation
This project is built on top of llvm-mos, not vanilla LLVM:
- llvm-mos already has the MOS ELF specification implemented
- It has the 6502 backend as a reference for 65xx-family conventions
- It has the assembler-level 65816 support to build on
- It has the SDK infrastructure for platform-specific runtime support
- GitHub: https://github.com/llvm-mos/llvm-mos
### 2.5 Architectural Decision: Separate W65816 Target
llvm-mos already defines `FeatureW65816` as a subtarget feature of MOS, and
tracks the missing codegen support in issue #321. We could have added 16-bit
register handling to the existing MOS target as a subtarget feature.
**We did not.** This project is a **separate W65816 target**, maintained as
our own fork of llvm-mos. Reasons:
- Clean register model. MOS's register classes assume 8-bit hardware; aliasing
A/X/Y across 8 and 16-bit widths inside MOS would require invasive changes
to an already-shipping target. A fresh target can define
`Acc8`/`Acc16`/`Idx8`/`Idx16` classes directly and let the REP/SEP pass
operate at the MIR level.
- Independent evolution. This codebase can move at its own pace without
coordinating against llvm-mos's 6502 stability guarantees.
- No upstream burden. We are not attempting to land this in llvm-mos; this is
not a subtarget-feature extension to MOS, it is a sibling target.
The MOS target remains available in the same source tree, unchanged. We
borrow infrastructure (ELF writer, MC layer patterns) but implement our own
registers, instructions, scheduling, and lowering.
---
## 3. Architecture
### 3.1 LLVM Backend Structure
A standard LLVM backend consists of:
```
Clang frontend
LLVM IR (architecture-independent)
Target-specific lowering (SelectionDAG or GlobalISel)
Machine IR (MIR)
Register allocation
Instruction scheduling
Code emission (assembly or object file)
```
The backend adds:
- **TableGen definitions:** Register file, instruction encodings, calling
conventions, operand types, instruction selection patterns
- **Target machine:** Configuration, subtarget features, data layout
- **Instruction selector:** Lowers LLVM IR operations to 65816 instructions
- **Register allocator customization:** Handles the special register width
and direct page constraints
- **Pass pipeline:** Custom optimization passes for REP/SEP scheduling and
direct page allocation
### 3.2 Register File Definition
```tablegen
// Processor status register bits
def MBit : Register<"m">; // accumulator width (0=16-bit, 1=8-bit)
def XBit : Register<"x">; // index width (0=16-bit, 1=8-bit)
// Main registers
def A : Register<"a">; // accumulator (8 or 16-bit)
def X : Register<"x">; // index X (8 or 16-bit)
def Y : Register<"y">; // index Y (8 or 16-bit)
def SP : Register<"sp">; // stack pointer (16-bit)
def DP : Register<"dp">; // direct page register (16-bit)
def DBR : Register<"dbr">; // data bank register (8-bit)
def PBR : Register<"pbr">; // program bank register (8-bit)
def PC : Register<"pc">; // program counter (16-bit)
def P : Register<"p">; // processor status
// Register classes
def Acc8 : RegisterClass<"W65816", [i8], 8, (add A)>;
def Acc16 : RegisterClass<"W65816", [i16], 16, (add A)>;
def Idx8 : RegisterClass<"W65816", [i8], 8, (add X, Y)>;
def Idx16 : RegisterClass<"W65816", [i16], 16, (add X, Y)>;
```
### 3.3 REP/SEP Scheduling Strategy
The core algorithmic challenge. Proposed approach:
**Phase 1 — Width inference:** For each basic block, determine the "natural"
width of each operation: loads/stores of i8 values prefer 8-bit mode, i16
values prefer 16-bit mode.
**Phase 2 — Width coalescing:** Run a dataflow analysis across the CFG. Assign
each basic block a preferred width that minimizes the total number of mode
switches on all edges. This is a minimum cut / graph partitioning problem.
A greedy approach works well in practice.
**Phase 3 — Transition insertion:** At points where the width changes, insert
REP or SEP pseudo-instructions. These are later lowered to real REP/SEP.
**Phase 4 — Peephole cleanup:** Eliminate redundant REP/SEP pairs. If a
block ends with SEP #$20 and the successor starts with REP #$20, and no
path skips the block, one of them is redundant.
### 3.4 Direct Page Allocation
A custom LLVM pass runs after register allocation:
1. Identify the GS/OS-safe direct page region. GS/OS uses $00-$9F for
toolbox and system use. The safe region for user code is $A0-$FF
(96 bytes). This may need to be configurable per project.
2. Score all local variables and spill slots by access frequency (using
LLVM's block frequency analysis).
3. Greedily allocate the highest-frequency variables to direct page
offsets, packing them tightly to fit within the 96-byte budget.
4. Rewrite all accesses to direct-page-allocated variables to use DP
addressing mode instead of stack-relative or absolute addressing.
### 3.5 Calling Convention
Standard function calls:
- Parameters passed on stack (push right-to-left, caller cleans)
- Return value in A (8-bit) or A (16-bit) or A:X (32-bit)
- Callee saves: DP, DBR, direct page contents if modified
- Stack frame: return address (3 bytes in native mode), then locals
GS/OS toolbox calls:
- Parameters pushed as a parameter block
- Tool call via JSL $E10000 (tool dispatcher)
- Custom call lowering needed — mark with a special ISD node and lower
to JSL with the correct tool number encoding
### 3.6 Memory Model
The Apple IIgs memory map relevant to code generation:
```
Bank 00: $0000-$01FF Zero page + stack (system reserved)
$0200-$9FFF User RAM (conventional)
$A000-$BFFF User RAM or LC bank
$C000-$CFFF I/O space
$D000-$FFFF ROM / language card
Bank 01: $0000-$FFFF Additional RAM
Bank E0: $0000-$FFFF Mega II (Apple //e on a chip) I/O space
Bank E1: $0000-$FFFF Video/reserved
Bank FC-FF: ROM banks
```
The default code model places code in bank 0 (or bank 1 for large programs)
and data in bank 0. Long (24-bit) pointers are needed for cross-bank access.
---
## 4. Key Optimizations
### 4.1 Register Width Selection (highest impact)
Using 16-bit accumulator for word operations vs. two 8-bit operations cuts
cycle counts roughly in half for arithmetic. The REP/SEP scheduling described
above is the primary mechanism.
### 4.2 Direct Page Promotion (second highest impact)
Direct page addressing saves 1 byte and 1 cycle per instruction versus
absolute addressing. For variables accessed in tight loops, this compounds
significantly. The allocation pass described above handles this.
### 4.3 Addressing Mode Selection
The instruction selector must choose the most efficient addressing mode:
- Direct page (8-bit offset): preferred for hot variables
- Absolute (16-bit address): for global/static data in same bank
- Long (24-bit address): for cross-bank data access
- Stack-relative: avoid where possible, expensive on 65816
- Indexed: X or Y register indexed variants of the above
### 4.4 MVN/MVP for Block Moves
The 65816 has block move instructions (MVN = move negative, MVP = move
positive) that move memory efficiently. These map to LLVM's memcpy/memmove
intrinsics. The backend should recognize and emit these for small to medium
copies rather than emitting a loop.
### 4.5 Stack Frame Minimization
Minimize spills to the stack — they use expensive stack-relative addressing.
Prefer direct page allocation for spill slots (via the DP allocator above).
---
## 5. Testing Strategy
### 5.1 MAME Lua Interface for Automated Testing
MAME's Lua scripting interface allows automated correctness testing against
real IIgs hardware emulation.
Test harness workflow:
1. Compile a C test case with the backend
2. Load the binary into MAME's IIgs emulation
3. A Lua script sets breakpoints, runs the code, checks register and
memory state against expected values
4. Report pass/fail
Example Lua test script structure:
```lua
emu.add_machine_reset_notifier(function()
local cpu = manager.machine.devices[":maincpu"]
-- breakpoint at end of test function
cpu.debug:bpset(0x010200, "", function()
local a = cpu.state["A"].value
local p = cpu.state["P"].value
local mBit = (p >> 5) & 1 -- M bit: 0=16-bit, 1=8-bit
-- validate result and register width state
if a ~= 0x1234 then
print(string.format("FAIL: A=0x%04X expected 0x1234", a))
elseif mBit ~= 0 then
print("FAIL: expected 16-bit accumulator mode")
else
print("PASS")
end
manager.machine:exit()
end)
end)
```
REP/SEP tracing — validate mode switch sequences:
```lua
-- trace all REP/SEP instructions to validate width scheduling
cpu.debug:wpset(0x000000, 1, "x", "", function()
-- check if instruction at PC is REP or SEP
local pc = cpu.state["PC"].value
local opcode = manager.machine.spaces["program"]:read_u8(pc)
if opcode == 0xC2 then -- REP
local operand = manager.machine.spaces["program"]:read_u8(pc+1)
print(string.format("REP #$%02X at $%04X", operand, pc))
elseif opcode == 0xE2 then -- SEP
local operand = manager.machine.spaces["program"]:read_u8(pc+1)
print(string.format("SEP #$%02X at $%04X", operand, pc))
end
end)
```
### 5.2 Calypsi Output Comparison
Compile the same C code with Calypsi and with our backend. Diff the assembly
output. Cases where our output is significantly worse (more instructions,
more REP/SEP transitions, missing direct page usage) are optimization bugs.
### 5.3 Test Categories
- Basic arithmetic (8-bit and 16-bit, mixed width)
- Pointer operations (direct page, absolute, long)
- Function calls (stack frame setup/teardown)
- GS/OS toolbox calls
- Struct/array access
- Mixed 8/16-bit operations (the REP/SEP stress test)
- memcpy/memset (MVN/MVP emission)
- Interrupt handlers (bank register save/restore)
---
## 6. GS/OS Integration
### 6.1 Runtime Library
A minimal runtime library is needed:
- `crt0.s`: startup code, sets native mode, initializes DP and DBR,
sets up stack, calls `main()`
- `libc`: subset of C standard library functions adapted for GS/OS
- GS/OS system call wrappers (file I/O, memory management, etc.)
### 6.2 ProDOS 16 / GS/OS Output Format
The linker must produce output in a format the IIgs can load:
- OMF (Object Module Format) — the native Apple IIgs executable format
- Alternatively, a raw binary image for simple programs
- The llvm-mos SDK's infrastructure for platform-specific output formats
can be adapted here
---
## 7. Implementation Order
1. **Fork llvm-mos** and create the W65816 target directory structure
2. **TableGen: registers and instruction set** — describe all 65816 registers,
addressing modes, and instructions
3. **Target machine descriptor** — data layout, calling convention basics,
feature flags (native mode, emulation mode)
4. **Instruction selector** — basic patterns for arithmetic, load/store,
branches; initially target a fixed 16-bit accumulator mode (no REP/SEP)
5. **Calling convention lowering** — stack-based parameter passing, return
values
6. **Basic test suite** — compile simple C functions, verify correctness in
MAME via Lua harness
7. **REP/SEP scheduling pass** — the core optimization; width inference,
coalescing, transition insertion
8. **Direct page allocator** — promote hot variables to DP addressing
9. **GS/OS toolbox call lowering** — custom ISD nodes for JSL-based calls
10. **Runtime library** — crt0, minimal libc, GS/OS wrappers
11. **OMF output** — linker support for IIgs executable format
12. **Optimization tuning** — compare against Calypsi, close gaps
---
## 8. Open Questions
1. **GS/OS direct page reservation:** Exactly which direct page bytes does
GS/OS reserve vs. what is safe for user code? Need to verify against
the GS/OS reference documentation.
2. **Bank model:** Should the default code model assume everything fits in
bank 0/1, or should we support a large memory model with 24-bit pointers
by default? Small model is simpler; large model is more powerful.
3. **Interrupt handler ABI:** What registers must an IIgs interrupt handler
save/restore? DBR and DP especially — they may be in an unknown state
when an interrupt fires.
4. **ORCA/C compatibility:** Do we try to be ABI-compatible with ORCA/C to
allow linking against existing IIgs libraries? Or define a clean new ABI?
ORCA/C compatibility would be valuable but constraining.
5. **REP/SEP in leaf functions:** For small leaf functions that only use one
width, we can omit mode switches entirely if the caller guarantees a
known state. Is a "width contract" calling convention attribute worth
implementing?
6. **Cycle counting in MAME:** MAME's IIgs emulation may not be fully
cycle-accurate. For performance regression testing, do we need real
hardware or a more cycle-accurate emulator?
---
## 9. Coding Conventions
- `camelCase` for functions and variables
- `PascalCase` with `T` suffix for types (e.g., `RegWidthT`, `DpSlotT`)
- `//` single-line comments throughout
- Target language: C++ (LLVM requirement)
- Follow llvm-mos coding style for backend code
- Follow LLVM coding standards for everything else
---
## 10. References
- llvm-mos repository: https://github.com/llvm-mos/llvm-mos
- llvm-mos SDK: https://github.com/llvm-mos/llvm-mos-sdk
- llvm-mos ELF spec: https://llvm-mos.org/wiki/ELF_specification
- llvm-mos 65816 issue #32: https://github.com/llvm-mos/llvm-mos/issues/32
- llvm-mos 65816 issue #321: https://github.com/llvm-mos/llvm-mos/issues/321
- WDC 65816 data sheet: available from westerndesigncenter.com
- Apple IIgs Hardware Reference: archive.org
- Apple IIgs GS/OS Reference: archive.org
- Calypsi compiler (quality benchmark): https://www.calypsi.cc/
- ORCA/C (open source reference): https://github.com/byteworksinc/ORCA-C
- jeremysrand/llvm-65816 (prior attempt): https://github.com/jeremysrand/llvm-65816

563
SESSION_STATE.md Normal file
View file

@ -0,0 +1,563 @@
# Session Resume — llvm816 project
Drop this into a new Claude Code session and say "read SESSION_STATE.md and
continue where we left off." Pairs with `LLVM_65816_DESIGN.md` (the design
doc — read that second).
---
## 1. Project in one sentence
Build an open-source LLVM/Clang backend for the WDC 65816 (Apple IIgs) that
matches or exceeds Calypsi's output quality, forked from llvm-mos but
maintained as our own separate W65816 target. User is Scott; expert C dev,
doesn't want hand-holding on LLVM or 65816 basics.
## 2. Where we are in the plan
Design doc section 7 lists a 12-step implementation order. We are at:
- [x] **Setup toolchain** (prior session)
- [x] **Architectural decision: separate W65816 target** (design doc §2.5)
- [x] **Repo-layout decision:** `src/` holds our authored files, `patches/`
holds modifications to upstream llvm-mos files, `tools/llvm-mos/` is
ephemeral and gitignored. `scripts/applyBackend.sh` stitches src +
patches into the clone.
- [x] **Step 1 — scaffold W65816 target directory.** 41 files under
`src/llvm/lib/Target/W65816/` + 2 files under
`src/clang/lib/Basic/Targets/`. 4 upstream patches under `patches/`.
- [x] **Step 2 — verify the skeleton fully compiles and links.** All 8
tablegen generators run clean, three static libs
(LLVMW65816Info/Desc/CodeGen) build, llc links with the target
registered, zero warnings in the W65816-local build.
`./bin/llc -march=w65816 -filetype=null /dev/null` → exit 0.
- [x] **Step 2a — real MC-layer instructions.** `W65816InstrInfo.td` now
holds ~90 real 65816 opcodes (LDA/STA/LDX/LDY/STX/STY across
immediate/DP/abs/DPX/AbsX/AbsY/long where applicable; ADC/SBC/CMP/
AND/ORA/EOR/BIT; INC/DEC/ASL/LSR/ROL/ROR; all transfers; stack
push/pull; REP/SEP/CLC/SEC/XCE/XBA; branches; JMP/JML/JSR/JSL;
RTS/RTL/RTI; MVN/MVP). Instructions whose size depends on M or X
bits exist as `_Imm8`/`_Imm16` pairs carrying the appropriate
TSFlag bits (MLow/MHigh/XLow/XHigh) for the future REP/SEP pass.
- [x] **Step 2b — wire MCCodeEmitter.** Tablegen `-gen-emitter` runs
cleanly; `W65816MCCodeEmitter.cpp` calls the tablegen-provided
`getBinaryCodeForInstr` and emits Size bytes little-endian.
- [x] **Step 2c — symbolic fixups.** Each operand class (imm8/imm16/
addrDP/addrAbs/addrLong/pcrel8/pcrel16) has its own
`EncoderMethod` that emits a `W65816::fixup_*` at the correct
byte offset for expression operands. `W65816AsmBackend::applyFixup`
patches the data bytes little-endian for resolved fixups and
defers to `maybeAddReloc` for unresolved ones.
`W65816ELFObjectWriter::getRelocType` returns placeholder
relocation numbers 1-5 (swap for canonical R_W65816_* names once
the ELF `EM_` is decided — §7 item 1).
- [x] **Step 2d — patch 0005 eliminates data-layout warning.** The
data-layout string for `Triple::w65816` lives in
`llvm/lib/TargetParser/TargetDataLayout.cpp`; `W65816TargetMachine`
now calls `TT.computeDataLayout()`. Zero warnings in the W65816
build.
- [x] **Step 2e — AsmParser scaffold.** 441-line
`AsmParser/W65816AsmParser.cpp` ported from MSP430, stripped of
register-operand handling (65816 has no MC register operands),
with width-narrowing predicates on each operand class so the
matcher picks the narrowest instruction variant the value fits
(e.g. `sta $10` → STA_DP, `sta $1000` → STA_Abs, `sta $10000`
STA_Long). `#` is emitted as a literal token to match the AsmString
tokenisation. Block-move (MVN/MVP) uses `addrDP` for both bank
bytes so `mvn $01, $02` parses.
- [x] **Step 2f — operand bit-field wiring.** Every `Inst*` class in
`W65816InstrFormats.td` now assigns named bitfields into
`Inst{N-8}` (e.g. `let Inst{15-8} = imm;`). Without this
tablegen emits an encoder that writes the opcode but leaves the
operand bytes as zero — we had that bug for an iteration.
- [x] **Step 2g — smoke-test script.** `scripts/smokeTest.sh` checks
llc registration, empty-module codegen, and llvm-mc encoding of
a representative instruction mix. Run with `--build` to rebuild
first.
- [x] **Step 2h — end-to-end ELF object.** `llvm-mc -filetype=obj`
produces a valid ELF with relocations at correct byte offsets.
Relocations are placeholder numbers 1-5 (§7 item 1 — decide
EM_/R_* mapping).
- [x] **Step 2i — Disassembler.** 190-line
`Disassembler/W65816Disassembler.cpp` tries 1/2/3/4-byte decode
tables in ascending size order. Custom decoder callbacks for
imm/addr/pcrel operands wrap raw bits into MCOperands. Mode-
ambiguous opcodes (LDA/LDX/LDY/ADC/SBC/CMP/AND/ORA/EOR/BIT/CPX/
CPY immediate forms) are parked in separate
DecoderTableW65816{MHigh,XHigh}16 tables and the scaffold only
reads the default tables — so those opcodes always disassemble
as 3-byte 16-bit-immediate forms until a mode-aware decoder
lands (alongside REP/SEP).
- [x] **Step 2j — register operands in the AsmParser.** Key fix found
via round-trip test: tablegen treats `a`, `x`, `y` in AsmStrings
(e.g. `"inc a"`, `"lda\t$addr, x"`) as references to the real
register records, so the matcher expects register operands, not
literal tokens. AsmParser now produces `k_Reg` operands for
these identifiers. Verified: `inc a` → 0x1A, `lda $1000, x`
0xBD,0x00,0x10, full ELF round-trip passes.
- [x] **Step 2k — smoke test covers disassembly.** The smoke test
now feeds raw bytes through `llvm-mc --disassemble` and checks
for expected mnemonics, so encoder/decoder asymmetries surface
immediately.
- [x] **Step 3a — first DAG patterns.** Type-as-mode model (approved).
`LDAi16imm` pseudo for i16 constants; `RTL` for retglue;
`emitPrologue` emits canonical `REP #$30`. Mode-dependent
`_Imm8` variants are `isCodeGenOnly` so the asm matcher never
picks them.
- [x] **Step 3c — single-arg function calls.** `LowerFormalArguments`
receives arg 0 in A; `LowerCall` passes arg 0 in A and JSL's
via a JSL pseudo to bridge the i16 symbol operand to the MC
`JSL_Long`'s 24-bit operand class. Result is back in A.
Multi-arg call lowering still wants a `PUSHA` SDNode + SP unwind
sequence — caller side currently fatals on >1 args.
- [x] **Step 3d — multi-arg via stack (callee side).**
`LowerFormalArguments` now reads arg 1+ from stack via
FrameIndex + load. `eliminateFrameIndex` translates LDAfi /
STAfi / ADCfi / SBCfi / ANDfi / ORAfi / EORfi / CMPfi pseudos
to their `LDA d,S` etc. counterparts with the offset baked in.
Stack-relative MC instructions are in place; AsmParser
recognises the `,s` suffix. Callee-side fully working: a
`define i16 @sum3(i16 %a, i16 %b, i16 %c)` compiles to
`clc; adc 4,s; clc; adc 6,s; rtl`.
- [x] **Step 3e — frame-index spill plumbing.** `storeRegToStackSlot`
and `loadRegFromStackSlot` emit STAfi / LDAfi pseudos so the
register allocator can spill Acc16 values when needed.
- [x] **Step 3f — multiplications via shifts.** Multiply by power-of-2
constants inherits the `shl` patterns (1/2/3/4 bits unrolled to
`asl a` sequences). Multiply by arbitrary constants and
runtime values fail at ISel pending library functions.
- [x] **Step 3h — clang front end builds.** Real C → 65816 machine
code via the full `clang -target w65816 -c` pipeline. Bumped
clang's `IntAlign`/`LongAlign`/`PointerAlign`/`SuitableAlign`
from 8 to 16; also overrode `allowsMisalignedMemoryAccesses` to
return true. `scripts/cDemo.sh` shows the full front-end
pipeline on a built-in 7-function demo. Additional patterns:
`INC_Abs`/`DEC_Abs` for `*p = *p + 1`; `ASRA16` (PHA;ASL;PLA;ROR
sequence) for signed shift-right by 1.
- [x] **Step 3i — frame reservation + epilogue.** `emitPrologue`
now emits `TSC; SEC; SBC #N; TCS` to reserve N bytes for locals
and spills, then `emitEpilogue` reverses with `TSC; CLC; ADC #N;
TCS` before the RTL. `eliminateFrameIndex` translates
FrameIndex operands into stack-relative offsets via
`disp = FrameOffset + StackSize`. `hasFPImpl` returns false
(no native FP — direct page would be the logical home). This
unblocks `clang -O0 -c` for pure-arithmetic functions (each
arg gets spilled to its own stack slot). Stack-relative
addressing modes for ADC/SBC/AND/ORA/EOR/CMP let the codegen
fold loads from frame indices into the carry-arithmetic ops.
- [x] **Step 3g — basic i8 codegen.** Acc8 patterns now cover:
`LDAi8imm` (constants), `INA_PSEUDO8` / `DEA_PSEUDO8` (inc/dec),
`ADCi8imm` / `SBCi8imm` (add/sub immediate), `ANDi8imm` /
`ORAi8imm` / `EORi8imm` (bitwise immediate), `LDA8abs` /
`STA8abs` (load/store via global), `ASLA8` / `LSRA8` (1-bit
shifts), `CMPi8imm` (compare against immediate, with BR_CC i8
lowering). Frame lowering scans the function IR for any i8
type usage (return, args, instruction values, operands) and
picks `REP #$10; SEP #$20` prologue when found, else
`REP #$30`. AsmPrinter masks i8 immediates to 8 bits before
printing so `i8 -16` shows `0xf0` rather than `0xfff0`.
Limitations: i8 mode is per-function only — mixed-mode
functions get the i8 prologue (8-bit A) and i16 ops fail.
Asm round-trip for i8 still loses M-mode info (the parser
can't disambiguate `lda #imm` between Imm8 and Imm16); use
`-filetype=obj` directly from llc to get the right encoding.
- [x] **Step 3b — globals, loads, stores, arithmetic, branches,
bitwise.** `LowerOperation` custom-lowers `GlobalAddress` and
`ExternalSymbol` to `W65816Wrapper(target...)`. Pseudo +
AsmPrinter-expansion family covers:
- `LDAi16imm`, `LDAabs`, `STAabs` (load/store/materialise via
Wrapper of global)
- `ADCi16imm`, `ADCabs`, `SBCi16imm`, `SBCabs` (add/sub with the
required CLC/SEC carry prefix)
- `ANDi16imm`, `ORAi16imm`, `EORi16imm` and their `*abs`
memory-fold variants
- `CMPi16imm`, `CMPabs` plus `W65816ISD::CMP` / `W65816ISD::BR_CC`
SDNodes; `LowerBR_CC` swaps constant-on-LHS forms and rewrites
SETULE/SETUGT/SETLE/SETGT to SETULT/SETUGE/SETLT/SETGE+1 so
the canonicalised DAG hits our patterns; condition-code map
covers BEQ/BNE/BCS/BCC plus signed BMI/BPL.
- `BRA` for unconditional `br`.
- `INA_PSEUDO` / `DEA_PSEUDO` for `add x, ±1``inc a` / `dec a`
- `ASLA16` / `LSRA16` for `shl x, 1` and `lshr x, 1``asl a` /
`lsr a`
- `NEGA16` for `0 - x``eor #$ffff; inc a`
- `(xor x, -1)``eor #$ffff` (bitwise NOT)
- Zero-extending byte load: `lda addr; and #$ff`
The end-to-end pipeline can now compile and assemble functions
that read/write globals, do arithmetic on them, and branch
conditionally — all with optimal-looking 65816 idioms (e.g.
`lda x ; clc ; adc y` for `*x + *y`).
- [ ] **Step 3i — open codegen gaps:**
1. **Multi-arg call lowering** (caller side). Callee side works;
caller still bails on >1 arg. Needs PUSHA SDNode + SP-unwind
in ADJCALLSTACKUP.
2. **Frame-reserved scratch space.** Prologue doesn't reserve
stack space for locals/spills, so any alloca'd value or
allocator-spilled value lands at a negative SP offset and
eliminateFrameIndex bails. Blocks: -O0 compilation of
functions with parameters; loops with PHIs that need to
compare two computed values; two-Acc16 binary ops in
general. Fix: emit `TSC; CLC; ADC #-N; TCS` (or PHA-loop)
in emitPrologue and the inverse in emitEpilogue, where N
is the function's frame size.
3. **Mixed-mode i8/i16.** Per-function mode only — the prologue
picks one mode; the other type's ops fail. REP/SEP scheduling
pass needed.
4. **Signed `(a - b)` overflow handling.** BMI/BPL based signed
comparisons are correct only when the subtraction can't
overflow; pathological values give wrong results.
5. **`sub imm, var`** and **`mul var, var`** (or non-power-of-2
constants). Need libcall support.
6. **SETCC and SELECT_CC i16.** Boolean conversions like
`(int)(cond != 0)` and `(cond) ? a : b` aren't selectable.
Custom lowering needed.
7. **Library functions.** `__mulhi3`, etc. — no runtime yet.
- [ ] **Step 4 — real frame lowering, calling convention, REP/SEP
scheduling pass.** The prologue `REP #$30` is unconditional;
the REP/SEP pass will remove it when redundant.
## 3. What is installed and where
All under `/home/scott/claude/llvm816/tools/`:
| Tool | Path | Notes |
|---|---|---|
| llvm-mos source | `tools/llvm-mos/` | shallow clone. Backend files are symlinked in from `src/`; patches applied on top. Reset cleanly via `scripts/updateLlvmMos.sh`. |
| llvm-mos build dir | `tools/llvm-mos-build/` | cmake-generated, ephemeral |
| llvm-mos-sdk | `tools/llvm-mos-sdk/` | prebuilt toolchain |
| MAME 0.264 | `/usr/games/mame` (apt) | supports `-console` (Lua) |
| Apple IIgs ROMs | `tools/mame/roms/apple2gs.zip`, `apple2gsr1.zip` | from archive.org |
| Calypsi 5.16 | `tools/calypsi/` | extracted .deb |
| ORCA/C source | `tools/orca-c/` | reference only |
`./setup.sh --verify-only` passed all checks as of the prior session.
## 4. Repo layout (current)
```
llvm816/ # git repo, branch main
├── LLVM_65816_DESIGN.md # tracked
├── SESSION_STATE.md # this file
├── setup.sh # tracked
├── scripts/ # tracked
│ ├── common.sh
│ ├── installDeps.sh installCalypsi.sh installOrcaC.sh
│ ├── installLlvmMos.sh # non-destructive (see §8)
│ ├── installMame.sh verify.sh
│ ├── applyBackend.sh # src/ + patches/ -> tools/llvm-mos/
│ └── updateLlvmMos.sh # reset clone, re-apply backend
├── src/ # authored files, tracked
│ ├── llvm/lib/Target/W65816/ # 41 files
│ │ ├── MCTargetDesc/ (10 files)
│ │ ├── TargetInfo/ (3 files)
│ │ └── (28 top-level files)
│ └── clang/lib/Basic/Targets/
│ ├── W65816.h
│ └── W65816.cpp
├── patches/ # unified diffs, tracked
│ ├── 0001-triple-add-w65816-arch.patch
│ ├── 0002-triple-cpp-add-w65816-cases.patch
│ ├── 0003-clang-basic-dispatch-w65816.patch
│ └── 0004-cmake-add-w65816-experimental.patch
├── tools/ # gitignored, ephemeral
└── .gitignore # excludes tools/, .cache/
```
## 5. Key architectural decisions
### 5.1 Separate target, not MOS subtarget feature
llvm-mos has `FeatureW65816` declared in `MOSDevices.td` but codegen
unimplemented (issue #321). We are NOT extending MOS. Reasons:
- We cannot upstream an AI-assisted backend to llvm-mos anyway.
- Clean register model: `Acc8`/`Acc16`/`Idx8`/`Idx16` as separate classes.
- Independent evolution.
Recorded in design doc §2.5.
### 5.2 Symlinks + patches, not a fork
`applyBackend.sh` symlinks every file under `src/` into the corresponding
path under `tools/llvm-mos/`, then applies each `patches/*.patch` with
`git apply`. Idempotent: skips already-current symlinks and
already-applied patches (detected via `git apply --reverse --check`).
`updateLlvmMos.sh` is the ONLY script allowed to destructively reset the
clone. It reverses all patches, removes our symlinks, `git reset --hard
FETCH_HEAD`, then re-runs `applyBackend.sh`.
`installLlvmMos.sh` refuses to touch the clone if it is dirty or off
main — this is deliberate to protect applied patches.
## 6. Concrete next actions (in order)
### 6.1 Function arguments
`LowerFormalArguments` and `LowerCall` still fatal-error. Without
arguments, every function we test has to use globals as inputs. The
plan: pass i8/i16 args via the stack (push right-to-left, caller
cleans), with the first 1-2 args optionally going in A or X for
register-passing. Calypsi output is the reference for ABI choices.
### 6.2 i8 codegen
Currently every function gets `REP #$30` (16-bit mode). For i8 ops
we need either:
- A scan-and-prepend approach: if the function has any i8 op, emit
`SEP #$20` after the REP for whichever mode dominates, plus
toggle pseudos around the off-mode regions.
- Or commit to widening all i8 to i16 pre-ISel (simpler, but uses 2x
the cycles for byte-heavy code).
This is the natural lead-in to the REP/SEP scheduling pass (§6.4).
### 6.3 Frame indices, stack locals
Add `eliminateFrameIndex` and frame-pointer pseudos so we can spill
to the stack. Today `W65816RegisterInfo::eliminateFrameIndex` is
`llvm_unreachable`. Stack accesses on 65816 are `,s` and `(,s),y`
indirect — needs new operand classes.
### 6.4 REP/SEP scheduling pass
The core algorithmic work. TSFlag bits on every mode-dependent
instruction are already in place; the pass walks MIR, dataflows the
required mode per region, and inserts/removes REP/SEP transitions to
minimise total mode switches. Design doc §3.3.
### 6.2 Wire frame lowering + calling convention (real)
`W65816FrameLowering.cpp` is still `llvm_unreachable`. The simplest
working version: establish an i16 stack pointer-based frame using the
native SP, locals accessed via stack-relative indirect via Y. Calypsi
output for a trivial function is a good model.
`W65816CallingConv.td` covers i8/i16 return in A but nothing for
arguments. Start with stack-based (push right-to-left, caller cleans)
per design doc §3.5.
### 6.3 Disassembler mode-aware decoding (deferred)
The scaffold disassembler always decodes LDA/LDX/LDY/ADC/SBC/CMP/AND/
ORA/EOR/BIT/CPX/CPY immediate forms as 3-byte 16-bit-immediate
variants. A real decoder should track the M/X bits across the stream
(consuming REP/SEP, XCE transitions) and choose between
`DecoderTableW65816` (default) and `DecoderTableW65816{MHigh,XHigh}16`
per instruction. Naturally pairs with the REP/SEP codegen pass since
both need the same M/X tracking model.
### 6.4 REP/SEP scheduling pass
The core algorithmic work (design doc §3.3). Every real instruction
now carries TSFlag bits indicating which M/X mode it requires. The
pass reads those, does the width-inference / coalescing / transition
insertion dataflow, and emits REP/SEP instructions at block
boundaries. Plan to spend multiple sessions.
### 6.5 Tidy-ups (can happen in any order)
- Decide ELF `EM_` value (§7 item 1). Currently `EM_NONE`, with
placeholder relocation numbers 1-5 in `W65816ELFObjectWriter`.
Swap for canonical `R_W65816_*` names once chosen.
- Replace ASCII-art mnemonics (`inc a`, `dec a`, `asl a`, etc.) with
proper InstAliases so both `INA` and `INC A` assemble to the same
opcode. Requires AsmParser (§6.3).
## 7. Open design questions flagged by the scaffold
1. **ELF `EM_` machine number.** `W65816ELFObjectWriter.cpp` uses
`ELF::EM_NONE` as a placeholder. llvm-mos uses `EM_MOS = 0x1966` for
the 6502 family. Decide: share `EM_MOS`, or pick a new value?
2. **Data layout string** is hardcoded in
`W65816TargetMachine.cpp` rather than routing through
`Triple::computeDataLayout()`. That is OK for now — when we're
ready to consolidate, add a case in `TargetDataLayout.cpp` and
switch to `TT.computeDataLayout()`.
3. **i32 return convention** — does i32 return in A:X or via a hidden
pointer? Currently `W65816CallingConv.td` only handles i8/i16. Design
doc §3.5 says "A:X for 32-bit" but this isn't modelled yet.
4. **Register aliasing for mode-dependent widths.** `Acc8` and `Acc16`
both contain physical register `A`. LLVM's allocator will not cope
with this correctly. The REP/SEP management pass (§3.3) is required.
Flagged per the design doc.
5. **Open questions from design doc §8** (GS/OS DP reservation, bank
memory model, interrupt ABI, ORCA/C ABI compat, width-contract
attribute, MAME cycle accuracy) — still unresolved. Punt until after
we have a working instruction set.
## 8. Gotchas + hard-won knowledge
- **`installLlvmMos.sh` is non-destructive now.** It refuses to reset
the clone if it is dirty or off main. Use `scripts/updateLlvmMos.sh`
to refresh (the only script allowed to reset).
- **MAME `-console` flag** is listed by `-showusage`, NOT `-help`.
- **`log()` in `common.sh` writes to stderr.** Don't change it.
- **llvm-mos has `FeatureW65816` but not working codegen** (issue #321).
- **`RemapAllTargetPseudoPointerOperands<PtrRegs>` is required** in
`W65816.td` or tablegen fails with 8 "missing target override for
pseudoinstruction using PointerLikeRegClass" errors. Don't remove it.
- **`Triple::w65816` placement in Triple.h:** inserted right after
`mos,` to keep the 65xx family clustered. See patch 0001.
- **Added to `LLVM_ALL_EXPERIMENTAL_TARGETS`** in `llvm/CMakeLists.txt`
so `-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=all` picks up W65816. Not
strictly required — passing the name explicitly also works.
- **Operand `OperandType` field wants LLVM's enum spelling**, not
shortened. Use `OPERAND_IMMEDIATE`, `OPERAND_MEMORY`, `OPERAND_PCREL`
(see `llvm/include/llvm/MC/MCInstrDesc.h` MCOI::OperandType).
`OPERAND_IMM` / `OPERAND_IMM8` / `OPERAND_IMM16` are NOT valid.
- **`PrintMethod` signature for PC-rel operands takes `Address`.**
Tablegen generates `printPCRel8(MI, Address, OpNo, O)` — 4 args, not
3. Non-PC-rel PrintMethods use the 3-arg form `(MI, OpNo, O)`.
- **Several `.cpp` files needed explicit `#include`s beyond what MSP430
ships with** because the tablegen-generated `.inc` references full
types: `W65816RegisterInfo.cpp` needs `W65816Subtarget.h` and
`W65816FrameLowering.h` (for `GET_REGINFO_TARGET_DESC`);
`W65816InstPrinter.cpp` needs `llvm/MC/MCAsmInfo.h` (for
`MAI.printExpr`).
- **Marker classes can't override mayLoad/mayStore via `let`.**
TableGen's multi-inheritance doesn't let unrelated sibling classes
touch fields from the base `Instruction`. Use `let isReturn = 1, ...
in { ... }` blocks at def sites instead (idiomatic LLVM style).
- **Data layout is hardcoded** in `W65816TargetMachine.cpp` rather than
computed from `TT.computeDataLayout()`, because `TargetDataLayout.cpp`
doesn't have a case for `w65816` yet. This produces one `-Wswitch`
warning in the llvm-mos build. §6.5 notes adding a 5th patch to
silence it.
## 9. Disk space recovery
If space is tight before resume:
```
# safe to delete — regenerable from setup.sh + applyBackend.sh:
rm -rf /home/scott/claude/llvm816/tools/
rm -rf /home/scott/claude/llvm816/.cache/
```
Regenerate with `./setup.sh` then `./scripts/applyBackend.sh`.
The `tools/llvm-mos-build/` directory alone is ~2 GB after a full
configure+tablegen. A full ninja build will be much more.
## 10. Quick verification commands for resume
```
# Verify the scaffold is in place:
ls src/llvm/lib/Target/W65816/ | wc -l # expect ~20 top-level files
ls patches/ # expect 4 .patch files
# Verify apply is clean:
./scripts/applyBackend.sh # expect 0 new, 44 current symlinks; 0 new, 4 applied patches
# Verify cmake configures:
cmake -S tools/llvm-mos/llvm -B tools/llvm-mos-build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DLLVM_TARGETS_TO_BUILD="" \
-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD="MOS;W65816" \
-DLLVM_ENABLE_PROJECTS="clang" \
-DLLVM_INCLUDE_TESTS=OFF -DLLVM_INCLUDE_EXAMPLES=OFF \
-DLLVM_INCLUDE_BENCHMARKS=OFF
# Verify full build + llc registration (slow first time, cached after):
( cd tools/llvm-mos-build && ninja LLVMW65816Info LLVMW65816Desc LLVMW65816CodeGen llc )
./tools/llvm-mos-build/bin/llc --version | grep w65816
./tools/llvm-mos-build/bin/llc -march=w65816 -filetype=null /dev/null ; echo $?
# Expect: grep matches; llc exits 0.
```
## 11. Files changed this session (not yet committed by user)
```
scripts/applyBackend.sh # idempotent src+patches apply
scripts/updateLlvmMos.sh # safe reset+reapply
scripts/installLlvmMos.sh # no longer destructively resets
scripts/smokeTest.sh # regression smoke test
src/llvm/lib/Target/W65816/ # full MC layer + first codegen:
# CodeGen scaffolds (~40 files)
# AsmParser/ (2 files)
# Disassembler/ (2 files)
# MCTargetDesc/ (11 files)
# TargetInfo/ (3 files)
# ~90 real instruction defs
# ~25 codegen pseudos +
# AsmPrinter expansion
src/clang/lib/Basic/Targets/W65816.{h,cpp}
patches/0001..0005.patch # upstream llvm-mos mods
SESSION_STATE.md # this file
```
The tools/ tree is all ephemeral (gitignored).
### What now works end-to-end
Try it yourself:
```
./scripts/cDemo.sh # built-in demo
./scripts/cDemo.sh path/to/your.c
```
Sample output for the built-in demo (real C → real 65816):
```
get_counter: lda counter ; rtl
set_counter: sta counter ; rtl
sum_with_target: clc ; adc target ; rtl
doubler: asl a ; rtl
half: lsr a ; rtl
reset: lda #0 ; sta counter ; rtl
answer: lda #42 ; rtl
```
### Detail: command-line invocations
```
# Round-trip asm -> bytes -> asm:
echo ' lda #0x1234' | ./bin/llvm-mc -arch=w65816 -show-encoding
# -> lda #0x1234 ; encoding: [0xa9,0x34,0x12]
echo '0xea 0xa9 0x34 0x12 0x6b' | ./bin/llvm-mc --disassemble --triple=w65816
# -> nop ; lda #0x1234 ; rtl
# Full asm -> ELF -> disasm:
./bin/llvm-mc -arch=w65816 -filetype=obj foo.s -o foo.o
./bin/llvm-objdump --triple=w65816 -d foo.o
# Real codegen. This .ll compiles cleanly:
@x = global i16 0
@y = global i16 0
define i16 @fib_step() {
%a = load i16, ptr @x
%b = load i16, ptr @y
%s = add i16 %a, %b
store i16 %a, ptr @y
store i16 %s, ptr @x
ret i16 %s
}
# llc emits idiomatic 65816:
# rep #0x30
# lda x; clc; adc y ; A = a + b
# sta x ; x = a + b
# ...
```
### What doesn't work yet
- **Multi-arg calls** (caller side). Callee accepts stack-passed
args; the matching push side is unimplemented. Functions with
more than one arg can be defined and compile correctly, but
cannot be called from another function.
- **Two-Acc16 cmp.** Loops with PHIs that need to compare two
computed values fail at ISel — only one A.
- **i8 ops** (always 16-bit mode for now).
- **Signed overflow** in CMP-based branches: BMI/BPL test the N flag
of the subtraction, which is incorrect when the subtract overflows.
- **`mul var, var`** (or by non-power-of-2 constants). Needs library
functions (`__mulhi3` etc.).
- **`sub imm, var`** (only `sub var, imm` works).
See §6.1-§6.4 for the next steps.

View file

@ -0,0 +1,12 @@
diff --git a/llvm/include/llvm/TargetParser/Triple.h b/llvm/include/llvm/TargetParser/Triple.h
index b31520b17..d51ac8bc1 100644
--- a/llvm/include/llvm/TargetParser/Triple.h
+++ b/llvm/include/llvm/TargetParser/Triple.h
@@ -69,6 +69,7 @@ public:
mips64, // MIPS64: mips64, mips64r6, mipsn32, mipsn32r6
mips64el, // MIPS64EL: mips64el, mips64r6el, mipsn32el, mipsn32r6el
mos, // MOS: mos 65xx
+ w65816, // WDC 65816: w65816 (Apple IIgs)
msp430, // MSP430: msp430
ppc, // PPC: powerpc
ppcle, // PPCLE: powerpc (little endian)

View file

@ -0,0 +1,77 @@
diff --git a/llvm/lib/TargetParser/Triple.cpp b/llvm/lib/TargetParser/Triple.cpp
index 8aef55224..b6e467274 100644
--- a/llvm/lib/TargetParser/Triple.cpp
+++ b/llvm/lib/TargetParser/Triple.cpp
@@ -80,6 +80,8 @@ StringRef Triple::getArchTypeName(ArchType Kind) {
return "mipsel";
case mos:
return "mos";
+ case w65816:
+ return "w65816";
case msp430:
return "msp430";
case nvptx64:
@@ -678,6 +680,7 @@ Triple::ArchType Triple::getArchTypeForLLVMName(StringRef Name) {
.Case("mips64", mips64)
.Case("mips64el", mips64el)
.Case("msp430", msp430)
+ .Case("w65816", w65816)
.Case("ppc64", ppc64)
.Case("ppc32", ppc)
.Case("ppc", ppc)
@@ -828,6 +831,7 @@ Triple::ArchType Triple::parseArch(StringRef ArchName) {
.Case("m68k", Triple::m68k)
.Case("mos", Triple::mos)
.Case("msp430", Triple::msp430)
+ .Case("w65816", Triple::w65816)
.Cases({"mips", "mipseb", "mipsallegrex", "mipsisa32r6", "mipsr6"},
Triple::mips)
.Cases({"mipsel", "mipsallegrexel", "mipsisa32r6el", "mipsr6el"},
@@ -1223,6 +1227,7 @@ static Triple::ObjectFormatType getDefaultFormat(const Triple &T) {
case Triple::mips:
case Triple::mos:
case Triple::msp430:
+ case Triple::w65816:
case Triple::nvptx64:
case Triple::nvptx:
case Triple::ppc64le:
@@ -1948,6 +1953,7 @@ unsigned Triple::getArchPointerBitWidth(llvm::Triple::ArchType Arch) {
case llvm::Triple::avr:
case llvm::Triple::mos:
case llvm::Triple::msp430:
+ case llvm::Triple::w65816:
return 16;
case llvm::Triple::aarch64_32:
@@ -2057,6 +2063,7 @@ Triple Triple::get32BitArchVariant() const {
case Triple::bpfel:
case Triple::mos:
case Triple::msp430:
+ case Triple::w65816:
case Triple::systemz:
case Triple::ve:
T.setArch(UnknownArch);
@@ -2176,6 +2183,7 @@ Triple Triple::get64BitArchVariant() const {
case Triple::m68k:
case Triple::mos:
case Triple::msp430:
+ case Triple::w65816:
case Triple::r600:
case Triple::shave:
case Triple::sparcel:
@@ -2303,6 +2311,7 @@ Triple Triple::getBigEndianArchVariant() const {
case Triple::loongarch64:
case Triple::mos:
case Triple::msp430:
+ case Triple::w65816:
case Triple::nvptx64:
case Triple::nvptx:
case Triple::r600:
@@ -2444,6 +2453,7 @@ bool Triple::isLittleEndian() const {
case Triple::mipsel:
case Triple::mos:
case Triple::msp430:
+ case Triple::w65816:
case Triple::nvptx64:
case Triple::nvptx:
case Triple::ppcle:

View file

@ -0,0 +1,34 @@
diff --git a/clang/lib/Basic/CMakeLists.txt b/clang/lib/Basic/CMakeLists.txt
index c71c1c6f0..a0c5fbb64 100644
--- a/clang/lib/Basic/CMakeLists.txt
+++ b/clang/lib/Basic/CMakeLists.txt
@@ -111,6 +111,7 @@ add_clang_library(clangBasic
Targets/M68k.cpp
Targets/MOS.cpp
Targets/MSP430.cpp
+ Targets/W65816.cpp
Targets/Mips.cpp
Targets/NVPTX.cpp
Targets/OSTargets.cpp
diff --git a/clang/lib/Basic/Targets.cpp b/clang/lib/Basic/Targets.cpp
index 4c0fdfdbc..fccc921de 100644
--- a/clang/lib/Basic/Targets.cpp
+++ b/clang/lib/Basic/Targets.cpp
@@ -28,6 +28,7 @@
#include "Targets/M68k.h"
#include "Targets/MOS.h"
#include "Targets/MSP430.h"
+#include "Targets/W65816.h"
#include "Targets/Mips.h"
#include "Targets/NVPTX.h"
#include "Targets/OSTargets.h"
@@ -287,6 +288,9 @@ std::unique_ptr<TargetInfo> AllocateTarget(const llvm::Triple &Triple,
case llvm::Triple::msp430:
return std::make_unique<MSP430TargetInfo>(Triple, Opts);
+ case llvm::Triple::w65816:
+ return std::make_unique<W65816TargetInfo>(Triple, Opts);
+
case llvm::Triple::mips:
switch (os) {
case llvm::Triple::Linux:

View file

@ -0,0 +1,12 @@
diff --git a/llvm/CMakeLists.txt b/llvm/CMakeLists.txt
index 6001928f9..015a239b5 100644
--- a/llvm/CMakeLists.txt
+++ b/llvm/CMakeLists.txt
@@ -596,6 +596,7 @@ set(LLVM_ALL_EXPERIMENTAL_TARGETS
CSKY
DirectX
M68k
+ W65816
Xtensa
)

View file

@ -0,0 +1,13 @@
diff --git a/llvm/lib/TargetParser/TargetDataLayout.cpp b/llvm/lib/TargetParser/TargetDataLayout.cpp
index 8837d2f91..b796d9e86 100644
--- a/llvm/lib/TargetParser/TargetDataLayout.cpp
+++ b/llvm/lib/TargetParser/TargetDataLayout.cpp
@@ -582,6 +582,8 @@ std::string Triple::computeDataLayout(StringRef ABIName) const {
return "e-m:e-p:16:8-p1:8:8-i16:8-i32:8-i64:8-f32:8-f64:8-a:8-Fi8-n8";
case Triple::msp430:
return "e-m:e-p:16:16-i32:16-i64:16-f32:16-f64:16-a:8-n8:16-S16";
+ case Triple::w65816:
+ return "e-m:e-p:16:8-i16:16-i32:16-n8:16-S16";
case Triple::ppc:
case Triple::ppcle:
case Triple::ppc64:

77
scripts/applyBackend.sh Executable file
View file

@ -0,0 +1,77 @@
#!/usr/bin/env bash
# Stitch our W65816 backend into the ephemeral llvm-mos checkout.
#
# Two-step process:
# 1. For every file under src/, create a symlink at the matching path
# inside tools/llvm-mos/. That way editing a file under src/ is
# immediately reflected in the build tree.
# 2. Apply every patch under patches/ with `git apply`. Patches that
# are already applied (git detects this) are skipped silently.
#
# Safe to re-run.
set -euo pipefail
source "$(dirname "$0")/common.sh"
LLVM_SRC="$TOOLS_DIR/llvm-mos"
SRC_TREE="$PROJECT_ROOT/src"
PATCH_DIR="$PROJECT_ROOT/patches"
[ -d "$LLVM_SRC/.git" ] || die "llvm-mos checkout not found at $LLVM_SRC; run setup.sh first"
[ -d "$SRC_TREE" ] || die "src/ tree missing"
# 1. Symlinks. For each file in src/<rel> we want a symlink at
# $LLVM_SRC/<rel>. Relative target path keeps the clone portable if
# someone moves it, though our setup.sh always uses an absolute path.
log "linking src/ files into $LLVM_SRC"
linkedCount=0
skippedCount=0
while IFS= read -r -d '' file; do
rel="${file#$SRC_TREE/}"
dest="$LLVM_SRC/$rel"
destDir="$(dirname "$dest")"
install -d "$destDir"
if [ -L "$dest" ]; then
currentTarget="$(readlink "$dest")"
if [ "$currentTarget" = "$file" ]; then
skippedCount=$((skippedCount + 1))
continue
fi
warn "replacing stale symlink: $rel"
rm "$dest"
elif [ -e "$dest" ]; then
die "refusing to overwrite non-symlink file: $dest"
fi
ln -s "$file" "$dest"
linkedCount=$((linkedCount + 1))
done < <(find "$SRC_TREE" -type f -print0)
log "symlinks: $linkedCount created, $skippedCount already current"
# 2. Patches. git apply is idempotent via --reverse-check: if a patch
# is already applied, `git apply --reverse --check` succeeds and we skip.
log "applying patches from $PATCH_DIR"
appliedCount=0
reappliedCount=0
for patch in "$PATCH_DIR"/*.patch; do
[ -f "$patch" ] || continue
name="$(basename "$patch")"
if git -C "$LLVM_SRC" apply --reverse --check "$patch" >/dev/null 2>&1; then
reappliedCount=$((reappliedCount + 1))
continue
fi
if ! git -C "$LLVM_SRC" apply --check "$patch" >/dev/null 2>&1; then
die "patch fails to apply cleanly: $name. The llvm-mos tree may have drifted; run scripts/updateLlvmMos.sh."
fi
git -C "$LLVM_SRC" apply "$patch"
log " applied: $name"
appliedCount=$((appliedCount + 1))
done
log "patches: $appliedCount newly applied, $reappliedCount already present"
log "backend stitched in at $LLVM_SRC"

46
scripts/cDemo.sh Executable file
View file

@ -0,0 +1,46 @@
#!/usr/bin/env bash
# Compile a small C file with clang -target w65816 and disassemble
# the result. Demonstrates the full toolchain front-to-back.
#
# Usage: scripts/cDemo.sh [source.c]
# Without an argument, compiles a built-in demo program.
set -euo pipefail
source "$(dirname "$0")/common.sh"
BUILD_DIR="$TOOLS_DIR/llvm-mos-build"
CLANG="$BUILD_DIR/bin/clang"
OBJDUMP="$BUILD_DIR/bin/llvm-objdump"
[ -x "$CLANG" ] || die "clang not built; run: ninja -C $BUILD_DIR clang"
[ -x "$OBJDUMP" ] || die "llvm-objdump not built"
src="${1:-}"
if [ -z "$src" ]; then
src="$(mktemp --suffix=.c)"
obj_cleanup_extra="$src"
cat > "$src" <<'EOF'
// Built-in demo: simple integer functions
int counter;
int target = 100;
int get_counter(void) { return counter; }
int set_counter(int v) { counter = v; return v; }
int sum_with_target(int x) { return x + target; }
int doubler(int x) { return x * 2; }
unsigned half(unsigned x) { return x / 2; } // unsigned: uses lsr
int reset(void) { counter = 0; return 0; }
int answer(void) { return 42; }
EOF
log "(no source given; using built-in demo)"
fi
obj="$(mktemp --suffix=.o)"
trap 'rm -f "$obj" ${obj_cleanup_extra:-}' EXIT
log "compiling $src with clang -target w65816 -O2"
"$CLANG" --target=w65816 -O2 -c "$src" -o "$obj"
log "disassembling result:"
echo
"$OBJDUMP" --triple=w65816 -d "$obj"

45
scripts/common.sh Executable file
View file

@ -0,0 +1,45 @@
# Shared helpers for all install scripts. Sourced, not executed.
set -euo pipefail
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TOOLS_DIR="$PROJECT_ROOT/tools"
DOWNLOAD_CACHE="$PROJECT_ROOT/.cache/downloads"
install -d "$TOOLS_DIR" "$DOWNLOAD_CACHE"
log() {
printf '\033[1;34m[llvm816]\033[0m %s\n' "$*" >&2
}
warn() {
printf '\033[1;33m[llvm816 WARN]\033[0m %s\n' "$*" >&2
}
die() {
printf '\033[1;31m[llvm816 FAIL]\033[0m %s\n' "$*" >&2
exit 1
}
# Download to cache unless already present. $1=url, $2=dest filename in cache.
fetchCached() {
local url="$1"
local name="$2"
local dest="$DOWNLOAD_CACHE/$name"
if [ -s "$dest" ]; then
log "cached: $name"
else
log "fetching: $url"
curl -fL --retry 3 --progress-bar -o "$dest.part" "$url"
mv "$dest.part" "$dest"
fi
printf '%s\n' "$dest"
}
needCmd() {
command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
}
haveCmd() {
command -v "$1" >/dev/null 2>&1
}

50
scripts/installCalypsi.sh Executable file
View file

@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Install Calypsi 65C816 toolchain as the output-quality benchmark.
# Self-contained: extract .deb payload into tools/calypsi rather than
# dpkg-install system-wide.
set -euo pipefail
source "$(dirname "$0")/common.sh"
CALYPSI_VERSION="5.16"
CALYPSI_DIR="$TOOLS_DIR/calypsi"
CALYPSI_DEB_URL="https://github.com/hth313/Calypsi-tool-chains/releases/download/${CALYPSI_VERSION}/calypsi-65816-${CALYPSI_VERSION}.deb"
if [ -x "$CALYPSI_DIR/usr/local/calypsi-65816/bin/cc65816" ] \
|| [ -x "$CALYPSI_DIR/opt/calypsi-65816/bin/cc65816" ] \
|| find "$CALYPSI_DIR" -maxdepth 6 -type f -name 'cc65816' 2>/dev/null | grep -q .; then
log "calypsi already installed under $CALYPSI_DIR"
exit 0
fi
deb="$(fetchCached "$CALYPSI_DEB_URL" "calypsi-65816-${CALYPSI_VERSION}.deb")"
log "extracting calypsi .deb payload"
install -d "$CALYPSI_DIR"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
( cd "$tmp" && ar x "$deb" )
payload=""
for cand in data.tar.xz data.tar.zst data.tar.gz data.tar.bz2; do
if [ -f "$tmp/$cand" ]; then
payload="$tmp/$cand"
break
fi
done
[ -n "$payload" ] || die "could not find data.tar.* inside $deb"
tar -xf "$payload" -C "$CALYPSI_DIR"
# Locate the bin directory. Calypsi puts tools under /opt or /usr/local
# depending on package revision; find it and symlink into tools/calypsi/bin
# for a stable path.
ccBin="$(find "$CALYPSI_DIR" -maxdepth 6 -type f -name 'cc65816' -executable 2>/dev/null | head -1 || true)"
[ -n "$ccBin" ] || die "calypsi cc65816 not found after extract (inspect $CALYPSI_DIR)"
binDir="$(dirname "$ccBin")"
ln -sfn "$binDir" "$CALYPSI_DIR/bin"
log "calypsi install done"
log " version: $CALYPSI_VERSION"
log " root: $CALYPSI_DIR"
log " binaries: $CALYPSI_DIR/bin -> $binDir"

38
scripts/installDeps.sh Executable file
View file

@ -0,0 +1,38 @@
#!/usr/bin/env bash
# Install system packages needed for llvm-mos build, MAME usage, and general dev.
set -euo pipefail
source "$(dirname "$0")/common.sh"
APT_PACKAGES=(
# llvm-mos build toolchain
build-essential
cmake
ninja-build
clang
lld
python3
python3-pip
git
zlib1g-dev
libedit-dev
libxml2-dev
libncurses-dev
# archive handling (calypsi ships zst; rom zips; llvm-mos-sdk tar.xz)
zstd
xz-utils
unzip
tar
# MAME Lua scripting & debug
lua5.4
liblua5.4-dev
# runtime utilities used by install scripts
curl
ca-certificates
)
log "installing apt packages (sudo required)"
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends "${APT_PACKAGES[@]}"
log "system dependencies installed"

83
scripts/installLlvmMos.sh Executable file
View file

@ -0,0 +1,83 @@
#!/usr/bin/env bash
# Install llvm-mos: clone source tree for backend development, plus
# download prebuilt SDK for reference/smoke-testing existing 6502 targets.
#
# Flags:
# --build also build the source tree with cmake/ninja (slow, ~30-60 min)
set -euo pipefail
source "$(dirname "$0")/common.sh"
doBuild=0
for arg in "$@"; do
case "$arg" in
--build) doBuild=1 ;;
*) die "unknown flag: $arg" ;;
esac
done
LLVM_SRC="$TOOLS_DIR/llvm-mos"
LLVM_BUILD="$TOOLS_DIR/llvm-mos-build"
LLVM_SDK="$TOOLS_DIR/llvm-mos-sdk"
SDK_URL="https://github.com/llvm-mos/llvm-mos-sdk/releases/latest/download/llvm-mos-linux.tar.xz"
# 1. Source tree for backend development.
#
# IMPORTANT: this clone is ephemeral scaffolding, not where we do work. Our
# own sources live in $PROJECT_ROOT/src/ and $PROJECT_ROOT/patches/ and are
# stitched into this tree by applyBackend.sh. That means a destructive
# reset here would stomp applied symlinks/patches — so we refuse to touch
# the clone if it looks modified. Use updateLlvmMos.sh to refresh safely.
if [ -d "$LLVM_SRC/.git" ]; then
currentBranch="$(git -C "$LLVM_SRC" rev-parse --abbrev-ref HEAD)"
if [ "$currentBranch" != "main" ]; then
warn "llvm-mos clone is on branch '$currentBranch' (not main); leaving untouched"
elif ! git -C "$LLVM_SRC" diff --quiet || ! git -C "$LLVM_SRC" diff --cached --quiet; then
warn "llvm-mos clone has local modifications; leaving untouched. Use scripts/updateLlvmMos.sh to refresh."
else
log "llvm-mos source already cloned; fetching and fast-forwarding main"
git -C "$LLVM_SRC" fetch --depth=1 origin main
git -C "$LLVM_SRC" merge --ff-only FETCH_HEAD || warn "fast-forward failed; leaving clone as-is"
fi
else
log "cloning llvm-mos (shallow)"
git clone --depth=1 https://github.com/llvm-mos/llvm-mos.git "$LLVM_SRC"
fi
# 2. Prebuilt SDK for testing/reference.
if [ -x "$LLVM_SDK/bin/mos-common-clang" ] || [ -x "$LLVM_SDK/bin/clang" ]; then
log "llvm-mos-sdk already extracted"
else
archive="$(fetchCached "$SDK_URL" "llvm-mos-linux.tar.xz")"
log "extracting llvm-mos-sdk"
install -d "$LLVM_SDK"
tar -xJf "$archive" -C "$LLVM_SDK" --strip-components=1
fi
# 3. Optional source build.
if [ "$doBuild" -eq 1 ]; then
needCmd cmake
needCmd ninja
log "configuring llvm-mos build (this takes a while)"
install -d "$LLVM_BUILD"
cmake -S "$LLVM_SRC/llvm" -B "$LLVM_BUILD" -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DLLVM_TARGETS_TO_BUILD="" \
-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD="MOS" \
-DLLVM_ENABLE_PROJECTS="clang;lld" \
-DLLVM_PARALLEL_LINK_JOBS=1 \
-DLLVM_USE_LINKER=lld \
-DLLVM_INCLUDE_TESTS=OFF \
-DLLVM_INCLUDE_EXAMPLES=OFF \
-DLLVM_INCLUDE_BENCHMARKS=OFF
log "building llvm-mos (background-friendly: use --build only when you have time)"
ninja -C "$LLVM_BUILD"
log "llvm-mos build complete: $LLVM_BUILD/bin/clang"
else
log "skipped source build; rerun with --build when ready (cmake+ninja)"
fi
log "llvm-mos install done"
log " source: $LLVM_SRC"
log " sdk: $LLVM_SDK"
[ "$doBuild" -eq 1 ] && log " build: $LLVM_BUILD"

46
scripts/installMame.sh Executable file
View file

@ -0,0 +1,46 @@
#!/usr/bin/env bash
# Install MAME (via apt) and fetch Apple IIgs ROM sets into the project-local
# rompath. Self-contained — MAME is launched with -rompath to point here, so
# this does not touch the user's ~/.mame directory.
set -euo pipefail
source "$(dirname "$0")/common.sh"
MAME_ROMS="$TOOLS_DIR/mame/roms"
ROM_BASE="https://archive.org/download/mame-0.240-roms-split_202201/MAME%200.240%20ROMs%20(split)"
install -d "$MAME_ROMS"
# 1. MAME binary via apt. Ubuntu noble ships 0.264.
if ! haveCmd mame; then
log "installing mame"
sudo apt-get install -y --no-install-recommends mame mame-tools
else
log "mame already installed: $(mame -version 2>/dev/null | head -1 || true)"
fi
# 2. ROM sets. apple2gs is current ROM 03; apple2gsr1 is ROM 01 (many 1986-era
# titles require this variant). Pull both so we can test against either.
for romName in apple2gs.zip apple2gsr1.zip; do
dest="$MAME_ROMS/$romName"
if [ -s "$dest" ]; then
log "rom already present: $romName"
continue
fi
log "downloading $romName"
curl -fL --retry 3 --progress-bar \
-o "$dest.part" "$ROM_BASE/$romName"
mv "$dest.part" "$dest"
done
# 3. Quick Lua capability check — we rely on -console for the test harness.
if mame -showusage 2>&1 | grep -q -- '-console'; then
log "mame Lua console available"
else
warn "mame binary does not appear to support -console / Lua scripting"
fi
log "mame install done"
log " binary: $(command -v mame)"
log " rompath: $MAME_ROMS"
log " launch: mame -rompath $MAME_ROMS apple2gs"

19
scripts/installOrcaC.sh Executable file
View file

@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Clone ORCA/C as an on-disk reference. It's an on-machine compiler — we only
# need the source to study codegen choices, not to build.
set -euo pipefail
source "$(dirname "$0")/common.sh"
ORCA_DIR="$TOOLS_DIR/orca-c"
ORCA_URL="https://github.com/byteworksinc/ORCA-C.git"
if [ -d "$ORCA_DIR/.git" ]; then
log "orca-c already cloned; pulling latest"
git -C "$ORCA_DIR" pull --ff-only
else
log "cloning ORCA/C reference source"
git clone --depth=1 "$ORCA_URL" "$ORCA_DIR"
fi
log "orca-c reference at $ORCA_DIR"

275
scripts/smokeTest.sh Executable file
View file

@ -0,0 +1,275 @@
#!/usr/bin/env bash
# W65816 backend smoke test. Run after any change to confirm the
# scaffold still builds and llc still registers the target. Non-zero
# exit on any failure.
#
# Usage: scripts/smokeTest.sh [--build]
# --build Run ninja to (re)build LLVMW65816* + llc before testing.
# Without this flag the script assumes tools/llvm-mos-build
# is already up to date.
set -euo pipefail
source "$(dirname "$0")/common.sh"
BUILD_DIR="$TOOLS_DIR/llvm-mos-build"
LLC="$BUILD_DIR/bin/llc"
LLVM_MC="$BUILD_DIR/bin/llvm-mc"
doBuild=0
for arg in "$@"; do
case "$arg" in
--build) doBuild=1 ;;
*) die "unknown flag: $arg" ;;
esac
done
[ -x "$LLC" ] || die "llc not found at $LLC; run setup.sh and applyBackend.sh, or pass --build"
if [ "$doBuild" -eq 1 ]; then
log "ninja LLVMW65816* llc llvm-mc llvm-objdump"
ninja -C "$BUILD_DIR" LLVMW65816Info LLVMW65816Desc LLVMW65816CodeGen \
LLVMW65816AsmParser LLVMW65816Disassembler llc llvm-mc llvm-objdump
fi
# 1. Target must be registered.
log "check: llc --version lists w65816"
if ! "$LLC" --version 2>/dev/null | grep -q "^[[:space:]]*w65816[[:space:]]"; then
die "llc does not list the w65816 target"
fi
# 2. Empty IR must compile to nothing.
log "check: llc -march=w65816 -filetype=null /dev/null exits 0"
"$LLC" -march=w65816 -filetype=null /dev/null
# 3. Trivial IR that shouldn't touch our (unimplemented) codegen paths.
tmp="$(mktemp --suffix=.ll)"
trap 'rm -f "$tmp"' EXIT
cat > "$tmp" <<'EOF'
; ModuleID = 'smoke'
target triple = "w65816-unknown-unknown"
; Empty module: exercises target initialization only.
EOF
log "check: llc accepts an empty module with w65816 triple"
"$LLC" -filetype=null "$tmp"
# 4. MC layer round-trip. Assemble a representative mix of addressing
# modes and mode-switching instructions and grep for the expected
# encoding bytes. Hex-byte strings are stable across llvm-mc
# formatting changes, unlike full-line string matching.
if [ -x "$LLVM_MC" ]; then
log "check: llvm-mc -arch=w65816 emits expected encodings"
# Only exercise instructions that round-trip cleanly:
# - LDA/LDX/LDY immediates without explicit force use the _Imm16
# form (codegen-dominant path). A pure `lda #x` assembles to
# LDA_Imm16 since the _Imm8 variant is isCodeGenOnly.
mcInput=' nop
rep #0x30
sep #0x20
lda #0x1234
sta 0x10
sta 0x1000
sta 0x010000
mvn 0x01, 0x02
jsl 0x012345'
mcOut="$(printf '%s\n' "$mcInput" | "$LLVM_MC" -arch=w65816 -show-encoding 2>&1)"
assertHas() {
if ! printf '%s\n' "$mcOut" | grep -qF "$1"; then
warn "missing expected encoding: $1"
printf '%s\n' "$mcOut" >&2
die "llvm-mc did not produce expected encoding"
fi
}
assertHas "[0xea]"
assertHas "[0xc2,0x30]"
assertHas "[0xe2,0x20]"
assertHas "[0xa9,0x34,0x12]"
assertHas "[0x85,0x10]"
assertHas "[0x8d,0x00,0x10]"
assertHas "[0x8f,0x00,0x00,0x01]"
assertHas "[0x54,0x01,0x02]"
assertHas "[0x22,0x45,0x23,0x01]"
else
warn "llvm-mc not built; skipping MC round-trip check"
fi
# 5. Disassembler round-trip. A raw byte stream fed to llvm-mc
# --disassemble should produce the mnemonic we expect.
if [ -x "$LLVM_MC" ]; then
log "check: llvm-mc --disassemble decodes bytes back to mnemonics"
disasmOut="$(printf '0xea 0xa9 0x34 0x12 0x85 0x10 0x8d 0x00 0x10 0x6b\n' \
| "$LLVM_MC" --disassemble --triple=w65816 2>&1)"
for mnem in "nop" "lda #0x1234" "sta 0x10" "sta 0x1000" "rtl"; do
if ! printf '%s\n' "$disasmOut" | grep -qF "$mnem"; then
warn "disassembler missing: $mnem"
printf '%s\n' "$disasmOut" >&2
die "disassembler round-trip failed"
fi
done
fi
# 6. End-to-end codegen: IR -> asm -> ELF -> disassembly.
# This is the first real codegen test: verifies that our LowerReturn,
# DAG pattern for the i16 constant pseudo, and prologue-emitting
# frame lowering produce runnable 65816 machine code.
OBJDUMP="$BUILD_DIR/bin/llvm-objdump"
if [ -x "$LLC" ] && [ -x "$LLVM_MC" ] && [ -x "$OBJDUMP" ]; then
log "check: end-to-end IR -> asm -> ELF -> disasm for a trivial function"
irFile="$(mktemp --suffix=.ll)"
sFile="$(mktemp --suffix=.s)"
oFile="$(mktemp --suffix=.o)"
trap 'rm -f "$irFile" "$sFile" "$oFile"' EXIT
cat > "$irFile" <<'EOF'
target triple = "w65816-unknown-unknown"
define i16 @answer() { ret i16 42 }
EOF
"$LLC" -march=w65816 "$irFile" -o "$sFile"
"$LLVM_MC" -arch=w65816 -filetype=obj "$sFile" -o "$oFile"
disasm="$("$OBJDUMP" --triple=w65816 -d "$oFile" 2>&1)"
for expect in "rep #0x30" "lda #0x2a" "rtl"; do
if ! printf '%s\n' "$disasm" | grep -qF "$expect"; then
warn "end-to-end pipeline missing: $expect"
printf '%s\n' "$disasm" >&2
die "end-to-end pipeline failed"
fi
done
fi
# 7. Real codegen check: a non-trivial function exercising globals,
# arithmetic, branches, bitwise. This tests our DAG selection
# patterns and AsmPrinter pseudo expansions.
if [ -x "$LLC" ]; then
log "check: llc compiles a multi-pattern function"
irFile="$(mktemp --suffix=.ll)"
sFile="$(mktemp --suffix=.s)"
trap 'rm -f "$irFile" "$sFile"' EXIT
cat > "$irFile" <<'EOF'
target triple = "w65816-unknown-unknown"
@a = global i16 0
@b = global i16 0
define i16 @demo() {
%x = load i16, ptr @a
%y = load i16, ptr @b
%s = add i16 %x, %y
%m = and i16 %s, 4095
%c = icmp ult i16 %m, 100
br i1 %c, label %lo, label %hi
lo:
ret i16 0
hi:
ret i16 %m
}
EOF
"$LLC" -march=w65816 "$irFile" -o "$sFile"
for expect in "rep #0x30" "lda a" "clc" "adc b" "and #0xfff" "cmp #0x64" "bcs" "rtl"; do
if ! grep -qF "$expect" "$sFile"; then
warn "multi-pattern test missing: $expect"
cat "$sFile" >&2
die "multi-pattern test failed"
fi
done
fi
# 8. Function call check: caller passes i16 in A, callee adds, returns.
if [ -x "$LLC" ]; then
log "check: llc compiles a function call (single i16 arg in A)"
irCallFile="$(mktemp --suffix=.ll)"
sCallFile="$(mktemp --suffix=.s)"
trap 'rm -f "$irFile" "$sFile" "$irCallFile" "$sCallFile"' EXIT
cat > "$irCallFile" <<'EOF'
target triple = "w65816-unknown-unknown"
define i16 @inc(i16 %x) {
%r = add i16 %x, 1
ret i16 %r
}
define i16 @caller() {
%r = call i16 @inc(i16 41)
ret i16 %r
}
EOF
"$LLC" -march=w65816 "$irCallFile" -o "$sCallFile"
# Caller passes 41 in A and JSL's inc. Inc is now an `inc a`
# peephole (was clc; adc #1 before the INA_PSEUDO pattern).
for expect in "lda #0x29" "jsl inc" "inc a"; do
if ! grep -qF "$expect" "$sCallFile"; then
warn "call test missing: $expect"
cat "$sCallFile" >&2
die "call test failed"
fi
done
fi
# 9. Multi-arg sum: 3-arg function reads args 1 and 2 via stack-relative
# addressing.
if [ -x "$LLC" ]; then
log "check: llc compiles a 3-arg function (stack-relative reads)"
irMaFile="$(mktemp --suffix=.ll)"
sMaFile="$(mktemp --suffix=.s)"
trap 'rm -f "$irFile" "$sFile" "$irCallFile" "$sCallFile" "$irMaFile" "$sMaFile"' EXIT
cat > "$irMaFile" <<'EOF'
target triple = "w65816-unknown-unknown"
define i16 @sum3(i16 %a, i16 %b, i16 %c) {
%ab = add i16 %a, %b
%r = add i16 %ab, %c
ret i16 %r
}
EOF
"$LLC" -march=w65816 "$irMaFile" -o "$sMaFile"
for expect in "adc 0x4, s" "adc 0x6, s" "rtl"; do
if ! grep -qF "$expect" "$sMaFile"; then
warn "multi-arg test missing: $expect"
cat "$sMaFile" >&2
die "multi-arg test failed"
fi
done
fi
# 10. i8 codegen: pure-i8 function uses SEP #$20 prologue and `inc a`.
if [ -x "$LLC" ]; then
log "check: llc compiles a pure-i8 function (SEP #\$20 prologue)"
irI8File="$(mktemp --suffix=.ll)"
sI8File="$(mktemp --suffix=.s)"
trap 'rm -f "$irFile" "$sFile" "$irCallFile" "$sCallFile" "$irMaFile" "$sMaFile" "$irI8File" "$sI8File"' EXIT
cat > "$irI8File" <<'EOF'
target triple = "w65816-unknown-unknown"
define i8 @i8_inc(i8 %x) {
%r = add i8 %x, 1
ret i8 %r
}
EOF
"$LLC" -march=w65816 "$irI8File" -o "$sI8File"
for expect in "sep #0x20" "inc a" "rtl"; do
if ! grep -qF "$expect" "$sI8File"; then
warn "i8 test missing: $expect"
cat "$sI8File" >&2
die "i8 test failed"
fi
done
fi
# 11. Real C through clang. Uses the clang front-end if it has been
# built; skipped otherwise (clang takes 15-30 minutes to build the
# first time; afterwards rebuilds are fast).
CLANG="$BUILD_DIR/bin/clang"
if [ -x "$CLANG" ] && [ -x "$OBJDUMP" ]; then
log "check: clang -target w65816 -O2 compiles a tiny C function"
cFile="$(mktemp --suffix=.c)"
oFile2="$(mktemp --suffix=.o)"
trap 'rm -f "$irFile" "$sFile" "$irCallFile" "$sCallFile" "$irMaFile" "$sMaFile" "$irI8File" "$sI8File" "$cFile" "$oFile2"' EXIT
cat > "$cFile" <<'EOF'
int answer(void) { return 42; }
EOF
"$CLANG" --target=w65816 -O2 -c "$cFile" -o "$oFile2"
disasm="$("$OBJDUMP" --triple=w65816 -d "$oFile2" 2>&1)"
for expect in "rep #0x30" "lda #0x2a" "rtl"; do
if ! printf '%s\n' "$disasm" | grep -qF "$expect"; then
warn "clang test missing: $expect"
printf '%s\n' "$disasm" >&2
die "clang end-to-end test failed"
fi
done
fi
log "all smoke checks passed"

56
scripts/updateLlvmMos.sh Executable file
View file

@ -0,0 +1,56 @@
#!/usr/bin/env bash
# Refresh the ephemeral llvm-mos checkout to the latest upstream main,
# then re-apply our backend (symlinks + patches).
#
# This is the ONLY script that is allowed to destructively reset the
# clone. installLlvmMos.sh intentionally refuses to do so because a
# reset would destroy any applied patches.
set -euo pipefail
source "$(dirname "$0")/common.sh"
LLVM_SRC="$TOOLS_DIR/llvm-mos"
[ -d "$LLVM_SRC/.git" ] || die "llvm-mos checkout not found; run setup.sh first"
currentBranch="$(git -C "$LLVM_SRC" rev-parse --abbrev-ref HEAD)"
if [ "$currentBranch" != "main" ]; then
die "llvm-mos is on branch '$currentBranch', expected main. Refusing to reset."
fi
# First un-apply our patches so the reset only discards upstream drift,
# not our work. Symlinks are unaffected by git reset since untracked
# directories are left alone (our symlinks live inside tracked dirs but
# are untracked files themselves).
log "reverting any applied patches"
for patch in "$PROJECT_ROOT"/patches/*.patch; do
[ -f "$patch" ] || continue
if git -C "$LLVM_SRC" apply --reverse --check "$patch" >/dev/null 2>&1; then
git -C "$LLVM_SRC" apply --reverse "$patch"
log " reverted: $(basename "$patch")"
fi
done
# Symlinks from applyBackend.sh are untracked files inside tracked
# directories. git reset --hard leaves them in place, but we clean
# them proactively so the fast-forward sees a truly clean tree.
log "removing symlinks under $LLVM_SRC pointing into $PROJECT_ROOT/src"
find "$LLVM_SRC" -type l | while read -r link; do
target="$(readlink -f "$link" || true)"
case "$target" in
"$PROJECT_ROOT"/src/*) rm "$link" ;;
esac
done
if ! git -C "$LLVM_SRC" diff --quiet || ! git -C "$LLVM_SRC" diff --cached --quiet; then
die "llvm-mos checkout still has local changes after cleanup. Inspect with: git -C $LLVM_SRC status"
fi
log "fetching and resetting to origin/main"
git -C "$LLVM_SRC" fetch --depth=1 origin main
git -C "$LLVM_SRC" reset --hard FETCH_HEAD
log "re-applying backend"
"$PROJECT_ROOT/scripts/applyBackend.sh"
log "update complete"

49
scripts/verify.sh Executable file
View file

@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Sanity-check every installed tool. Exits non-zero if anything is missing.
set -euo pipefail
source "$(dirname "$0")/common.sh"
fails=0
check() {
local label="$1"; shift
if "$@" >/dev/null 2>&1; then
printf ' [ OK ] %s\n' "$label"
else
printf ' [FAIL] %s (%s)\n' "$label" "$*"
fails=$((fails + 1))
fi
}
log "verifying toolchain"
# Build tools
check "cmake" cmake --version
check "ninja" ninja --version
check "clang (host)" clang --version
check "git" git --version
# llvm-mos source + SDK
check "llvm-mos source tree" test -d "$TOOLS_DIR/llvm-mos/.git"
check "llvm-mos-sdk extracted" test -x "$TOOLS_DIR/llvm-mos-sdk/bin/mos-common-clang"
# MAME + ROMs
check "mame binary" command -v mame
check "mame Lua console support" bash -c "mame -showusage 2>&1 | grep -q -- '-console'"
check "rom: apple2gs.zip" test -s "$TOOLS_DIR/mame/roms/apple2gs.zip"
check "rom: apple2gsr1.zip" test -s "$TOOLS_DIR/mame/roms/apple2gsr1.zip"
# Calypsi benchmark
check "calypsi cc65816" test -x "$TOOLS_DIR/calypsi/bin/cc65816"
# ORCA/C reference
check "orca-c source" test -d "$TOOLS_DIR/orca-c/.git"
echo
if [ "$fails" -eq 0 ]; then
log "all checks passed"
exit 0
else
die "$fails check(s) failed"
fi

88
setup.sh Executable file
View file

@ -0,0 +1,88 @@
#!/usr/bin/env bash
# Top-level installer for the llvm816 project. Installs everything into
# ./tools/ so the tree is self-contained and deletable.
#
# Usage:
# ./setup.sh # install everything (no llvm-mos source build)
# ./setup.sh --build-llvm # also cmake+ninja build llvm-mos (slow)
# ./setup.sh --skip-deps # skip apt packages
# ./setup.sh --skip-llvm # skip llvm-mos
# ./setup.sh --skip-mame # skip MAME + ROM fetch
# ./setup.sh --skip-calypsi # skip Calypsi
# ./setup.sh --skip-orca # skip ORCA/C reference
# ./setup.sh --verify-only # run verification only
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/scripts/common.sh"
doDeps=1
doLlvm=1
doMame=1
doCalypsi=1
doOrca=1
doVerify=1
buildLlvm=0
verifyOnly=0
for arg in "$@"; do
case "$arg" in
--skip-deps) doDeps=0 ;;
--skip-llvm) doLlvm=0 ;;
--skip-mame) doMame=0 ;;
--skip-calypsi) doCalypsi=0 ;;
--skip-orca) doOrca=0 ;;
--skip-verify) doVerify=0 ;;
--build-llvm) buildLlvm=1 ;;
--verify-only) verifyOnly=1 ;;
-h|--help)
sed -n '2,15p' "$0"
exit 0
;;
*) die "unknown flag: $arg" ;;
esac
done
if [ "$verifyOnly" -eq 1 ]; then
exec "$SCRIPT_DIR/scripts/verify.sh"
fi
log "project root: $PROJECT_ROOT"
log "tools dir: $TOOLS_DIR"
if [ "$doDeps" -eq 1 ]; then
log "=== 1/5 system dependencies ==="
bash "$SCRIPT_DIR/scripts/installDeps.sh"
fi
if [ "$doLlvm" -eq 1 ]; then
log "=== 2/5 llvm-mos ==="
if [ "$buildLlvm" -eq 1 ]; then
bash "$SCRIPT_DIR/scripts/installLlvmMos.sh" --build
else
bash "$SCRIPT_DIR/scripts/installLlvmMos.sh"
fi
fi
if [ "$doMame" -eq 1 ]; then
log "=== 3/5 mame + iigs roms ==="
bash "$SCRIPT_DIR/scripts/installMame.sh"
fi
if [ "$doCalypsi" -eq 1 ]; then
log "=== 4/5 calypsi ==="
bash "$SCRIPT_DIR/scripts/installCalypsi.sh"
fi
if [ "$doOrca" -eq 1 ]; then
log "=== 5/5 orca-c reference ==="
bash "$SCRIPT_DIR/scripts/installOrcaC.sh"
fi
if [ "$doVerify" -eq 1 ]; then
log "=== verify ==="
bash "$SCRIPT_DIR/scripts/verify.sh" || warn "verification reported failures; see above"
fi
log "setup complete"

View file

@ -0,0 +1,32 @@
//===--- W65816.cpp - Implement W65816 target feature support -------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file implements W65816 TargetInfo objects.
//
//===----------------------------------------------------------------------===//
#include "W65816.h"
#include "clang/Basic/MacroBuilder.h"
using namespace clang;
using namespace clang::targets;
const char *const W65816TargetInfo::GCCRegNames[] = {
"a", "x", "y", "sp", "dp", "dbr", "pbr", "pc", "p"
};
ArrayRef<const char *> W65816TargetInfo::getGCCRegNames() const {
return llvm::ArrayRef(GCCRegNames);
}
void W65816TargetInfo::getTargetDefines(const LangOptions &Opts,
MacroBuilder &Builder) const {
Builder.defineMacro("W65816");
Builder.defineMacro("__W65816__");
Builder.defineMacro("__APPLE2GS__");
}

View file

@ -0,0 +1,84 @@
//===--- W65816.h - Declare W65816 target feature support -------*- 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
//
//===----------------------------------------------------------------------===//
//
// This file declares W65816 TargetInfo objects.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_LIB_BASIC_TARGETS_W65816_H
#define LLVM_CLANG_LIB_BASIC_TARGETS_W65816_H
#include "clang/Basic/TargetInfo.h"
#include "clang/Basic/TargetOptions.h"
#include "llvm/Support/Compiler.h"
#include "llvm/TargetParser/Triple.h"
namespace clang {
namespace targets {
class LLVM_LIBRARY_VISIBILITY W65816TargetInfo : public TargetInfo {
static const char *const GCCRegNames[];
public:
W65816TargetInfo(const llvm::Triple &Triple, const TargetOptions &)
: TargetInfo(Triple) {
TLSSupported = false;
IntWidth = 16;
IntAlign = 16;
LongWidth = 32;
LongLongWidth = 64;
LongAlign = LongLongAlign = 16;
FloatWidth = 32;
FloatAlign = 16;
DoubleWidth = LongDoubleWidth = 64;
DoubleAlign = LongDoubleAlign = 16;
PointerWidth = 16;
PointerAlign = 16;
SuitableAlign = 16;
SizeType = UnsignedInt;
IntMaxType = SignedLongLong;
IntPtrType = SignedInt;
PtrDiffType = SignedInt;
SigAtomicType = SignedLong;
resetDataLayout("e-m:e-p:16:8-i16:16-i32:16-n8:16-S16");
}
void getTargetDefines(const LangOptions &Opts,
MacroBuilder &Builder) const override;
llvm::SmallVector<Builtin::InfosShard> getTargetBuiltins() const override {
return {};
}
bool allowsLargerPreferedTypeAlignment() const override { return false; }
bool hasFeature(StringRef Feature) const override {
return Feature == "w65816";
}
ArrayRef<const char *> getGCCRegNames() const override;
ArrayRef<TargetInfo::GCCRegAlias> getGCCRegAliases() const override {
return {};
}
bool validateAsmConstraint(const char *&Name,
TargetInfo::ConstraintInfo &info) const override {
return false;
}
std::string_view getClobbers() const override { return ""; }
BuiltinVaListKind getBuiltinVaListKind() const override {
return TargetInfo::CharPtrBuiltinVaList;
}
};
} // namespace targets
} // namespace clang
#endif // LLVM_CLANG_LIB_BASIC_TARGETS_W65816_H

View file

@ -0,0 +1,15 @@
add_llvm_component_library(LLVMW65816AsmParser
W65816AsmParser.cpp
LINK_COMPONENTS
CodeGenTypes
MC
MCParser
Support
TargetParser
W65816Desc
W65816Info
ADD_TO_COMPONENT
W65816
)

View file

@ -0,0 +1,511 @@
//===- W65816AsmParser.cpp - Parse W65816 assembly to MCInst instructions -===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Scaffold AsmParser for the WDC 65816 / Apple IIgs target. Modeled after
// MSP430AsmParser.cpp but stripped of register-operand and addressing-mode
// handling that does not apply to the 65816:
//
// * The 65816 MC operand set is value-typed only (immediate, address,
// PC-relative). Instructions implicitly operate on A, X, Y, S, DBR, PBR,
// and D, so the parser never builds register operands. The ", x" / ", y"
// suffixes on indexed addressing modes are tokens, not registers.
//
// * There is no ".b" / ".w" mnemonic suffix; the 65816 encodes operand
// width in the opcode byte itself, and DP/Abs/Long are chosen by value
// fit, not by mnemonic spelling.
//
// KNOWN LIMITATION (AsmString vs parser):
//
// W65816InstrFormats.td bakes the literal text ", x" and ", y" into the
// AsmString for indexed forms, e.g. the AsmString for LDA_AbsX is
// "lda\t$addr, x". TableGen's AsmMatcher expects distinct operand classes
// for each addressing variant rather than literal sub-operand text, so the
// generated matcher may fail to route "lda $1000, x" to LDA_AbsX even
// though the parser produces (token "lda", addr $1000, token "x").
//
// The correct long-term fix is to split the operand classes by indexing
// (addrAbsIdxX, addrAbsIdxY, addrDPIdxX, addrDPIdxY, ...) and either give
// them custom parser methods that consume ", x" / ", y" or let the matcher
// own those tokens via ParserMethod/SuperClasses. That is out of scope for
// this scaffolding task; for now we accept the matcher miss on indexed
// forms while the non-indexed and immediate forms match correctly.
//
//===----------------------------------------------------------------------===//
#include "MCTargetDesc/W65816MCTargetDesc.h"
#include "TargetInfo/W65816TargetInfo.h"
#include "llvm/ADT/APInt.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/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/Debug.h"
#include "llvm/Support/SMLoc.h"
#define DEBUG_TYPE "w65816-asm-parser"
using namespace llvm;
namespace {
/// A parsed W65816 assembly operand.
///
/// The 65816 MC layer sees only three operand flavors: tokens (mnemonic and
/// the literal ", x" / ", y" suffixes baked into AsmStrings), immediates
/// (prefixed with '#' in source), and values used as addresses or branch
/// targets. There are no register operands on this target; all register
/// use is implicit in the opcode.
class W65816Operand : public MCParsedAsmOperand {
enum KindTy {
k_Tok,
k_Reg,
k_Imm,
k_Addr
} Kind;
struct TokTy {
const char *Data;
unsigned Length;
};
union {
TokTy Tok;
unsigned RegNum;
const MCExpr *Imm;
const MCExpr *Addr;
};
SMLoc Start, End;
public:
W65816Operand(StringRef T, SMLoc S)
: Kind(k_Tok), Tok({T.data(), (unsigned)T.size()}), Start(S),
End(SMLoc::getFromPointer(T.data() + T.size())) {}
W65816Operand(unsigned R, SMLoc S, SMLoc E)
: Kind(k_Reg), RegNum(R), Start(S), End(E) {}
W65816Operand(KindTy K, const MCExpr *E, SMLoc S, SMLoc E2)
: Kind(K), Start(S), End(E2) {
if (K == k_Imm)
Imm = E;
else
Addr = E;
}
// --- Factory helpers ----------------------------------------------------
static std::unique_ptr<W65816Operand> createToken(StringRef Str, SMLoc S) {
return std::make_unique<W65816Operand>(Str, S);
}
static std::unique_ptr<W65816Operand> createReg(unsigned R, SMLoc S,
SMLoc E) {
return std::make_unique<W65816Operand>(R, S, E);
}
static std::unique_ptr<W65816Operand> createImm(const MCExpr *Val, SMLoc S,
SMLoc E) {
return std::make_unique<W65816Operand>(k_Imm, Val, S, E);
}
static std::unique_ptr<W65816Operand> createAddr(const MCExpr *Val, SMLoc S,
SMLoc E) {
return std::make_unique<W65816Operand>(k_Addr, Val, S, E);
}
// --- MCParsedAsmOperand hooks ------------------------------------------
bool isReg() const override { return Kind == k_Reg; }
bool isImm() const override { return Kind == k_Imm; }
bool isToken() const override { return Kind == k_Tok; }
bool isMem() const override { return false; }
MCRegister getReg() const override {
assert(Kind == k_Reg && "not a register");
return MCRegister(RegNum);
}
StringRef getToken() const {
assert(Kind == k_Tok && "not a token");
return StringRef(Tok.Data, Tok.Length);
}
SMLoc getStartLoc() const override { return Start; }
SMLoc getEndLoc() const override { return End; }
// --- AsmMatcher operand-class predicates -------------------------------
//
// The matcher picks the narrowest instruction variant whose operand
// predicates all match. Constants narrow by value fit; symbolic
// expressions (labels, globals) default to the canonical 16-bit form
// for absolute addresses and PC-relative branches, falling through
// narrower variants only when the value is a constant that fits.
// 24-bit `long` and `pcrel16` are opt-in (only constants in the wider
// range, or future explicit addressing-mode syntax).
static bool constFitsUnsigned(const MCExpr *Expr, unsigned width) {
if (const auto *CE = dyn_cast<MCConstantExpr>(Expr)) {
int64_t V = CE->getValue();
uint64_t mask = width >= 64 ? ~uint64_t(0) : ((uint64_t(1) << width) - 1);
if (V >= 0)
return uint64_t(V) <= mask;
int64_t minSigned = width >= 64 ? INT64_MIN
: -(int64_t(1) << (width - 1));
return V >= minSigned;
}
return false;
}
static bool isConstant(const MCExpr *Expr) {
return Expr && isa<MCConstantExpr>(Expr);
}
bool isImm8() const { return isImm() && constFitsUnsigned(Imm, 8); }
bool isImm16() const {
return isImm() && (!isConstant(Imm) || constFitsUnsigned(Imm, 16));
}
bool isAddrDP() const {
return Kind == k_Addr && isConstant(Addr) && constFitsUnsigned(Addr, 8);
}
bool isAddrAbs() const {
return Kind == k_Addr &&
(!isConstant(Addr) || constFitsUnsigned(Addr, 16));
}
bool isAddrLong() const {
if (Kind != k_Addr)
return false;
if (!isConstant(Addr))
// Symbolic — accept; if a same-mnemonic narrower form exists (Abs)
// the matcher will prefer it.
return true;
// Constant — only match if it doesn't already fit in 16 bits, so
// pure constants in the 0..0xFFFF range route to AddrAbs.
return constFitsUnsigned(Addr, 24) && !constFitsUnsigned(Addr, 16);
}
bool isPCRel8() const {
return Kind == k_Addr && isConstant(Addr) && constFitsUnsigned(Addr, 8);
}
bool isPCRel16() const {
return Kind == k_Addr &&
(!isConstant(Addr) || constFitsUnsigned(Addr, 16));
}
// --- addXxxOperands used by the generated matcher ----------------------
void addExprOperand(MCInst &Inst, const MCExpr *Expr) const {
if (!Expr)
Inst.addOperand(MCOperand::createImm(0));
else if (const MCConstantExpr *CE = dyn_cast<MCConstantExpr>(Expr))
Inst.addOperand(MCOperand::createImm(CE->getValue()));
else
Inst.addOperand(MCOperand::createExpr(Expr));
}
void addImmOperands(MCInst &Inst, unsigned N) const {
assert(N == 1 && "invalid number of operands");
assert(Kind == k_Imm && "not an immediate");
addExprOperand(Inst, Imm);
}
void addRegOperands(MCInst &Inst, unsigned N) const {
assert(N == 1 && "invalid number of operands");
assert(Kind == k_Reg && "not a register");
Inst.addOperand(MCOperand::createReg(MCRegister(RegNum)));
}
// Address operand classes (DP/Abs/Long) and PC-relative branch targets
// all render through the same path: emit either a constant or an
// MCExpr. The width-specific encoding is the encoder's job, not ours.
void addAddrDPOperands(MCInst &Inst, unsigned N) const {
assert(N == 1 && "invalid number of operands");
assert(Kind == k_Addr && "not an address");
addExprOperand(Inst, Addr);
}
void addAddrAbsOperands(MCInst &Inst, unsigned N) const {
addAddrDPOperands(Inst, N);
}
void addAddrLongOperands(MCInst &Inst, unsigned N) const {
addAddrDPOperands(Inst, N);
}
void addPCRel8Operands(MCInst &Inst, unsigned N) const {
addAddrDPOperands(Inst, N);
}
void addPCRel16Operands(MCInst &Inst, unsigned N) const {
addAddrDPOperands(Inst, N);
}
void print(raw_ostream &O, const MCAsmInfo &MAI) const override {
switch (Kind) {
case k_Tok:
O << "Token " << StringRef(Tok.Data, Tok.Length);
break;
case k_Imm:
O << "Immediate ";
MAI.printExpr(O, *Imm);
break;
case k_Addr:
O << "Address ";
MAI.printExpr(O, *Addr);
break;
}
}
};
/// Parses W65816 assembly from a stream.
class W65816AsmParser : public MCTargetAsmParser {
MCAsmParser &Parser;
bool matchAndEmitInstruction(SMLoc IDLoc, unsigned &Opcode,
OperandVector &Operands, MCStreamer &Out,
uint64_t &ErrorInfo,
bool MatchingInlineAsm) override;
// The 65816 has no register operands. Both register hooks always
// report no-match; the lexer-driven operand parser never asks for one
// either. We still have to implement them because MCTargetAsmParser
// declares them pure virtual / abstract.
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;
ParseStatus parseDirective(AsmToken DirectiveID) override;
bool parseLiteralValues(unsigned Size, SMLoc L);
bool parseOperand(OperandVector &Operands);
MCAsmParser &getParser() const { return Parser; }
AsmLexer &getLexer() const { return Parser.getLexer(); }
/// @name Auto-generated Matcher Functions
/// {
#define GET_ASSEMBLER_HEADER
#include "W65816GenAsmMatcher.inc"
/// }
public:
W65816AsmParser(const MCSubtargetInfo &STI, MCAsmParser &Parser,
const MCInstrInfo &MII, const MCTargetOptions &Options)
: MCTargetAsmParser(Options, STI, MII), Parser(Parser) {
MCAsmParserExtension::Initialize(Parser);
setAvailableFeatures(ComputeAvailableFeatures(STI.getFeatureBits()));
}
};
} // end anonymous namespace
bool W65816AsmParser::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);
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 = ((W65816Operand &)*Operands[ErrorInfo]).getStartLoc();
if (ErrorLoc == SMLoc())
ErrorLoc = Loc;
}
return Error(ErrorLoc, "invalid operand for instruction");
}
default:
return true;
}
}
bool W65816AsmParser::parseRegister(MCRegister &Reg, SMLoc &StartLoc,
SMLoc &EndLoc) {
// No register operands on the 65816. A true return signals failure.
return true;
}
ParseStatus W65816AsmParser::tryParseRegister(MCRegister &Reg, SMLoc &StartLoc,
SMLoc &EndLoc) {
return ParseStatus::NoMatch;
}
bool W65816AsmParser::parseInstruction(ParseInstructionInfo &Info,
StringRef Name, SMLoc NameLoc,
OperandVector &Operands) {
// Some 65816 implied-accumulator forms appear in our AsmStrings as a
// two-word spelling ("inc a", "asl a", etc.). Fold the " a" back into
// the mnemonic token here so the matcher sees exactly what the printer
// emitted. The bare "inc"/"asl"/... spellings remain routed to their
// memory variants.
Operands.push_back(W65816Operand::createToken(Name, NameLoc));
// If there are no operands, we're done (covers nop, rts, rtl, clc, etc.
// and the implied-accumulator forms handled above).
if (getLexer().is(AsmToken::EndOfStatement)) {
Parser.Lex();
return false;
}
// Implied-accumulator form: "<mnem> a" (e.g. "inc a", "asl a"). The
// AsmString for these uses the A register as an operand, so the matcher
// expects a register operand here, not a literal token.
if (getLexer().is(AsmToken::Identifier) &&
getLexer().getTok().getIdentifier().equals_insensitive("a")) {
SMLoc S = getLexer().getTok().getLoc();
SMLoc E = SMLoc::getFromPointer(S.getPointer() + 1);
Operands.push_back(W65816Operand::createReg(W65816::A, S, E));
Parser.Lex(); // eat "a"
if (getLexer().isNot(AsmToken::EndOfStatement)) {
SMLoc ErrLoc = getLexer().getLoc();
Parser.eatToEndOfStatement();
return Error(ErrLoc, "unexpected token");
}
Parser.Lex();
return false;
}
// Parse the first real operand (immediate or address expression).
if (parseOperand(Operands))
return true;
// Handle trailing ", x" / ", y" / ", <expr>" (block-move second operand).
while (getLexer().is(AsmToken::Comma)) {
Parser.Lex(); // eat comma
// Indexed suffix: ", x" / ", y" / ", s". X and Y are real
// register operands at the matcher level (the matcher generates
// MCK_X / MCK_Y from the named registers). S has no register def
// — the matcher treats it as a literal token (MCK_s) — so we push
// it as a token instead of as a register.
if (getLexer().is(AsmToken::Identifier)) {
StringRef Ident = getLexer().getTok().getIdentifier();
if (Ident.equals_insensitive("x") || Ident.equals_insensitive("y")) {
SMLoc S = getLexer().getTok().getLoc();
SMLoc E = SMLoc::getFromPointer(S.getPointer() + 1);
unsigned Reg = Ident.equals_insensitive("x") ? W65816::X
: W65816::Y;
Operands.push_back(W65816Operand::createReg(Reg, S, E));
Parser.Lex();
continue;
}
if (Ident.equals_insensitive("s")) {
SMLoc S = getLexer().getTok().getLoc();
Operands.push_back(W65816Operand::createToken("s", S));
Parser.Lex();
continue;
}
}
// Otherwise it must be another expression operand (e.g. block-move
// "mvn dst, src" where both operands are immediate bank bytes).
if (parseOperand(Operands))
return true;
}
if (getLexer().isNot(AsmToken::EndOfStatement)) {
SMLoc ErrLoc = getLexer().getLoc();
Parser.eatToEndOfStatement();
return Error(ErrLoc, "unexpected token");
}
Parser.Lex(); // consume EndOfStatement
return false;
}
bool W65816AsmParser::parseOperand(OperandVector &Operands) {
SMLoc S = getLexer().getLoc();
// Immediate form: '#' <expression>. The generated AsmMatcher tokenises
// '#' in the AsmString (e.g. "lda\t#$imm") as a separate MCK__HASH_
// token, so we push it as a literal and then the immediate operand.
if (getLexer().is(AsmToken::Hash)) {
SMLoc HashLoc = S;
Parser.Lex(); // eat '#'
Operands.push_back(W65816Operand::createToken("#", HashLoc));
SMLoc ImmStart = getLexer().getLoc();
const MCExpr *Val;
if (Parser.parseExpression(Val))
return Error(HashLoc, "expected immediate expression after '#'");
SMLoc E = getLexer().getLoc();
Operands.push_back(W65816Operand::createImm(Val, ImmStart, E));
return false;
}
// Anything else is an address / branch-target / block-move byte. We
// rely on the standard LLVM asm expression parser, which accepts
// identifiers (labels), decimal integers, hex ($... via MCAsmInfo
// configuration or 0x...), binary (%... when the lexer allows it),
// and arithmetic.
const MCExpr *Val;
if (Parser.parseExpression(Val))
return Error(S, "expected expression");
SMLoc E = getLexer().getLoc();
Operands.push_back(W65816Operand::createAddr(Val, S, E));
return false;
}
ParseStatus W65816AsmParser::parseDirective(AsmToken DirectiveID) {
StringRef IDVal = DirectiveID.getIdentifier().lower();
unsigned Size = 0;
if (IDVal == ".long")
Size = 4;
else if (IDVal == ".word" || IDVal == ".short")
Size = 2;
else if (IDVal == ".byte")
Size = 1;
else
return ParseStatus::NoMatch;
if (parseLiteralValues(Size, DirectiveID.getLoc()))
return ParseStatus::Failure;
return ParseStatus::Success;
}
bool W65816AsmParser::parseLiteralValues(unsigned Size, SMLoc L) {
auto parseOne = [&]() -> bool {
const MCExpr *Value;
if (getParser().parseExpression(Value))
return true;
getParser().getStreamer().emitValue(Value, Size, L);
return false;
};
return (parseMany(parseOne));
}
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeW65816AsmParser() {
RegisterMCAsmParser<W65816AsmParser> X(getTheW65816Target());
}
#define GET_REGISTER_MATCHER
#define GET_MATCHER_IMPLEMENTATION
#include "W65816GenAsmMatcher.inc"

View file

@ -0,0 +1,51 @@
add_llvm_component_group(W65816)
set(LLVM_TARGET_DEFINITIONS W65816.td)
tablegen(LLVM W65816GenAsmMatcher.inc -gen-asm-matcher)
tablegen(LLVM W65816GenAsmWriter.inc -gen-asm-writer)
tablegen(LLVM W65816GenCallingConv.inc -gen-callingconv)
tablegen(LLVM W65816GenDAGISel.inc -gen-dag-isel)
tablegen(LLVM W65816GenDisassemblerTables.inc -gen-disassembler)
tablegen(LLVM W65816GenInstrInfo.inc -gen-instr-info)
tablegen(LLVM W65816GenMCCodeEmitter.inc -gen-emitter)
tablegen(LLVM W65816GenRegisterInfo.inc -gen-register-info)
tablegen(LLVM W65816GenSDNodeInfo.inc -gen-sd-node-info)
tablegen(LLVM W65816GenSubtargetInfo.inc -gen-subtarget)
add_public_tablegen_target(W65816CommonTableGen)
add_llvm_target(W65816CodeGen
W65816ISelDAGToDAG.cpp
W65816ISelLowering.cpp
W65816InstrInfo.cpp
W65816FrameLowering.cpp
W65816MachineFunctionInfo.cpp
W65816RegisterInfo.cpp
W65816SelectionDAGInfo.cpp
W65816Subtarget.cpp
W65816TargetMachine.cpp
W65816AsmPrinter.cpp
W65816MCInstLower.cpp
LINK_COMPONENTS
AsmPrinter
CodeGen
CodeGenTypes
Core
MC
SelectionDAG
Support
Target
TargetParser
W65816Desc
W65816Info
ADD_TO_COMPONENT
W65816
)
add_subdirectory(AsmParser)
add_subdirectory(Disassembler)
add_subdirectory(MCTargetDesc)
add_subdirectory(TargetInfo)

View file

@ -0,0 +1,13 @@
add_llvm_component_library(LLVMW65816Disassembler
W65816Disassembler.cpp
LINK_COMPONENTS
MCDisassembler
Support
TargetParser
W65816Desc
W65816Info
ADD_TO_COMPONENT
W65816
)

View file

@ -0,0 +1,190 @@
//===-- W65816Disassembler.cpp - Disassembler for W65816 -----------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file implements the W65816Disassembler class, which converts a byte
// stream into a stream of MCInsts. W65816 instructions are 1-4 bytes long:
// a single-byte opcode followed by 0-3 bytes of little-endian operand data.
//
// For every opcode that has both an _Imm8 and _Imm16 variant (LDA, LDX, LDY,
// ADC, SBC, CMP, AND, ORA, EOR, BIT, CPX, CPY), the bytes on the wire are
// identical for both forms up to the opcode; the real operand width depends
// on the M/X processor-mode bits. The TableGen descriptions put the _Imm8
// forms into the "W65816MHigh" / "W65816XHigh" decoder namespaces, so the
// default "W65816" table contains only the 3-byte _Imm16 forms. This
// scaffold disassembler reads exclusively from the default table, so those
// mnemonics always decode as 3-byte (16-bit) immediates. A future mode-
// tracking pass will consult the MHigh / XHigh tables when MReg / XReg are
// set by preceding REP/SEP instructions.
//
//===----------------------------------------------------------------------===//
#include "MCTargetDesc/W65816MCTargetDesc.h"
#include "TargetInfo/W65816TargetInfo.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"
using namespace llvm;
using namespace llvm::MCD;
#define DEBUG_TYPE "w65816-disassembler"
using DecodeStatus = MCDisassembler::DecodeStatus;
namespace {
class W65816Disassembler : public MCDisassembler {
public:
W65816Disassembler(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
//===----------------------------------------------------------------------===//
// Operand decoder callbacks. The encodings are little-endian immediates
// immediately following the opcode; each callback just creates an MCOperand
// whose integer value is the raw field bits. The printer does the pretty-
// printing (hex, '$' prefix, etc.).
//===----------------------------------------------------------------------===//
static DecodeStatus decodeImm8(MCInst &Inst, uint64_t Imm, uint64_t Address,
const MCDisassembler *Decoder) {
Inst.addOperand(MCOperand::createImm(Imm & 0xFF));
return MCDisassembler::Success;
}
static DecodeStatus decodeImm16(MCInst &Inst, uint64_t Imm, uint64_t Address,
const MCDisassembler *Decoder) {
Inst.addOperand(MCOperand::createImm(Imm & 0xFFFF));
return MCDisassembler::Success;
}
static DecodeStatus decodeAddr8(MCInst &Inst, uint64_t Imm, uint64_t Address,
const MCDisassembler *Decoder) {
Inst.addOperand(MCOperand::createImm(Imm & 0xFF));
return MCDisassembler::Success;
}
static DecodeStatus decodeAddr16(MCInst &Inst, uint64_t Imm, uint64_t Address,
const MCDisassembler *Decoder) {
Inst.addOperand(MCOperand::createImm(Imm & 0xFFFF));
return MCDisassembler::Success;
}
static DecodeStatus decodeAddr24(MCInst &Inst, uint64_t Imm, uint64_t Address,
const MCDisassembler *Decoder) {
Inst.addOperand(MCOperand::createImm(Imm & 0xFFFFFF));
return MCDisassembler::Success;
}
static DecodeStatus decodePCRel8(MCInst &Inst, uint64_t Imm, uint64_t Address,
const MCDisassembler *Decoder) {
// Sign-extend the 8-bit displacement so the printer shows the correct
// signed offset. Address-symbolization happens later if requested.
int8_t Disp = static_cast<int8_t>(Imm & 0xFF);
Inst.addOperand(MCOperand::createImm(Disp));
return MCDisassembler::Success;
}
static DecodeStatus decodePCRel16(MCInst &Inst, uint64_t Imm, uint64_t Address,
const MCDisassembler *Decoder) {
int16_t Disp = static_cast<int16_t>(Imm & 0xFFFF);
Inst.addOperand(MCOperand::createImm(Disp));
return MCDisassembler::Success;
}
// The generated decoder tables reference the decode callbacks above by name,
// so this include must come AFTER their declarations.
#include "W65816GenDisassemblerTables.inc"
//===----------------------------------------------------------------------===//
// Size-dispatch: try each 1/2/3/4-byte table in turn from shortest to longest,
// accepting the first successful decode. The tablegen tables are keyed on
// the full little-endian byte pattern, so a single-byte implied-operand
// opcode like NOP (0xEA) matches in DecoderTableW658168 and shorter-circuits
// any accidental collision with a longer encoding that happens to begin with
// the same byte. Every W65816 opcode byte uniquely determines the
// instruction (modulo the M/X-mode ambiguity documented above), so the
// first successful decode is always the correct one.
//===----------------------------------------------------------------------===//
DecodeStatus W65816Disassembler::getInstruction(MCInst &MI, uint64_t &Size,
ArrayRef<uint8_t> Bytes,
uint64_t Address,
raw_ostream &CStream) const {
struct TableEntry {
const uint8_t *Table;
unsigned Bytes;
};
static const TableEntry Tables[] = {
{ DecoderTableW658168, 1 },
{ DecoderTableW6581616, 2 },
{ DecoderTableW6581624, 3 },
{ DecoderTableW6581632, 4 },
};
for (const TableEntry &T : Tables) {
if (Bytes.size() < T.Bytes) {
continue;
}
uint64_t Insn = 0;
for (unsigned I = 0; I < T.Bytes; ++I) {
Insn |= static_cast<uint64_t>(Bytes[I]) << (8 * I);
}
MCInst Candidate;
DecodeStatus Result = decodeInstruction(T.Table, Candidate, Insn, Address,
this, STI);
if (Result != MCDisassembler::Fail) {
MI = Candidate;
Size = T.Bytes;
return Result;
}
}
Size = 1;
return MCDisassembler::Fail;
}
//===----------------------------------------------------------------------===//
// Registration.
//===----------------------------------------------------------------------===//
static MCDisassembler *createW65816Disassembler(const Target &T,
const MCSubtargetInfo &STI,
MCContext &Ctx) {
return new W65816Disassembler(STI, Ctx);
}
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeW65816Disassembler() {
TargetRegistry::RegisterMCDisassembler(getTheW65816Target(),
createW65816Disassembler);
}

View file

@ -0,0 +1,17 @@
add_llvm_component_library(LLVMW65816Desc
W65816AsmBackend.cpp
W65816ELFObjectWriter.cpp
W65816ELFStreamer.cpp
W65816InstPrinter.cpp
W65816MCAsmInfo.cpp
W65816MCCodeEmitter.cpp
W65816MCTargetDesc.cpp
LINK_COMPONENTS
MC
Support
W65816Info
ADD_TO_COMPONENT
W65816
)

View file

@ -0,0 +1,116 @@
//===-- W65816AsmBackend.cpp - W65816 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
//
//===----------------------------------------------------------------------===//
//
// Skeleton assembler backend. Fixup resolution, relaxation and nop
// generation are left unimplemented; they will be filled in once the
// instruction encodings are defined.
//
//===----------------------------------------------------------------------===//
#include "MCTargetDesc/W65816FixupKinds.h"
#include "MCTargetDesc/W65816MCTargetDesc.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/MC/MCAsmBackend.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCELFObjectWriter.h"
#include "llvm/MC/MCObjectWriter.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/MCTargetOptions.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
namespace {
class W65816AsmBackend : public MCAsmBackend {
uint8_t OSABI;
public:
W65816AsmBackend(const MCSubtargetInfo &STI, uint8_t OSABI)
: MCAsmBackend(llvm::endianness::little), OSABI(OSABI) {}
~W65816AsmBackend() override = default;
void applyFixup(const MCFragment &F, const MCFixup &Fixup,
const MCValue &Target, uint8_t *Data, uint64_t Value,
bool IsResolved) override {
maybeAddReloc(F, Fixup, Target, Value, IsResolved);
if (!IsResolved) {
// Leave the bytes zero; the linker will fill them via the recorded
// relocation.
return;
}
unsigned Offset = Fixup.getOffset();
unsigned Width;
switch (Fixup.getKind()) {
case W65816::fixup_8:
case W65816::fixup_8_pcrel:
Width = 1;
break;
case W65816::fixup_16:
case W65816::fixup_16_pcrel:
Width = 2;
break;
case W65816::fixup_24:
Width = 3;
break;
default:
// Generic FK_Data_* kinds are already handled by the generic code
// in the object writer; nothing to patch here.
return;
}
// Little-endian patch.
for (unsigned i = 0; i < Width; ++i) {
Data[Offset + i] = static_cast<uint8_t>((Value >> (8 * i)) & 0xff);
}
}
std::unique_ptr<MCObjectTargetWriter>
createObjectTargetWriter() const override {
return createW65816ELFObjectWriter(OSABI);
}
MCFixupKindInfo getFixupKindInfo(MCFixupKind Kind) const override {
// clang-format off
const static MCFixupKindInfo Infos[W65816::NumTargetFixupKinds] = {
// name offset bits flags
{"fixup_8", 0, 8, 0},
{"fixup_16", 0, 16, 0},
{"fixup_24", 0, 24, 0},
{"fixup_8_pcrel", 0, 8, 0},
{"fixup_16_pcrel", 0, 16, 0},
};
// clang-format on
static_assert(std::size(Infos) == W65816::NumTargetFixupKinds,
"Not all fixup kinds added to Infos array");
if (Kind < FirstTargetFixupKind)
return MCAsmBackend::getFixupKindInfo(Kind);
return Infos[Kind - FirstTargetFixupKind];
}
bool writeNopData(raw_ostream &OS, uint64_t Count,
const MCSubtargetInfo *STI) const override {
// The 65816 NOP is a single 0xEA byte.
for (uint64_t I = 0; I < Count; ++I)
OS << char(0xEA);
return true;
}
};
} // end anonymous namespace
MCAsmBackend *llvm::createW65816MCAsmBackend(const Target &T,
const MCSubtargetInfo &STI,
const MCRegisterInfo &MRI,
const MCTargetOptions &Options) {
return new W65816AsmBackend(STI, ELF::ELFOSABI_STANDALONE);
}

View file

@ -0,0 +1,62 @@
//===-- W65816ELFObjectWriter.cpp - W65816 ELF 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
//
//===----------------------------------------------------------------------===//
//
// Skeleton ELF object writer. Relocation types will be assigned once the
// W65816 ELF ABI is finalised.
//
//===----------------------------------------------------------------------===//
#include "MCTargetDesc/W65816FixupKinds.h"
#include "MCTargetDesc/W65816MCTargetDesc.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/MC/MCELFObjectWriter.h"
#include "llvm/MC/MCFixup.h"
#include "llvm/MC/MCObjectWriter.h"
#include "llvm/MC/MCValue.h"
#include "llvm/Support/ErrorHandling.h"
using namespace llvm;
namespace {
class W65816ELFObjectWriter : public MCELFObjectTargetWriter {
public:
// EM_NONE is a placeholder -- the real EM_ value for 65816 will be supplied
// once the llvm-mos ELF specification is extended for the W65816 target.
explicit W65816ELFObjectWriter(uint8_t OSABI)
: MCELFObjectTargetWriter(/*Is64Bit=*/false, OSABI, ELF::EM_NONE,
/*HasRelocationAddend=*/true) {}
~W65816ELFObjectWriter() override = default;
protected:
unsigned getRelocType(const MCFixup &Fixup, const MCValue &,
bool IsPCRel) const override {
// Placeholder relocation numbers. We are using EM_NONE so the full
// (EM_, R_*) pair is unique; once a real EM_ value is assigned for the
// W65816 target (see SESSION_STATE.md open question on ELF EM_), swap
// these for the canonical R_W65816_* names.
switch (Fixup.getKind()) {
case W65816::fixup_8: return 1; // R_W65816_IMM8
case W65816::fixup_16: return 2; // R_W65816_IMM16
case W65816::fixup_24: return 3; // R_W65816_IMM24
case W65816::fixup_8_pcrel: return 4; // R_W65816_PCREL8
case W65816::fixup_16_pcrel: return 5; // R_W65816_PCREL16
default:
llvm_unreachable("W65816: unknown fixup kind");
}
}
};
} // end anonymous namespace
std::unique_ptr<MCObjectTargetWriter>
llvm::createW65816ELFObjectWriter(uint8_t OSABI) {
return std::make_unique<W65816ELFObjectWriter>(OSABI);
}

View file

@ -0,0 +1,40 @@
//===-- W65816ELFStreamer.cpp - W65816 ELF Target Streamer Methods --------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file provides a stub W65816 ELF target streamer. The MSP430 reference
// emits a vendor build-attributes section here; the W65816 equivalent (code
// model, DP reservation, etc.) will be added later.
//
//===----------------------------------------------------------------------===//
#include "W65816MCTargetDesc.h"
#include "llvm/MC/MCELFStreamer.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/MC/MCSubtargetInfo.h"
using namespace llvm;
namespace llvm {
class W65816TargetELFStreamer : public MCTargetStreamer {
public:
W65816TargetELFStreamer(MCStreamer &S, const MCSubtargetInfo &STI)
: MCTargetStreamer(S) {
// No vendor attributes are emitted yet; this is intentionally empty.
}
};
MCTargetStreamer *
createW65816ObjectTargetStreamer(MCStreamer &S, const MCSubtargetInfo &STI) {
const Triple &TT = STI.getTargetTriple();
if (TT.isOSBinFormatELF())
return new W65816TargetELFStreamer(S, STI);
return nullptr;
}
} // namespace llvm

View file

@ -0,0 +1,41 @@
//===-- W65816FixupKinds.h - W65816 Specific Fixup Entries ------*- 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_W65816_MCTARGETDESC_W65816FIXUPKINDS_H
#define LLVM_LIB_TARGET_W65816_MCTARGETDESC_W65816FIXUPKINDS_H
#include "llvm/MC/MCFixup.h"
#undef W65816
namespace llvm {
namespace W65816 {
// This table must stay in sync with the MCFixupKindInfo table in
// W65816AsmBackend.cpp. Only a bare minimum is defined for the skeleton.
enum Fixups {
// 8-bit absolute fixup (immediate byte).
fixup_8 = FirstTargetFixupKind,
// 16-bit absolute fixup (low two bytes of a 24-bit address).
fixup_16,
// 24-bit absolute fixup (long address).
fixup_24,
// 8-bit PC-relative fixup (short branch).
fixup_8_pcrel,
// 16-bit PC-relative fixup (long branch / BRL).
fixup_16_pcrel,
// Marker
LastTargetFixupKind,
NumTargetFixupKinds = LastTargetFixupKind - FirstTargetFixupKind
};
} // end namespace W65816
} // end namespace llvm
#endif

View file

@ -0,0 +1,118 @@
//===-- W65816InstPrinter.cpp - Convert W65816 MCInst to assembly syntax --===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This class prints a W65816 MCInst to a .s file. The skeleton does not yet
// implement any real instructions so the printer mostly defers to the table-
// generated routines and reports unimplemented cases with llvm_unreachable.
//
//===----------------------------------------------------------------------===//
#include "W65816InstPrinter.h"
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/MCExpr.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCRegisterInfo.h"
#include "llvm/MC/MCSymbol.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
#define DEBUG_TYPE "asm-printer"
// Include the auto-generated portion of the assembly writer.
#define PRINT_ALIAS_INSTR
#include "W65816GenAsmWriter.inc"
void W65816InstPrinter::printRegName(raw_ostream &O, MCRegister Reg) {
O << getRegisterName(Reg);
}
void W65816InstPrinter::printInst(const MCInst *MI, uint64_t Address,
StringRef Annot, const MCSubtargetInfo &STI,
raw_ostream &O) {
if (!printAliasInstr(MI, Address, O))
printInstruction(MI, Address, O);
printAnnotation(O, Annot);
}
void W65816InstPrinter::printOperand(const MCInst *MI, unsigned OpNo,
raw_ostream &O) {
const MCOperand &Op = MI->getOperand(OpNo);
if (Op.isReg()) {
O << getRegisterName(Op.getReg());
return;
}
if (Op.isImm()) {
// The '#' prefix lives in the AsmString (e.g. "lda\t#$imm") so we
// only emit the numeric value here. 65816 immediates are at most
// 16 bits; we print the low 16 to avoid sign-extended int64 noise
// like `0xffffffffffffffff` for i16 -1.
O << "0x";
O.write_hex(Op.getImm() & 0xffff);
return;
}
assert(Op.isExpr() && "unknown operand kind in printOperand");
MAI.printExpr(O, *Op.getExpr());
}
// Address operand helpers. We use LLVM's standard `0x` hex prefix rather
// than the 65816-native `$` prefix so that our own AsmParser (which uses
// the stock LLVM expression lexer) can round-trip our output. Teaching
// the lexer about `$` is a future cleanup; until then the printed form
// is compatible-with-itself rather than idiomatic WDC syntax.
static void printAddrImm(const MCOperand &Op, unsigned width, raw_ostream &O,
const MCAsmInfo &MAI) {
if (Op.isImm()) {
uint64_t mask = width >= 64 ? ~uint64_t(0) : ((uint64_t(1) << width) - 1);
O << "0x";
O.write_hex(Op.getImm() & mask);
return;
}
assert(Op.isExpr() && "unknown address operand kind");
MAI.printExpr(O, *Op.getExpr());
}
void W65816InstPrinter::printAddrDP(const MCInst *MI, unsigned OpNo,
raw_ostream &O) {
printAddrImm(MI->getOperand(OpNo), 8, O, MAI);
}
void W65816InstPrinter::printAddrAbs(const MCInst *MI, unsigned OpNo,
raw_ostream &O) {
printAddrImm(MI->getOperand(OpNo), 16, O, MAI);
}
void W65816InstPrinter::printAddrLong(const MCInst *MI, unsigned OpNo,
raw_ostream &O) {
printAddrImm(MI->getOperand(OpNo), 24, O, MAI);
}
void W65816InstPrinter::printPCRel8(const MCInst *MI, uint64_t Address,
unsigned OpNo, raw_ostream &O) {
const MCOperand &Op = MI->getOperand(OpNo);
if (Op.isImm()) {
O << Op.getImm();
return;
}
assert(Op.isExpr() && "unknown pc-rel operand kind");
MAI.printExpr(O, *Op.getExpr());
}
void W65816InstPrinter::printPCRel16(const MCInst *MI, uint64_t Address,
unsigned OpNo, raw_ostream &O) {
printPCRel8(MI, Address, OpNo, O);
}
void W65816InstPrinter::printFrameMem(const MCInst *MI, unsigned OpNo,
raw_ostream &O) {
// Frame-index loads/stores are expanded into LDA/STA d,S in
// W65816RegisterInfo::eliminateFrameIndex before they ever reach
// the printer. This callback exists only to satisfy tablegen.
llvm_unreachable("W65816: frame-mem pseudo printed without expansion");
}

View file

@ -0,0 +1,55 @@
//= W65816InstPrinter.h - Convert W65816 MCInst to assembly syntax -*- 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
//
//===----------------------------------------------------------------------===//
//
// This class prints a W65816 MCInst to a .s file.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_W65816_MCTARGETDESC_W65816INSTPRINTER_H
#define LLVM_LIB_TARGET_W65816_MCTARGETDESC_W65816INSTPRINTER_H
#include "llvm/MC/MCInstPrinter.h"
namespace llvm {
class W65816InstPrinter : public MCInstPrinter {
public:
W65816InstPrinter(const MCAsmInfo &MAI, const MCInstrInfo &MII,
const MCRegisterInfo &MRI)
: MCInstPrinter(MAI, MII, MRI) {}
void printRegName(raw_ostream &O, MCRegister Reg) override;
void printInst(const MCInst *MI, uint64_t Address, StringRef Annot,
const MCSubtargetInfo &STI, raw_ostream &O) override;
// Autogenerated by tblgen.
std::pair<const char *, uint64_t> getMnemonic(const MCInst &MI) const override;
void printInstruction(const MCInst *MI, uint64_t Address, raw_ostream &O);
bool printAliasInstr(const MCInst *MI, uint64_t Address, raw_ostream &O);
void printCustomAliasOperand(const MCInst *MI, uint64_t Address,
unsigned OpIdx, unsigned PrintMethodIdx,
raw_ostream &O);
static const char *getRegisterName(MCRegister Reg);
private:
void printOperand(const MCInst *MI, unsigned OpNo, raw_ostream &O);
void printAddrDP(const MCInst *MI, unsigned OpNo, raw_ostream &O);
void printAddrAbs(const MCInst *MI, unsigned OpNo, raw_ostream &O);
void printAddrLong(const MCInst *MI, unsigned OpNo, raw_ostream &O);
void printPCRel8(const MCInst *MI, uint64_t Address, unsigned OpNo,
raw_ostream &O);
void printPCRel16(const MCInst *MI, uint64_t Address, unsigned OpNo,
raw_ostream &O);
// Frame-index pseudos never reach the printer (they are expanded in
// the AsmPrinter), but tablegen still requires the declaration.
void printFrameMem(const MCInst *MI, unsigned OpNo, raw_ostream &O);
};
} // namespace llvm
#endif

View file

@ -0,0 +1,30 @@
//===-- W65816MCAsmInfo.cpp - W65816 asm properties -----------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file contains the implementation of the W65816MCAsmInfo class.
//
//===----------------------------------------------------------------------===//
#include "W65816MCAsmInfo.h"
using namespace llvm;
void W65816MCAsmInfo::anchor() {}
W65816MCAsmInfo::W65816MCAsmInfo(const Triple &TT) {
// The 65816 address space is 24 bits but LLVM's ELF writer emits 32-bit
// pointer-sized DWARF entries; store pointers as 32 bits for DWARF.
CodePointerSize = 4;
CalleeSaveStackSlotSize = 2;
CommentString = ";";
AlignmentIsInBytes = false;
UsesELFSectionDirectiveForBSS = true;
SupportsDebugInformation = true;
}

View file

@ -0,0 +1,30 @@
//===-- W65816MCAsmInfo.h - W65816 asm properties --------------*- 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
//
//===----------------------------------------------------------------------===//
//
// This file contains the declaration of the W65816MCAsmInfo class.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_W65816_MCTARGETDESC_W65816MCASMINFO_H
#define LLVM_LIB_TARGET_W65816_MCTARGETDESC_W65816MCASMINFO_H
#include "llvm/MC/MCAsmInfoELF.h"
namespace llvm {
class Triple;
class W65816MCAsmInfo : public MCAsmInfoELF {
void anchor() override;
public:
explicit W65816MCAsmInfo(const Triple &TT);
};
} // namespace llvm
#endif

View file

@ -0,0 +1,200 @@
//===-- W65816MCCodeEmitter.cpp - Convert W65816 code to machine code -----===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// 65816 instructions are 1-4 bytes: an 8-bit opcode followed by 0-3 bytes of
// operand data (immediate / direct-page / absolute / long / relative). The
// tablegen-generated `getBinaryCodeForInstr` packs the full instruction into
// the low Size*8 bits of a uint64_t in little-endian order; we just copy
// that many bytes out.
//
// Each operand class carries an `EncoderMethod` naming one of the
// `encode*` helpers below. Those helpers return the immediate value for
// literal operands and emit a relocation fixup for symbolic operands,
// tracking the per-instruction byte offset so block-move and other
// multi-operand instructions get correctly-positioned fixups.
//
//===----------------------------------------------------------------------===//
#include "MCTargetDesc/W65816FixupKinds.h"
#include "MCTargetDesc/W65816MCTargetDesc.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/MC/MCCodeEmitter.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"
#define DEBUG_TYPE "mccodeemitter"
namespace llvm {
class W65816MCCodeEmitter : public MCCodeEmitter {
MCContext &Ctx;
const MCInstrInfo &MCII;
// Byte position within the current instruction for the next operand's
// fixup. Reset to 1 at the start of each encodeInstruction call (byte 0
// is always the opcode).
mutable unsigned OperandOffset = 1;
// Tablegen-provided.
uint64_t getBinaryCodeForInstr(const MCInst &MI,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const;
// Fallback operand encoder used when a custom EncoderMethod is not set.
unsigned getMachineOpValue(const MCInst &MI, const MCOperand &MO,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const;
// Per-operand encoders. Each pulls one MCOperand from the instruction,
// returns its literal bits for immediate operands, or emits a fixup of
// the appropriate kind (and returns 0) for expression operands.
unsigned encodeImm8(const MCInst &MI, unsigned OpIdx,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const;
unsigned encodeImm16(const MCInst &MI, unsigned OpIdx,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const;
unsigned encodeAddr8(const MCInst &MI, unsigned OpIdx,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const;
unsigned encodeAddr16(const MCInst &MI, unsigned OpIdx,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const;
unsigned encodeAddr24(const MCInst &MI, unsigned OpIdx,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const;
unsigned encodePCRel8(const MCInst &MI, unsigned OpIdx,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const;
unsigned encodePCRel16(const MCInst &MI, unsigned OpIdx,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const;
// Shared implementation for every encode* helper. `width` is the number
// of bytes of the operand as it appears in the instruction stream; it
// both advances the per-instruction offset and selects the fixup kind.
unsigned encodeOperand(const MCInst &MI, unsigned OpIdx, unsigned width,
W65816::Fixups fixupKind,
SmallVectorImpl<MCFixup> &Fixups) const;
public:
W65816MCCodeEmitter(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;
};
void W65816MCCodeEmitter::encodeInstruction(const MCInst &MI,
SmallVectorImpl<char> &CB,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const {
const MCInstrDesc &Desc = MCII.get(MI.getOpcode());
unsigned Size = Desc.getSize();
// First operand byte lives right after the opcode.
OperandOffset = 1;
uint64_t Bits = getBinaryCodeForInstr(MI, Fixups, STI);
for (unsigned i = 0; i < Size; ++i) {
CB.push_back(static_cast<char>((Bits >> (8 * i)) & 0xff));
}
}
unsigned
W65816MCCodeEmitter::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());
}
// All symbolic operands should be routed through the per-operand-class
// EncoderMethods below. Reaching this path means an operand was defined
// without a custom encoder.
llvm_unreachable("W65816: symbolic operand lacks EncoderMethod");
}
unsigned W65816MCCodeEmitter::encodeOperand(const MCInst &MI, unsigned OpIdx,
unsigned width,
W65816::Fixups fixupKind,
SmallVectorImpl<MCFixup> &Fixups) const {
const MCOperand &MO = MI.getOperand(OpIdx);
unsigned offsetForThisOperand = OperandOffset;
OperandOffset += width;
if (MO.isImm()) {
return static_cast<unsigned>(MO.getImm());
}
assert(MO.isExpr() && "unexpected operand kind for encoded operand");
Fixups.push_back(MCFixup::create(offsetForThisOperand, MO.getExpr(),
MCFixupKind(fixupKind)));
return 0;
}
unsigned W65816MCCodeEmitter::encodeImm8(const MCInst &MI, unsigned OpIdx,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const {
return encodeOperand(MI, OpIdx, 1, W65816::fixup_8, Fixups);
}
unsigned W65816MCCodeEmitter::encodeImm16(const MCInst &MI, unsigned OpIdx,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const {
return encodeOperand(MI, OpIdx, 2, W65816::fixup_16, Fixups);
}
unsigned W65816MCCodeEmitter::encodeAddr8(const MCInst &MI, unsigned OpIdx,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const {
return encodeOperand(MI, OpIdx, 1, W65816::fixup_8, Fixups);
}
unsigned W65816MCCodeEmitter::encodeAddr16(const MCInst &MI, unsigned OpIdx,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const {
return encodeOperand(MI, OpIdx, 2, W65816::fixup_16, Fixups);
}
unsigned W65816MCCodeEmitter::encodeAddr24(const MCInst &MI, unsigned OpIdx,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const {
return encodeOperand(MI, OpIdx, 3, W65816::fixup_24, Fixups);
}
unsigned W65816MCCodeEmitter::encodePCRel8(const MCInst &MI, unsigned OpIdx,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const {
return encodeOperand(MI, OpIdx, 1, W65816::fixup_8_pcrel, Fixups);
}
unsigned W65816MCCodeEmitter::encodePCRel16(const MCInst &MI, unsigned OpIdx,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const {
return encodeOperand(MI, OpIdx, 2, W65816::fixup_16_pcrel, Fixups);
}
MCCodeEmitter *createW65816MCCodeEmitter(const MCInstrInfo &MCII,
MCContext &Ctx) {
return new W65816MCCodeEmitter(Ctx, MCII);
}
#include "W65816GenMCCodeEmitter.inc"
} // namespace llvm

View file

@ -0,0 +1,83 @@
//===-- W65816MCTargetDesc.cpp - W65816 Target Descriptions ---------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file provides W65816 specific target descriptions.
//
//===----------------------------------------------------------------------===//
#include "W65816MCTargetDesc.h"
#include "W65816InstPrinter.h"
#include "W65816MCAsmInfo.h"
#include "TargetInfo/W65816TargetInfo.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCRegisterInfo.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/Compiler.h"
using namespace llvm;
#define GET_INSTRINFO_MC_DESC
#define ENABLE_INSTR_PREDICATE_VERIFIER
#include "W65816GenInstrInfo.inc"
#define GET_SUBTARGETINFO_MC_DESC
#include "W65816GenSubtargetInfo.inc"
#define GET_REGINFO_MC_DESC
#include "W65816GenRegisterInfo.inc"
static MCInstrInfo *createW65816MCInstrInfo() {
MCInstrInfo *X = new MCInstrInfo();
InitW65816MCInstrInfo(X);
return X;
}
static MCRegisterInfo *createW65816MCRegisterInfo(const Triple &TT) {
MCRegisterInfo *X = new MCRegisterInfo();
InitW65816MCRegisterInfo(X, W65816::PC);
return X;
}
static MCAsmInfo *createW65816MCAsmInfo(const MCRegisterInfo &MRI,
const Triple &TT,
const MCTargetOptions &Options) {
// Initial DWARF CFI frame state is deliberately omitted for the skeleton;
// it will be added once frame lowering is fully described.
return new W65816MCAsmInfo(TT);
}
static MCSubtargetInfo *
createW65816MCSubtargetInfo(const Triple &TT, StringRef CPU, StringRef FS) {
return createW65816MCSubtargetInfoImpl(TT, CPU, /*TuneCPU=*/CPU, FS);
}
static MCInstPrinter *createW65816MCInstPrinter(const Triple &T,
unsigned SyntaxVariant,
const MCAsmInfo &MAI,
const MCInstrInfo &MII,
const MCRegisterInfo &MRI) {
if (SyntaxVariant == 0)
return new W65816InstPrinter(MAI, MII, MRI);
return nullptr;
}
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeW65816TargetMC() {
Target &T = getTheW65816Target();
TargetRegistry::RegisterMCAsmInfo(T, createW65816MCAsmInfo);
TargetRegistry::RegisterMCInstrInfo(T, createW65816MCInstrInfo);
TargetRegistry::RegisterMCRegInfo(T, createW65816MCRegisterInfo);
TargetRegistry::RegisterMCSubtargetInfo(T, createW65816MCSubtargetInfo);
TargetRegistry::RegisterMCInstPrinter(T, createW65816MCInstPrinter);
TargetRegistry::RegisterMCCodeEmitter(T, createW65816MCCodeEmitter);
TargetRegistry::RegisterMCAsmBackend(T, createW65816MCAsmBackend);
TargetRegistry::RegisterObjectTargetStreamer(
T, createW65816ObjectTargetStreamer);
}

View file

@ -0,0 +1,61 @@
//===-- W65816MCTargetDesc.h - W65816 Target Descriptions -------*- 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
//
//===----------------------------------------------------------------------===//
//
// This file provides W65816 specific target descriptions.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_W65816_MCTARGETDESC_W65816MCTARGETDESC_H
#define LLVM_LIB_TARGET_W65816_MCTARGETDESC_W65816MCTARGETDESC_H
#include "llvm/Support/DataTypes.h"
#include <memory>
namespace llvm {
class Target;
class MCAsmBackend;
class MCCodeEmitter;
class MCInstrInfo;
class MCSubtargetInfo;
class MCRegisterInfo;
class MCContext;
class MCTargetOptions;
class MCObjectTargetWriter;
class MCStreamer;
class MCTargetStreamer;
/// Creates a machine code emitter for W65816.
MCCodeEmitter *createW65816MCCodeEmitter(const MCInstrInfo &MCII,
MCContext &Ctx);
MCAsmBackend *createW65816MCAsmBackend(const Target &T,
const MCSubtargetInfo &STI,
const MCRegisterInfo &MRI,
const MCTargetOptions &Options);
MCTargetStreamer *
createW65816ObjectTargetStreamer(MCStreamer &S, const MCSubtargetInfo &STI);
std::unique_ptr<MCObjectTargetWriter>
createW65816ELFObjectWriter(uint8_t OSABI);
} // End llvm namespace
// Defines symbolic names for W65816 registers.
#define GET_REGINFO_ENUM
#include "W65816GenRegisterInfo.inc"
// Defines symbolic names for the W65816 instructions.
#define GET_INSTRINFO_ENUM
#define GET_INSTRINFO_MC_HELPER_DECLS
#include "W65816GenInstrInfo.inc"
#define GET_SUBTARGETINFO_ENUM
#include "W65816GenSubtargetInfo.inc"
#endif

View file

@ -0,0 +1,10 @@
add_llvm_component_library(LLVMW65816Info
W65816TargetInfo.cpp
LINK_COMPONENTS
MC
Support
ADD_TO_COMPONENT
W65816
)

View file

@ -0,0 +1,23 @@
//===-- W65816TargetInfo.cpp - W65816 Target Implementation ---------------===//
//
// 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 "TargetInfo/W65816TargetInfo.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/Compiler.h"
using namespace llvm;
Target &llvm::getTheW65816Target() {
static Target TheW65816Target;
return TheW65816Target;
}
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeW65816TargetInfo() {
RegisterTarget<Triple::w65816> X(getTheW65816Target(), "w65816",
"WDC 65816 [experimental]", "W65816");
}

View file

@ -0,0 +1,20 @@
//===-- W65816TargetInfo.h - W65816 Target Implementation -------*- 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_W65816_TARGETINFO_W65816TARGETINFO_H
#define LLVM_LIB_TARGET_W65816_TARGETINFO_W65816TARGETINFO_H
namespace llvm {
class Target;
Target &getTheW65816Target();
} // namespace llvm
#endif // LLVM_LIB_TARGET_W65816_TARGETINFO_W65816TARGETINFO_H

View file

@ -0,0 +1,50 @@
//==-- W65816.h - Top-level interface for W65816 representation --*- 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
//
//===----------------------------------------------------------------------===//
//
// This file contains the entry points for global functions defined in the
// LLVM W65816 backend.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_W65816_W65816_H
#define LLVM_LIB_TARGET_W65816_W65816_H
#include "MCTargetDesc/W65816MCTargetDesc.h"
#include "llvm/Target/TargetMachine.h"
namespace W65816CC {
// 65816 branch condition codes. Encoded as i8 immediate operands in
// the BR_CC SDNode and tablegen patterns.
enum CondCode {
COND_EQ = 0, // BEQ
COND_NE = 1, // BNE
COND_HS = 2, // BCS (unsigned >=)
COND_LO = 3, // BCC (unsigned <)
COND_MI = 4, // BMI (negative)
COND_PL = 5, // BPL (non-negative)
COND_VS = 6, // BVS (overflow)
COND_VC = 7, // BVC (no overflow)
COND_INVALID = -1
};
} // namespace W65816CC
namespace llvm {
class FunctionPass;
class W65816TargetMachine;
class PassRegistry;
FunctionPass *createW65816ISelDag(W65816TargetMachine &TM,
CodeGenOptLevel OptLevel);
void initializeW65816AsmPrinterPass(PassRegistry &);
void initializeW65816DAGToDAGISelLegacyPass(PassRegistry &);
} // namespace llvm
#endif

View file

@ -0,0 +1,76 @@
//===-- W65816.td - Describe the W65816 Target Machine -----*- tablegen -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
// This is the top level entry point for the W65816 target.
//===----------------------------------------------------------------------===//
//===----------------------------------------------------------------------===//
// Target-independent interfaces
//===----------------------------------------------------------------------===//
include "llvm/Target/Target.td"
//===----------------------------------------------------------------------===//
// Subtarget Features.
//===----------------------------------------------------------------------===//
// No subtarget features are defined for the skeleton. Later work will add
// features for emulation/native mode, DP location, etc.
//===----------------------------------------------------------------------===//
// W65816 supported processors.
//===----------------------------------------------------------------------===//
class Proc<string Name, list<SubtargetFeature> Features>
: Processor<Name, NoItineraries, Features>;
def : Proc<"generic", []>;
def : Proc<"w65816", []>;
//===----------------------------------------------------------------------===//
// Register File Description
//===----------------------------------------------------------------------===//
include "W65816RegisterInfo.td"
//===----------------------------------------------------------------------===//
// Calling Convention Description
//===----------------------------------------------------------------------===//
include "W65816CallingConv.td"
//===----------------------------------------------------------------------===//
// Instruction Descriptions
//===----------------------------------------------------------------------===//
include "W65816InstrInfo.td"
defm : RemapAllTargetPseudoPointerOperands<PtrRegs>;
def W65816InstrInfo : InstrInfo;
//===---------------------------------------------------------------------===//
// Assembly Printers
//===---------------------------------------------------------------------===//
def W65816AsmWriter : AsmWriter {
string AsmWriterClassName = "InstPrinter";
}
//===---------------------------------------------------------------------===//
// Assembly Parsers
//===---------------------------------------------------------------------===//
def W65816AsmParser : AsmParser;
//===----------------------------------------------------------------------===//
// Target Declaration
//===----------------------------------------------------------------------===//
def W65816 : Target {
let InstructionSet = W65816InstrInfo;
let AssemblyParsers = [W65816AsmParser];
}

View file

@ -0,0 +1,323 @@
//===-- W65816AsmPrinter.cpp - W65816 LLVM assembly 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
//
//===----------------------------------------------------------------------===//
//
// Skeleton assembly printer. The MCInst lowering path is wired up but no
// target-specific operand formatting is implemented yet.
//
//===----------------------------------------------------------------------===//
#include "MCTargetDesc/W65816InstPrinter.h"
#include "W65816MCInstLower.h"
#include "W65816TargetMachine.h"
#include "TargetInfo/W65816TargetInfo.h"
#include "llvm/CodeGen/AsmPrinter.h"
#include "llvm/CodeGen/MachineInstr.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/Compiler.h"
using namespace llvm;
#define DEBUG_TYPE "asm-printer"
namespace {
class W65816AsmPrinter : public AsmPrinter {
public:
W65816AsmPrinter(TargetMachine &TM, std::unique_ptr<MCStreamer> Streamer)
: AsmPrinter(TM, std::move(Streamer), ID) {}
StringRef getPassName() const override { return "W65816 Assembly Printer"; }
void emitInstruction(const MachineInstr *MI) override;
static char ID;
};
} // end anonymous namespace
// Convert a single MachineOperand to an MCOperand using the standard
// rules (register / immediate / global / external / etc.). Used by
// pseudo expansion to lift symbolic operands; mirrors the per-operand
// path in W65816MCInstLower::Lower.
static MCOperand lowerOperand(const MachineOperand &MO,
W65816MCInstLower &Lower) {
switch (MO.getType()) {
case MachineOperand::MO_Register:
return MCOperand::createReg(MO.getReg());
case MachineOperand::MO_Immediate:
return MCOperand::createImm(MO.getImm());
case MachineOperand::MO_GlobalAddress:
return Lower.LowerSymbolOperand(MO, Lower.GetGlobalAddressSymbol(MO));
case MachineOperand::MO_ExternalSymbol:
return Lower.LowerSymbolOperand(MO, Lower.GetExternalSymbolSymbol(MO));
case MachineOperand::MO_BlockAddress:
return Lower.LowerSymbolOperand(MO, Lower.GetBlockAddressSymbol(MO));
case MachineOperand::MO_JumpTableIndex:
return Lower.LowerSymbolOperand(MO, Lower.GetJumpTableSymbol(MO));
case MachineOperand::MO_ConstantPoolIndex:
return Lower.LowerSymbolOperand(MO, Lower.GetConstantPoolIndexSymbol(MO));
default:
llvm_unreachable("W65816: unsupported operand type in pseudo lower");
}
}
void W65816AsmPrinter::emitInstruction(const MachineInstr *MI) {
W65816_MC::verifyInstructionPredicates(MI->getOpcode(),
getSubtargetInfo().getFeatureBits());
W65816MCInstLower MCInstLowering(OutContext, *this);
// Expand codegen pseudos into their MC-layer realisations. Keep this
// switch in sync with the pseudo defs at the bottom of
// W65816InstrInfo.td. All single-output pseudos here have operand
// layout (outs $dst, ins $value); we drop $dst (it lives implicitly in
// A) and lift $value into the MC instruction's single source operand.
switch (MI->getOpcode()) {
default:
break;
case W65816::LDAi16imm: {
MCInst Lda;
Lda.setOpcode(W65816::LDA_Imm16);
Lda.addOperand(lowerOperand(MI->getOperand(1), MCInstLowering));
EmitToStreamer(*OutStreamer, Lda);
return;
}
case W65816::LDAi8imm: {
MCInst Lda;
Lda.setOpcode(W65816::LDA_Imm8);
int64_t Val = MI->getOperand(1).getImm() & 0xFF;
Lda.addOperand(MCOperand::createImm(Val));
EmitToStreamer(*OutStreamer, Lda);
return;
}
case W65816::LDAabs: {
MCInst Lda;
Lda.setOpcode(W65816::LDA_Abs);
Lda.addOperand(lowerOperand(MI->getOperand(1), MCInstLowering));
EmitToStreamer(*OutStreamer, Lda);
return;
}
case W65816::STAabs: {
// STAabs is (outs), (ins Acc16:$src, addr:$addr). The MC STA_Abs
// takes only $addr; $src lives in the implicit A.
MCInst Sta;
Sta.setOpcode(W65816::STA_Abs);
Sta.addOperand(lowerOperand(MI->getOperand(1), MCInstLowering));
EmitToStreamer(*OutStreamer, Sta);
return;
}
case W65816::ADCi16imm:
case W65816::SBCi16imm: {
bool IsSub = MI->getOpcode() == W65816::SBCi16imm;
MCInst Carry;
Carry.setOpcode(IsSub ? W65816::SEC : W65816::CLC);
EmitToStreamer(*OutStreamer, Carry);
MCInst Op;
Op.setOpcode(IsSub ? W65816::SBC_Imm16 : W65816::ADC_Imm16);
Op.addOperand(lowerOperand(MI->getOperand(2), MCInstLowering));
EmitToStreamer(*OutStreamer, Op);
return;
}
case W65816::ADCi8imm:
case W65816::SBCi8imm: {
bool IsSub = MI->getOpcode() == W65816::SBCi8imm;
MCInst Carry;
Carry.setOpcode(IsSub ? W65816::SEC : W65816::CLC);
EmitToStreamer(*OutStreamer, Carry);
MCInst Op;
Op.setOpcode(IsSub ? W65816::SBC_Imm8 : W65816::ADC_Imm8);
int64_t Val = MI->getOperand(2).getImm() & 0xFF;
Op.addOperand(MCOperand::createImm(Val));
EmitToStreamer(*OutStreamer, Op);
return;
}
case W65816::ANDi8imm:
case W65816::ORAi8imm:
case W65816::EORi8imm: {
MCInst Op;
unsigned mc = 0;
switch (MI->getOpcode()) {
case W65816::ANDi8imm: mc = W65816::AND_Imm8; break;
case W65816::ORAi8imm: mc = W65816::ORA_Imm8; break;
case W65816::EORi8imm: mc = W65816::EOR_Imm8; break;
}
Op.setOpcode(mc);
// Mask to 8 bits so the printer doesn't show the sign-extended
// i8 value as a wider hex literal (e.g. -16 → 0xFFF0); the
// encoder only takes the low byte anyway.
int64_t Val = MI->getOperand(2).getImm() & 0xFF;
Op.addOperand(MCOperand::createImm(Val));
EmitToStreamer(*OutStreamer, Op);
return;
}
case W65816::LDA8abs: {
MCInst Lda;
Lda.setOpcode(W65816::LDA_Abs);
Lda.addOperand(lowerOperand(MI->getOperand(1), MCInstLowering));
EmitToStreamer(*OutStreamer, Lda);
return;
}
case W65816::STA8abs: {
MCInst Sta;
Sta.setOpcode(W65816::STA_Abs);
Sta.addOperand(lowerOperand(MI->getOperand(1), MCInstLowering));
EmitToStreamer(*OutStreamer, Sta);
return;
}
case W65816::ADCabs:
case W65816::SBCabs: {
bool IsSub = MI->getOpcode() == W65816::SBCabs;
MCInst Carry;
Carry.setOpcode(IsSub ? W65816::SEC : W65816::CLC);
EmitToStreamer(*OutStreamer, Carry);
MCInst Op;
Op.setOpcode(IsSub ? W65816::SBC_Abs : W65816::ADC_Abs);
Op.addOperand(lowerOperand(MI->getOperand(2), MCInstLowering));
EmitToStreamer(*OutStreamer, Op);
return;
}
case W65816::CMPi16imm: {
// CMPi16imm has (outs), (ins Acc16:$lhs, i16imm:$rhs); MC needs only
// the immediate.
MCInst Cmp;
Cmp.setOpcode(W65816::CMP_Imm16);
Cmp.addOperand(lowerOperand(MI->getOperand(1), MCInstLowering));
EmitToStreamer(*OutStreamer, Cmp);
return;
}
case W65816::CMPi8imm: {
MCInst Cmp;
Cmp.setOpcode(W65816::CMP_Imm8);
int64_t Val = MI->getOperand(1).getImm() & 0xFF;
Cmp.addOperand(MCOperand::createImm(Val));
EmitToStreamer(*OutStreamer, Cmp);
return;
}
case W65816::CMPabs: {
MCInst Cmp;
Cmp.setOpcode(W65816::CMP_Abs);
Cmp.addOperand(lowerOperand(MI->getOperand(1), MCInstLowering));
EmitToStreamer(*OutStreamer, Cmp);
return;
}
// Bitwise immediate / memory pseudos: simple opcode swap, no carry
// prefix. Operand 0 = $dst (output, tied), 1 = $src (use), 2 = imm/addr.
case W65816::ANDi16imm:
case W65816::ORAi16imm:
case W65816::EORi16imm: {
MCInst Op;
unsigned mc = 0;
switch (MI->getOpcode()) {
case W65816::ANDi16imm: mc = W65816::AND_Imm16; break;
case W65816::ORAi16imm: mc = W65816::ORA_Imm16; break;
case W65816::EORi16imm: mc = W65816::EOR_Imm16; break;
}
Op.setOpcode(mc);
Op.addOperand(lowerOperand(MI->getOperand(2), MCInstLowering));
EmitToStreamer(*OutStreamer, Op);
return;
}
case W65816::ANDabs:
case W65816::ORAabs:
case W65816::EORabs: {
MCInst Op;
unsigned mc = 0;
switch (MI->getOpcode()) {
case W65816::ANDabs: mc = W65816::AND_Abs; break;
case W65816::ORAabs: mc = W65816::ORA_Abs; break;
case W65816::EORabs: mc = W65816::EOR_Abs; break;
}
Op.setOpcode(mc);
Op.addOperand(lowerOperand(MI->getOperand(2), MCInstLowering));
EmitToStreamer(*OutStreamer, Op);
return;
}
case W65816::JSLpseudo: {
MCInst Jsl;
Jsl.setOpcode(W65816::JSL_Long);
Jsl.addOperand(lowerOperand(MI->getOperand(0), MCInstLowering));
EmitToStreamer(*OutStreamer, Jsl);
return;
}
case W65816::ASLA16: {
MCInst Asl;
Asl.setOpcode(W65816::ASL_A);
EmitToStreamer(*OutStreamer, Asl);
return;
}
case W65816::LSRA16:
case W65816::LSRA8: {
MCInst Lsr;
Lsr.setOpcode(W65816::LSR_A);
EmitToStreamer(*OutStreamer, Lsr);
return;
}
case W65816::ASLA8: {
MCInst Asl;
Asl.setOpcode(W65816::ASL_A);
EmitToStreamer(*OutStreamer, Asl);
return;
}
case W65816::ASRA16: {
// PHA ; ASL A (sets carry from sign bit) ; PLA ; ROR A
MCInst pha; pha.setOpcode(W65816::PHA); EmitToStreamer(*OutStreamer, pha);
MCInst asl; asl.setOpcode(W65816::ASL_A); EmitToStreamer(*OutStreamer, asl);
MCInst pla; pla.setOpcode(W65816::PLA); EmitToStreamer(*OutStreamer, pla);
MCInst ror; ror.setOpcode(W65816::ROR_A); EmitToStreamer(*OutStreamer, ror);
return;
}
case W65816::INA_PSEUDO: {
MCInst In;
In.setOpcode(W65816::INA);
EmitToStreamer(*OutStreamer, In);
return;
}
case W65816::DEA_PSEUDO:
case W65816::DEA_PSEUDO8: {
MCInst De;
De.setOpcode(W65816::DEA);
EmitToStreamer(*OutStreamer, De);
return;
}
case W65816::INA_PSEUDO8: {
MCInst In;
In.setOpcode(W65816::INA);
EmitToStreamer(*OutStreamer, In);
return;
}
case W65816::NEGA16: {
// EOR #$FFFF; INC A.
MCInst Eor;
Eor.setOpcode(W65816::EOR_Imm16);
Eor.addOperand(MCOperand::createImm(0xFFFF));
EmitToStreamer(*OutStreamer, Eor);
MCInst Inc;
Inc.setOpcode(W65816::INA);
EmitToStreamer(*OutStreamer, Inc);
return;
}
}
MCInst TmpInst;
MCInstLowering.Lower(MI, TmpInst);
EmitToStreamer(*OutStreamer, TmpInst);
}
char W65816AsmPrinter::ID = 0;
INITIALIZE_PASS(W65816AsmPrinter, "w65816-asm-printer",
"W65816 Assembly Printer", false, false)
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeW65816AsmPrinter() {
RegisterAsmPrinter<W65816AsmPrinter> X(getTheW65816Target());
}

View file

@ -0,0 +1,37 @@
//==- W65816CallingConv.td - Calling Conventions for W65816 -*- tablegen -*-==//
//
// 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
//
//===----------------------------------------------------------------------===//
// This describes the calling conventions for the W65816 architecture.
//
// The 65816 standard C calling convention passes parameters on the stack and
// returns values in the accumulator. Tool-call and interrupt ABIs will be
// added in a later change; only the skeleton is defined here.
//===----------------------------------------------------------------------===//
//===----------------------------------------------------------------------===//
// W65816 Return Value Calling Convention
//===----------------------------------------------------------------------===//
def RetCC_W65816 : CallingConv<[
// i8 values are returned in the 8-bit accumulator.
CCIfType<[i8], CCAssignToReg<[A]>>,
// i16 values are returned in the 16-bit accumulator (same physical reg).
CCIfType<[i16], CCAssignToReg<[A]>>
]>;
//===----------------------------------------------------------------------===//
// W65816 Argument Calling Convention
//===----------------------------------------------------------------------===//
def CC_W65816 : CallingConv<[
// Pass byval aggregates on the stack.
CCIfByVal<CCPassByVal<2, 1>>,
// Promote i8 arguments to i16 when passed on the stack.
CCIfType<[i8], CCPromoteToType<i16>>,
// Integer arguments occupy 2-byte stack slots with 1-byte alignment.
CCIfType<[i16], CCAssignToStack<2, 1>>
]>;

View file

@ -0,0 +1,148 @@
//===-- W65816FrameLowering.cpp - W65816 Frame Information ----------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Skeleton frame lowering. The 65816 stack grows downward and has a 1-byte
// alignment; prologue and epilogue emission will be implemented alongside
// the full calling convention.
//
//===----------------------------------------------------------------------===//
#include "W65816FrameLowering.h"
#include "W65816InstrInfo.h"
#include "W65816Subtarget.h"
#include "llvm/CodeGen/MachineFrameInfo.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/ErrorHandling.h"
using namespace llvm;
W65816FrameLowering::W65816FrameLowering(const W65816Subtarget &STI)
: TargetFrameLowering(TargetFrameLowering::StackGrowsDown, Align(1), 0,
Align(1)) {}
bool W65816FrameLowering::hasFPImpl(const MachineFunction &MF) const {
// The 65816 has no native frame pointer — direct page would be the
// logical home for one but we don't reserve it. Return false so
// every function uses pure stack-relative addressing for locals.
// Variable-size objects (alloca with non-constant size) and
// frameaddress() intrinsics will need a real FP eventually.
return false;
}
bool W65816FrameLowering::hasReservedCallFrame(const MachineFunction &MF) const {
return !MF.getFrameInfo().hasVarSizedObjects();
}
void W65816FrameLowering::emitPrologue(MachineFunction &MF,
MachineBasicBlock &MBB) const {
// Pick canonical mode based on whether the function uses any 8-bit
// accumulator values. Pure-i16 functions get REP #$30 (16-bit M+X);
// any-i8 functions get REP #$10 + SEP #$20 (16-bit X, 8-bit A).
// This is a coarse heuristic until the REP/SEP scheduling pass
// (design doc §3.3) can insert mode transitions per-region.
const W65816Subtarget &STI = MF.getSubtarget<W65816Subtarget>();
const W65816InstrInfo &TII = *STI.getInstrInfo();
const MachineRegisterInfo &MRI = MF.getRegInfo();
MachineBasicBlock::iterator MBBI = MBB.begin();
DebugLoc DL;
// Heuristic: scan the function body for any value with i8 type.
// Captures both signature types and internal i8 ops (e.g. a void
// function that loads / stores bytes). An eventual full
// mode-dependence analysis (the REP/SEP pass) will replace this.
bool UsesAcc8 = false;
const Function &F = MF.getFunction();
auto isI8 = [](Type *T) { return T && T->isIntegerTy(8); };
if (isI8(F.getReturnType()))
UsesAcc8 = true;
for (const Argument &Arg : F.args()) {
if (isI8(Arg.getType())) {
UsesAcc8 = true;
break;
}
}
if (!UsesAcc8) {
for (const BasicBlock &BB : F) {
if (UsesAcc8) break;
for (const Instruction &I : BB) {
if (isI8(I.getType())) {
UsesAcc8 = true;
break;
}
for (const Value *Op : I.operands()) {
if (isI8(Op->getType())) {
UsesAcc8 = true;
break;
}
}
if (UsesAcc8) break;
}
}
}
(void)MRI;
if (UsesAcc8) {
BuildMI(MBB, MBBI, DL, TII.get(W65816::REP)).addImm(0x10);
BuildMI(MBB, MBBI, DL, TII.get(W65816::SEP)).addImm(0x20);
} else {
BuildMI(MBB, MBBI, DL, TII.get(W65816::REP)).addImm(0x30);
}
// Reserve stack space for locals/spills if any. Sequence is
// `TSC ; SEC ; SBC #N ; TCS` to subtract N from S in 16-bit mode.
// Skipped for i8 functions for now since the stack adjustment uses
// the 16-bit accumulator (would need a save/restore around it).
uint64_t StackSize = MF.getFrameInfo().getStackSize();
if (StackSize > 0 && !UsesAcc8) {
BuildMI(MBB, MBBI, DL, TII.get(W65816::TSC));
BuildMI(MBB, MBBI, DL, TII.get(W65816::SEC));
BuildMI(MBB, MBBI, DL, TII.get(W65816::SBC_Imm16))
.addImm(StackSize);
BuildMI(MBB, MBBI, DL, TII.get(W65816::TCS));
}
}
void W65816FrameLowering::emitEpilogue(MachineFunction &MF,
MachineBasicBlock &MBB) const {
// Mirror image of the prologue: release any reserved frame bytes
// before the RTL.
uint64_t StackSize = MF.getFrameInfo().getStackSize();
if (StackSize == 0)
return;
const W65816Subtarget &STI = MF.getSubtarget<W65816Subtarget>();
const W65816InstrInfo &TII = *STI.getInstrInfo();
MachineBasicBlock::iterator MBBI = MBB.getLastNonDebugInstr();
// Insert before the terminator (the return).
DebugLoc DL = MBBI != MBB.end() ? MBBI->getDebugLoc() : DebugLoc();
const Function &F = MF.getFunction();
bool UsesAcc8 = F.getReturnType()->isIntegerTy(8);
if (!UsesAcc8) {
for (const Argument &Arg : F.args()) {
if (Arg.getType()->isIntegerTy(8)) { UsesAcc8 = true; break; }
}
}
if (UsesAcc8) return; // Cannot 16-bit math while in 8-bit mode.
BuildMI(MBB, MBBI, DL, TII.get(W65816::TSC));
BuildMI(MBB, MBBI, DL, TII.get(W65816::CLC));
BuildMI(MBB, MBBI, DL, TII.get(W65816::ADC_Imm16))
.addImm(StackSize);
BuildMI(MBB, MBBI, DL, TII.get(W65816::TCS));
}
MachineBasicBlock::iterator W65816FrameLowering::eliminateCallFramePseudoInstr(
MachineFunction &MF, MachineBasicBlock &MBB,
MachineBasicBlock::iterator I) const {
// Drop ADJCALLSTACKDOWN/UP with no replacement for now.
return MBB.erase(I);
}

View file

@ -0,0 +1,39 @@
//==- W65816FrameLowering.h - Define frame lowering for W65816 --*- 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_W65816_W65816FRAMELOWERING_H
#define LLVM_LIB_TARGET_W65816_W65816FRAMELOWERING_H
#include "llvm/CodeGen/TargetFrameLowering.h"
namespace llvm {
class W65816Subtarget;
class W65816FrameLowering : public TargetFrameLowering {
protected:
bool hasFPImpl(const MachineFunction &MF) const override;
public:
explicit W65816FrameLowering(const W65816Subtarget &STI);
void emitPrologue(MachineFunction &MF,
MachineBasicBlock &MBB) const override;
void emitEpilogue(MachineFunction &MF,
MachineBasicBlock &MBB) const override;
MachineBasicBlock::iterator
eliminateCallFramePseudoInstr(MachineFunction &MF, MachineBasicBlock &MBB,
MachineBasicBlock::iterator I) const override;
bool hasReservedCallFrame(const MachineFunction &MF) const override;
};
} // namespace llvm
#endif

View file

@ -0,0 +1,87 @@
//===-- W65816ISelDAGToDAG.cpp - DAG to DAG inst selector for W65816 ------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Skeleton DAG-to-DAG selector for the W65816. Falls back to the TableGen
// selector for every node; real custom selection (frame-index materialisation,
// addressing-mode matching, etc.) will be added once instructions exist.
//
//===----------------------------------------------------------------------===//
#include "W65816.h"
#include "W65816SelectionDAGInfo.h"
#include "W65816TargetMachine.h"
#include "llvm/CodeGen/SelectionDAG.h"
#include "llvm/CodeGen/SelectionDAGISel.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/ErrorHandling.h"
using namespace llvm;
#define DEBUG_TYPE "w65816-isel"
#define PASS_NAME "W65816 DAG->DAG Pattern Instruction Selection"
namespace {
class W65816DAGToDAGISel : public SelectionDAGISel {
public:
W65816DAGToDAGISel() = delete;
W65816DAGToDAGISel(W65816TargetMachine &TM, CodeGenOptLevel OptLevel)
: SelectionDAGISel(TM, OptLevel) {}
private:
#include "W65816GenDAGISel.inc"
void Select(SDNode *N) override;
// ComplexPattern selector: matches a FrameIndex SDValue and returns
// it (plus a zero offset) so LDAfi / STAfi can fold the FI directly
// into their operand list.
bool SelectFrameIndex(SDValue N, SDValue &Base, SDValue &Offset);
};
class W65816DAGToDAGISelLegacy : public SelectionDAGISelLegacy {
public:
static char ID;
W65816DAGToDAGISelLegacy(W65816TargetMachine &TM, CodeGenOptLevel OptLevel)
: SelectionDAGISelLegacy(
ID, std::make_unique<W65816DAGToDAGISel>(TM, OptLevel)) {}
};
} // end anonymous namespace
char W65816DAGToDAGISelLegacy::ID;
INITIALIZE_PASS(W65816DAGToDAGISelLegacy, DEBUG_TYPE, PASS_NAME, false, false)
FunctionPass *llvm::createW65816ISelDag(W65816TargetMachine &TM,
CodeGenOptLevel OptLevel) {
return new W65816DAGToDAGISelLegacy(TM, OptLevel);
}
void W65816DAGToDAGISel::Select(SDNode *Node) {
if (Node->isMachineOpcode()) {
Node->setNodeId(-1);
return;
}
// Defer to the auto-generated selector for everything else. Custom
// selection paths (frame-index, wrapper, etc.) will land here later.
SelectCode(Node);
}
bool W65816DAGToDAGISel::SelectFrameIndex(SDValue N, SDValue &Base,
SDValue &Offset) {
if (auto *FIN = dyn_cast<FrameIndexSDNode>(N)) {
Base = CurDAG->getTargetFrameIndex(FIN->getIndex(), MVT::i16);
Offset = CurDAG->getTargetConstant(0, SDLoc(N), MVT::i16);
return true;
}
return false;
}

View file

@ -0,0 +1,329 @@
//===-- W65816ISelLowering.cpp - W65816 DAG Lowering Implementation -------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Minimum DAG lowering sufficient for a no-argument function returning an
// i16 constant. Argument passing and non-trivial calls still unimplemented.
//
//===----------------------------------------------------------------------===//
#include "W65816ISelLowering.h"
#include "W65816SelectionDAGInfo.h"
#include "W65816Subtarget.h"
#include "llvm/CodeGen/MachineFrameInfo.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/CodeGen/SelectionDAG.h"
#include "llvm/CodeGen/TargetLoweringObjectFileImpl.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/ErrorHandling.h"
using namespace llvm;
#define DEBUG_TYPE "w65816-lower"
W65816TargetLowering::W65816TargetLowering(const TargetMachine &TM,
const W65816Subtarget &STI)
: TargetLowering(TM, STI) {
// Register classes for the two scalar modes. The register allocator sees
// A, X and Y as both 8-bit and 16-bit; a later REP/SEP pass is responsible
// for ensuring the dynamic mode matches the selected class.
addRegisterClass(MVT::i8, &W65816::Acc8RegClass);
addRegisterClass(MVT::i16, &W65816::Acc16RegClass);
computeRegisterProperties(STI.getRegisterInfo());
setStackPointerRegisterToSaveRestore(W65816::SP);
setBooleanContents(ZeroOrOneBooleanContent);
setBooleanVectorContents(ZeroOrOneBooleanContent);
// GlobalAddress and ExternalSymbol: lower to W65816ISD::Wrapper so a
// tablegen pattern can fold them into instruction operands.
setOperationAction(ISD::GlobalAddress, MVT::i16, Custom);
setOperationAction(ISD::ExternalSymbol, MVT::i16, Custom);
// BR_CC is custom-lowered to a CMP + W65816ISD::BR_CC chain so we can
// emit the right BEQ/BNE/BCS/BCC mnemonic per condition.
setOperationAction(ISD::BR_CC, MVT::i16, Custom);
setOperationAction(ISD::BR_CC, MVT::i8, Custom);
setOperationAction(ISD::BRCOND, MVT::Other, Expand);
setOperationAction(ISD::BR_JT, MVT::Other, Expand);
// SELECT / SELECT_CC: leave as default for now. Expanding either
// currently infinite-loops because they cross-expand into each other
// without a base case. Custom lowering to a Bxx + branch + phi
// pattern is the right fix; tracked separately.
// The 65816 has no hardware multiplier or divider. Multiply by a
// power-of-two constant is auto-rewritten to shifts by the DAG
// combiner; arbitrary multiply / divide / mod fail to select today.
// Real support needs (a) library functions (`__mulhi3` etc.) and
// (b) multi-arg call lowering — both are tracked separately.
setOperationAction(ISD::MULHU, MVT::i16, Expand);
setOperationAction(ISD::MULHS, MVT::i16, Expand);
setOperationAction(ISD::SMUL_LOHI, MVT::i16, Expand);
setOperationAction(ISD::UMUL_LOHI, MVT::i16, Expand);
}
// Map an LLVM SETCC condition to a W65816 branch. Returns the condition
// code along with possibly-swapped LHS/RHS; some signed comparisons are
// rewritten to use unsigned ones with a tweaked operand because the
// 65816 has no native signed branch other than BMI/BPL on a value, not
// on a comparison result.
// Map an LLVM SETCC condition to a 65816 branch. Unsigned codes use
// BCS/BCC after CMP. Signed SETLT/SETGE map to BMI/BPL — correct only
// when the comparison cannot overflow. For values produced by typical
// C arithmetic on i16 this is usually fine; values near INT16_MIN/MAX
// could give wrong results until we emit the BVS handling sequence.
// SETGT / SETLE are rewritten to SETLT / SETGE with constant + 1 in
// LowerBR_CC, mirroring the SETULE / SETUGT path.
static W65816CC::CondCode mapCC(ISD::CondCode CC) {
switch (CC) {
case ISD::SETEQ: return W65816CC::COND_EQ;
case ISD::SETNE: return W65816CC::COND_NE;
case ISD::SETUGE: return W65816CC::COND_HS;
case ISD::SETULT: return W65816CC::COND_LO;
case ISD::SETLT: return W65816CC::COND_MI;
case ISD::SETGE: return W65816CC::COND_PL;
default:
return W65816CC::COND_INVALID;
}
}
SDValue W65816TargetLowering::LowerBR_CC(SDValue Op, SelectionDAG &DAG) const {
SDValue Chain = Op.getOperand(0);
ISD::CondCode CC = cast<CondCodeSDNode>(Op.getOperand(1))->get();
SDValue LHS = Op.getOperand(2);
SDValue RHS = Op.getOperand(3);
SDValue Dest = Op.getOperand(4);
SDLoc DL(Op);
// CMP wants the comparand (constant or memory) on the right. If a DAG
// pre-pass put the constant on the left, swap and flip the condition.
if (isa<ConstantSDNode>(LHS) && !isa<ConstantSDNode>(RHS)) {
std::swap(LHS, RHS);
CC = ISD::getSetCCSwappedOperands(CC);
}
// Rewrite SETULE / SETUGT / SETLE / SETGT to SETULT / SETUGE / SETLT /
// SETGE with constant +/- 1. This keeps the variable on the LHS (so
// our pattern matches) and lets us use the BCS / BCC / BMI / BPL
// mnemonics natively. Only valid when the constant is not at its
// signed/unsigned boundary; for now we just bail in that pathological
// case.
if (auto *RhsConst = dyn_cast<ConstantSDNode>(RHS)) {
int64_t V = RhsConst->getSExtValue();
if (CC == ISD::SETULE && (uint64_t)V < 0xffff) {
RHS = DAG.getConstant(V + 1, DL, RHS.getValueType());
CC = ISD::SETULT;
} else if (CC == ISD::SETUGT && (uint64_t)V < 0xffff) {
RHS = DAG.getConstant(V + 1, DL, RHS.getValueType());
CC = ISD::SETUGE;
} else if (CC == ISD::SETLE && V < 0x7fff) {
RHS = DAG.getConstant(V + 1, DL, RHS.getValueType());
CC = ISD::SETLT;
} else if (CC == ISD::SETGT && V < 0x7fff) {
RHS = DAG.getConstant(V + 1, DL, RHS.getValueType());
CC = ISD::SETGE;
}
}
// Final fallback: any condition we didn't handle yet might still be
// representable by swapping operands (e.g. SETUGT b a → SETULT a b).
// Try once if the direct map doesn't recognise it.
W65816CC::CondCode TCC = mapCC(CC);
if (TCC == W65816CC::COND_INVALID) {
std::swap(LHS, RHS);
CC = ISD::getSetCCSwappedOperands(CC);
TCC = mapCC(CC);
}
if (TCC == W65816CC::COND_INVALID)
report_fatal_error("W65816: branch condition not yet implemented");
SDValue Glue = DAG.getNode(W65816ISD::CMP, DL, MVT::Glue, LHS, RHS);
SDValue CCOp = DAG.getTargetConstant(TCC, DL, MVT::i8);
return DAG.getNode(W65816ISD::BR_CC, DL, MVT::Other, Chain, Dest, CCOp,
Glue);
}
SDValue W65816TargetLowering::LowerOperation(SDValue Op,
SelectionDAG &DAG) const {
switch (Op.getOpcode()) {
case ISD::GlobalAddress: return LowerGlobalAddress(Op, DAG);
case ISD::ExternalSymbol: return LowerExternalSymbol(Op, DAG);
case ISD::BR_CC: return LowerBR_CC(Op, DAG);
default:
llvm_unreachable("W65816: unexpected operation in LowerOperation");
}
}
SDValue W65816TargetLowering::LowerGlobalAddress(SDValue Op,
SelectionDAG &DAG) const {
auto *GA = cast<GlobalAddressSDNode>(Op);
SDLoc DL(Op);
SDValue Tgt = DAG.getTargetGlobalAddress(GA->getGlobal(), DL, MVT::i16,
GA->getOffset());
return DAG.getNode(W65816ISD::Wrapper, DL, MVT::i16, Tgt);
}
SDValue W65816TargetLowering::LowerExternalSymbol(SDValue Op,
SelectionDAG &DAG) const {
auto *ES = cast<ExternalSymbolSDNode>(Op);
SDLoc DL(Op);
SDValue Tgt = DAG.getTargetExternalSymbol(ES->getSymbol(), MVT::i16);
return DAG.getNode(W65816ISD::Wrapper, DL, MVT::i16, Tgt);
}
SDValue W65816TargetLowering::LowerFormalArguments(
SDValue Chain, CallingConv::ID CallConv, bool IsVarArg,
const SmallVectorImpl<ISD::InputArg> &Ins, const SDLoc &DL,
SelectionDAG &DAG, SmallVectorImpl<SDValue> &InVals) const {
// ABI: first i16/i8 argument is passed in A; remaining arguments are
// pushed by the caller right-to-left and read via stack-relative
// addressing. After JSL pushes 3 bytes of return address, the layout
// viewed from the callee is:
// (high addr) arg N-1
// ...
// arg 1
// ret-addr-bank <- (4,S) when M=0
// ret-addr-hi <- (3,S)
// ret-addr-lo <- (2,S)
// (low addr) <next push> <- (1,S)
//
// Each i16 stack arg occupies 2 bytes. arg 1 lives at (4,S).
if (IsVarArg)
report_fatal_error("W65816: vararg functions not yet supported");
MachineFunction &MF = DAG.getMachineFunction();
MachineFrameInfo &MFI = MF.getFrameInfo();
MachineRegisterInfo &MRI = MF.getRegInfo();
unsigned ArgIdx = 0;
// Stack offset is measured from S+1 (the WDC convention) and grows
// upward as we walk through the stack-passed args.
unsigned StackOffset = 4; // Skip 3 ret-addr bytes; first slot at S+4.
for (const ISD::InputArg &Arg : Ins) {
MVT VT = Arg.VT;
if (VT != MVT::i16 && VT != MVT::i8)
report_fatal_error("W65816: argument type not yet supported");
if (ArgIdx == 0) {
// First arg in A.
Register VReg = MRI.createVirtualRegister(
VT == MVT::i16 ? &W65816::Acc16RegClass : &W65816::Acc8RegClass);
MRI.addLiveIn(W65816::A, VReg);
InVals.push_back(DAG.getCopyFromReg(Chain, DL, VReg, VT));
} else {
// Subsequent args are loaded from the stack. Use a fixed frame
// object positioned at the absolute stack offset; the
// eliminateFrameIndex pass turns it into LDA d,S.
unsigned ObjSize = (VT == MVT::i16) ? 2 : 1;
int FI = MFI.CreateFixedObject(ObjSize, StackOffset, /*Immutable*/true);
StackOffset += ObjSize;
SDValue FIN = DAG.getFrameIndex(FI, MVT::i16);
InVals.push_back(DAG.getLoad(
VT, DL, Chain, FIN,
MachinePointerInfo::getFixedStack(MF, FI)));
}
++ArgIdx;
}
return Chain;
}
SDValue
W65816TargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI,
SmallVectorImpl<SDValue> &InVals) const {
// Single-arg version: arg 0 in A; LowerFormalArguments accepts
// additional args via the stack, but this side doesn't yet emit the
// pushes. Multi-arg call lowering wants a PUSHA pseudo with proper
// SP unwinding via TSC/ADC #N/TCS in the ADJCALLSTACKUP pseudo —
// tracked separately.
SelectionDAG &DAG = CLI.DAG;
SDLoc &DL = CLI.DL;
SDValue Chain = CLI.Chain;
SDValue Callee = CLI.Callee;
auto &Outs = CLI.Outs;
auto &OutVals = CLI.OutVals;
auto &Ins = CLI.Ins;
if (CLI.IsTailCall)
CLI.IsTailCall = false;
if (Outs.size() > 1)
report_fatal_error("W65816: multi-argument calls not yet supported");
if (Ins.size() > 1)
report_fatal_error("W65816: multi-return calls not yet supported");
Chain = DAG.getCALLSEQ_START(Chain, 0, 0, DL);
SDValue Glue;
if (!OutVals.empty()) {
MVT VT = Outs[0].VT;
Chain = DAG.getCopyToReg(Chain, DL, W65816::A, OutVals[0], Glue);
Glue = Chain.getValue(1);
}
if (auto *GA = dyn_cast<GlobalAddressSDNode>(Callee))
Callee = DAG.getTargetGlobalAddress(GA->getGlobal(), DL, MVT::i16);
else if (auto *ES = dyn_cast<ExternalSymbolSDNode>(Callee))
Callee = DAG.getTargetExternalSymbol(ES->getSymbol(), MVT::i16);
SmallVector<SDValue, 4> CallOps = {Chain, Callee};
if (!OutVals.empty())
CallOps.push_back(DAG.getRegister(W65816::A, Outs[0].VT));
if (Glue.getNode())
CallOps.push_back(Glue);
Chain = DAG.getNode(W65816ISD::CALL, DL,
DAG.getVTList(MVT::Other, MVT::Glue), CallOps);
Glue = Chain.getValue(1);
Chain = DAG.getCALLSEQ_END(Chain, 0, 0, Glue, DL);
Glue = Chain.getValue(1);
for (const ISD::InputArg &Arg : Ins) {
MVT VT = Arg.VT;
if (VT != MVT::i16 && VT != MVT::i8)
report_fatal_error("W65816: return type not yet supported");
SDValue V = DAG.getCopyFromReg(Chain, DL, W65816::A, VT, Glue);
Chain = V.getValue(1);
Glue = V.getValue(2);
InVals.push_back(V);
}
return Chain;
}
SDValue W65816TargetLowering::LowerReturn(
SDValue Chain, CallingConv::ID CallConv, bool IsVarArg,
const SmallVectorImpl<ISD::OutputArg> &Outs,
const SmallVectorImpl<SDValue> &OutVals, const SDLoc &DL,
SelectionDAG &DAG) const {
// Copy scalar return values into A and emit a retglue chain. Supports
// one i16 return today; i8 would use the same A register in 8-bit mode,
// and larger returns (i32 A:X, structures via hidden pointer) are future
// work.
// Copy each scalar return value into A and reference A in the RET_GLUE
// operand list so the register allocator keeps the defining instructions
// alive (otherwise dead-MI elimination strips them — the physreg copy
// alone is not enough of a liveness signal).
SDValue Glue;
SmallVector<SDValue, 4> RetOps(1, Chain);
for (unsigned i = 0, e = Outs.size(); i != e; ++i) {
MVT VT = Outs[i].VT;
if (VT != MVT::i16 && VT != MVT::i8)
report_fatal_error("W65816: return type not yet supported");
Chain = DAG.getCopyToReg(Chain, DL, W65816::A, OutVals[i], Glue);
Glue = Chain.getValue(1);
RetOps.push_back(DAG.getRegister(W65816::A, VT));
}
RetOps[0] = Chain;
if (Glue.getNode())
RetOps.push_back(Glue);
return DAG.getNode(W65816ISD::RET_GLUE, DL, MVT::Other, RetOps);
}

View file

@ -0,0 +1,70 @@
//===-- W65816ISelLowering.h - W65816 DAG Lowering Interface ----*- 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
//
//===----------------------------------------------------------------------===//
//
// This file defines the interfaces that W65816 uses to lower LLVM code into a
// selection DAG.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_W65816_W65816ISELLOWERING_H
#define LLVM_LIB_TARGET_W65816_W65816ISELLOWERING_H
#include "W65816.h"
#include "llvm/CodeGen/TargetLowering.h"
// W65816ISD::{Wrapper, CALL, RET_GLUE, ...} are defined by TableGen in
// W65816GenSDNodeInfo.inc; that header is included transitively via
// W65816SelectionDAGInfo.h.
namespace llvm {
class W65816Subtarget;
class W65816TargetLowering : public TargetLowering {
public:
explicit W65816TargetLowering(const TargetMachine &TM,
const W65816Subtarget &STI);
SDValue LowerFormalArguments(SDValue Chain, CallingConv::ID CallConv,
bool IsVarArg,
const SmallVectorImpl<ISD::InputArg> &Ins,
const SDLoc &DL, SelectionDAG &DAG,
SmallVectorImpl<SDValue> &InVals) const override;
SDValue LowerCall(TargetLowering::CallLoweringInfo &CLI,
SmallVectorImpl<SDValue> &InVals) const override;
SDValue LowerReturn(SDValue Chain, CallingConv::ID CallConv, bool IsVarArg,
const SmallVectorImpl<ISD::OutputArg> &Outs,
const SmallVectorImpl<SDValue> &OutVals, const SDLoc &DL,
SelectionDAG &DAG) const override;
SDValue LowerOperation(SDValue Op, SelectionDAG &DAG) const override;
// The 65816 has no alignment requirement on memory access — any
// address is fine. Telling LLVM this lets it emit single 16-bit
// loads/stores even when the IR alignment is 1, instead of
// synthesising the value from two byte loads + shl-or.
bool allowsMisalignedMemoryAccesses(EVT VT, unsigned AddrSpace = 0,
Align Alignment = Align(1),
MachineMemOperand::Flags Flags =
MachineMemOperand::MONone,
unsigned *Fast = nullptr) const override {
if (Fast) *Fast = 1;
return true;
}
private:
SDValue LowerGlobalAddress(SDValue Op, SelectionDAG &DAG) const;
SDValue LowerExternalSymbol(SDValue Op, SelectionDAG &DAG) const;
SDValue LowerBR_CC(SDValue Op, SelectionDAG &DAG) const;
};
} // namespace llvm
#endif

View file

@ -0,0 +1,281 @@
//===-- W65816InstrFormats.td - W65816 Instruction Formats -*- tablegen -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Instruction format definitions for the W65816. Every WDC 65816 instruction
// is 1-4 bytes: a single-byte opcode followed by 0-3 bytes of immediate data,
// direct-page offset, absolute address, long address, or PC-relative
// displacement.
//
// TSFlags encode which processor-mode bits (M, X) an instruction depends on.
// Bit 0: instruction assumes M clear (16-bit accumulator).
// Bit 1: instruction assumes M set (8-bit accumulator).
// Bit 2: instruction assumes X clear (16-bit index).
// Bit 3: instruction assumes X set (8-bit index).
// Instructions whose behavior does not depend on M/X leave all four bits 0.
// The REP/SEP scheduling pass consumes these bits to decide where to insert
// mode transitions.
//
//===----------------------------------------------------------------------===//
// Base instruction class. Subclasses set Size and fill in the opcode byte
// and operand fields.
class W65816Inst<dag outs, dag ins, string asmstr, list<dag> pattern = []>
: Instruction {
let Namespace = "W65816";
// The disassembler scaffold reads from the default "W65816" decoder table.
// Mode-ambiguous variants (LDA_Imm8, LDX_Imm8, ...) override this to
// "W65816MHigh" or "W65816XHigh" so they end up in separate tables that
// the scaffold does NOT consult, eliminating same-opcode conflicts in the
// default table. See comment in W65816InstrInfo.td.
let DecoderNamespace = "W65816";
let OutOperandList = outs;
let InOperandList = ins;
let AsmString = asmstr;
let Pattern = pattern;
// MC-layer-only instructions conservatively mark side effects so the
// scheduler can't reorder them. Lowering targets override as needed.
let mayLoad = true;
let mayStore = true;
let hasSideEffects = true;
// Mode-dependence bits consumed by the REP/SEP pass.
bit MLow = 0;
bit MHigh = 0;
bit XLow = 0;
bit XHigh = 0;
let TSFlags{0} = MLow;
let TSFlags{1} = MHigh;
let TSFlags{2} = XLow;
let TSFlags{3} = XHigh;
}
// Pseudo-instruction: expands away before emission.
class W65816Pseudo<dag outs, dag ins, string asmstr, list<dag> pattern>
: W65816Inst<outs, ins, asmstr, pattern> {
let isPseudo = 1;
let Size = 0;
let mayLoad = false;
let mayStore = false;
let hasSideEffects = false;
}
//===----------------------------------------------------------------------===//
// Operand classes.
//===----------------------------------------------------------------------===//
// Custom DiagnosticType values would require matching Match_<type> enum
// values in the parser; we rely on the generic Match_InvalidOperand
// message for now.
class W65816AsmOperand<string name> : AsmOperandClass {
let Name = name;
}
class W65816ImmOp<string name> : W65816AsmOperand<name> {
let RenderMethod = "addImmOperands";
}
class W65816AddrOp<string name> : W65816AsmOperand<name>;
class W65816PCRelOp<string name> : W65816AsmOperand<name>;
def imm8 : Operand<i16> {
let ParserMatchClass = W65816ImmOp<"Imm8">;
let OperandType = "OPERAND_IMMEDIATE";
let Type = i8;
let EncoderMethod = "encodeImm8";
let DecoderMethod = "decodeImm8";
}
def imm16 : Operand<i16> {
let ParserMatchClass = W65816ImmOp<"Imm16">;
let OperandType = "OPERAND_IMMEDIATE";
let Type = i16;
let EncoderMethod = "encodeImm16";
let DecoderMethod = "decodeImm16";
}
def addrDP : Operand<i16> {
let ParserMatchClass = W65816AddrOp<"AddrDP">;
let OperandType = "OPERAND_MEMORY";
let Type = i8;
let PrintMethod = "printAddrDP";
let EncoderMethod = "encodeAddr8";
let DecoderMethod = "decodeAddr8";
}
def addrAbs : Operand<i16> {
let ParserMatchClass = W65816AddrOp<"AddrAbs">;
let OperandType = "OPERAND_MEMORY";
let Type = i16;
let PrintMethod = "printAddrAbs";
let EncoderMethod = "encodeAddr16";
let DecoderMethod = "decodeAddr16";
}
def addrLong : Operand<i32> {
let ParserMatchClass = W65816AddrOp<"AddrLong">;
let OperandType = "OPERAND_MEMORY";
let Type = i32;
let PrintMethod = "printAddrLong";
let EncoderMethod = "encodeAddr24";
let DecoderMethod = "decodeAddr24";
}
def pcrel8 : Operand<OtherVT> {
let ParserMatchClass = W65816PCRelOp<"PCRel8">;
let OperandType = "OPERAND_PCREL";
let PrintMethod = "printPCRel8";
let EncoderMethod = "encodePCRel8";
let DecoderMethod = "decodePCRel8";
}
def pcrel16 : Operand<OtherVT> {
let ParserMatchClass = W65816PCRelOp<"PCRel16">;
let OperandType = "OPERAND_PCREL";
let PrintMethod = "printPCRel16";
let EncoderMethod = "encodePCRel16";
let DecoderMethod = "decodePCRel16";
}
//===----------------------------------------------------------------------===//
// Size-classified base instructions. Size is in bytes including opcode.
//===----------------------------------------------------------------------===//
// Single-byte implied-operand instruction.
class Inst1<bits<8> opcode, string asmstr>
: W65816Inst<(outs), (ins), asmstr> {
let Size = 1;
bits<8> Inst;
let Inst{7-0} = opcode;
}
//===----------------------------------------------------------------------===//
// Addressing-mode shorthand classes.
//
// Each class declares a concrete 16/24/32-bit instruction encoding and wires
// the named operand(s) into the bit positions following the opcode byte.
// Without the explicit `let Inst{...} = name;` assignments, tablegen emits an
// encoder that writes the opcode but leaves the operand bytes as zero.
//===----------------------------------------------------------------------===//
class InstImplied<bits<8> op, string mnem> : Inst1<op, mnem>;
class InstImm8<bits<8> op, string mnem>
: W65816Inst<(outs), (ins imm8:$imm), !strconcat(mnem, "\t#$imm")> {
let Size = 2;
bits<8> imm;
bits<16> Inst;
let Inst{7-0} = op;
let Inst{15-8} = imm;
}
class InstImm16<bits<8> op, string mnem>
: W65816Inst<(outs), (ins imm16:$imm), !strconcat(mnem, "\t#$imm")> {
let Size = 3;
bits<16> imm;
bits<24> Inst;
let Inst{7-0} = op;
let Inst{23-8} = imm;
}
class InstDP<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrDP:$addr), !strconcat(mnem, "\t$addr")> {
let Size = 2;
bits<8> addr;
bits<16> Inst;
let Inst{7-0} = op;
let Inst{15-8} = addr;
}
class InstAbs<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrAbs:$addr), !strconcat(mnem, "\t$addr")> {
let Size = 3;
bits<16> addr;
bits<24> Inst;
let Inst{7-0} = op;
let Inst{23-8} = addr;
}
class InstAbsLong<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrLong:$addr), !strconcat(mnem, "\t$addr")> {
let Size = 4;
bits<24> addr;
bits<32> Inst;
let Inst{7-0} = op;
let Inst{31-8} = addr;
}
class InstAbsX<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrAbs:$addr), !strconcat(mnem, "\t$addr, x")> {
let Size = 3;
bits<16> addr;
bits<24> Inst;
let Inst{7-0} = op;
let Inst{23-8} = addr;
}
class InstAbsY<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrAbs:$addr), !strconcat(mnem, "\t$addr, y")> {
let Size = 3;
bits<16> addr;
bits<24> Inst;
let Inst{7-0} = op;
let Inst{23-8} = addr;
}
class InstDPX<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrDP:$addr), !strconcat(mnem, "\t$addr, x")> {
let Size = 2;
bits<8> addr;
bits<16> Inst;
let Inst{7-0} = op;
let Inst{15-8} = addr;
}
class InstDPY<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrDP:$addr), !strconcat(mnem, "\t$addr, y")> {
let Size = 2;
bits<8> addr;
bits<16> Inst;
let Inst{7-0} = op;
let Inst{15-8} = addr;
}
// Stack-relative addressing. Operand is an unsigned 8-bit offset added
// to S; reads / writes the byte (or word, in 16-bit M mode) at that
// stack address. Shares the addrDP operand class for parsing.
class InstStackRel<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrDP:$off), !strconcat(mnem, "\t$off, s")> {
let Size = 2;
bits<8> off;
bits<16> Inst;
let Inst{7-0} = op;
let Inst{15-8} = off;
}
class InstPCRel8<bits<8> op, string mnem>
: W65816Inst<(outs), (ins pcrel8:$dest), !strconcat(mnem, "\t$dest")> {
let Size = 2;
bits<8> dest;
bits<16> Inst;
let Inst{7-0} = op;
let Inst{15-8} = dest;
}
class InstPCRel16<bits<8> op, string mnem>
: W65816Inst<(outs), (ins pcrel16:$dest), !strconcat(mnem, "\t$dest")> {
let Size = 3;
bits<16> dest;
bits<24> Inst;
let Inst{7-0} = op;
let Inst{23-8} = dest;
}
// Branch / return / call flags are applied at the def site via `let`
// blocks (idiomatic LLVM approach) rather than through mixin classes, since
// TableGen's multi-inheritance can't override fields from unrelated bases.

View file

@ -0,0 +1,73 @@
//===-- W65816InstrInfo.cpp - W65816 Instruction Information --------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Skeleton instruction-info implementation. Real register copy and stack
// spill/reload lowering will be added once the instruction set is described.
//
//===----------------------------------------------------------------------===//
#include "W65816InstrInfo.h"
#include "W65816.h"
#include "W65816Subtarget.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/Support/ErrorHandling.h"
using namespace llvm;
#define GET_INSTRINFO_CTOR_DTOR
#include "W65816GenInstrInfo.inc"
void W65816InstrInfo::anchor() {}
W65816InstrInfo::W65816InstrInfo(const W65816Subtarget &STI)
: W65816GenInstrInfo(STI, RI, W65816::ADJCALLSTACKDOWN,
W65816::ADJCALLSTACKUP),
RI() {}
void W65816InstrInfo::copyPhysReg(MachineBasicBlock &MBB,
MachineBasicBlock::iterator I,
const DebugLoc &DL, Register DestReg,
Register SrcReg, bool KillSrc,
bool RenamableDest, bool RenamableSrc) const {
// The only Acc16 register is A; copies between A and itself are no-ops.
// Cross-class copies (e.g. A → X) need TAX/TXA pairs which we don't
// need yet — bail loudly so we notice when the time comes.
if (DestReg == SrcReg)
return;
if (DestReg == W65816::A && SrcReg == W65816::A)
return;
llvm_unreachable("W65816: cross-class copyPhysReg not yet implemented");
}
void W65816InstrInfo::storeRegToStackSlot(
MachineBasicBlock &MBB, MachineBasicBlock::iterator MI, Register SrcReg,
bool isKill, int FrameIdx, const TargetRegisterClass *RC, Register VReg,
MachineInstr::MIFlag Flags) const {
// STAfi gets eliminated by W65816RegisterInfo::eliminateFrameIndex into
// a real STA d,S. Source is implicit A; emit the pseudo with the FI
// and zero offset.
DebugLoc DL = MI != MBB.end() ? MI->getDebugLoc() : DebugLoc();
BuildMI(MBB, MI, DL, get(W65816::STAfi))
.addReg(SrcReg, getKillRegState(isKill))
.addFrameIndex(FrameIdx)
.addImm(0);
}
void W65816InstrInfo::loadRegFromStackSlot(MachineBasicBlock &MBB,
MachineBasicBlock::iterator MI,
Register DestReg, int FrameIdx,
const TargetRegisterClass *RC,
Register VReg, unsigned SubReg,
MachineInstr::MIFlag Flags) const {
// Mirror image of storeRegToStackSlot: emit LDAfi, which the frame
// index pass turns into LDA d,S.
DebugLoc DL = MI != MBB.end() ? MI->getDebugLoc() : DebugLoc();
BuildMI(MBB, MI, DL, get(W65816::LDAfi), DestReg)
.addFrameIndex(FrameIdx)
.addImm(0);
}

View file

@ -0,0 +1,53 @@
//===-- W65816InstrInfo.h - W65816 Instruction Information ------*- 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
//
//===----------------------------------------------------------------------===//
//
// This file contains the W65816 implementation of the TargetInstrInfo class.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_W65816_W65816INSTRINFO_H
#define LLVM_LIB_TARGET_W65816_W65816INSTRINFO_H
#include "W65816RegisterInfo.h"
#include "llvm/CodeGen/TargetInstrInfo.h"
#define GET_INSTRINFO_HEADER
#include "W65816GenInstrInfo.inc"
namespace llvm {
class W65816Subtarget;
class W65816InstrInfo : public W65816GenInstrInfo {
const W65816RegisterInfo RI;
virtual void anchor();
public:
explicit W65816InstrInfo(const W65816Subtarget &STI);
const W65816RegisterInfo &getRegisterInfo() const { return RI; }
void copyPhysReg(MachineBasicBlock &MBB, MachineBasicBlock::iterator I,
const DebugLoc &DL, Register DestReg, Register SrcReg,
bool KillSrc, bool RenamableDest = false,
bool RenamableSrc = false) const override;
void storeRegToStackSlot(
MachineBasicBlock &MBB, MachineBasicBlock::iterator MI, Register SrcReg,
bool isKill, int FrameIndex, const TargetRegisterClass *RC, Register VReg,
MachineInstr::MIFlag Flags = MachineInstr::NoFlags) const override;
void loadRegFromStackSlot(
MachineBasicBlock &MBB, MachineBasicBlock::iterator MI, Register DestReg,
int FrameIdx, const TargetRegisterClass *RC, Register VReg,
unsigned SubReg = 0,
MachineInstr::MIFlag Flags = MachineInstr::NoFlags) const override;
};
} // namespace llvm
#endif

View file

@ -0,0 +1,776 @@
//===-- W65816InstrInfo.td - W65816 Instruction defs -------*- tablegen -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// W65816 instruction description. This file defines the MC-layer instruction
// encodings for the core 65816 instruction set. DAG-selection patterns will
// be added incrementally on top of these MC instructions.
//
//===----------------------------------------------------------------------===//
include "W65816InstrFormats.td"
//===----------------------------------------------------------------------===//
// Type Profiles
//===----------------------------------------------------------------------===//
def SDT_W65816Call : SDTypeProfile<0, -1, [SDTCisVT<0, iPTR>]>;
def SDT_W65816CallSeqStart : SDCallSeqStart<[SDTCisVT<0, i16>,
SDTCisVT<1, i16>]>;
def SDT_W65816CallSeqEnd : SDCallSeqEnd<[SDTCisVT<0, i16>, SDTCisVT<1, i16>]>;
def SDT_W65816Wrapper : SDTypeProfile<1, 1, [SDTCisSameAs<0, 1>,
SDTCisPtrTy<0>]>;
def SDT_W65816Cmp : SDTypeProfile<0, 2, [SDTCisSameAs<0, 1>,
SDTCisInt<0>]>;
// (CMP allows both i16 and i8 operands.)
def SDT_W65816BrCC : SDTypeProfile<0, 2, [SDTCisVT<0, OtherVT>,
SDTCisVT<1, i8>]>;
//===----------------------------------------------------------------------===//
// W65816-specific SDNodes
//===----------------------------------------------------------------------===//
def W65816retglue : SDNode<"W65816ISD::RET_GLUE", SDTNone,
[SDNPHasChain, SDNPOptInGlue, SDNPVariadic]>;
def W65816call : SDNode<"W65816ISD::CALL", SDT_W65816Call,
[SDNPHasChain, SDNPOutGlue, SDNPOptInGlue,
SDNPVariadic]>;
def W65816callseq_start :
SDNode<"ISD::CALLSEQ_START", SDT_W65816CallSeqStart,
[SDNPHasChain, SDNPOutGlue]>;
def W65816callseq_end :
SDNode<"ISD::CALLSEQ_END", SDT_W65816CallSeqEnd,
[SDNPHasChain, SDNPOptInGlue, SDNPOutGlue]>;
def W65816Wrapper : SDNode<"W65816ISD::Wrapper", SDT_W65816Wrapper>;
// Comparison: produces a Glue value (carrying processor flags).
def W65816cmp : SDNode<"W65816ISD::CMP", SDT_W65816Cmp, [SDNPOutGlue]>;
// Conditional branch: takes (Chain, Dest, CC, Glue from CMP).
def W65816brcc : SDNode<"W65816ISD::BR_CC", SDT_W65816BrCC,
[SDNPHasChain, SDNPInGlue]>;
//===----------------------------------------------------------------------===//
// Pseudo Instructions
//===----------------------------------------------------------------------===//
let Defs = [SP], Uses = [SP] in {
def ADJCALLSTACKDOWN : W65816Pseudo<(outs),
(ins i16imm:$amt1, i16imm:$amt2),
"# ADJCALLSTACKDOWN $amt1 $amt2",
[(W65816callseq_start timm:$amt1,
timm:$amt2)]>;
def ADJCALLSTACKUP : W65816Pseudo<(outs),
(ins i16imm:$amt1, i16imm:$amt2),
"# ADJCALLSTACKUP $amt1 $amt2",
[(W65816callseq_end timm:$amt1,
timm:$amt2)]>;
}
let isReMaterializable = 1 in
def ADDframe : W65816Pseudo<(outs PtrRegs:$dst),
(ins i16imm:$base, i16imm:$offset),
"# ADDframe PSEUDO", []>;
// The retglue node lowers directly to RTL (see Returns section below).
// No separate RET pseudo the real MC instruction handles the pattern.
//===----------------------------------------------------------------------===//
// Codegen pseudos that expand to MC instructions in the AsmPrinter.
//
// These pseudos carry DAG patterns with explicit output operands so the
// generic code generator can allocate them; the MC-layer instructions they
// expand to have the opcode encoding but no virtual output (the result lives
// in the implicit A register). W65816AsmPrinter::emitInstruction maps each
// pseudo here to its real MC counterpart.
//===----------------------------------------------------------------------===//
let isAsCheapAsAMove = 1, isReMaterializable = 1,
hasSideEffects = 0, mayLoad = 0, mayStore = 0 in {
def LDAi16imm : W65816Pseudo<(outs Acc16:$dst), (ins i16imm:$imm),
"# LDAi16imm $dst, $imm",
[(set Acc16:$dst, (i16 imm:$imm))]>;
def LDAi8imm : W65816Pseudo<(outs Acc8:$dst), (ins i8imm:$imm),
"# LDAi8imm $dst, $imm",
[(set Acc8:$dst, (i8 imm:$imm))]>;
}
// Materialise a 16-bit address (global / external symbol) into A. Same
// pseudo as for an immediate constant it expands to LDA_Imm16 with the
// symbol as the operand, which the MC encoder turns into a fixup_16.
def : Pat<(i16 (W65816Wrapper tglobaladdr:$g)),
(LDAi16imm tglobaladdr:$g)>;
def : Pat<(i16 (W65816Wrapper texternalsym:$s)),
(LDAi16imm texternalsym:$s)>;
// 8-bit add/sub of an immediate.
let Constraints = "$src = $dst",
hasSideEffects = 0, mayLoad = 0, mayStore = 0 in {
def ADCi8imm : W65816Pseudo<(outs Acc8:$dst),
(ins Acc8:$src, i8imm:$imm),
"# ADCi8imm $dst, $src, $imm",
[(set Acc8:$dst, (add Acc8:$src, imm:$imm))]>;
def SBCi8imm : W65816Pseudo<(outs Acc8:$dst),
(ins Acc8:$src, i8imm:$imm),
"# SBCi8imm $dst, $src, $imm",
[(set Acc8:$dst, (sub Acc8:$src, imm:$imm))]>;
def ANDi8imm : W65816Pseudo<(outs Acc8:$dst),
(ins Acc8:$src, i8imm:$imm),
"# ANDi8imm $dst, $src, $imm",
[(set Acc8:$dst, (and Acc8:$src, imm:$imm))]>;
def ORAi8imm : W65816Pseudo<(outs Acc8:$dst),
(ins Acc8:$src, i8imm:$imm),
"# ORAi8imm $dst, $src, $imm",
[(set Acc8:$dst, (or Acc8:$src, imm:$imm))]>;
def EORi8imm : W65816Pseudo<(outs Acc8:$dst),
(ins Acc8:$src, i8imm:$imm),
"# EORi8imm $dst, $src, $imm",
[(set Acc8:$dst, (xor Acc8:$src, imm:$imm))]>;
}
// 8-bit load / store via a 16-bit absolute address.
let mayLoad = 1, hasSideEffects = 0, mayStore = 0 in {
def LDA8abs : W65816Pseudo<(outs Acc8:$dst), (ins i32imm:$addr),
"# LDA8abs $dst, $addr", []>;
}
let mayStore = 1, hasSideEffects = 0, mayLoad = 0 in {
def STA8abs : W65816Pseudo<(outs), (ins Acc8:$src, i32imm:$addr),
"# STA8abs $src, $addr", []>;
}
def : Pat<(i8 (load (W65816Wrapper tglobaladdr:$g))),
(LDA8abs tglobaladdr:$g)>;
def : Pat<(i8 (load (W65816Wrapper texternalsym:$s))),
(LDA8abs texternalsym:$s)>;
def : Pat<(store Acc8:$src, (W65816Wrapper tglobaladdr:$g)),
(STA8abs Acc8:$src, tglobaladdr:$g)>;
def : Pat<(store Acc8:$src, (W65816Wrapper texternalsym:$s)),
(STA8abs Acc8:$src, texternalsym:$s)>;
// Load 16 bits via a 16-bit absolute address. Currently only matches
// loads from a Wrapper(global); direct constant-pointer loads come once
// we add an addressing-mode complex pattern.
let mayLoad = 1, hasSideEffects = 0, mayStore = 0 in {
def LDAabs : W65816Pseudo<(outs Acc16:$dst), (ins i32imm:$addr),
"# LDAabs $dst, $addr", []>;
}
def : Pat<(i16 (load (W65816Wrapper tglobaladdr:$g))),
(LDAabs tglobaladdr:$g)>;
def : Pat<(i16 (load (W65816Wrapper texternalsym:$s))),
(LDAabs texternalsym:$s)>;
// Store 16 bits to a 16-bit absolute address.
let mayStore = 1, hasSideEffects = 0, mayLoad = 0 in {
def STAabs : W65816Pseudo<(outs), (ins Acc16:$src, i32imm:$addr),
"# STAabs $src, $addr", []>;
}
def : Pat<(store Acc16:$src, (W65816Wrapper tglobaladdr:$g)),
(STAabs Acc16:$src, tglobaladdr:$g)>;
def : Pat<(store Acc16:$src, (W65816Wrapper texternalsym:$s)),
(STAabs Acc16:$src, texternalsym:$s)>;
// 16-bit ADD: expands to CLC + ADC_Imm16. The 65816 ADC sums with the
// carry flag, so a clean add needs CLC first. Constraints tie the
// source and dest to A there is only one Acc16 register so this is
// implicit, but stating it lets the register allocator coalesce
// without needing a COPY.
let Constraints = "$src = $dst",
hasSideEffects = 0, mayLoad = 0, mayStore = 0 in {
def ADCi16imm : W65816Pseudo<(outs Acc16:$dst),
(ins Acc16:$src, i16imm:$imm),
"# ADCi16imm $dst, $src, $imm",
[(set Acc16:$dst,
(add Acc16:$src, imm:$imm))]>;
def SBCi16imm : W65816Pseudo<(outs Acc16:$dst),
(ins Acc16:$src, i16imm:$imm),
"# SBCi16imm $dst, $src, $imm",
[(set Acc16:$dst,
(sub Acc16:$src, imm:$imm))]>;
}
// ADC/SBC from a 16-bit absolute address. Folds a load on the
// right-hand side of an add/sub into the carry-arithmetic op.
let Constraints = "$src = $dst",
hasSideEffects = 0, mayLoad = 1, mayStore = 0 in {
def ADCabs : W65816Pseudo<(outs Acc16:$dst),
(ins Acc16:$src, i32imm:$addr),
"# ADCabs $dst, $src, $addr", []>;
def SBCabs : W65816Pseudo<(outs Acc16:$dst),
(ins Acc16:$src, i32imm:$addr),
"# SBCabs $dst, $src, $addr", []>;
}
def : Pat<(add Acc16:$src,
(i16 (load (W65816Wrapper tglobaladdr:$g)))),
(ADCabs Acc16:$src, tglobaladdr:$g)>;
def : Pat<(add Acc16:$src,
(i16 (load (W65816Wrapper texternalsym:$s)))),
(ADCabs Acc16:$src, texternalsym:$s)>;
def : Pat<(sub Acc16:$src,
(i16 (load (W65816Wrapper tglobaladdr:$g)))),
(SBCabs Acc16:$src, tglobaladdr:$g)>;
def : Pat<(sub Acc16:$src,
(i16 (load (W65816Wrapper texternalsym:$s)))),
(SBCabs Acc16:$src, texternalsym:$s)>;
// (add Acc16, Acc16) same value added to itself, equivalent to a 1-bit
// left shift. Pattern needs a tied input so the result lands in A.
let Constraints = "$src = $dst",
hasSideEffects = 0, mayLoad = 0, mayStore = 0 in {
def ASLA16 : W65816Pseudo<(outs Acc16:$dst), (ins Acc16:$src),
"# ASLA16 $dst, $src",
[(set Acc16:$dst, (add Acc16:$src, Acc16:$src))]>;
}
// 1-bit shift left of the accumulator: shl x, 1.
def : Pat<(shl Acc16:$src, (i16 1)), (ASLA16 Acc16:$src)>;
// 1-bit logical shift right. Pseudo because the MC LSR_A has no
// virtual output operand.
let Constraints = "$src = $dst",
hasSideEffects = 0, mayLoad = 0, mayStore = 0 in {
def LSRA16 : W65816Pseudo<(outs Acc16:$dst), (ins Acc16:$src),
"# LSRA16 $dst, $src",
[(set Acc16:$dst, (srl Acc16:$src, (i16 1)))]>;
def ASLA8 : W65816Pseudo<(outs Acc8:$dst), (ins Acc8:$src),
"# ASLA8 $dst, $src",
[(set Acc8:$dst, (shl Acc8:$src, (i8 1)))]>;
def LSRA8 : W65816Pseudo<(outs Acc8:$dst), (ins Acc8:$src),
"# LSRA8 $dst, $src",
[(set Acc8:$dst, (srl Acc8:$src, (i8 1)))]>;
// Signed shift right by 1: copy A's high bit into carry, then ROR
// to bring it back into A's high bit while halving the rest. The
// AsmPrinter expands this to the 4-instruction PHA;ASL;PLA;ROR
// sequence.
def ASRA16 : W65816Pseudo<(outs Acc16:$dst), (ins Acc16:$src),
"# ASRA16 $dst, $src",
[(set Acc16:$dst, (sra Acc16:$src, (i16 1)))]> {
let Constraints = "$src = $dst";
}
}
// Shifts by small constants unroll into 2-4 single-bit shifts.
// Anything beyond 4 bits would benefit from a loop or a XBA-and-mask
// trick; left for a future peephole.
def : Pat<(shl Acc16:$src, (i16 2)), (ASLA16 (ASLA16 Acc16:$src))>;
def : Pat<(shl Acc16:$src, (i16 3)),
(ASLA16 (ASLA16 (ASLA16 Acc16:$src)))>;
def : Pat<(shl Acc16:$src, (i16 4)),
(ASLA16 (ASLA16 (ASLA16 (ASLA16 Acc16:$src))))>;
def : Pat<(srl Acc16:$src, (i16 2)), (LSRA16 (LSRA16 Acc16:$src))>;
def : Pat<(srl Acc16:$src, (i16 3)),
(LSRA16 (LSRA16 (LSRA16 Acc16:$src)))>;
def : Pat<(srl Acc16:$src, (i16 4)),
(LSRA16 (LSRA16 (LSRA16 (LSRA16 Acc16:$src))))>;
// Increment / decrement of A by 1. Match `(add x, 1)` and `(add x, -1)`
// (LLVM canonicalises sub-by-1 to add-by-(-1)).
let Constraints = "$src = $dst",
hasSideEffects = 0, mayLoad = 0, mayStore = 0 in {
def INA_PSEUDO : W65816Pseudo<(outs Acc16:$dst), (ins Acc16:$src),
"# INA_PSEUDO $dst, $src",
[(set Acc16:$dst, (add Acc16:$src, (i16 1)))]>;
def DEA_PSEUDO : W65816Pseudo<(outs Acc16:$dst), (ins Acc16:$src),
"# DEA_PSEUDO $dst, $src",
[(set Acc16:$dst, (add Acc16:$src, (i16 -1)))]>;
def INA_PSEUDO8 : W65816Pseudo<(outs Acc8:$dst), (ins Acc8:$src),
"# INA_PSEUDO8 $dst, $src",
[(set Acc8:$dst, (add Acc8:$src, (i8 1)))]>;
def DEA_PSEUDO8 : W65816Pseudo<(outs Acc8:$dst), (ins Acc8:$src),
"# DEA_PSEUDO8 $dst, $src",
[(set Acc8:$dst, (add Acc8:$src, (i8 -1)))]>;
}
// Two's-complement negation: `0 - x` `EOR #$FFFF; INC A` (i.e.
// bitwise-not then add 1). Catches (sub 0, x) which LLVM uses for
// `-x` and the `abs` intrinsic.
let Constraints = "$src = $dst",
hasSideEffects = 0, mayLoad = 0, mayStore = 0 in {
def NEGA16 : W65816Pseudo<(outs Acc16:$dst), (ins Acc16:$src),
"# NEGA16 $dst, $src",
[(set Acc16:$dst, (sub (i16 0), Acc16:$src))]>;
}
// Bitwise NOT pattern moved below EORi16imm definition.
// 16-bit bitwise ops: AND / OR / XOR against an immediate or memory
// operand. Same shape as ADCi16imm / ADCabs minus the carry prefix
// (these don't read/write the carry flag).
let Constraints = "$src = $dst",
hasSideEffects = 0, mayLoad = 0, mayStore = 0 in {
def ANDi16imm : W65816Pseudo<(outs Acc16:$dst),
(ins Acc16:$src, i16imm:$imm),
"# ANDi16imm $dst, $src, $imm",
[(set Acc16:$dst,
(and Acc16:$src, imm:$imm))]>;
def ORAi16imm : W65816Pseudo<(outs Acc16:$dst),
(ins Acc16:$src, i16imm:$imm),
"# ORAi16imm $dst, $src, $imm",
[(set Acc16:$dst,
(or Acc16:$src, imm:$imm))]>;
def EORi16imm : W65816Pseudo<(outs Acc16:$dst),
(ins Acc16:$src, i16imm:$imm),
"# EORi16imm $dst, $src, $imm",
[(set Acc16:$dst,
(xor Acc16:$src, imm:$imm))]>;
}
let Constraints = "$src = $dst",
hasSideEffects = 0, mayLoad = 1, mayStore = 0 in {
def ANDabs : W65816Pseudo<(outs Acc16:$dst),
(ins Acc16:$src, i32imm:$addr),
"# ANDabs $dst, $src, $addr", []>;
def ORAabs : W65816Pseudo<(outs Acc16:$dst),
(ins Acc16:$src, i32imm:$addr),
"# ORAabs $dst, $src, $addr", []>;
def EORabs : W65816Pseudo<(outs Acc16:$dst),
(ins Acc16:$src, i32imm:$addr),
"# EORabs $dst, $src, $addr", []>;
}
def : Pat<(and Acc16:$src, (i16 (load (W65816Wrapper tglobaladdr:$g)))),
(ANDabs Acc16:$src, tglobaladdr:$g)>;
def : Pat<(or Acc16:$src, (i16 (load (W65816Wrapper tglobaladdr:$g)))),
(ORAabs Acc16:$src, tglobaladdr:$g)>;
def : Pat<(xor Acc16:$src, (i16 (load (W65816Wrapper tglobaladdr:$g)))),
(EORabs Acc16:$src, tglobaladdr:$g)>;
// Bitwise NOT: x ^ 0xFFFF. LLVM lowers `~x` and i1 inversion through
// this; emit a single EOR #$FFFF via the bitwise pseudo above.
def : Pat<(xor Acc16:$src, (i16 -1)),
(EORi16imm Acc16:$src, 0xFFFF)>;
// Frame-index loads/stores: take a FrameIndex + offset (packed into a
// single MIOperandInfo) and expand (in eliminateFrameIndex) into an
// LDA / STA d,S with the offset baked in. Used by LowerFormalArguments
// to read stack-passed arguments and by spill/reload via
// storeRegToStackSlot.
def memfi : Operand<i16> {
let MIOperandInfo = (ops i32imm, i32imm);
let PrintMethod = "printFrameMem";
}
let mayLoad = 1, hasSideEffects = 0, mayStore = 0 in {
def LDAfi : W65816Pseudo<(outs Acc16:$dst), (ins memfi:$addr),
"# LDAfi $dst, $addr", []>;
}
let mayStore = 1, hasSideEffects = 0, mayLoad = 0 in {
def STAfi : W65816Pseudo<(outs),
(ins Acc16:$src, memfi:$addr),
"# STAfi $src, $addr", []>;
}
// ComplexPattern bridging FrameIndex SDValues to memfi. See
// SelectFrameIndex in W65816ISelDAGToDAG.cpp.
def addr_fi : ComplexPattern<i16, 2, "SelectFrameIndex", [frameindex]>;
def : Pat<(i16 (load addr_fi:$addr)),
(LDAfi addr_fi:$addr)>;
def : Pat<(store Acc16:$src, addr_fi:$addr),
(STAfi Acc16:$src, addr_fi:$addr)>;
// Frame-index folding into ADC / SBC / AND / ORA / EOR / CMP. Same
// shape as the *abs variants but the second operand is a stack slot.
let Constraints = "$src = $dst",
hasSideEffects = 0, mayLoad = 1, mayStore = 0 in {
def ADCfi : W65816Pseudo<(outs Acc16:$dst), (ins Acc16:$src, memfi:$addr),
"# ADCfi $dst, $src, $addr", []>;
def SBCfi : W65816Pseudo<(outs Acc16:$dst), (ins Acc16:$src, memfi:$addr),
"# SBCfi $dst, $src, $addr", []>;
def ANDfi : W65816Pseudo<(outs Acc16:$dst), (ins Acc16:$src, memfi:$addr),
"# ANDfi $dst, $src, $addr", []>;
def ORAfi : W65816Pseudo<(outs Acc16:$dst), (ins Acc16:$src, memfi:$addr),
"# ORAfi $dst, $src, $addr", []>;
def EORfi : W65816Pseudo<(outs Acc16:$dst), (ins Acc16:$src, memfi:$addr),
"# EORfi $dst, $src, $addr", []>;
}
let hasSideEffects = 0, mayLoad = 1, mayStore = 0, Defs = [P] in {
def CMPfi : W65816Pseudo<(outs), (ins Acc16:$lhs, memfi:$addr),
"# CMPfi $lhs, $addr", []>;
}
def : Pat<(add Acc16:$src, (i16 (load addr_fi:$addr))),
(ADCfi Acc16:$src, addr_fi:$addr)>;
def : Pat<(sub Acc16:$src, (i16 (load addr_fi:$addr))),
(SBCfi Acc16:$src, addr_fi:$addr)>;
def : Pat<(and Acc16:$src, (i16 (load addr_fi:$addr))),
(ANDfi Acc16:$src, addr_fi:$addr)>;
def : Pat<(or Acc16:$src, (i16 (load addr_fi:$addr))),
(ORAfi Acc16:$src, addr_fi:$addr)>;
def : Pat<(xor Acc16:$src, (i16 (load addr_fi:$addr))),
(EORfi Acc16:$src, addr_fi:$addr)>;
def : Pat<(W65816cmp Acc16:$lhs, (i16 (load addr_fi:$addr))),
(CMPfi Acc16:$lhs, addr_fi:$addr)>;
// Zero-extending byte load: 16-bit LDA reads two bytes (the byte we want
// plus the next byte), then mask the high byte with AND #$00FF. Reads
// one byte past the source fine for standalone bytes in the bank-0
// data area but caller must ensure addr+1 is safe to read. A future
// optimisation could use SEP/REP transitions to do a true 8-bit load.
def : Pat<(i16 (zextloadi8 (W65816Wrapper tglobaladdr:$g))),
(ANDi16imm (LDAabs tglobaladdr:$g), 0xFF)>;
def : Pat<(i16 (zextloadi8 (W65816Wrapper texternalsym:$s))),
(ANDi16imm (LDAabs texternalsym:$s), 0xFF)>;
// CMP / branches. CMP sets the flags via the W65816cmp SDNode (glue
// out); the W65816brcc node consumes the glue and dispatches to the
// right Bxx instruction by condition code.
let hasSideEffects = 0, mayLoad = 0, mayStore = 0, Defs = [P] in {
def CMPi16imm : W65816Pseudo<(outs), (ins Acc16:$lhs, i16imm:$rhs),
"# CMPi16imm $lhs, $rhs",
[(W65816cmp Acc16:$lhs, (i16 imm:$rhs))]>;
def CMPi8imm : W65816Pseudo<(outs), (ins Acc8:$lhs, i8imm:$rhs),
"# CMPi8imm $lhs, $rhs",
[(W65816cmp Acc8:$lhs, (i8 imm:$rhs))]>;
}
let hasSideEffects = 0, mayLoad = 1, mayStore = 0, Defs = [P] in {
def CMPabs : W65816Pseudo<(outs), (ins Acc16:$lhs, i32imm:$addr),
"# CMPabs $lhs, $addr", []>;
}
def : Pat<(W65816cmp Acc16:$lhs,
(i16 (load (W65816Wrapper tglobaladdr:$g)))),
(CMPabs Acc16:$lhs, tglobaladdr:$g)>;
def : Pat<(W65816cmp Acc16:$lhs,
(i16 (load (W65816Wrapper texternalsym:$s)))),
(CMPabs Acc16:$lhs, texternalsym:$s)>;
// Two-Acc16 ops: deferred needs proper frame setup so the register
// allocator can spill one operand to a local stack slot. Without
// reserved frame space, the spill goes to a negative SP offset and
// eliminateFrameIndex bails. See SESSION_STATE §6 for the
// dependency chain.
// (memory inc/dec patterns moved below INC_Abs/DEC_Abs defs.)
// (Branch patterns moved below the Real Instructions section since
// they reference instruction defs.)
//===----------------------------------------------------------------------===//
// Real Instructions
//
// Opcodes taken from the WDC W65C816S data sheet. Instructions whose size
// depends on the M or X bits exist in two variants (Imm8 / Imm16) and carry
// TSFlags bits indicating which processor mode they assume; the REP/SEP
// scheduling pass uses those to verify/insert mode transitions.
//
// Disassembler note: for every opcode that has both an _Imm8 and an _Imm16
// form (LDA/LDX/LDY/ADC/SBC/CMP/AND/ORA/EOR/BIT/CPX/CPY), the two forms share
// the same opcode byte but differ in operand width according to M/X mode.
// The scaffold disassembler only consults the default "W65816" decoder
// table, so we push the _Imm8 variants into namespaces "W65816MHigh" /
// "W65816XHigh". That keeps only one variant per opcode in the default
// table (the 3-byte _Imm16 form for M-dependent insns, and the 3-byte
// _Imm16 form for X-dependent insns), so `llvm-objdump -d` always decodes
// these as 16-bit immediates until the mode-aware decoder lands.
//===----------------------------------------------------------------------===//
//---------------------------------------------------------------- CPU control
def NOP : InstImplied<0xEA, "nop"> {
let mayLoad = 0; let mayStore = 0; let hasSideEffects = 0;
}
def REP : InstImm8<0xC2, "rep"> {
let hasSideEffects = 1;
let mayLoad = 0; let mayStore = 0;
}
def SEP : InstImm8<0xE2, "sep"> {
let hasSideEffects = 1;
let mayLoad = 0; let mayStore = 0;
}
def CLC : InstImplied<0x18, "clc"> { let mayLoad = 0; let mayStore = 0; }
def SEC : InstImplied<0x38, "sec"> { let mayLoad = 0; let mayStore = 0; }
def CLI : InstImplied<0x58, "cli"> { let mayLoad = 0; let mayStore = 0; }
def SEI : InstImplied<0x78, "sei"> { let mayLoad = 0; let mayStore = 0; }
def CLD : InstImplied<0xD8, "cld"> { let mayLoad = 0; let mayStore = 0; }
def SED : InstImplied<0xF8, "sed"> { let mayLoad = 0; let mayStore = 0; }
def CLV : InstImplied<0xB8, "clv"> { let mayLoad = 0; let mayStore = 0; }
def XCE : InstImplied<0xFB, "xce"> { let mayLoad = 0; let mayStore = 0; }
def XBA : InstImplied<0xEB, "xba"> { let mayLoad = 0; let mayStore = 0; }
def WAI : InstImplied<0xCB, "wai">;
def STP : InstImplied<0xDB, "stp">;
//---------------------------------------------------------------- LDA (load A)
// The `_Imm8` forms of the mode-dependent load/arith/compare ops are
// marked isCodeGenOnly so the asm matcher never picks them our
// AsmParser has no way to know the current M/X bits, so it always
// reaches for the _Imm16 form. Codegen can still select _Imm8
// explicitly once we have 8-bit patterns.
def LDA_Imm8 : InstImm8<0xA9, "lda"> { let MHigh = 1; let DecoderNamespace = "W65816MHigh"; let isCodeGenOnly = 1; }
def LDA_Imm16 : InstImm16<0xA9, "lda"> { let MLow = 1; }
def LDA_DP : InstDP<0xA5, "lda">;
def LDA_Abs : InstAbs<0xAD, "lda">;
def LDA_Long : InstAbsLong<0xAF, "lda">;
def LDA_DPX : InstDPX<0xB5, "lda">;
def LDA_AbsX : InstAbsX<0xBD, "lda">;
def LDA_AbsY : InstAbsY<0xB9, "lda">;
//---------------------------------------------------------------- STA (store A)
def STA_DP : InstDP<0x85, "sta">;
def STA_Abs : InstAbs<0x8D, "sta">;
def STA_Long : InstAbsLong<0x8F, "sta">;
def STA_DPX : InstDPX<0x95, "sta">;
def STA_AbsX : InstAbsX<0x9D, "sta">;
def STA_AbsY : InstAbsY<0x99, "sta">;
//---------------------------------------------------------------- LDX (load X)
def LDX_Imm8 : InstImm8<0xA2, "ldx"> { let XHigh = 1; let DecoderNamespace = "W65816XHigh"; let isCodeGenOnly = 1; }
def LDX_Imm16 : InstImm16<0xA2, "ldx"> { let XLow = 1; }
def LDX_DP : InstDP<0xA6, "ldx">;
def LDX_Abs : InstAbs<0xAE, "ldx">;
def LDX_DPY : InstDPY<0xB6, "ldx">;
def LDX_AbsY : InstAbsY<0xBE, "ldx">;
//---------------------------------------------------------------- STX (store X)
def STX_DP : InstDP<0x86, "stx">;
def STX_Abs : InstAbs<0x8E, "stx">;
def STX_DPY : InstDPY<0x96, "stx">;
//---------------------------------------------------------------- LDY (load Y)
def LDY_Imm8 : InstImm8<0xA0, "ldy"> { let XHigh = 1; let DecoderNamespace = "W65816XHigh"; let isCodeGenOnly = 1; }
def LDY_Imm16 : InstImm16<0xA0, "ldy"> { let XLow = 1; }
def LDY_DP : InstDP<0xA4, "ldy">;
def LDY_Abs : InstAbs<0xAC, "ldy">;
def LDY_DPX : InstDPX<0xB4, "ldy">;
def LDY_AbsX : InstAbsX<0xBC, "ldy">;
//---------------------------------------------------------------- STY (store Y)
def STY_DP : InstDP<0x84, "sty">;
def STY_Abs : InstAbs<0x8C, "sty">;
def STY_DPX : InstDPX<0x94, "sty">;
//------------------------------------------------------------------------- ADC
def ADC_Imm8 : InstImm8<0x69, "adc"> { let MHigh = 1; let DecoderNamespace = "W65816MHigh"; let isCodeGenOnly = 1; }
def ADC_Imm16 : InstImm16<0x69, "adc"> { let MLow = 1; }
def ADC_DP : InstDP<0x65, "adc">;
def ADC_Abs : InstAbs<0x6D, "adc">;
def ADC_DPX : InstDPX<0x75, "adc">;
def ADC_AbsX : InstAbsX<0x7D, "adc">;
def ADC_AbsY : InstAbsY<0x79, "adc">;
//------------------------------------------------------------------------- SBC
def SBC_Imm8 : InstImm8<0xE9, "sbc"> { let MHigh = 1; let DecoderNamespace = "W65816MHigh"; let isCodeGenOnly = 1; }
def SBC_Imm16 : InstImm16<0xE9, "sbc"> { let MLow = 1; }
def SBC_DP : InstDP<0xE5, "sbc">;
def SBC_Abs : InstAbs<0xED, "sbc">;
def SBC_DPX : InstDPX<0xF5, "sbc">;
def SBC_AbsX : InstAbsX<0xFD, "sbc">;
def SBC_AbsY : InstAbsY<0xF9, "sbc">;
//------------------------------------------------------------------------- CMP
def CMP_Imm8 : InstImm8<0xC9, "cmp"> { let MHigh = 1; let mayLoad=0; let mayStore=0; let DecoderNamespace = "W65816MHigh"; let isCodeGenOnly = 1; }
def CMP_Imm16 : InstImm16<0xC9, "cmp"> { let MLow = 1; let mayLoad=0; let mayStore=0; }
def CMP_DP : InstDP<0xC5, "cmp"> { let mayStore = 0; }
def CMP_Abs : InstAbs<0xCD, "cmp"> { let mayStore = 0; }
def CMP_DPX : InstDPX<0xD5, "cmp"> { let mayStore = 0; }
def CMP_AbsX : InstAbsX<0xDD, "cmp"> { let mayStore = 0; }
def CMP_AbsY : InstAbsY<0xD9, "cmp"> { let mayStore = 0; }
//---------------------------------------------------------------- CPX/CPY
def CPX_Imm8 : InstImm8<0xE0, "cpx"> { let XHigh = 1; let mayLoad=0; let mayStore=0; let DecoderNamespace = "W65816XHigh"; let isCodeGenOnly = 1; }
def CPX_Imm16 : InstImm16<0xE0, "cpx"> { let XLow = 1; let mayLoad=0; let mayStore=0; }
def CPX_DP : InstDP<0xE4, "cpx"> { let mayStore = 0; }
def CPX_Abs : InstAbs<0xEC, "cpx"> { let mayStore = 0; }
def CPY_Imm8 : InstImm8<0xC0, "cpy"> { let XHigh = 1; let mayLoad=0; let mayStore=0; let DecoderNamespace = "W65816XHigh"; let isCodeGenOnly = 1; }
def CPY_Imm16 : InstImm16<0xC0, "cpy"> { let XLow = 1; let mayLoad=0; let mayStore=0; }
def CPY_DP : InstDP<0xC4, "cpy"> { let mayStore = 0; }
def CPY_Abs : InstAbs<0xCC, "cpy"> { let mayStore = 0; }
//---------------------------------------------------------------- AND/ORA/EOR
def AND_Imm8 : InstImm8<0x29, "and"> { let MHigh = 1; let mayLoad=0; let mayStore=0; let DecoderNamespace = "W65816MHigh"; let isCodeGenOnly = 1; }
def AND_Imm16 : InstImm16<0x29, "and"> { let MLow = 1; let mayLoad=0; let mayStore=0; }
def AND_DP : InstDP<0x25, "and"> { let mayStore = 0; }
def AND_Abs : InstAbs<0x2D, "and"> { let mayStore = 0; }
def ORA_Imm8 : InstImm8<0x09, "ora"> { let MHigh = 1; let mayLoad=0; let mayStore=0; let DecoderNamespace = "W65816MHigh"; let isCodeGenOnly = 1; }
def ORA_Imm16 : InstImm16<0x09, "ora"> { let MLow = 1; let mayLoad=0; let mayStore=0; }
def ORA_DP : InstDP<0x05, "ora"> { let mayStore = 0; }
def ORA_Abs : InstAbs<0x0D, "ora"> { let mayStore = 0; }
def EOR_Imm8 : InstImm8<0x49, "eor"> { let MHigh = 1; let mayLoad=0; let mayStore=0; let DecoderNamespace = "W65816MHigh"; let isCodeGenOnly = 1; }
def EOR_Imm16 : InstImm16<0x49, "eor"> { let MLow = 1; let mayLoad=0; let mayStore=0; }
def EOR_DP : InstDP<0x45, "eor"> { let mayStore = 0; }
def EOR_Abs : InstAbs<0x4D, "eor"> { let mayStore = 0; }
def BIT_Imm8 : InstImm8<0x89, "bit"> { let MHigh = 1; let mayLoad=0; let mayStore=0; let DecoderNamespace = "W65816MHigh"; let isCodeGenOnly = 1; }
def BIT_Imm16 : InstImm16<0x89, "bit"> { let MLow = 1; let mayLoad=0; let mayStore=0; }
def BIT_DP : InstDP<0x24, "bit"> { let mayStore = 0; }
def BIT_Abs : InstAbs<0x2C, "bit"> { let mayStore = 0; }
//---------------------------------------------------------------- INC/DEC
def INA : InstImplied<0x1A, "inc a"> { let mayLoad = 0; let mayStore = 0; }
def DEA : InstImplied<0x3A, "dec a"> { let mayLoad = 0; let mayStore = 0; }
def INX : InstImplied<0xE8, "inx"> { let mayLoad = 0; let mayStore = 0; }
def DEX : InstImplied<0xCA, "dex"> { let mayLoad = 0; let mayStore = 0; }
def INY : InstImplied<0xC8, "iny"> { let mayLoad = 0; let mayStore = 0; }
def DEY : InstImplied<0x88, "dey"> { let mayLoad = 0; let mayStore = 0; }
def INC_DP : InstDP<0xE6, "inc">;
def INC_Abs : InstAbs<0xEE, "inc">;
def INC_DPX : InstDPX<0xF6, "inc">;
def INC_AbsX: InstAbsX<0xFE, "inc">;
def DEC_DP : InstDP<0xC6, "dec">;
def DEC_Abs : InstAbs<0xCE, "dec">;
def DEC_DPX : InstDPX<0xD6, "dec">;
def DEC_AbsX: InstAbsX<0xDE, "dec">;
//---------------------------------------------------------------- Shifts
def ASL_A : InstImplied<0x0A, "asl a"> { let mayLoad = 0; let mayStore = 0; }
def LSR_A : InstImplied<0x4A, "lsr a"> { let mayLoad = 0; let mayStore = 0; }
def ROL_A : InstImplied<0x2A, "rol a"> { let mayLoad = 0; let mayStore = 0; }
def ROR_A : InstImplied<0x6A, "ror a"> { let mayLoad = 0; let mayStore = 0; }
def ASL_DP : InstDP<0x06, "asl">;
def ASL_Abs : InstAbs<0x0E, "asl">;
def LSR_DP : InstDP<0x46, "lsr">;
def LSR_Abs : InstAbs<0x4E, "lsr">;
def ROL_DP : InstDP<0x26, "rol">;
def ROL_Abs : InstAbs<0x2E, "rol">;
def ROR_DP : InstDP<0x66, "ror">;
def ROR_Abs : InstAbs<0x6E, "ror">;
//---------------------------------------------------------------- Transfers
def TAX : InstImplied<0xAA, "tax"> { let mayLoad = 0; let mayStore = 0; }
def TAY : InstImplied<0xA8, "tay"> { let mayLoad = 0; let mayStore = 0; }
def TXA : InstImplied<0x8A, "txa"> { let mayLoad = 0; let mayStore = 0; }
def TYA : InstImplied<0x98, "tya"> { let mayLoad = 0; let mayStore = 0; }
def TXY : InstImplied<0x9B, "txy"> { let mayLoad = 0; let mayStore = 0; }
def TYX : InstImplied<0xBB, "tyx"> { let mayLoad = 0; let mayStore = 0; }
def TXS : InstImplied<0x9A, "txs"> { let mayLoad = 0; let mayStore = 0; }
def TSX : InstImplied<0xBA, "tsx"> { let mayLoad = 0; let mayStore = 0; }
def TCD : InstImplied<0x5B, "tcd"> { let mayLoad = 0; let mayStore = 0; }
def TDC : InstImplied<0x7B, "tdc"> { let mayLoad = 0; let mayStore = 0; }
def TCS : InstImplied<0x1B, "tcs"> { let mayLoad = 0; let mayStore = 0; }
def TSC : InstImplied<0x3B, "tsc"> { let mayLoad = 0; let mayStore = 0; }
//---------------------------------------------------------------- Stack push/pull
def PHA : InstImplied<0x48, "pha">;
def PLA : InstImplied<0x68, "pla">;
def PHX : InstImplied<0xDA, "phx">;
def PLX : InstImplied<0xFA, "plx">;
def PHY : InstImplied<0x5A, "phy">;
def PLY : InstImplied<0x7A, "ply">;
def PHP : InstImplied<0x08, "php">;
def PLP : InstImplied<0x28, "plp">;
def PHB : InstImplied<0x8B, "phb">;
def PLB : InstImplied<0xAB, "plb">;
def PHD : InstImplied<0x0B, "phd">;
def PLD : InstImplied<0x2B, "pld">;
def PHK : InstImplied<0x4B, "phk">;
def PEA : InstAbs<0xF4, "pea">;
def PER : InstPCRel16<0x62, "per">;
//---------------------------------------------------------------- Branches
let isBranch = 1, isTerminator = 1, mayLoad = 0, mayStore = 0 in {
def BEQ : InstPCRel8<0xF0, "beq">;
def BNE : InstPCRel8<0xD0, "bne">;
def BCS : InstPCRel8<0xB0, "bcs">;
def BCC : InstPCRel8<0x90, "bcc">;
def BMI : InstPCRel8<0x30, "bmi">;
def BPL : InstPCRel8<0x10, "bpl">;
def BVS : InstPCRel8<0x70, "bvs">;
def BVC : InstPCRel8<0x50, "bvc">;
}
let isBranch = 1, isTerminator = 1, isBarrier = 1, mayLoad = 0, mayStore = 0 in {
def BRA : InstPCRel8<0x80, "bra">;
def BRL : InstPCRel16<0x82, "brl">;
def JMP_Abs : InstAbs<0x4C, "jmp">;
def JML_Long : InstAbsLong<0x5C, "jml">;
}
//---------------------------------------------------------------- Calls
let isCall = 1, mayLoad = 0, mayStore = 0 in {
def JSR_Abs : InstAbs<0x20, "jsr">;
def JSL_Long : InstAbsLong<0x22, "jsl">;
}
//---------------------------------------------------------------- Returns
let isReturn = 1, isTerminator = 1, isBarrier = 1, mayLoad = 0, mayStore = 0 in {
def RTS : InstImplied<0x60, "rts">;
def RTI : InstImplied<0x40, "rti">;
// RTL is the 65816 long return; we select it for the generic retglue node.
def RTL : InstImplied<0x6B, "rtl"> {
let Pattern = [(W65816retglue)];
}
}
//---------------------------------------------------------------- Block move
// MVN/MVP are 3 bytes: opcode + destBank + srcBank. WDC writes the
// operand order as "dst, src" but the bytes on the wire are dst-then-src.
// Block-move operands are bank bytes written without a '#' prefix
// (e.g. `mvn $01, $02`), so the parser produces AddrDP-kind operands,
// not immediates. Use addrDP here to match that; the encoder path is
// identical since both are single-byte values.
class InstBlockMove<bits<8> op, string mnem>
: W65816Inst<(outs), (ins addrDP:$dst, addrDP:$src),
!strconcat(mnem, "\t$dst, $src")> {
let Size = 3;
bits<8> dst;
bits<8> src;
bits<24> Inst;
let Inst{7-0} = op;
let Inst{15-8} = dst;
let Inst{23-16} = src;
}
def MVN : InstBlockMove<0x54, "mvn">;
def MVP : InstBlockMove<0x44, "mvp">;
//---------------------------------------------------------------- Stack-rel
def LDA_StackRel : InstStackRel<0xA3, "lda">;
def STA_StackRel : InstStackRel<0x83, "sta">;
def ADC_StackRel : InstStackRel<0x63, "adc">;
def SBC_StackRel : InstStackRel<0xE3, "sbc">;
def CMP_StackRel : InstStackRel<0xC3, "cmp">;
def AND_StackRel : InstStackRel<0x23, "and">;
def ORA_StackRel : InstStackRel<0x03, "ora">;
def EOR_StackRel : InstStackRel<0x43, "eor">;
//===----------------------------------------------------------------------===//
// Branch patterns (placed after the Bxx defs).
//
// W65816brcc takes (Dest, CondCode) plus a glue from W65816cmp. The CC
// constant maps to one of the eight Bxx instructions. Values mirror
// W65816CC::CondCode in W65816.h.
//===----------------------------------------------------------------------===//
def : Pat<(W65816brcc bb:$dest, (i8 0)), (BEQ bb:$dest)>;
def : Pat<(W65816brcc bb:$dest, (i8 1)), (BNE bb:$dest)>;
def : Pat<(W65816brcc bb:$dest, (i8 2)), (BCS bb:$dest)>;
def : Pat<(W65816brcc bb:$dest, (i8 3)), (BCC bb:$dest)>;
def : Pat<(W65816brcc bb:$dest, (i8 4)), (BMI bb:$dest)>;
def : Pat<(W65816brcc bb:$dest, (i8 5)), (BPL bb:$dest)>;
def : Pat<(W65816brcc bb:$dest, (i8 6)), (BVS bb:$dest)>;
def : Pat<(W65816brcc bb:$dest, (i8 7)), (BVC bb:$dest)>;
// Unconditional branch from generic ISD::BR.
def : Pat<(br bb:$dest), (BRA bb:$dest)>;
// Memory inc/dec: `*p = *p + 1` `INC abs`. Single-instruction RMW
// instead of LDA CLC ADC #1 STA.
def : Pat<(store
(i16 (add (i16 (load (W65816Wrapper tglobaladdr:$g))),
(i16 1))),
(W65816Wrapper tglobaladdr:$g)),
(INC_Abs tglobaladdr:$g)>;
def : Pat<(store
(i16 (add (i16 (load (W65816Wrapper tglobaladdr:$g))),
(i16 -1))),
(W65816Wrapper tglobaladdr:$g)),
(DEC_Abs tglobaladdr:$g)>;
// Direct call to a global / external symbol. We use JSL (24-bit
// long jump-and-link) and RTL pairing throughout matches the
// IIgs convention where main is entered via JSL, and means a
// function doesn't have to know how it was called to choose its
// return instruction. A pseudo bridges the i16 symbol operand
// to JSL_Long's 24-bit operand class.
let isCall = 1, hasSideEffects = 0, mayLoad = 0, mayStore = 0,
Defs = [A] in {
def JSLpseudo : W65816Pseudo<(outs), (ins i16imm:$dst),
"# JSLpseudo $dst", []>;
}
def : Pat<(W65816call (i16 tglobaladdr:$dst)), (JSLpseudo tglobaladdr:$dst)>;
def : Pat<(W65816call (i16 texternalsym:$dst)), (JSLpseudo texternalsym:$dst)>;

View file

@ -0,0 +1,120 @@
//===-- W65816MCInstLower.cpp - Convert W65816 MachineInstr to MCInst -----===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file contains code to lower W65816 MachineInstrs to their
// corresponding MCInst records. The skeleton follows the MSP430 pattern
// closely but does not yet understand any target-specific operand flags.
//
//===----------------------------------------------------------------------===//
#include "W65816MCInstLower.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/CodeGen/AsmPrinter.h"
#include "llvm/CodeGen/MachineBasicBlock.h"
#include "llvm/CodeGen/MachineInstr.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCExpr.h"
#include "llvm/MC/MCInst.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Target/TargetMachine.h"
using namespace llvm;
MCSymbol *
W65816MCInstLower::GetGlobalAddressSymbol(const MachineOperand &MO) const {
assert(MO.getTargetFlags() == 0 && "unknown target flag on GV operand");
return Printer.getSymbol(MO.getGlobal());
}
MCSymbol *
W65816MCInstLower::GetExternalSymbolSymbol(const MachineOperand &MO) const {
assert(MO.getTargetFlags() == 0 && "unknown target flag on ES operand");
return Printer.GetExternalSymbolSymbol(MO.getSymbolName());
}
MCSymbol *W65816MCInstLower::GetJumpTableSymbol(const MachineOperand &MO) const {
const DataLayout &DL = Printer.getDataLayout();
SmallString<256> Name;
raw_svector_ostream(Name)
<< DL.getInternalSymbolPrefix() << "JTI" << Printer.getFunctionNumber()
<< '_' << MO.getIndex();
assert(MO.getTargetFlags() == 0 && "unknown target flag on JT operand");
return Ctx.getOrCreateSymbol(Name);
}
MCSymbol *
W65816MCInstLower::GetConstantPoolIndexSymbol(const MachineOperand &MO) const {
const DataLayout &DL = Printer.getDataLayout();
SmallString<256> Name;
raw_svector_ostream(Name)
<< DL.getInternalSymbolPrefix() << "CPI" << Printer.getFunctionNumber()
<< '_' << MO.getIndex();
assert(MO.getTargetFlags() == 0 && "unknown target flag on CPI operand");
return Ctx.getOrCreateSymbol(Name);
}
MCSymbol *
W65816MCInstLower::GetBlockAddressSymbol(const MachineOperand &MO) const {
assert(MO.getTargetFlags() == 0 && "unknown target flag on BA operand");
return Printer.GetBlockAddressSymbol(MO.getBlockAddress());
}
MCOperand W65816MCInstLower::LowerSymbolOperand(const MachineOperand &MO,
MCSymbol *Sym) const {
const MCExpr *Expr = MCSymbolRefExpr::create(Sym, Ctx);
if (!MO.isJTI() && MO.getOffset())
Expr = MCBinaryExpr::createAdd(
Expr, MCConstantExpr::create(MO.getOffset(), Ctx), Ctx);
return MCOperand::createExpr(Expr);
}
void W65816MCInstLower::Lower(const MachineInstr *MI, MCInst &OutMI) const {
OutMI.setOpcode(MI->getOpcode());
for (const MachineOperand &MO : MI->operands()) {
MCOperand MCOp;
switch (MO.getType()) {
default:
MI->print(errs());
llvm_unreachable("W65816: unknown operand type in MCInstLower");
case MachineOperand::MO_Register:
if (MO.isImplicit())
continue;
MCOp = MCOperand::createReg(MO.getReg());
break;
case MachineOperand::MO_Immediate:
MCOp = MCOperand::createImm(MO.getImm());
break;
case MachineOperand::MO_MachineBasicBlock:
MCOp = MCOperand::createExpr(
MCSymbolRefExpr::create(MO.getMBB()->getSymbol(), Ctx));
break;
case MachineOperand::MO_GlobalAddress:
MCOp = LowerSymbolOperand(MO, GetGlobalAddressSymbol(MO));
break;
case MachineOperand::MO_ExternalSymbol:
MCOp = LowerSymbolOperand(MO, GetExternalSymbolSymbol(MO));
break;
case MachineOperand::MO_JumpTableIndex:
MCOp = LowerSymbolOperand(MO, GetJumpTableSymbol(MO));
break;
case MachineOperand::MO_ConstantPoolIndex:
MCOp = LowerSymbolOperand(MO, GetConstantPoolIndexSymbol(MO));
break;
case MachineOperand::MO_BlockAddress:
MCOp = LowerSymbolOperand(MO, GetBlockAddressSymbol(MO));
break;
case MachineOperand::MO_RegisterMask:
continue;
}
OutMI.addOperand(MCOp);
}
}

View file

@ -0,0 +1,46 @@
//===-- W65816MCInstLower.h - Lower MachineInstr to MCInst ------*- 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_W65816_W65816MCINSTLOWER_H
#define LLVM_LIB_TARGET_W65816_W65816MCINSTLOWER_H
#include "llvm/Support/Compiler.h"
namespace llvm {
class AsmPrinter;
class MCContext;
class MCInst;
class MCOperand;
class MCSymbol;
class MachineInstr;
class MachineOperand;
/// W65816MCInstLower - Lower a MachineInstr into an MCInst.
class LLVM_LIBRARY_VISIBILITY W65816MCInstLower {
MCContext &Ctx;
AsmPrinter &Printer;
public:
W65816MCInstLower(MCContext &Ctx, AsmPrinter &Printer)
: Ctx(Ctx), Printer(Printer) {}
void Lower(const MachineInstr *MI, MCInst &OutMI) const;
MCOperand LowerSymbolOperand(const MachineOperand &MO, MCSymbol *Sym) const;
MCSymbol *GetGlobalAddressSymbol(const MachineOperand &MO) const;
MCSymbol *GetExternalSymbolSymbol(const MachineOperand &MO) const;
MCSymbol *GetJumpTableSymbol(const MachineOperand &MO) const;
MCSymbol *GetConstantPoolIndexSymbol(const MachineOperand &MO) const;
MCSymbol *GetBlockAddressSymbol(const MachineOperand &MO) const;
};
} // namespace llvm
#endif

View file

@ -0,0 +1,20 @@
//===-- W65816MachineFunctionInfo.cpp - W65816 machine function info ------===//
//
// 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 "W65816MachineFunctionInfo.h"
using namespace llvm;
void W65816MachineFunctionInfo::anchor() {}
MachineFunctionInfo *W65816MachineFunctionInfo::clone(
BumpPtrAllocator &Allocator, MachineFunction &DestMF,
const DenseMap<MachineBasicBlock *, MachineBasicBlock *> &Src2DstMBB)
const {
return DestMF.cloneInfo<W65816MachineFunctionInfo>(*this);
}

View file

@ -0,0 +1,63 @@
//=== W65816MachineFunctionInfo.h - W65816 machine function info -*- 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
//
//===----------------------------------------------------------------------===//
//
// This file declares W65816-specific per-machine-function information.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_W65816_W65816MACHINEFUNCTIONINFO_H
#define LLVM_LIB_TARGET_W65816_W65816MACHINEFUNCTIONINFO_H
#include "llvm/CodeGen/MachineFunction.h"
namespace llvm {
/// W65816MachineFunctionInfo - per-MachineFunction private target-specific
/// information for the W65816.
class W65816MachineFunctionInfo : public MachineFunctionInfo {
virtual void anchor();
/// Size of the callee-saved register portion of the stack frame in bytes.
unsigned CalleeSavedFrameSize = 0;
/// FrameIndex for the return-address slot.
int ReturnAddrIndex = 0;
/// FrameIndex for the start of the varargs area.
int VarArgsFrameIndex = 0;
/// Virtual register holding the struct-return pointer for sret returns.
Register SRetReturnReg;
public:
W65816MachineFunctionInfo() = default;
W65816MachineFunctionInfo(const Function &F, const TargetSubtargetInfo *STI) {
}
MachineFunctionInfo *
clone(BumpPtrAllocator &Allocator, MachineFunction &DestMF,
const DenseMap<MachineBasicBlock *, MachineBasicBlock *> &Src2DstMBB)
const override;
unsigned getCalleeSavedFrameSize() const { return CalleeSavedFrameSize; }
void setCalleeSavedFrameSize(unsigned Bytes) { CalleeSavedFrameSize = Bytes; }
Register getSRetReturnReg() const { return SRetReturnReg; }
void setSRetReturnReg(Register Reg) { SRetReturnReg = Reg; }
int getRAIndex() const { return ReturnAddrIndex; }
void setRAIndex(int Index) { ReturnAddrIndex = Index; }
int getVarArgsFrameIndex() const { return VarArgsFrameIndex; }
void setVarArgsFrameIndex(int Index) { VarArgsFrameIndex = Index; }
};
} // namespace llvm
#endif

View file

@ -0,0 +1,119 @@
//===-- W65816RegisterInfo.cpp - W65816 Register Information --------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Skeleton implementation of the W65816 register info. The callee-saved
// register list, reserved set and frame-index elimination logic are
// deliberately minimal; they will be fleshed out alongside frame lowering.
//
//===----------------------------------------------------------------------===//
#include "W65816RegisterInfo.h"
#include "W65816.h"
#include "W65816FrameLowering.h"
#include "W65816InstrInfo.h"
#include "W65816Subtarget.h"
#include "llvm/ADT/BitVector.h"
#include "llvm/CodeGen/MachineFrameInfo.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/Support/ErrorHandling.h"
using namespace llvm;
#define DEBUG_TYPE "w65816-reg-info"
#define GET_REGINFO_TARGET_DESC
#include "W65816GenRegisterInfo.inc"
W65816RegisterInfo::W65816RegisterInfo() : W65816GenRegisterInfo(W65816::PC) {}
const MCPhysReg *
W65816RegisterInfo::getCalleeSavedRegs(const MachineFunction *MF) const {
// The 65816 C calling convention preserves DP and DBR across calls.
static const MCPhysReg CalleeSavedRegs[] = {W65816::DP, W65816::DBR, 0};
return CalleeSavedRegs;
}
BitVector W65816RegisterInfo::getReservedRegs(const MachineFunction &MF) const {
BitVector Reserved(getNumRegs());
// SP, PC, P, PBR and DBR are all special-purpose registers the allocator
// must never pick. DP is allocatable in principle but is treated as
// reserved for the skeleton until direct-page management lands.
Reserved.set(W65816::SP);
Reserved.set(W65816::PC);
Reserved.set(W65816::P);
Reserved.set(W65816::PBR);
Reserved.set(W65816::DBR);
Reserved.set(W65816::DP);
return Reserved;
}
const TargetRegisterClass *
W65816RegisterInfo::getPointerRegClass(unsigned Kind) const {
return &W65816::PtrRegsRegClass;
}
bool W65816RegisterInfo::eliminateFrameIndex(MachineBasicBlock::iterator II,
int SPAdj, unsigned FIOperandNum,
RegScavenger *RS) const {
MachineInstr &MI = *II;
MachineFunction &MF = *MI.getParent()->getParent();
const MachineFrameInfo &MFI = MF.getFrameInfo();
const W65816InstrInfo &TII = *MF.getSubtarget<W65816Subtarget>().getInstrInfo();
unsigned Opc = MI.getOpcode();
unsigned NewOpc = 0;
bool NeedsCarryPrefix = false;
bool IsSub = false;
switch (Opc) {
case W65816::LDAfi: NewOpc = W65816::LDA_StackRel; break;
case W65816::STAfi: NewOpc = W65816::STA_StackRel; break;
case W65816::ADCfi: NewOpc = W65816::ADC_StackRel; NeedsCarryPrefix = true; break;
case W65816::SBCfi: NewOpc = W65816::SBC_StackRel; NeedsCarryPrefix = true; IsSub = true; break;
case W65816::ANDfi: NewOpc = W65816::AND_StackRel; break;
case W65816::ORAfi: NewOpc = W65816::ORA_StackRel; break;
case W65816::EORfi: NewOpc = W65816::EOR_StackRel; break;
case W65816::CMPfi: NewOpc = W65816::CMP_StackRel; break;
default:
llvm_unreachable("W65816: unhandled instruction in eliminateFrameIndex");
}
int FI = MI.getOperand(FIOperandNum).getIndex();
int FrameOffset = MFI.getObjectOffset(FI);
int ImmOffset = MI.getOperand(FIOperandNum + 1).getImm();
// WDC stack-relative addressing: `LDA disp,S` computes effective
// address S + disp. Both fixed objects (args) and local objects
// are stored at addresses relative to entry-SP; my prologue has
// shifted S down by StackSize. So:
// address = entry_S + FrameOffset
// S = entry_S - StackSize
// disp = address - S
// = FrameOffset + StackSize
int Offset = FrameOffset + ImmOffset + (int)MFI.getStackSize();
if (Offset < 0 || Offset > 0xFF) {
report_fatal_error("W65816: frame offset out of stack-relative range");
}
// Emit the carry-prep instruction first if the operation needs it.
if (NeedsCarryPrefix) {
BuildMI(*MI.getParent(), II, MI.getDebugLoc(),
TII.get(IsSub ? W65816::SEC : W65816::CLC));
}
BuildMI(*MI.getParent(), II, MI.getDebugLoc(), TII.get(NewOpc))
.addImm(Offset);
MI.eraseFromParent();
return true;
}
Register W65816RegisterInfo::getFrameRegister(const MachineFunction &MF) const {
return W65816::SP;
}

View file

@ -0,0 +1,43 @@
//===-- W65816RegisterInfo.h - W65816 Register Information Impl -*- 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
//
//===----------------------------------------------------------------------===//
//
// This file contains the W65816 implementation of the TargetRegisterInfo
// class.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_W65816_W65816REGISTERINFO_H
#define LLVM_LIB_TARGET_W65816_W65816REGISTERINFO_H
#include "llvm/CodeGen/TargetRegisterInfo.h"
#define GET_REGINFO_HEADER
#include "W65816GenRegisterInfo.inc"
namespace llvm {
class W65816RegisterInfo : public W65816GenRegisterInfo {
public:
W65816RegisterInfo();
const MCPhysReg *getCalleeSavedRegs(const MachineFunction *MF) const override;
BitVector getReservedRegs(const MachineFunction &MF) const override;
const TargetRegisterClass *
getPointerRegClass(unsigned Kind = 0) const override;
bool eliminateFrameIndex(MachineBasicBlock::iterator II, int SPAdj,
unsigned FIOperandNum,
RegScavenger *RS = nullptr) const override;
Register getFrameRegister(const MachineFunction &MF) const override;
};
} // namespace llvm
#endif

View file

@ -0,0 +1,61 @@
//===-- W65816RegisterInfo.td - W65816 Register defs -------*- tablegen -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
//===----------------------------------------------------------------------===//
// Declarations that describe the W65816 register file
//===----------------------------------------------------------------------===//
class W65816Reg<bits<4> num, string n> : Register<n> {
field bits<4> Num = num;
let Namespace = "W65816";
let HWEncoding{3-0} = num;
let DwarfNumbers = [num];
}
//===----------------------------------------------------------------------===//
// Registers
//===----------------------------------------------------------------------===//
//
// The 65816 registers are variable-width: A, X and Y are each 8 or 16 bits
// wide depending on the M and X bits in the processor status register. For
// the skeleton we model each physical register once and create parallel 8-bit
// and 16-bit register classes that share the same physical register. A later
// pass is responsible for managing REP/SEP transitions and verifying that the
// selected width matches the current processor mode.
//
def A : W65816Reg<0, "a">, DwarfRegNum<[0]>;
def X : W65816Reg<1, "x">, DwarfRegNum<[1]>;
def Y : W65816Reg<2, "y">, DwarfRegNum<[2]>;
def SP : W65816Reg<3, "sp">, DwarfRegNum<[3]>;
def DP : W65816Reg<4, "dp">, DwarfRegNum<[4]>;
def DBR : W65816Reg<5, "dbr">, DwarfRegNum<[5]>;
def PBR : W65816Reg<6, "pbr">, DwarfRegNum<[6]>;
def PC : W65816Reg<7, "pc">, DwarfRegNum<[7]>;
def P : W65816Reg<8, "p">, DwarfRegNum<[8]>;
//===----------------------------------------------------------------------===//
// Register Classes
//===----------------------------------------------------------------------===//
//
// Acc8/Acc16 hold the accumulator A in 8-bit and 16-bit mode respectively.
// Idx8/Idx16 hold the index registers X and Y in 8-bit and 16-bit mode.
// PtrRegs holds the stack pointer for framing and pointer arithmetic; DP is
// reserved and not allocatable.
def Acc8 : RegisterClass<"W65816", [i8], 8, (add A)>;
def Acc16 : RegisterClass<"W65816", [i16], 16, (add A)>;
def Idx8 : RegisterClass<"W65816", [i8], 8, (add X, Y)>;
def Idx16 : RegisterClass<"W65816", [i16], 16, (add X, Y)>;
def PtrRegs : RegisterClass<"W65816", [i16], 16, (add SP)>;
// Single-register class for the processor status register, used for condition
// code modeling. Not currently allocatable.
def StatusReg : RegisterClass<"W65816", [i8], 8, (add P)> {
let isAllocatable = 0;
}

View file

@ -0,0 +1,19 @@
//===-- W65816SelectionDAGInfo.cpp - W65816 SelectionDAG Info -------------===//
//
// 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 "W65816SelectionDAGInfo.h"
#define GET_SDNODE_DESC
#include "W65816GenSDNodeInfo.inc"
using namespace llvm;
W65816SelectionDAGInfo::W65816SelectionDAGInfo()
: SelectionDAGGenTargetInfo(W65816GenSDNodeInfo) {}
W65816SelectionDAGInfo::~W65816SelectionDAGInfo() = default;

View file

@ -0,0 +1,27 @@
//===-- W65816SelectionDAGInfo.h - W65816 SelectionDAG Info -----*- 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_W65816_W65816SELECTIONDAGINFO_H
#define LLVM_LIB_TARGET_W65816_W65816SELECTIONDAGINFO_H
#include "llvm/CodeGen/SelectionDAGTargetInfo.h"
#define GET_SDNODE_ENUM
#include "W65816GenSDNodeInfo.inc"
namespace llvm {
class W65816SelectionDAGInfo : public SelectionDAGGenTargetInfo {
public:
W65816SelectionDAGInfo();
~W65816SelectionDAGInfo() override;
};
} // namespace llvm
#endif

View file

@ -0,0 +1,49 @@
//===-- W65816Subtarget.cpp - W65816 Subtarget Information ----------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file implements the W65816 specific subclass of TargetSubtargetInfo.
//
//===----------------------------------------------------------------------===//
#include "W65816Subtarget.h"
#include "W65816SelectionDAGInfo.h"
#include "llvm/MC/TargetRegistry.h"
using namespace llvm;
#define DEBUG_TYPE "w65816-subtarget"
#define GET_SUBTARGETINFO_TARGET_DESC
#define GET_SUBTARGETINFO_CTOR
#include "W65816GenSubtargetInfo.inc"
void W65816Subtarget::anchor() {}
W65816Subtarget &
W65816Subtarget::initializeSubtargetDependencies(StringRef CPU, StringRef FS) {
StringRef CPUName = CPU;
if (CPUName.empty())
CPUName = "w65816";
ParseSubtargetFeatures(CPUName, /*TuneCPU=*/CPUName, FS);
return *this;
}
W65816Subtarget::W65816Subtarget(const Triple &TT, const std::string &CPU,
const std::string &FS, const TargetMachine &TM)
: W65816GenSubtargetInfo(TT, CPU, /*TuneCPU=*/CPU, FS),
InstrInfo(initializeSubtargetDependencies(CPU, FS)), TLInfo(TM, *this),
FrameLowering(*this) {
TSInfo = std::make_unique<W65816SelectionDAGInfo>();
}
W65816Subtarget::~W65816Subtarget() = default;
const SelectionDAGTargetInfo *W65816Subtarget::getSelectionDAGInfo() const {
return TSInfo.get();
}

View file

@ -0,0 +1,67 @@
//===-- W65816Subtarget.h - Define Subtarget for the W65816 ----*- 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
//
//===----------------------------------------------------------------------===//
//
// This file declares the W65816 specific subclass of TargetSubtargetInfo.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_W65816_W65816SUBTARGET_H
#define LLVM_LIB_TARGET_W65816_W65816SUBTARGET_H
#include "W65816FrameLowering.h"
#include "W65816ISelLowering.h"
#include "W65816InstrInfo.h"
#include "W65816RegisterInfo.h"
#include "llvm/CodeGen/TargetSubtargetInfo.h"
#include "llvm/IR/DataLayout.h"
#include <memory>
#include <string>
#define GET_SUBTARGETINFO_HEADER
#include "W65816GenSubtargetInfo.inc"
namespace llvm {
class StringRef;
class W65816Subtarget : public W65816GenSubtargetInfo {
virtual void anchor();
W65816InstrInfo InstrInfo;
W65816TargetLowering TLInfo;
std::unique_ptr<const SelectionDAGTargetInfo> TSInfo;
W65816FrameLowering FrameLowering;
public:
W65816Subtarget(const Triple &TT, const std::string &CPU,
const std::string &FS, const TargetMachine &TM);
~W65816Subtarget() override;
W65816Subtarget &initializeSubtargetDependencies(StringRef CPU, StringRef FS);
/// Parses features string setting specified subtarget options. Generated
/// by TableGen.
void ParseSubtargetFeatures(StringRef CPU, StringRef TuneCPU, StringRef FS);
const TargetFrameLowering *getFrameLowering() const override {
return &FrameLowering;
}
const W65816InstrInfo *getInstrInfo() const override { return &InstrInfo; }
const W65816RegisterInfo *getRegisterInfo() const override {
return &getInstrInfo()->getRegisterInfo();
}
const W65816TargetLowering *getTargetLowering() const override {
return &TLInfo;
}
const SelectionDAGTargetInfo *getSelectionDAGInfo() const override;
};
} // namespace llvm
#endif

View file

@ -0,0 +1,95 @@
//===-- W65816TargetMachine.cpp - Define TargetMachine for W65816 ---------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Top-level implementation for the W65816 target.
//
//===----------------------------------------------------------------------===//
#include "W65816TargetMachine.h"
#include "W65816.h"
#include "W65816MachineFunctionInfo.h"
#include "TargetInfo/W65816TargetInfo.h"
#include "llvm/CodeGen/Passes.h"
#include "llvm/CodeGen/TargetLoweringObjectFileImpl.h"
#include "llvm/CodeGen/TargetPassConfig.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/Compiler.h"
#include <optional>
using namespace llvm;
// Data layout for the 65816 lives in Triple::computeDataLayout via
// patches/0005-target-data-layout-w65816.patch. The string is:
// e - little endian
// m:e - ELF-style symbol mangling
// p:16:8 - 16-bit pointers, 8-bit stack alignment
// i16:16 - 16-bit integers aligned to 16 bits
// i32:16 - 32-bit integers aligned to 16 bits
// n8:16 - native integer widths
// S16 - 16-bit natural stack alignment
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeW65816Target() {
RegisterTargetMachine<W65816TargetMachine> X(getTheW65816Target());
PassRegistry &PR = *PassRegistry::getPassRegistry();
initializeW65816AsmPrinterPass(PR);
initializeW65816DAGToDAGISelLegacyPass(PR);
}
static Reloc::Model getEffectiveRelocModel(std::optional<Reloc::Model> RM) {
return RM.value_or(Reloc::Static);
}
W65816TargetMachine::W65816TargetMachine(const Target &T, const Triple &TT,
StringRef CPU, StringRef FS,
const TargetOptions &Options,
std::optional<Reloc::Model> RM,
std::optional<CodeModel::Model> CM,
CodeGenOptLevel OL, bool JIT)
: CodeGenTargetMachineImpl(T, TT.computeDataLayout(), TT, CPU, FS, Options,
getEffectiveRelocModel(RM),
getEffectiveCodeModel(CM, CodeModel::Small), OL),
TLOF(std::make_unique<TargetLoweringObjectFileELF>()),
Subtarget(TT, std::string(CPU), std::string(FS), *this) {
initAsmInfo();
}
W65816TargetMachine::~W65816TargetMachine() = default;
namespace {
/// W65816 Code Generator Pass Configuration Options.
class W65816PassConfig : public TargetPassConfig {
public:
W65816PassConfig(W65816TargetMachine &TM, PassManagerBase &PM)
: TargetPassConfig(TM, PM) {}
W65816TargetMachine &getW65816TargetMachine() const {
return getTM<W65816TargetMachine>();
}
bool addInstSelector() override;
};
} // namespace
TargetPassConfig *W65816TargetMachine::createPassConfig(PassManagerBase &PM) {
return new W65816PassConfig(*this, PM);
}
MachineFunctionInfo *W65816TargetMachine::createMachineFunctionInfo(
BumpPtrAllocator &Allocator, const Function &F,
const TargetSubtargetInfo *STI) const {
return W65816MachineFunctionInfo::create<W65816MachineFunctionInfo>(Allocator,
F, STI);
}
bool W65816PassConfig::addInstSelector() {
addPass(createW65816ISelDag(getW65816TargetMachine(), getOptLevel()));
return false;
}

View file

@ -0,0 +1,53 @@
//===-- W65816TargetMachine.h - Define TargetMachine for W65816 -*- 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
//
//===----------------------------------------------------------------------===//
//
// This file declares the W65816 specific subclass of TargetMachine.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_W65816_W65816TARGETMACHINE_H
#define LLVM_LIB_TARGET_W65816_W65816TARGETMACHINE_H
#include "W65816Subtarget.h"
#include "llvm/CodeGen/CodeGenTargetMachineImpl.h"
#include <optional>
namespace llvm {
class StringRef;
class W65816TargetMachine : public CodeGenTargetMachineImpl {
std::unique_ptr<TargetLoweringObjectFile> TLOF;
W65816Subtarget Subtarget;
public:
W65816TargetMachine(const Target &T, const Triple &TT, StringRef CPU,
StringRef FS, const TargetOptions &Options,
std::optional<Reloc::Model> RM,
std::optional<CodeModel::Model> CM, CodeGenOptLevel OL,
bool JIT);
~W65816TargetMachine() override;
const W65816Subtarget *getSubtargetImpl(const Function &F) const override {
return &Subtarget;
}
TargetPassConfig *createPassConfig(PassManagerBase &PM) override;
TargetLoweringObjectFile *getObjFileLowering() const override {
return TLOF.get();
}
MachineFunctionInfo *
createMachineFunctionInfo(BumpPtrAllocator &Allocator, const Function &F,
const TargetSubtargetInfo *STI) const override;
};
} // namespace llvm
#endif