commit 873eab4922ee0b7c77a228b66816ec81f275ebbf Author: Scott Duensing Date: Sat Apr 25 17:07:28 2026 -0500 Checkpoint. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cdd6ac6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Ephemeral: regenerable via setup.sh +tools/ +.cache/ + +# Claude Code tool state +.claude/ + +# Editor / OS +*.swp +*.swo +.DS_Store diff --git a/LLVM_65816_DESIGN.md b/LLVM_65816_DESIGN.md new file mode 100644 index 0000000..4b01e94 --- /dev/null +++ b/LLVM_65816_DESIGN.md @@ -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 diff --git a/SESSION_STATE.md b/SESSION_STATE.md new file mode 100644 index 0000000..1953623 --- /dev/null +++ b/SESSION_STATE.md @@ -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` 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. diff --git a/patches/0001-triple-add-w65816-arch.patch b/patches/0001-triple-add-w65816-arch.patch new file mode 100644 index 0000000..7b141c5 --- /dev/null +++ b/patches/0001-triple-add-w65816-arch.patch @@ -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) diff --git a/patches/0002-triple-cpp-add-w65816-cases.patch b/patches/0002-triple-cpp-add-w65816-cases.patch new file mode 100644 index 0000000..4f8cc6c --- /dev/null +++ b/patches/0002-triple-cpp-add-w65816-cases.patch @@ -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: diff --git a/patches/0003-clang-basic-dispatch-w65816.patch b/patches/0003-clang-basic-dispatch-w65816.patch new file mode 100644 index 0000000..c4f4e8f --- /dev/null +++ b/patches/0003-clang-basic-dispatch-w65816.patch @@ -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 AllocateTarget(const llvm::Triple &Triple, + case llvm::Triple::msp430: + return std::make_unique(Triple, Opts); + ++ case llvm::Triple::w65816: ++ return std::make_unique(Triple, Opts); ++ + case llvm::Triple::mips: + switch (os) { + case llvm::Triple::Linux: diff --git a/patches/0004-cmake-add-w65816-experimental.patch b/patches/0004-cmake-add-w65816-experimental.patch new file mode 100644 index 0000000..7c8a220 --- /dev/null +++ b/patches/0004-cmake-add-w65816-experimental.patch @@ -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 + ) + diff --git a/patches/0005-target-data-layout-w65816.patch b/patches/0005-target-data-layout-w65816.patch new file mode 100644 index 0000000..ecc8e11 --- /dev/null +++ b/patches/0005-target-data-layout-w65816.patch @@ -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: diff --git a/scripts/applyBackend.sh b/scripts/applyBackend.sh new file mode 100755 index 0000000..b6622e1 --- /dev/null +++ b/scripts/applyBackend.sh @@ -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/ we want a symlink at +# $LLVM_SRC/. 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" diff --git a/scripts/cDemo.sh b/scripts/cDemo.sh new file mode 100755 index 0000000..1a860ee --- /dev/null +++ b/scripts/cDemo.sh @@ -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" diff --git a/scripts/common.sh b/scripts/common.sh new file mode 100755 index 0000000..37e3ab8 --- /dev/null +++ b/scripts/common.sh @@ -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 +} diff --git a/scripts/installCalypsi.sh b/scripts/installCalypsi.sh new file mode 100755 index 0000000..03eaf5a --- /dev/null +++ b/scripts/installCalypsi.sh @@ -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" diff --git a/scripts/installDeps.sh b/scripts/installDeps.sh new file mode 100755 index 0000000..c1e74ec --- /dev/null +++ b/scripts/installDeps.sh @@ -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" diff --git a/scripts/installLlvmMos.sh b/scripts/installLlvmMos.sh new file mode 100755 index 0000000..4be384c --- /dev/null +++ b/scripts/installLlvmMos.sh @@ -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" diff --git a/scripts/installMame.sh b/scripts/installMame.sh new file mode 100755 index 0000000..fe42200 --- /dev/null +++ b/scripts/installMame.sh @@ -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" diff --git a/scripts/installOrcaC.sh b/scripts/installOrcaC.sh new file mode 100755 index 0000000..bf8e4cc --- /dev/null +++ b/scripts/installOrcaC.sh @@ -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" diff --git a/scripts/smokeTest.sh b/scripts/smokeTest.sh new file mode 100755 index 0000000..8de958d --- /dev/null +++ b/scripts/smokeTest.sh @@ -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" diff --git a/scripts/updateLlvmMos.sh b/scripts/updateLlvmMos.sh new file mode 100755 index 0000000..606676f --- /dev/null +++ b/scripts/updateLlvmMos.sh @@ -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" diff --git a/scripts/verify.sh b/scripts/verify.sh new file mode 100755 index 0000000..e209893 --- /dev/null +++ b/scripts/verify.sh @@ -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 diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..c8bac22 --- /dev/null +++ b/setup.sh @@ -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" diff --git a/src/clang/lib/Basic/Targets/W65816.cpp b/src/clang/lib/Basic/Targets/W65816.cpp new file mode 100644 index 0000000..1d03313 --- /dev/null +++ b/src/clang/lib/Basic/Targets/W65816.cpp @@ -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 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__"); +} diff --git a/src/clang/lib/Basic/Targets/W65816.h b/src/clang/lib/Basic/Targets/W65816.h new file mode 100644 index 0000000..bad4855 --- /dev/null +++ b/src/clang/lib/Basic/Targets/W65816.h @@ -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 getTargetBuiltins() const override { + return {}; + } + + bool allowsLargerPreferedTypeAlignment() const override { return false; } + + bool hasFeature(StringRef Feature) const override { + return Feature == "w65816"; + } + + ArrayRef getGCCRegNames() const override; + + ArrayRef 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 diff --git a/src/llvm/lib/Target/W65816/AsmParser/CMakeLists.txt b/src/llvm/lib/Target/W65816/AsmParser/CMakeLists.txt new file mode 100644 index 0000000..5dfb67a --- /dev/null +++ b/src/llvm/lib/Target/W65816/AsmParser/CMakeLists.txt @@ -0,0 +1,15 @@ +add_llvm_component_library(LLVMW65816AsmParser + W65816AsmParser.cpp + + LINK_COMPONENTS + CodeGenTypes + MC + MCParser + Support + TargetParser + W65816Desc + W65816Info + + ADD_TO_COMPONENT + W65816 + ) diff --git a/src/llvm/lib/Target/W65816/AsmParser/W65816AsmParser.cpp b/src/llvm/lib/Target/W65816/AsmParser/W65816AsmParser.cpp new file mode 100644 index 0000000..24c28bd --- /dev/null +++ b/src/llvm/lib/Target/W65816/AsmParser/W65816AsmParser.cpp @@ -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 createToken(StringRef Str, SMLoc S) { + return std::make_unique(Str, S); + } + + static std::unique_ptr createReg(unsigned R, SMLoc S, + SMLoc E) { + return std::make_unique(R, S, E); + } + + static std::unique_ptr createImm(const MCExpr *Val, SMLoc S, + SMLoc E) { + return std::make_unique(k_Imm, Val, S, E); + } + + static std::unique_ptr createAddr(const MCExpr *Val, SMLoc S, + SMLoc E) { + return std::make_unique(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(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(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(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: " 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" / ", " (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: '#' . 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 X(getTheW65816Target()); +} + +#define GET_REGISTER_MATCHER +#define GET_MATCHER_IMPLEMENTATION +#include "W65816GenAsmMatcher.inc" diff --git a/src/llvm/lib/Target/W65816/CMakeLists.txt b/src/llvm/lib/Target/W65816/CMakeLists.txt new file mode 100644 index 0000000..2000020 --- /dev/null +++ b/src/llvm/lib/Target/W65816/CMakeLists.txt @@ -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) diff --git a/src/llvm/lib/Target/W65816/Disassembler/CMakeLists.txt b/src/llvm/lib/Target/W65816/Disassembler/CMakeLists.txt new file mode 100644 index 0000000..8f7f4d2 --- /dev/null +++ b/src/llvm/lib/Target/W65816/Disassembler/CMakeLists.txt @@ -0,0 +1,13 @@ +add_llvm_component_library(LLVMW65816Disassembler + W65816Disassembler.cpp + + LINK_COMPONENTS + MCDisassembler + Support + TargetParser + W65816Desc + W65816Info + + ADD_TO_COMPONENT + W65816 + ) diff --git a/src/llvm/lib/Target/W65816/Disassembler/W65816Disassembler.cpp b/src/llvm/lib/Target/W65816/Disassembler/W65816Disassembler.cpp new file mode 100644 index 0000000..1f0ff52 --- /dev/null +++ b/src/llvm/lib/Target/W65816/Disassembler/W65816Disassembler.cpp @@ -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 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(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(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 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(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); +} diff --git a/src/llvm/lib/Target/W65816/MCTargetDesc/CMakeLists.txt b/src/llvm/lib/Target/W65816/MCTargetDesc/CMakeLists.txt new file mode 100644 index 0000000..9d1ba6f --- /dev/null +++ b/src/llvm/lib/Target/W65816/MCTargetDesc/CMakeLists.txt @@ -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 + ) diff --git a/src/llvm/lib/Target/W65816/MCTargetDesc/W65816AsmBackend.cpp b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816AsmBackend.cpp new file mode 100644 index 0000000..a2cf6a6 --- /dev/null +++ b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816AsmBackend.cpp @@ -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((Value >> (8 * i)) & 0xff); + } + } + + std::unique_ptr + 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); +} diff --git a/src/llvm/lib/Target/W65816/MCTargetDesc/W65816ELFObjectWriter.cpp b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816ELFObjectWriter.cpp new file mode 100644 index 0000000..0c18137 --- /dev/null +++ b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816ELFObjectWriter.cpp @@ -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 +llvm::createW65816ELFObjectWriter(uint8_t OSABI) { + return std::make_unique(OSABI); +} diff --git a/src/llvm/lib/Target/W65816/MCTargetDesc/W65816ELFStreamer.cpp b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816ELFStreamer.cpp new file mode 100644 index 0000000..7743d12 --- /dev/null +++ b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816ELFStreamer.cpp @@ -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 diff --git a/src/llvm/lib/Target/W65816/MCTargetDesc/W65816FixupKinds.h b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816FixupKinds.h new file mode 100644 index 0000000..d724bce --- /dev/null +++ b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816FixupKinds.h @@ -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 diff --git a/src/llvm/lib/Target/W65816/MCTargetDesc/W65816InstPrinter.cpp b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816InstPrinter.cpp new file mode 100644 index 0000000..8578367 --- /dev/null +++ b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816InstPrinter.cpp @@ -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"); +} diff --git a/src/llvm/lib/Target/W65816/MCTargetDesc/W65816InstPrinter.h b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816InstPrinter.h new file mode 100644 index 0000000..d296013 --- /dev/null +++ b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816InstPrinter.h @@ -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 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 diff --git a/src/llvm/lib/Target/W65816/MCTargetDesc/W65816MCAsmInfo.cpp b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816MCAsmInfo.cpp new file mode 100644 index 0000000..53f564d --- /dev/null +++ b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816MCAsmInfo.cpp @@ -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; +} diff --git a/src/llvm/lib/Target/W65816/MCTargetDesc/W65816MCAsmInfo.h b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816MCAsmInfo.h new file mode 100644 index 0000000..57da24a --- /dev/null +++ b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816MCAsmInfo.h @@ -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 diff --git a/src/llvm/lib/Target/W65816/MCTargetDesc/W65816MCCodeEmitter.cpp b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816MCCodeEmitter.cpp new file mode 100644 index 0000000..27267c2 --- /dev/null +++ b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816MCCodeEmitter.cpp @@ -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 &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 &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 &Fixups, + const MCSubtargetInfo &STI) const; + unsigned encodeImm16(const MCInst &MI, unsigned OpIdx, + SmallVectorImpl &Fixups, + const MCSubtargetInfo &STI) const; + unsigned encodeAddr8(const MCInst &MI, unsigned OpIdx, + SmallVectorImpl &Fixups, + const MCSubtargetInfo &STI) const; + unsigned encodeAddr16(const MCInst &MI, unsigned OpIdx, + SmallVectorImpl &Fixups, + const MCSubtargetInfo &STI) const; + unsigned encodeAddr24(const MCInst &MI, unsigned OpIdx, + SmallVectorImpl &Fixups, + const MCSubtargetInfo &STI) const; + unsigned encodePCRel8(const MCInst &MI, unsigned OpIdx, + SmallVectorImpl &Fixups, + const MCSubtargetInfo &STI) const; + unsigned encodePCRel16(const MCInst &MI, unsigned OpIdx, + SmallVectorImpl &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 &Fixups) const; + +public: + W65816MCCodeEmitter(MCContext &Ctx, const MCInstrInfo &MCII) + : Ctx(Ctx), MCII(MCII) {} + + void encodeInstruction(const MCInst &MI, SmallVectorImpl &CB, + SmallVectorImpl &Fixups, + const MCSubtargetInfo &STI) const override; +}; + +void W65816MCCodeEmitter::encodeInstruction(const MCInst &MI, + SmallVectorImpl &CB, + SmallVectorImpl &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((Bits >> (8 * i)) & 0xff)); + } +} + +unsigned +W65816MCCodeEmitter::getMachineOpValue(const MCInst &MI, const MCOperand &MO, + SmallVectorImpl &Fixups, + const MCSubtargetInfo &STI) const { + if (MO.isReg()) { + return Ctx.getRegisterInfo()->getEncodingValue(MO.getReg()); + } + if (MO.isImm()) { + return static_cast(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 &Fixups) const { + const MCOperand &MO = MI.getOperand(OpIdx); + unsigned offsetForThisOperand = OperandOffset; + OperandOffset += width; + + if (MO.isImm()) { + return static_cast(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 &Fixups, + const MCSubtargetInfo &STI) const { + return encodeOperand(MI, OpIdx, 1, W65816::fixup_8, Fixups); +} + +unsigned W65816MCCodeEmitter::encodeImm16(const MCInst &MI, unsigned OpIdx, + SmallVectorImpl &Fixups, + const MCSubtargetInfo &STI) const { + return encodeOperand(MI, OpIdx, 2, W65816::fixup_16, Fixups); +} + +unsigned W65816MCCodeEmitter::encodeAddr8(const MCInst &MI, unsigned OpIdx, + SmallVectorImpl &Fixups, + const MCSubtargetInfo &STI) const { + return encodeOperand(MI, OpIdx, 1, W65816::fixup_8, Fixups); +} + +unsigned W65816MCCodeEmitter::encodeAddr16(const MCInst &MI, unsigned OpIdx, + SmallVectorImpl &Fixups, + const MCSubtargetInfo &STI) const { + return encodeOperand(MI, OpIdx, 2, W65816::fixup_16, Fixups); +} + +unsigned W65816MCCodeEmitter::encodeAddr24(const MCInst &MI, unsigned OpIdx, + SmallVectorImpl &Fixups, + const MCSubtargetInfo &STI) const { + return encodeOperand(MI, OpIdx, 3, W65816::fixup_24, Fixups); +} + +unsigned W65816MCCodeEmitter::encodePCRel8(const MCInst &MI, unsigned OpIdx, + SmallVectorImpl &Fixups, + const MCSubtargetInfo &STI) const { + return encodeOperand(MI, OpIdx, 1, W65816::fixup_8_pcrel, Fixups); +} + +unsigned W65816MCCodeEmitter::encodePCRel16(const MCInst &MI, unsigned OpIdx, + SmallVectorImpl &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 diff --git a/src/llvm/lib/Target/W65816/MCTargetDesc/W65816MCTargetDesc.cpp b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816MCTargetDesc.cpp new file mode 100644 index 0000000..fcf4b70 --- /dev/null +++ b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816MCTargetDesc.cpp @@ -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); +} diff --git a/src/llvm/lib/Target/W65816/MCTargetDesc/W65816MCTargetDesc.h b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816MCTargetDesc.h new file mode 100644 index 0000000..20e6a0c --- /dev/null +++ b/src/llvm/lib/Target/W65816/MCTargetDesc/W65816MCTargetDesc.h @@ -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 + +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 +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 diff --git a/src/llvm/lib/Target/W65816/TargetInfo/CMakeLists.txt b/src/llvm/lib/Target/W65816/TargetInfo/CMakeLists.txt new file mode 100644 index 0000000..c3f7211 --- /dev/null +++ b/src/llvm/lib/Target/W65816/TargetInfo/CMakeLists.txt @@ -0,0 +1,10 @@ +add_llvm_component_library(LLVMW65816Info + W65816TargetInfo.cpp + + LINK_COMPONENTS + MC + Support + + ADD_TO_COMPONENT + W65816 + ) diff --git a/src/llvm/lib/Target/W65816/TargetInfo/W65816TargetInfo.cpp b/src/llvm/lib/Target/W65816/TargetInfo/W65816TargetInfo.cpp new file mode 100644 index 0000000..dfc1aea --- /dev/null +++ b/src/llvm/lib/Target/W65816/TargetInfo/W65816TargetInfo.cpp @@ -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 X(getTheW65816Target(), "w65816", + "WDC 65816 [experimental]", "W65816"); +} diff --git a/src/llvm/lib/Target/W65816/TargetInfo/W65816TargetInfo.h b/src/llvm/lib/Target/W65816/TargetInfo/W65816TargetInfo.h new file mode 100644 index 0000000..2cc234a --- /dev/null +++ b/src/llvm/lib/Target/W65816/TargetInfo/W65816TargetInfo.h @@ -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 diff --git a/src/llvm/lib/Target/W65816/W65816.h b/src/llvm/lib/Target/W65816/W65816.h new file mode 100644 index 0000000..ae90fc2 --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816.h @@ -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 diff --git a/src/llvm/lib/Target/W65816/W65816.td b/src/llvm/lib/Target/W65816/W65816.td new file mode 100644 index 0000000..31ba687 --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816.td @@ -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 Features> + : Processor; + +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; + +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]; +} diff --git a/src/llvm/lib/Target/W65816/W65816AsmPrinter.cpp b/src/llvm/lib/Target/W65816/W65816AsmPrinter.cpp new file mode 100644 index 0000000..e5c9a4f --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816AsmPrinter.cpp @@ -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 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 X(getTheW65816Target()); +} diff --git a/src/llvm/lib/Target/W65816/W65816CallingConv.td b/src/llvm/lib/Target/W65816/W65816CallingConv.td new file mode 100644 index 0000000..7bf96fb --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816CallingConv.td @@ -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>, + + // Promote i8 arguments to i16 when passed on the stack. + CCIfType<[i8], CCPromoteToType>, + + // Integer arguments occupy 2-byte stack slots with 1-byte alignment. + CCIfType<[i16], CCAssignToStack<2, 1>> +]>; diff --git a/src/llvm/lib/Target/W65816/W65816FrameLowering.cpp b/src/llvm/lib/Target/W65816/W65816FrameLowering.cpp new file mode 100644 index 0000000..c4346d1 --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816FrameLowering.cpp @@ -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(); + 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(); + 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); +} diff --git a/src/llvm/lib/Target/W65816/W65816FrameLowering.h b/src/llvm/lib/Target/W65816/W65816FrameLowering.h new file mode 100644 index 0000000..ca3561c --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816FrameLowering.h @@ -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 diff --git a/src/llvm/lib/Target/W65816/W65816ISelDAGToDAG.cpp b/src/llvm/lib/Target/W65816/W65816ISelDAGToDAG.cpp new file mode 100644 index 0000000..108a36c --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816ISelDAGToDAG.cpp @@ -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(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(N)) { + Base = CurDAG->getTargetFrameIndex(FIN->getIndex(), MVT::i16); + Offset = CurDAG->getTargetConstant(0, SDLoc(N), MVT::i16); + return true; + } + return false; +} diff --git a/src/llvm/lib/Target/W65816/W65816ISelLowering.cpp b/src/llvm/lib/Target/W65816/W65816ISelLowering.cpp new file mode 100644 index 0000000..7c5de40 --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816ISelLowering.cpp @@ -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(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(LHS) && !isa(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(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(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(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 &Ins, const SDLoc &DL, + SelectionDAG &DAG, SmallVectorImpl &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) <- (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 &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(Callee)) + Callee = DAG.getTargetGlobalAddress(GA->getGlobal(), DL, MVT::i16); + else if (auto *ES = dyn_cast(Callee)) + Callee = DAG.getTargetExternalSymbol(ES->getSymbol(), MVT::i16); + + SmallVector 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 &Outs, + const SmallVectorImpl &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 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); +} diff --git a/src/llvm/lib/Target/W65816/W65816ISelLowering.h b/src/llvm/lib/Target/W65816/W65816ISelLowering.h new file mode 100644 index 0000000..7755bf3 --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816ISelLowering.h @@ -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 &Ins, + const SDLoc &DL, SelectionDAG &DAG, + SmallVectorImpl &InVals) const override; + + SDValue LowerCall(TargetLowering::CallLoweringInfo &CLI, + SmallVectorImpl &InVals) const override; + + SDValue LowerReturn(SDValue Chain, CallingConv::ID CallConv, bool IsVarArg, + const SmallVectorImpl &Outs, + const SmallVectorImpl &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 diff --git a/src/llvm/lib/Target/W65816/W65816InstrFormats.td b/src/llvm/lib/Target/W65816/W65816InstrFormats.td new file mode 100644 index 0000000..30305f8 --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816InstrFormats.td @@ -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 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 pattern> + : W65816Inst { + let isPseudo = 1; + let Size = 0; + let mayLoad = false; + let mayStore = false; + let hasSideEffects = false; +} + +//===----------------------------------------------------------------------===// +// Operand classes. +//===----------------------------------------------------------------------===// + +// Custom DiagnosticType values would require matching Match_ enum +// values in the parser; we rely on the generic Match_InvalidOperand +// message for now. +class W65816AsmOperand : AsmOperandClass { + let Name = name; +} + +class W65816ImmOp : W65816AsmOperand { + let RenderMethod = "addImmOperands"; +} + +class W65816AddrOp : W65816AsmOperand; +class W65816PCRelOp : W65816AsmOperand; + +def imm8 : Operand { + let ParserMatchClass = W65816ImmOp<"Imm8">; + let OperandType = "OPERAND_IMMEDIATE"; + let Type = i8; + let EncoderMethod = "encodeImm8"; + let DecoderMethod = "decodeImm8"; +} + +def imm16 : Operand { + let ParserMatchClass = W65816ImmOp<"Imm16">; + let OperandType = "OPERAND_IMMEDIATE"; + let Type = i16; + let EncoderMethod = "encodeImm16"; + let DecoderMethod = "decodeImm16"; +} + +def addrDP : Operand { + let ParserMatchClass = W65816AddrOp<"AddrDP">; + let OperandType = "OPERAND_MEMORY"; + let Type = i8; + let PrintMethod = "printAddrDP"; + let EncoderMethod = "encodeAddr8"; + let DecoderMethod = "decodeAddr8"; +} + +def addrAbs : Operand { + let ParserMatchClass = W65816AddrOp<"AddrAbs">; + let OperandType = "OPERAND_MEMORY"; + let Type = i16; + let PrintMethod = "printAddrAbs"; + let EncoderMethod = "encodeAddr16"; + let DecoderMethod = "decodeAddr16"; +} + +def addrLong : Operand { + let ParserMatchClass = W65816AddrOp<"AddrLong">; + let OperandType = "OPERAND_MEMORY"; + let Type = i32; + let PrintMethod = "printAddrLong"; + let EncoderMethod = "encodeAddr24"; + let DecoderMethod = "decodeAddr24"; +} + +def pcrel8 : Operand { + let ParserMatchClass = W65816PCRelOp<"PCRel8">; + let OperandType = "OPERAND_PCREL"; + let PrintMethod = "printPCRel8"; + let EncoderMethod = "encodePCRel8"; + let DecoderMethod = "decodePCRel8"; +} + +def pcrel16 : Operand { + 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 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 op, string mnem> : Inst1; + +class InstImm8 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 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 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 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 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 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 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 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 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 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 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 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. diff --git a/src/llvm/lib/Target/W65816/W65816InstrInfo.cpp b/src/llvm/lib/Target/W65816/W65816InstrInfo.cpp new file mode 100644 index 0000000..d7708b1 --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816InstrInfo.cpp @@ -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); +} diff --git a/src/llvm/lib/Target/W65816/W65816InstrInfo.h b/src/llvm/lib/Target/W65816/W65816InstrInfo.h new file mode 100644 index 0000000..19fc860 --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816InstrInfo.h @@ -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 diff --git a/src/llvm/lib/Target/W65816/W65816InstrInfo.td b/src/llvm/lib/Target/W65816/W65816InstrInfo.td new file mode 100644 index 0000000..5db2373 --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816InstrInfo.td @@ -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 { + 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; + +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 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)>; diff --git a/src/llvm/lib/Target/W65816/W65816MCInstLower.cpp b/src/llvm/lib/Target/W65816/W65816MCInstLower.cpp new file mode 100644 index 0000000..6c59940 --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816MCInstLower.cpp @@ -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); + } +} diff --git a/src/llvm/lib/Target/W65816/W65816MCInstLower.h b/src/llvm/lib/Target/W65816/W65816MCInstLower.h new file mode 100644 index 0000000..aa44f3b --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816MCInstLower.h @@ -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 diff --git a/src/llvm/lib/Target/W65816/W65816MachineFunctionInfo.cpp b/src/llvm/lib/Target/W65816/W65816MachineFunctionInfo.cpp new file mode 100644 index 0000000..502e537 --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816MachineFunctionInfo.cpp @@ -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 &Src2DstMBB) + const { + return DestMF.cloneInfo(*this); +} diff --git a/src/llvm/lib/Target/W65816/W65816MachineFunctionInfo.h b/src/llvm/lib/Target/W65816/W65816MachineFunctionInfo.h new file mode 100644 index 0000000..bc9c7ec --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816MachineFunctionInfo.h @@ -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 &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 diff --git a/src/llvm/lib/Target/W65816/W65816RegisterInfo.cpp b/src/llvm/lib/Target/W65816/W65816RegisterInfo.cpp new file mode 100644 index 0000000..4e8f7f9 --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816RegisterInfo.cpp @@ -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().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; +} diff --git a/src/llvm/lib/Target/W65816/W65816RegisterInfo.h b/src/llvm/lib/Target/W65816/W65816RegisterInfo.h new file mode 100644 index 0000000..d6fd1f3 --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816RegisterInfo.h @@ -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 diff --git a/src/llvm/lib/Target/W65816/W65816RegisterInfo.td b/src/llvm/lib/Target/W65816/W65816RegisterInfo.td new file mode 100644 index 0000000..6bc80b8 --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816RegisterInfo.td @@ -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 num, string n> : Register { + 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; +} diff --git a/src/llvm/lib/Target/W65816/W65816SelectionDAGInfo.cpp b/src/llvm/lib/Target/W65816/W65816SelectionDAGInfo.cpp new file mode 100644 index 0000000..b24d6b3 --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816SelectionDAGInfo.cpp @@ -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; diff --git a/src/llvm/lib/Target/W65816/W65816SelectionDAGInfo.h b/src/llvm/lib/Target/W65816/W65816SelectionDAGInfo.h new file mode 100644 index 0000000..a274095 --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816SelectionDAGInfo.h @@ -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 diff --git a/src/llvm/lib/Target/W65816/W65816Subtarget.cpp b/src/llvm/lib/Target/W65816/W65816Subtarget.cpp new file mode 100644 index 0000000..c1ed42a --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816Subtarget.cpp @@ -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(); +} + +W65816Subtarget::~W65816Subtarget() = default; + +const SelectionDAGTargetInfo *W65816Subtarget::getSelectionDAGInfo() const { + return TSInfo.get(); +} diff --git a/src/llvm/lib/Target/W65816/W65816Subtarget.h b/src/llvm/lib/Target/W65816/W65816Subtarget.h new file mode 100644 index 0000000..e3cb5eb --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816Subtarget.h @@ -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 +#include + +#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 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 diff --git a/src/llvm/lib/Target/W65816/W65816TargetMachine.cpp b/src/llvm/lib/Target/W65816/W65816TargetMachine.cpp new file mode 100644 index 0000000..e24f832 --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816TargetMachine.cpp @@ -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 + +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 X(getTheW65816Target()); + PassRegistry &PR = *PassRegistry::getPassRegistry(); + initializeW65816AsmPrinterPass(PR); + initializeW65816DAGToDAGISelLegacyPass(PR); +} + +static Reloc::Model getEffectiveRelocModel(std::optional RM) { + return RM.value_or(Reloc::Static); +} + +W65816TargetMachine::W65816TargetMachine(const Target &T, const Triple &TT, + StringRef CPU, StringRef FS, + const TargetOptions &Options, + std::optional RM, + std::optional CM, + CodeGenOptLevel OL, bool JIT) + : CodeGenTargetMachineImpl(T, TT.computeDataLayout(), TT, CPU, FS, Options, + getEffectiveRelocModel(RM), + getEffectiveCodeModel(CM, CodeModel::Small), OL), + TLOF(std::make_unique()), + 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(); + } + + 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(Allocator, + F, STI); +} + +bool W65816PassConfig::addInstSelector() { + addPass(createW65816ISelDag(getW65816TargetMachine(), getOptLevel())); + return false; +} diff --git a/src/llvm/lib/Target/W65816/W65816TargetMachine.h b/src/llvm/lib/Target/W65816/W65816TargetMachine.h new file mode 100644 index 0000000..b2d0e3f --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816TargetMachine.h @@ -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 + +namespace llvm { + +class StringRef; + +class W65816TargetMachine : public CodeGenTargetMachineImpl { + std::unique_ptr TLOF; + W65816Subtarget Subtarget; + +public: + W65816TargetMachine(const Target &T, const Triple &TT, StringRef CPU, + StringRef FS, const TargetOptions &Options, + std::optional RM, + std::optional 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