diff --git a/codeCore/src/prog8/code/core/ICompilationTarget.kt b/codeCore/src/prog8/code/core/ICompilationTarget.kt index 1c7c840c6..406165924 100644 --- a/codeCore/src/prog8/code/core/ICompilationTarget.kt +++ b/codeCore/src/prog8/code/core/ICompilationTarget.kt @@ -14,7 +14,6 @@ interface ICompilationTarget: IStringEncoding, IMemSizer { val FLOAT_MAX_NEGATIVE: Double val FLOAT_MAX_POSITIVE: Double val FLOAT_MEM_SIZE: UInt - val STARTUP_CODE_RESERVED_SIZE: UInt // this is here, so that certain compiler targets are able to tune this val PROGRAM_LOAD_ADDRESS : UInt val PROGRAM_MEMTOP_ADDRESS: UInt val BSSHIGHRAM_START: UInt diff --git a/codeCore/src/prog8/code/target/C128Target.kt b/codeCore/src/prog8/code/target/C128Target.kt index 86f545639..4715a84b3 100644 --- a/codeCore/src/prog8/code/target/C128Target.kt +++ b/codeCore/src/prog8/code/target/C128Target.kt @@ -29,7 +29,6 @@ class C128Target: ICompilationTarget, override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt() - override val STARTUP_CODE_RESERVED_SIZE = 20u override val PROGRAM_LOAD_ADDRESS = 0x1c01u override val PROGRAM_MEMTOP_ADDRESS = 0xc000u diff --git a/codeCore/src/prog8/code/target/C64Target.kt b/codeCore/src/prog8/code/target/C64Target.kt index d973b5400..92d8b7d5a 100644 --- a/codeCore/src/prog8/code/target/C64Target.kt +++ b/codeCore/src/prog8/code/target/C64Target.kt @@ -32,7 +32,6 @@ class C64Target: ICompilationTarget, override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt() - override val STARTUP_CODE_RESERVED_SIZE = 20u override val PROGRAM_LOAD_ADDRESS = 0x0801u override val PROGRAM_MEMTOP_ADDRESS = 0xcfe0u // $a000 if floats are used // note that at $cfe0-$cfff are the 16 'virtual registers' R0-R15 diff --git a/codeCore/src/prog8/code/target/ConfigFileTarget.kt b/codeCore/src/prog8/code/target/ConfigFileTarget.kt index 6dfcc51b0..aa0257c64 100644 --- a/codeCore/src/prog8/code/target/ConfigFileTarget.kt +++ b/codeCore/src/prog8/code/target/ConfigFileTarget.kt @@ -20,7 +20,6 @@ class ConfigFileTarget( override val cpu: CpuType, override val PROGRAM_LOAD_ADDRESS: UInt, override val PROGRAM_MEMTOP_ADDRESS: UInt, - override val STARTUP_CODE_RESERVED_SIZE: UInt, override val BSSHIGHRAM_START: UInt, override val BSSHIGHRAM_END: UInt, override val BSSGOLDENRAM_START: UInt, @@ -128,7 +127,6 @@ class ConfigFileTarget( cpuType, props.getInteger("load_address"), props.getInteger("memtop"), - 0u, // used only in a very specific error condition check in a certain scenario... props.getInteger("bss_highram_start"), props.getInteger("bss_highram_end"), props.getInteger("bss_goldenram_start"), diff --git a/codeCore/src/prog8/code/target/Cx16Target.kt b/codeCore/src/prog8/code/target/Cx16Target.kt index 550a1f67d..ec5e9bce2 100644 --- a/codeCore/src/prog8/code/target/Cx16Target.kt +++ b/codeCore/src/prog8/code/target/Cx16Target.kt @@ -29,7 +29,6 @@ class Cx16Target: ICompilationTarget, override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt() - override val STARTUP_CODE_RESERVED_SIZE = 20u override val PROGRAM_LOAD_ADDRESS = 0x0801u override val PROGRAM_MEMTOP_ADDRESS = 0x9f00u diff --git a/codeCore/src/prog8/code/target/PETTarget.kt b/codeCore/src/prog8/code/target/PETTarget.kt index 85040cf56..3bd90b5fc 100644 --- a/codeCore/src/prog8/code/target/PETTarget.kt +++ b/codeCore/src/prog8/code/target/PETTarget.kt @@ -28,7 +28,6 @@ class PETTarget: ICompilationTarget, override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt() - override val STARTUP_CODE_RESERVED_SIZE = 20u override val PROGRAM_LOAD_ADDRESS = 0x0401u override val PROGRAM_MEMTOP_ADDRESS = 0x8000u diff --git a/codeCore/src/prog8/code/target/VMTarget.kt b/codeCore/src/prog8/code/target/VMTarget.kt index 88ca37d35..2cc717e86 100644 --- a/codeCore/src/prog8/code/target/VMTarget.kt +++ b/codeCore/src/prog8/code/target/VMTarget.kt @@ -31,7 +31,6 @@ class VMTarget: ICompilationTarget, override val FLOAT_MAX_POSITIVE = Double.MAX_VALUE override val FLOAT_MAX_NEGATIVE = -Double.MAX_VALUE override val FLOAT_MEM_SIZE = VMTarget.FLOAT_MEM_SIZE.toUInt() - override val STARTUP_CODE_RESERVED_SIZE = 0u // not actually used override val PROGRAM_LOAD_ADDRESS = 0u // not actually used override val PROGRAM_MEMTOP_ADDRESS = 0xffffu // not actually used diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 63de06acc..dcd03cf22 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -362,11 +362,6 @@ internal class AstChecker(private val program: Program, if (addr!=null) { if (addr > 65535u) errors.err($$"block address must be valid integer 0..$ffff", block.position) - if(compilerOptions.loadAddress!=0u) { - val gapsize = compilerOptions.compTarget.STARTUP_CODE_RESERVED_SIZE - if (addr < compilerOptions.loadAddress + gapsize) - errors.err("block address must be at least program load address + $gapsize (to allow for startup logic)", block.position) - } } for (statement in block.statements) { diff --git a/docs/source/design/codegen-design.md b/docs/source/design/codegen-design.md new file mode 100644 index 000000000..b9b7d7f03 --- /dev/null +++ b/docs/source/design/codegen-design.md @@ -0,0 +1,687 @@ +# Code Generator Backend Design (IR-based) + +This document describes the design of a code generator backend for the Prog8 compiler, +targeting 6502 (and eventually 68000). It focuses on the abstract concepts and conventions +that a new IR-based code generator should implement. + +> **Warning:** +> +> The **old** 6502 code generator walks the SimpleAST (`PtProgram`, `PtAssignment`, +> `PtFunctionCall`, etc.) to emit assembly. **A new code generator MUST NOT do this.** +> +> Instead, it must work exclusively from the **.p8ir IR file**. The IR is fundamentally +> different from the AST -- it is a linear sequence of instructions (load, store, add, +> branch, call, etc.) grouped into chunks, operating on virtual registers and memory +> locations. The code generator's job is to translate each IR instruction into the +> target's equivalent instructions, not to walk AST nodes. +> +> The .p8ir file contains every detail needed to generate equivalent assembly: +> memory layout, symbol definitions, subroutine chunks with their instruction bodies, +> register allocations, and data sections. The IR code generator and virtual machine +> demonstrate this approach -- study those modules rather than the old SimpleAST-based +> code generator. + +## 1. Entry Point + +The code generator implements the `ICodeGeneratorBackend` interface: + +```kotlin +interface ICodeGeneratorBackend { + fun generate(program: PtProgram, + symbolTable: SymbolTable, + options: CompilationOptions, + errors: IErrorReporter + ): IAssemblyProgram? +} +``` + +It receives the full program AST (already analyzed and optimized), a symbol table, +compilation options, and an error reporter. It produces an `IAssemblyProgram` +which can be assembled into a binary. + +> **Note:** +> The above interface is used by the **old** SimpleAST-based code generators. +> A new IR-based code generator will likely need a different entry point that takes +> the parsed IR file (`IntermediateProgram`) instead of a `PtProgram` AST. + +## 2. Memory Sections + +The generated assembly organizes data into these sections: + +**Constants** + Read-only values embedded in the code section. Includes numeric constants + (simple value equates), float constants (emitted as raw bytes), and memory + slab data blocks. + +**Zero-Page Variables** + Fast-access zero-page RAM (addresses $02-$FF). Used for frequently accessed + scalar variables. Allocation decisions are based on: + - Explicit `%zeropage` directive (`require` / `prefer` / `dontcare` / `not`) + - Usage frequency scoring (weighted by type size and loop nesting depth) + - Split-word arrays of 2-byte elements can be stored as two separate byte + arrays (LSB and MSB halves) for efficient indexed access with no scaling. + +**BSS (Uninitialized Data)** + Zero-initialized variables in regular RAM. This section is cleared at program + startup. Includes: + - Module-level and local variables without initializers + - The virtual register file (`p8_regfile`, 400 bytes for 200 word-sized registers) + - `dirty` variables (not required to be consistent across subroutine calls) + - Memory slabs (runtime-allocated blocks, in sub-section `BSS_SLABS`) + +**BSS_NOCLEAR** + A sub-section of BSS that is NOT zeroed at startup or on subroutine entry. + Used for: + - `dirty` variables (explicitly marked as undefined on subroutine entry) + - Float evaluation temporary variables + - Any variable where the runtime cost of zeroing is not justified + + The memory is still allocated in RAM, but its initial content is undefined. + +**BSS Relocation (Golden RAM / High Bank)** + BSS variables can be relocated to alternative memory regions: + - **Golden RAM**: A special RAM area on some targets (e.g., C128) that persists + across system resets + - **High Bank**: Extended RAM bank on targets that support banked memory (e.g., CX16) + + When relocation is active via `options.varsGolden` / `options.varsHighBank` + and `options.slabsGolden` / `options.slabsHighBank`: + - `BSS_NOCLEAR` and `BSS_SLABS` are emitted at the non-relocated location + - `prog8_program_end` is emitted before the relocated sections + - An `* = relocatedStart` directive switches to the relocated area + - `BSS` is emitted in the relocated area with an overflow check + - A `PROG8_VARSHIGH_RAMBANK` equate defines the active RAM bank + +**Initialized Data** + Data with compile-time initial values. Only arrays and strings can have + non-zero initializers; scalar numeric variables with non-zero initial values + are initialized via runtime assignment statements. + +**Struct Instances** + Statically allocated struct instances, with optional initial values. + - Instances **without** initial values go into the `BSS` section (zeroed at startup) + - Instances **with** initial values go into the `STRUCTINSTANCES` section + (initialized data, inline with the program) + - Struct type definitions (`IRStStructDef` from the IR symbol table) are emitted + as 64tass `.struct` blocks with named fields and parameterized constructors, + enabling `.dstruct` directives in data emission + +**Variable Alignment** + Variables may have an alignment constraint (`align: UInt` in + `IRStStaticVariable` and `IRStMemorySlab`). When `align > 1`, + a `.align N` directive is emitted before the variable allocation. + This ensures the variable starts at an address that is a multiple of N. + Alignment only applies to non-zero-page variables (strings and arrays). + +**Split-Word Arrays** + Arrays of 2-byte elements (words, pointers) may be stored in "split" layout + (`DataType.isSplitWordArray`) as two separate byte arrays: + - `variable_lsb`: the low bytes of each element + - `variable_msb`: the high bytes of each element + + This enables indexed access with a simple 8-bit index (no scaling by 2). + In the generated assembly, split-word arrays with initial values emit a + helper array directive (`_array_$varname := ...`) followed by `_lsb` + and `_msb` labels with `<` / `>` byte-selectors. + Uninitialized split-word arrays are emitted as two `.fill` allocations. + +**Memory Slabs** + Runtime-allocated memory blocks (`IRStMemorySlab`). Emitted in the + `BSS_SLABS` section (which is part of BSS but separated for optional + relocation). Each slab gets a `.fill` directive with its size. + +**Scratch Space** + A small set of fixed zero-page locations reserved for temporary values during + expression evaluation (e.g., scratch bytes, scratch words, a scratch pointer). + +## 3. Symbol Naming + +Symbols are prefixed to avoid collisions with assembler built-in symbols and +to encode their kind: + +| Kind | Prefix | Example | +|----------|--------|--------------------------| +| Block | p8b_ | `p8b_main` | +| Sub | p8s_ | `p8b_main.p8s_start` | +| Variable | p8v_ | `p8b_main.p8v_x` | +| Constant | p8c_ | `p8c_MYCONST` | +| Label | p8l_ | `p8l_mylabel` | +| Struct | p8t_ | `p8t_point` | +| Instance | p8i_ | `p8i_screen` | + +**Scoped names** use the target assembler's dot notation (e.g., `p8b_main.p8s_start`) +to create nested symbol scopes. When generating code inside a subroutine, local +references should use relative/abbreviated names to make the assembly listing +more readable. + +Symbol prefixing is performed before code generation by walking the AST and +modifying node names, then constructing a new `SymbolTable` from the prefixed AST. + +## 4. Label Naming + +Control flow labels use a monotonically increasing sequence number appended to +a descriptive prefix. The sequence counter is preserved across the entire +compilation unit to guarantee uniqueness. + +Common label prefixes: + - `for_loop_N`, `for_end_N` -- for-loop body and exit + - `repeat_N`, `repeatend_N` -- repeat-loop body and exit + - `for_modified_N` -- self-modifying comparison values + - `if_else_N`, `if_end_N` -- conditional branches + - `short_circuit_skip_N`, `short_circuit_end_N` -- short-circuit evaluation + +A label stack tracks the current innermost loop's exit label, used by +`break` / `exit` statements. + +## 5. Startup and Initialization + +The generated program's entry point performs these steps in order: + +**Phase 1: Program Header (`emitHeader` + `emitLauncher`)** + - Set CPU type directive and encoding + - Emit zero-page scratch register definitions as equates + - Emit user-supplied symbol definitions from command line (`-esa`) + - Generate launcher stub based on `options.output` and `options.launcher`: + + - `PRG` with `BASIC` launcher: generates a BASIC SYS line with + `prog8_entrypoint` label, `.word` / `.null` directives for the + BASIC stub + - `PRG` with `NONE` launcher: raw header with load address + - `RAW`: raw header with load address + - `XEX`: Atari XEX header with load address + - `LIBRARY`: minimal header with `jmp main.start` + + - Emit `prog8_program_start` label at the beginning of the program + - Emit startup sequence (`emitStartupSequence`): + + - `cld` instruction + - Save stack pointer for `sys.exit()` (`tsx` / `stx prog8_lib.orig_stackpointer`) + - Call `init_system` and `init_system_phase2` (unless `noSysInit`) + - Call `prog8_lib.program_startup_clear_bss` to zero the BSS section + - Call `run_global_inits` for block-level variable initializers + - Call the user's `main.start` subroutine + - On return, jump to `cleanup_at_exit` which calls `sys.poweroff_system` + +**Phase 2: Main Entry Point (generated inside the `main.start` subroutine)** + - Clear the BSS section (zero all uninitialized variables) + - For each block, call its block-level variable initializer subroutine + - Block initializers contain the runtime assignment statements for variables + with non-zero initial values + - For zero-page arrays/strings with initial values, copy initial data from + ROM-stored blocks into the zero-page locations + - Execute the user's program statements + +**Phase 3: Program Cleanup** + - After main returns, jump to cleanup routine (resets system, etc.) + - A `prog8_program_end` label is emitted at the very end of the generated + output (after code, data, and BSS sections). This label marks the end of + the program in memory and is used by the `progend()` library routine and + memory layout calculations (e.g., `MEMORY TOP`). + - A **memtop overflow check** is emitted after `prog8_program_end` using + 64tass's `.cerror` directive: + `.cerror * >= memtopAddress, "Program too long..."` + This catches program overflow at assembly time. + +### 5.1 Subroutine Entry Initialization + +There is no dynamic stack frame in the traditional sense. Local variables are +statically allocated in BSS (not on the stack). Their initial values (if any) +are established by assignment statements within the subroutine body. +`Dirty` local variables are explicitly documented as being undefined on +subroutine entry (they get whatever value was left in memory). + +## 6. Subroutine Calling Convention + +### 6.1 Argument Passing + +Arguments are passed via CPU registers when possible, falling back to +passing through static parameter variable slots. + +**Optimized register passing (simple integer types):** + - 1 byte argument: passed in accumulator (A) + - 2 byte arguments: first in A, second in Y + - 1 word argument: passed in AY (A=lo, Y=hi) + - 1 long argument: passed in combined register pair (e.g., virtual registers) + +**General case (more arguments or complex types):** + - Arguments are assigned to the subroutine's statically allocated parameter + variables before the `jsr` call + - Complex expressions that might clobber registers are evaluated first, + simpler register-based arguments last + +**External assembly subroutines (`PtAsmSub`) with explicit register specs:** + - Arguments follow the declared register specification + - Evaluation order is optimized: combined long regs -> virtual word regs -> + paired CPU regs -> single CPU regs -> float regs -> carry flag + +### 6.2 Return Values + +| Type | Return Convention | +|----------------------------------------|------------------------------------------------| +| Byte | Accumulator (A) | +| Word | Accumulator + Y register (A=lo, Y=hi) | +| Long | Combined register pair (e.g., virtual registers)| +| Float | Floating-point accumulator (FAC1) | +| Pointer (address of array/string) | Accumulator + Y register (A=lo, Y=hi) | + +**Multi-value returns:** + - First return value goes in A (byte) or AY (word) + - Subsequent return values use virtual registers (assigned downwards: + R15, R14, ...) + - Float returns via FAC1 are assigned *last* to avoid clobbering by + other return value computations + - Status flag returns (Carry, Zero, Negative, Overflow) are extracted + from CPU flags and converted to byte values + +### 6.3 Caller/Callee Responsibilities + +- **Caller** is responsible for saving/restoring registers that must be + preserved across the call (notably the X register, which is often used + as an index base pointer) +- **Callee** may freely use A, X, Y registers, scratch zero-page locations, + and floating-point accumulators; it is NOT responsible for saving them +- **Static parameters** are assigned by the caller before `jsr`; on entry + the callee can assume they hold the correct values + +### 6.4 Subroutine Types + +- **Regular subroutines (`PtSub`)**: Generated as standard `jsr` targets + with a `rts` return +- **External assembly subroutines (`PtAsmSub`)**: May have explicit register + parameter specifications; optional `inline` flag causes the body to be + emitted directly at the call site instead of via `jsr` +- **Banked calls (`JSRFAR`)**: Subroutine address and bank are embedded + as data words/bytes after the `jsr` instruction + +## 7. Expression Evaluation + +Expression evaluation uses a tiered strategy: + +**Tier 1: Direct (register-based)** + Simple expressions involving constants, variables, and basic operators + are evaluated directly using CPU registers and scratch zero-page + locations. Results remain in registers for immediate use. + +**Tier 2: Stack-based fallback** + Complex expressions (e.g., nested arithmetic, mixed types, float operations) + are evaluated using a stack discipline on the 6502 hardware stack or via + pre-allocated temporary variables. The exact strategy depends on the + target's register set and addressing modes. + +**Tier 3: Augmented (in-place) assignments** + Compound assignment operators (`+=`, `-=`, `*=`, etc.) on variables, + array elements, memory-mapped locations, and pointer dereferences are + handled as read-modify-write operations targeting the storage location + directly, avoiding unnecessary temporary copies. + +## 8. Control Flow + +**If/Else:** + Conditional branches test a comparison result (byte, word, long, float) + against zero. The comparison operator (`==`, `!=`, `<`, `<=`, `>`, + `>=`) generates the appropriate conditional branch instructions. + Branch inversion optimization is used: a `beq` over a `jmp` can be + replaced with `bne` to the target. + +**Loops (For, Repeat):** + - For-loops over numeric ranges generate an index variable, a loop body + label, an increment/decrement, and a conditional back-branch to the + loop top + - For-loops iterating over arrays/strings generate index and pointer + variables with appropriate bounds checks + - Repeat-loops are simpler: a loop body label with a back-branch + (unconditional for `repeat`, conditional for `repeat while/until`) + - Self-modifying code may be used for comparison immediates in some + targets (when not in ROMable mode) + +**Ternary expressions (`condition ? trueval : falseval`):** + Generated as a conditional branch around the false branch, with the + result stored in a common destination register or variable. + +## 9. Floating Point + +Floating-point operations use the target machine's ROM-based floating-point +routines. Each target platform provides (or must provide) entry points for: + - Float addition, subtraction, multiplication, division + - Float-to-integer and integer-to-float conversion + - Float comparison and sign manipulation + - The floating-point accumulator (FAC1) holds the primary operand and result + +The compiler emits `jsr` calls to these platform-provided routines. +Float constants are emitted as raw bytes matching the target's +floating-point byte format. + +## 10. Memory-Mapped I/O + +Memory-mapped variables (for hardware registers, etc.) are generated as +simple address equates. Reading from them translates to a load from that +address; writing to them translates to a store to that address. + +## 11. Struct Handling + +Struct types are emitted as assembler struct definitions (or equivalent), +allowing field access by name with automatic offset calculation. +Struct instances are allocated linearly in memory; fields are laid out +sequentially. Pointer dereferencing through a struct chain (e.g., +`ptr.field.subfield`) walks the chain of offsets using a scratch pointer +register. + +## 12. Multi-Target Considerations (6502 vs. 68000) + +The design should accommodate multiple CPU targets: + +**Common abstractions:** + - `Zeropage` or `Fast RAM` concept -- a small, fast address range for + frequently accessed variables + - Scratch registers -- fixed temporary storage for expression evaluation + - CPU register set -- the primary working registers exposed by the ISA + - Addressing modes -- how memory operands are specified + +**Target-specific differences to abstract:** + - Size and availability of CPU registers (6502: A, X, Y, flags; 68000: + D0-D7, A0-A7) + - Stack discipline (6502: hardware stack is limited; 68000: full + general-purpose stack) + - Instruction set capabilities (e.g., `STZ` on 65C02, `MUL` on 68000) + - Floating-point support (ROM routines vs. FPU) + - Addressing modes (6502: limited; 68000: rich orthogonal set) + +### 12.1 6502 vs. 65C02 Instruction Set Differences + +The 65C02 is a superset of the 6502. A new code generator should prefer 65C02 +instructions when targeting that CPU (the CX16 target uses 65C02). Below are the +instruction choices that differ: + +**Zero stores:** + - 6502: `lda #0` / `sta addr` (2 instructions) + - 65C02: `stz addr` (1 instruction, does not affect flags) + +**Register save/restore (X and Y):** + - 6502: `txa` / `pha` / `pla` / `tax` (4 instructions) + - 65C02: `phx` / `plx` (2 instructions, likewise for Y with `phy`/`ply`) + +**Accumulator increment/decrement:** + - 6502: `clc` / `adc #1` (2 instructions, clobbers carry) + - 65C02: `ina` / `dea` (1 instruction, does not clobber carry) + +**Unconditional branch (short range):** + - 6502: `jmp addr` (3 bytes, 3 cycles) + - 65C02: `bra addr` (2 bytes, 3 cycles, limited range) + +**Zero-page indirect addressing (without Y offset):** + - 6502: `lda ($zp),y` with `ldy #0` (2 instructions, clobbers Y) + - 65C02: `lda ($zp)` (1 instruction, direct zero-page indirect) + +**Bit manipulation (test and reset/set):** + - 6502: `lda mask` / `and mask` / `sta addr` or `ora` + - 65C02: `trb addr` (test and reset bits), `tsb addr` (test and set bits) + +**Stack-relative addressing (local variable access):** + - 6502: requires manual stack pointer with `tsx` / `lda $0100,x` + - 65C02: `lda $NN,s` / `sta $NN,s` (stack-relative addressing built-in) + +**65816 compatibility note:** The Commander X16 can use a 65816 CPU which is +mostly 65C02 compatible but lacks the `rmb`, `smb`, `bbs`, `bbr` +instructions. The code generator should avoid these instructions when targeting +CX16 even if a 65C02 CPU is selected, or add a post-generation scan to reject +them. + +A code generator should check `options.compTarget.cpu` at the point of +emitting these instructions and select the appropriate variant, rather than +relying on a post-processing pass. + +## 13. Lessons from the Existing 6502 Code Generator + +This section documents design decisions, pitfalls, and notable patterns from the +existing 6502 code generator that a new IR-based backend should consider. + +### 13.1 Assignment Engine as Central Abstraction + +The existing code generator models all data movement as `AsmAssignment` +objects connecting an `AsmAssignSource` to one or more `AsmAssignTarget` +instances. This decouples "what value to move" from "how to move it" and +provides a clean dispatch table for all (source, target) type combinations. +A new backend should adopt a similar model. + +Source kinds: literal number, literal boolean, variable, array element, +memory address, CPU register, expression (complex). + +Target kinds: variable, array element, memory address, CPU register, pointer +dereference, void (discard result). + +### 13.2 Static Variable Allocation, No Stack Frames + +All variables are statically allocated. There is no runtime stack frame for +local variables -- they live in BSS or zero-page and are initialized by +explicit assignments in the subroutine body. Parameters are also statically +allocated. This avoids stack depth issues on the 6502 (which has a 256-byte +hardware stack) but means recursion is not supported. + +Dirty variables (`%option dirty`) are documented as undefined on subroutine +entry; the optimizer omits their re-initialization at subroutine entry. This is +worth preserving as an optimization hint. + +### 13.3 Zero-Page Scratch Variables + +A small set of fixed zero-page locations is reserved for temporary values +during expression evaluation: + +| Name | Size | Purpose | +|---------------|--------|-----------------------------------| +| SCRATCH_B1 | 1 byte | General-purpose byte temp | +| SCRATCH_REG | 1 byte | Register save/restore temp | +| SCRATCH_W1 | 2 words| General-purpose word temp (2) | +| SCRATCH_PTR | 2 bytes| Pointer for indirect access | + +These are defined in the header and used throughout the generated code. +A new backend should reserve equivalent scratch space and use it consistently. +Note that the byte-sized `SCRATCH_REG` aliases the low byte of a word scratch +location -- this is a known fragility to be handled carefully. + +### 13.4 Zero-Page vs. Non-Zero-Page Code Paths + +Many operations have two code paths depending on whether a variable is in +zero-page or not. For example, pointer indirection through a zero-page +variable uses `(zp),y` addressing directly, while a non-ZP pointer must +first be copied to a scratch pointer in zero-page. A new backend should track +this distinction and generate different code accordingly. + +The `isZpVar()` check is used pervasively. In an IR-based backend, this +information should be available as a property of the variable's storage +allocation. + +### 13.5 Floating-Point is Entirely Library-Driven + +All floating-point operations call into target-specific ROM routines via +`jsr`. The compiler does not generate inline FP code. The floating-point +accumulator (FAC1) is the primary operand register, FAC2 is secondary. +Float-to-integer conversion, comparison, and arithmetic all go through these +library entry points. + +Float constants are deduplicated globally across the entire program and +emitted as raw bytes in the target's float byte format (typically 5 bytes). + +### 13.6 Self-Modifying Code for Non-ROMable Targets + +When `romable=false` (the default), the code generator may use self-modifying +code patterns (e.g., writing comparison immediates into `cmp #xx` operands) +to avoid temporary variable overhead. This is incompatible with ROM-based +programs and must be disabled when `romable=true`. A new backend should +check the romable flag before emitting self-modifying patterns. + +### 13.7 Assembly-Level Peephole Optimization is Necessary + +The existing code generator runs a post-generation peephole optimizer on the +assembly text. Key optimizations to preserve in a new backend: + +- **Store-load elimination:** `sta X / lda X` -> remove the redundant load +- **Load-load elimination:** `lda V / sta X / lda V` (same value) -> remove +- **Push-pop elimination:** `pha / pla` -> remove both +- **JSR+RTS to JMP:** `jsr Sub / rts` -> `jmp Sub` +- **Comparison with zero:** `lda X / cmp #0` -> remove `cmp` (flags already set) +- **Branch inversion:** `beq L / jmp A / L:` -> `bne A` (shorter) +- **Canceled increments:** `iny / dey` -> remove both + +**Critical safety concern:** The optimizer must NOT eliminate loads from +I/O-mapped addresses (reading hardware registers has side effects). The +code generator should track which addresses are I/O and communicate this +to the optimizer, or accept that certain loads cannot be eliminated. + +### 13.8 Symbol Prefixing is Done Before Code Generation + +All user symbols are renamed with type prefixes (`p8b_`, `p8s_`, etc.) +by walking the AST and modifying node names *before* code generation. A new +`SymbolTable` is then constructed from the prefixed AST. This means the +symbol table used during code generation is NOT the same instance produced +by earlier compilation phases. + +A new backend should either adopt the same approach or work with unprefixed +symbols and let the assembler output handle name mangling differently. +Key implications: +- Struct field names are **not** prefixed during the prefix pass (they are + handled lazily at code generation time). +- After renaming, all `subType` references in the AST must be updated to + point to entries in the new symbol table. +- The `SymbolTable`'s cached `flat` property becomes stale after renaming + and must be rebuilt. + +### 13.9 CPU Register Model Abstraction + +The existing code uses a rich `RegisterOrPair` enum to model registers: + +| Register | Composition | Notes | +|----------------|----------------|----------------------------------| +| A | Single byte | Accumulator | +| X | Single byte | Index register | +| Y | Single byte | Index register | +| AY | Word | A (lo), Y (hi) | +| AX | Word | A (lo), X (hi) | +| FAC1 | Float (5 byte) | FP accumulator | +| FAC2 | Float (5 byte) | FP secondary | +| R0-R15 | Byte/Word | CX16 virtual registers (memory-mapped I/O) | +| R0R1-R14R15 | Long (4 byte) | Paired virtual registers | +| Status flags | Bit | Carry, Zero, Negative, Overflow | + +A new backend should define a similar abstract register set that maps onto +the target CPU's actual registers, with size and pairing information. + +### 13.10 Fixed-Point Scratch Registers on CX16 + +On the Commander X16 target, there are 16 virtual registers (R0-R15) at fixed +memory-mapped I/O addresses. These are used as fast temporary storage for +intermediate values, multi-word operations, and multi-value returns. +R14/R15 are reserved for long return values; R15 alone is used for secondary +word return values. A new backend should define which (if any) target-specific +fast registers are available and how they are allocated. + +### 13.11 Struct Instance Initialization Quirk + +Zero-page string and array variables with compile-time initial values require +special handling: their init data is stored in a ROM block (labeled with a +`_init_value` suffix), and a runtime `memcopy` copies the data from ROM +into the zero-page locations at startup. This is necessary because zero-page +locations cannot have static initializers in most binary formats. + +A new backend should replicate this pattern for any variables in a "fast" +memory region that lacks static initialization support. + +### 13.12 Float Eval Result Variables + +Per-subroutine temporary float variables (`subroutineFloatEvalResultVar1` +and `subroutineFloatEvalResultVar2`, 5 bytes each) are allocated in +BSS_NOCLEAR for intermediate float expression results. A new backend should +provide equivalent scratch space for float temporaries, sized according to +the target's float byte format. + +The `dirty` attribute is set on these temporaries because they don't need +initialization. + +### 13.13 Pitfall: `TODO` and `AssemblyError` for Unimplemented Paths + +The existing code generator uses `throw AssemblyError(...)` and `TODO()` +for type combinations or edge cases that are not yet implemented. This is a +placeholder for incomplete features. A new backend should either implement +these paths cleanly or define a clear error-reporting strategy for unsupported +operations. + +Several of these TODOs have been fixed in the IR codegen's builtin function +handling (`BuiltinFuncGen.kt`): `rol`, `ror`, `rol2`, `ror2`, `setlsb`, and +`setmsb` on pointer-deref arrays now fall through to address-computation +paths instead of throwing. The corresponding TODOs remain in the 6502 codegen +(`BuiltinFunctionsAsmGen.kt`). + +### 13.14 Source Line References in Assembly Output + +The generated assembly should include comments referencing the original +`.p8` source file and line number for each generated block of instructions. +This is controlled by the `includeSourcelines` compilation option. + +In the old code generator (AST-based), each AST node carries a `Position` +object with file, line, and column information. The code generator tracks +the last emitted source line and inserts `; source: filename.p8:NN` +comments whenever the line changes. + +For an IR-based code generator, source positions are stored at the +`IRCodeChunk` level via `sourceLinesPositions: List` +(rather than per-instruction). The code generator should emit a source +comment at the beginning of each chunk (or at subroutine entry) using +the first non-dummy position from this list. A `lastSourceLine` tracker +avoids duplicate consecutive comments. + +The comment format should be: + +``` +; source: filename.p8:42 +``` + +### 13.15 Output Type and Launcher Handling + +The code generator must handle multiple output formats: +- `PRG` with `BASIC` launcher: emits a BASIC SYS stub with a + `prog8_entrypoint` label, `.word` / `.null` directives +- `PRG` with `NONE` launcher: raw header at load address +- `RAW`: raw binary at load address +- `XEX`: Atari XEX format header +- `LIBRARY`: minimal `jmp main.start` header + +All formats emit `prog8_program_start` at the entry point and share a +common startup sequence: `cld`, stack save via `tsx`/`stx prog8_lib.orig_stackpointer`, +system initialization calls, BSS clearing, and jump to `main.start`. +After `main.start` returns, execution jumps to `cleanup_at_exit` +which calls `sys.poweroff_system`. + +The `includeSourcelines` option and user-supplied symbol definitions +(`options.symbolDefs` / `-esa`) are also emitted in the header. + +### 13.16 The `prog8_program_end` Label + +The code generator must emit a `prog8_program_end` label at the very end +of the generated output (after all code, data, and BSS sections). This label +marks where the program ends in memory and is used by: + +- The `progend()` library routine +- Memory layout calculations (`MEMORY TOP`) +- Determining available space between program end and hardware boundaries + +The old codegen emits this label at the end of its `footer()` method, +positioned after all sections. A new code generator should place it as the +last thing before assembly symbols and final output. + +### 13.17 BSS_NOCLEAR and BSS Section Organization + +Variables in BSS are split into two categories: +- **Clean** (`dirty == false`): cleared to zero at program startup. + These go into the `BSS` section. +- **Dirty** (`dirty == true`): cleared at program startup but NOT at each + subroutine entry. These go into `BSS_NOCLEAR`. The runtime cost of + zeroing on every call is avoided while still having deterministic initial + state at program start. + +The virtual register file (`p8_regfile`) always lives in `BSS` (cleared). +Memory slabs go into a separate `BSS_SLABS` sub-section to allow selective +relocation (e.g., to golden RAM or a high memory bank). + +A `PROG8_VARSHIGH_RAMBANK` equate documents which RAM bank is active for +relocated BSS sections. When relocation is active, non-relocatable sections +(`BSS_NOCLEAR`, `BSS_SLABS`) are emitted before the `* =` directive +that switches to the relocated area. diff --git a/examples/test.p8 b/examples/test.p8 index 20de1a0de..8330bff2e 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,84 +1,14 @@ %import textio %option no_sysinit %zeropage basicsafe - -; This file exercises real crashes in the compiler's built-in -; function handling. The same TODO guards exist in BOTH the IR codegen -; (BuiltinFuncGen.kt) and the existing 6502 codegen -; (BuiltinFunctionsAsmGen.kt) - so the crashes occur regardless of -; the chosen target. HOWEVER, the two backends differ in which -; patterns they crash on: -; -; pattern IR codegen 6502 codegen -; rol(plain[const]) works works -; rol(ptr.arr[const]) CRASH CRASH -; rol(ptr.arr[idx]) works CRASH -; setlsb(ptr.arr[i], v) CRASH CRASH -; -; Crashing patterns (commented out, uncomment to trigger the TODO): -; -; rol/ror/rol2/ror2(ptr.arr[const]) -; TODO at BuiltinFuncGen.kt:767 -; TODO at BuiltinFunctionsAsmGen.kt:763,841,950,1024 -; -; rol/ror/rol2/ror2(ptr.arr[idx]) -; Works in IR codegen (falls through to runtime path at line 794) -; TODO at BuiltinFunctionsAsmGen.kt:763,841,950,1024 -; (6502 codegen has no fallthrough) -; -; setlsb/setmsb(ptr.arr[i], v) -; TODO at BuiltinFuncGen.kt:835 (any index) -; TODO at BuiltinFunctionsAsmGen.kt:1139 (any index) -; Unreachable: TODO at BuiltinFuncGen.kt:870 (non-split branch -; needs PtIdentifier base, which is never a pointer-deref) -; -; The fix in both backends is to fall through to the existing non-const -; code path (using translateExpression(arg) in the IR codegen, the -; loadScaledArrayIndexIntoRegister helper in the 6502 codegen) instead -; of throwing. The cost is a small loss of optimization for the -; affected patterns. - -Data { - struct Node { - uword[5] arr - } -} +%encoding iso main { - ^^Data.Node @shared target - ^^Data.Node @shared ptr = &target - uword[5] @shared plain - ubyte @shared value = 99 - ubyte @shared idx = 2 - sub start() { - ; --- working on both targets: const index on a plain array --- - rol(plain[2]) - ror(plain[2]) - rol2(plain[2]) - ror2(plain[2]) - - ; --- working on IR only: non-const index on a typed pointer --- - ; (crashes 6502 codegen at BuiltinFunctionsAsmGen.kt:763,841,950,1024) - rol(ptr.arr[idx]) - ror(ptr.arr[idx]) - - ; --- broken on both: const index on a typed pointer --- - ; Uncomment to trigger: - ; BuiltinFuncGen.kt:767 (IR) - ; BuiltinFunctionsAsmGen.kt:763,841,950,1024 (6502) - rol(ptr.arr[2]) - ror(ptr.arr[2]) - rol2(ptr.arr[2]) - ror2(ptr.arr[2]) - - ; --- broken on both: any index on a typed pointer (setlsb/setmsb) --- - ; Uncomment to trigger: - ; BuiltinFuncGen.kt:835 (IR) - ; BuiltinFunctionsAsmGen.kt:1139 (6502) - setlsb(ptr.arr[idx], value) - setmsb(ptr.arr[idx], value) - setlsb(ptr.arr[2], value) - setmsb(ptr.arr[2], value) + txt.iso() + txt.print("hello world\n") + ;; txt.print_uw(12345) + txt.nl() + sys.poweroff_system() } } diff --git a/new6502gen/src/codegen/CodeGenerator.kt b/new6502gen/src/codegen/CodeGenerator.kt index 9c9d90d14..6df7e7f58 100644 --- a/new6502gen/src/codegen/CodeGenerator.kt +++ b/new6502gen/src/codegen/CodeGenerator.kt @@ -1,11 +1,1065 @@ +/* + * Main 6502/65C02 code generator for the new6502gen subproject. + * Reads an IRProgram (from .p8ir files) and outputs 64tass-compatible assembly. + * + * Architecture: + * - 200 virtual word registers (r0-r199) in BSS at `p8_regfile` (400 bytes) + * - Calling convention slots s0-s5 map to physical CPU registers (A, X, Y, AX, AY, XY) + * - LOADHR/STOREHR transfer between virtual regs and physical CPU registers via slot number + * - Math routines (mul/div/mod) are emitted as jsr calls to external helpers + * + * Instruction translation is dispatched to extension functions in separate files: + * InstrLoadStore.kt, InstrArithmetic.kt, InstrBitwise.kt, InstrBranch.kt, InstrControl.kt + * + * Not yet implemented: + * - Floating point operations + * - CALLI, CALLFAR, CALLFARVB + * - Some advanced math (SQRT, trig, etc.) + * - Some bitwise operations (ASRM, LSRM, LSLM, ROXRM, ROXLM) + */ + package codegen -import prog8.code.core.CpuType -import prog8.intermediate.IRProgram +import prog8.code.GENERATED_LABEL_PREFIX +import prog8.code.core.* +import prog8.intermediate.* +import java.nio.file.Path -class CodeGenerator(program: IRProgram, cpu: CpuType): ICodeGenerator { - override fun generate() { - TODO("Not yet implemented") +class CodeGenerator(private val program: IRProgram, private val target: ICompilationTarget) : ICodeGenerator { + private val output = StringBuilder() + private val cpu get() = target.cpu + + companion object { + const val NUM_REGISTERS = 200 + const val REGFILE_LABEL = "p8_regfile" } -} + init { + require(target.cpu != CpuType.VIRTUAL) + require(target.FLOAT_MEM_SIZE==5u) { "only 5-byte float format supported"} + } + + private var labelSeqCounter = 0 + private var lastSourceLine = -1 + + fun makeLabel(prefix: String): String { + val label = "${prefix}_$labelSeqCounter" + labelSeqCounter++ + return label + } + + fun emitSourceComment(positions: List) { + val pos = positions.firstOrNull { it != Position.DUMMY } ?: return + if (pos.line == lastSourceLine) return + lastSourceLine = pos.line + val fileOnly = pos.file.substringAfterLast('/').substringAfterLast('\\') + emitRaw("\t; source: $fileOnly:${pos.line}") + } + + override fun generate(): Boolean { + emitHeader() + emitWeakDeclarations() + emitConstants() + emitCode() + emitDataSection() + emitBssSection() + + val options = program.options + val asmFile = options.outputDir.resolve("${program.name}.asm") + try { + asmFile.toFile().writeText(output.toString()) + } catch (e: Exception) { + System.err.println("Failed to write assembly file: ${e.message}") + return false + } + + if (!options.quiet) + println("Assembly written to $asmFile") + + return assemble() + } + + private fun assemble(): Boolean { + val options = program.options + val name = program.name + val outputDir = options.outputDir + val asmFile = outputDir.resolve("$name.asm") + val viceMonListFile = outputDir.resolve("$name.vice-mon-list") + val listFile = outputDir.resolve("$name.list") + + val assemblerCommand: List + + fun addRemainingOptions(command: MutableList, programFile: Path, assembly: Path): List { + if(options.compTarget.additionalAssemblerOptions.isNotEmpty()) + command.addAll(options.compTarget.additionalAssemblerOptions) + command.addAll(listOf("--output", programFile.toString(), assembly.toString())) + return command + } + + when(options.output) { + OutputType.PRG -> { + val prgFile = outputDir.resolve("$name.prg") + val command = mutableListOf("64tass", "--cbm-prg", "--ascii", "--case-sensitive", "--long-branch", + "-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=$viceMonListFile") + if(options.warnSymbolShadowing) + command.add("-Wshadow") + else + command.add("-Wno-shadow") + if(options.asmQuiet) + command.add("--quiet") + if(options.asmListfile) + command.add("--list=$listFile") + assemblerCommand = addRemainingOptions(command, prgFile, asmFile) + if(!options.quiet) + println("\nCreating prg for target ${options.compTarget.name}.") + } + OutputType.XEX -> { + val xexFile = outputDir.resolve("$name.xex") + val command = mutableListOf("64tass", "--atari-xex", "--case-sensitive", "--long-branch", + "-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=$viceMonListFile") + if(options.warnSymbolShadowing) + command.add("-Wshadow") + else + command.add("-Wno-shadow") + if(options.asmQuiet) + command.add("--quiet") + if(options.asmListfile) + command.add("--list=$listFile") + assemblerCommand = addRemainingOptions(command, xexFile, asmFile) + if(!options.quiet) + println("\nCreating xex for target ${options.compTarget.name}.") + } + OutputType.RAW -> { + val binFile = outputDir.resolve("$name.bin") + val command = mutableListOf("64tass", "--nostart", "--case-sensitive", "--long-branch", + "-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=$viceMonListFile") + if(options.warnSymbolShadowing) + command.add("-Wshadow") + else + command.add("-Wno-shadow") + if(options.asmQuiet) + command.add("--quiet") + if(options.asmListfile) + command.add("--list=$listFile") + assemblerCommand = addRemainingOptions(command, binFile, asmFile) + if(!options.quiet) + println("\nCreating raw binary for target ${options.compTarget.name}.") + } + OutputType.LIBRARY -> { + val binFile = outputDir.resolve("$name.bin") + val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch", + "-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=$viceMonListFile") + if(options.warnSymbolShadowing) + command.add("-Wshadow") + else + command.add("-Wno-shadow") + if(options.asmQuiet) + command.add("--quiet") + if(options.asmListfile) + command.add("--list=$listFile") + if(options.compTarget.name in listOf("c64", "c128", "pet32")) { + if(!options.quiet) + println("\nCreating binary library file with header for target ${options.compTarget.name}.") + command.add("--cbm-prg") + } else { + if(!options.quiet) + println("\nCreating binary library file without header for target ${options.compTarget.name}.") + command.add("--nostart") + } + assemblerCommand = addRemainingOptions(command, binFile, asmFile) + } + } + + val proc = ProcessBuilder(assemblerCommand) + .redirectErrorStream(true) + + if(options.quiet) { + proc.redirectOutput(ProcessBuilder.Redirect.DISCARD) + } + + val process = try { + proc.start() + } catch (e: Exception) { + System.err.println("assembler failed to start: ${e.message}") + return false + } + + if(!options.quiet) { + process.inputStream.bufferedReader().use { reader -> + reader.forEachLine { println(it) } + } + } + + val result = process.waitFor() + if(result == 0) { + removeGeneratedLabelsFromMonlist(viceMonListFile) + generateBreakpointList(viceMonListFile) + } + return result == 0 + } + + private fun removeGeneratedLabelsFromMonlist(viceMonListFile: Path) { + val pattern = Regex("""al (\w+) \S+$GENERATED_LABEL_PREFIX.+?""") + val lines = viceMonListFile.toFile().readLines() + viceMonListFile.toFile().outputStream().bufferedWriter().use { + for(line in lines) { + if(pattern.matchEntire(line)==null) + it.write(line+"\n") + } + } + } + + private fun generateBreakpointList(viceMonListFile: Path) { + val breakpoints = mutableListOf() + val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") + for(line in viceMonListFile.toFile().readLines()) { + val match = pattern.matchEntire(line) + if(match != null) + breakpoints.add("break $" + match.groupValues[1]) + } + val num = breakpoints.size + breakpoints.add(0, "; breakpoint list now follows") + breakpoints.add(1, "; $num breakpoints have been defined") + breakpoints.add(2, "del") + viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n") + } + + // === emit helpers === + + fun emitLine(code: String, comment: String = "") { + if (comment.isNotEmpty()) + output.appendLine(" $code ; $comment") + else + output.appendLine(" $code") + } + + fun emitLabel(label: String) { + // Anonymous labels (+, ++, -) should not have a trailing colon in 64tass + if (label.all { it == '+' || it == '-' }) + output.appendLine(label) + else + output.appendLine("$label:") + } + + fun emitRaw(code: String) { + output.appendLine(code) + } + + // === register helpers === + // Virtual register file layout: each register is 2 bytes (low, high) at p8_regfile + reg*2 + + fun regAddrLo(reg: Int): String = "$REGFILE_LABEL+${reg * 2}" + fun regAddrHi(reg: Int): String = "$REGFILE_LABEL+${reg * 2 + 1}" + fun regAddr(reg: Int): String = "$REGFILE_LABEL+${reg * 2}" + + // === label helpers === + // Convert Prog8 scoped names to 64tass-compatible assembly symbols. + // Matches the naming mechanism from the existing 6502 codegen (AsmGen.fixNameSymbols) + // but with extra handling for :: in enum constants and byte-selector prefixes from IR. + + fun fixNameSymbols(name: String): String { + var n = name + // Replace angle brackets used in auto-generated label names (like existing codegen) + n = n.replace("<", "prog8_") + n = n.replace(">", "") + // Replace static variable hooks (like existing codegen) + n = n.replace("prog8_lib.P8ZP_SCRATCH_", "P8ZP_SCRATCH_") + // Prog8 :: scope operator in enum constants + n = n.replace("::", "_") + // Hyphens are not valid in 64tass labels + n = n.replace("-", "_") + return n + } + + fun constLabel(name: String): String = "p8c_${fixNameSymbols(name).replace(".", "_")}" + + // Handle IR symbol references that may have LSB (<) or MSB (>) byte-selector prefix + fun asmSymbolRef(name: String): String { + val (prefix, rest) = when { + name.startsWith('<') -> "<" to name.drop(1) + name.startsWith('>') -> ">" to name.drop(1) + else -> "" to name + } + return "$prefix${fixNameSymbols(rest)}" + } + + fun resolveSymbolRef(name: String): String { + val node = program.st.lookup(name) + return when (node) { + is IRStConstant -> constLabel(name) + is IRStStaticVariable, is IRStMemVar, + is IRStMemorySlab, + is IRStStructInstance -> fixNameSymbols(name) + else -> name // external/library symbol, keep as-is + } + } + + fun dataTypeSize(dt: IRDataType): Int = when (dt) { + IRDataType.BYTE -> 1 + IRDataType.WORD -> 2 + IRDataType.LONG -> 4 + IRDataType.FLOAT -> target.FLOAT_MEM_SIZE.toInt() + } + + /** Zero-page temporary pointer address for address computation (2 bytes). */ + val ZP_TEMP: String get() = "\$${target.zeropage.SCRATCH_PTR.toString(16)}" + + // === CPU-aware instruction helpers === + // 65C02 supports stz; plain 6502 needs lda #0 / sta instead + + fun emitStoreZero(target: String, comment: String = "") { + if (cpu == CpuType.CPU65C02) + emitLine("stz $target", comment) + else { + emitLine("lda #0", comment) + emitLine("sta $target") + } + } + + fun is65C02() = cpu == CpuType.CPU65C02 + + // === header and startup === + + // track external (library) symbols referenced during code generation + private val externalRefs = mutableSetOf() + + private fun emitHeader() { + val options = program.options + emitRaw("; Program: ${program.name}") + emitRaw("; Generated by prog8-newgen (new 6502 codegen)") + emitRaw("; Target CPU: ${cpu.name}") + emitRaw("; Output: ${options.output.name} Launcher: ${options.launcher.name}") + emitRaw("") + when (cpu) { + CpuType.CPU65C02 -> emitRaw(".cpu \"65c02\"") + CpuType.CPU6502 -> emitRaw(".cpu \"6502\"") + CpuType.VIRTUAL -> emitRaw(".cpu \"6502\"") + } + emitRaw(".enc 'none'") + emitRaw("") + val zp = target.zeropage + emitRaw("; zero-page scratch registers (used for address computation and temp values)") + emitRaw("P8ZP_SCRATCH_B1 = \$${zp.SCRATCH_B1.toString(16)} ; byte") + emitRaw("P8ZP_SCRATCH_REG = \$${zp.SCRATCH_REG.toString(16)} ; byte (must be B1+1)") + emitRaw("P8ZP_SCRATCH_W1 = \$${zp.SCRATCH_W1.toString(16)} ; word (2 bytes)") + emitRaw("P8ZP_SCRATCH_W2 = \$${zp.SCRATCH_W2.toString(16)} ; word (2 bytes)") + emitRaw("P8ZP_SCRATCH_PTR = \$${zp.SCRATCH_PTR.toString(16)} ; word (pointer)") + emitRaw("") + + // user-supplied symbol definitions + if (options.symbolDefs.isNotEmpty()) { + emitRaw("; -- user supplied symbols on the command line") + for ((name, value) in options.symbolDefs) { + emitRaw("$name = $value") + } + emitRaw("") + } + + // launcher / output type header + emitLauncher() + } + + private fun emitLauncher() { + val options = program.options + val loadAddr = options.loadAddress + + when (options.output) { + OutputType.LIBRARY -> { + emitRaw("; ---- library assembler program ----") + emitRaw("* = ${"$"}${loadAddr.toString(16)}") + emitLabel("prog8_program_start") + emitLine("jmp main.start") + emitRaw("") + } + + OutputType.RAW -> { + emitRaw("; ---- raw assembler program ----") + emitRaw("* = ${"$"}${loadAddr.toString(16)}") + emitLabel("prog8_program_start") + emitStartupSequence() + } + + OutputType.PRG -> { + when (options.launcher) { + CbmPrgLauncherType.BASIC -> { + emitRaw("; ---- basic program with sys call ----") + emitRaw("* = ${"$"}${loadAddr.toString(16)}") + emitLabel("prog8_program_start") + emitRaw(" .word (+), ${java.time.LocalDate.now().year}") + val entryAddr = "prog8_entrypoint" + emitRaw(" .null \$9e, format(' %d ', prog8_entrypoint), \$3a, \$8f, ' prog8'") + emitLabel("+") + emitRaw(" .word 0") + emitLabel("prog8_entrypoint") + emitStartupSequence() + } + CbmPrgLauncherType.NONE -> { + emitRaw("; ---- program without basic sys call ----") + emitRaw("* = ${"$"}${loadAddr.toString(16)}") + emitLabel("prog8_program_start") + emitStartupSequence() + } + } + } + + OutputType.XEX -> { + emitRaw("; ---- atari xex program ----") + emitRaw("* = ${"$"}${loadAddr.toString(16)}") + emitLabel("prog8_program_start") + emitStartupSequence() + } + } + } + + private fun emitStartupSequence() { + emitLine("cld") + emitLine("tsx", "save stackpointer for sys.exit()") + emitLine("stx prog8_lib.orig_stackpointer") + if (!program.options.noSysInit) { + emitLine("jsr p8_sys_startup.init_system") + emitLine("jsr p8_sys_startup.init_system_phase2") + } + emitLine("jsr prog8_lib.program_startup_clear_bss", "clear BSS section") + emitLine("jsr run_global_inits", "run block-level variable initializers") + emitLine("jsr main.start") + emitLine("jmp cleanup_at_exit") + emitRaw("") + emitLabel("cleanup_at_exit") + emitLine("jsr sys.poweroff_system") + emitRaw("") + } + + private fun emitWeakDeclarations() { + emitRaw("; External library symbol declarations") + emitRaw(".weak") + emitRaw(" p8_sys_startup.init_system = 0") + emitRaw(" p8_sys_startup.init_system_phase2 = 0") + emitRaw(" math_tmp = 0") + emitRaw(" math_mul8 = 0") + emitRaw(" math_mul16 = 0") + emitRaw(" math_div8 = 0") + emitRaw(" math_div16 = 0") + emitRaw(" math_mod8 = 0") + emitRaw(" math_mod16 = 0") + emitRaw(" math_divmod8 = 0") + emitRaw(" math_divmod16 = 0") + emitRaw(" sys .block") + emitRaw(" poweroff_system:") + emitRaw(" memset:") + emitRaw(" memcopy:") + emitRaw(" wait:") + emitRaw(" .bend") + emitRaw(" txt .block") + emitRaw(" color:") + emitRaw(" color2:") + emitRaw(" getchr:") + emitRaw(" getclr:") + emitRaw(" plot:") + emitRaw(" print:") + emitRaw(" print_ub:") + emitRaw(" print_uw:") + emitRaw(" setcc:") + emitRaw(" setcc2:") + emitRaw(" .bend") + emitRaw(" cx16 .block") + emitRaw(" set_screen_mode:") + emitRaw(" vpoke:") + emitRaw(" set_vsync_irq_handler:") + emitRaw(" enable_irq_handlers:") + emitRaw(" .bend") + emitRaw(" cbm .block") + emitRaw(" RDTIM16:") + emitRaw(" .bend") + emitRaw(" math .block") + emitRaw(" rnd:") + emitRaw(" .bend") + emitRaw(" psg2 .block") + emitRaw(" init:") + emitRaw(" update:") + emitRaw(" voice:") + emitRaw(" frequency:") + emitRaw(" envelope:") + emitRaw(" .bend") + emitRaw(" prog8_slabs .block") + emitRaw(" .bend") + emitRaw(" prog8_interned_strings .block") + emitRaw(" .bend") + emitRaw(" conv .block") + emitRaw(" .bend") + emitRaw(" prog8 .block") + emitRaw(" clear_bss:") + emitRaw(" .bend") + emitRaw(" prog8_lib .block") + emitRaw(" orig_stackpointer:") + emitRaw(" program_startup_clear_bss:") + emitRaw(" sqrt_long .block") + emitRaw(" num:") + emitRaw(" resultword:") + emitRaw(" .bend") + emitRaw(" .bend") + emitRaw(" prog8_bss_section_start:") + emitRaw(" prog8_bss_section_size:") + emitRaw(".endweak") + emitRaw("") + } + + private fun emitConstants() { + val emitted = mutableSetOf() + emitRaw("; Constants") + for (c in program.st.allConstants()) { + val cv = c.value + val csn = c.memorySlabName + if (cv != null) { + // emit with mangled name (for generated code references via constLabel) + val label = constLabel(c.name) + if (!emitted.add(label)) continue + emitRaw("$label = ${cv.toLong()}") + // also emit with original scoped name (for inline assembly references) + emitRaw("${c.name} = ${cv.toLong()}") + } else if (csn != null) { + val slab = program.st.lookup(csn) as? IRStMemorySlab + if (slab != null) { + val label = constLabel(c.name) + val slabRef = fixNameSymbols(slab.name) + if (!emitted.add(label)) continue + emitRaw("$label = $slabRef") + emitRaw("${c.name} = $slabRef") + } + } + } + emitRaw("") + + // Memory-mapped I/O addresses (used by inline assembly). + // Skip P8ZP_SCRATCH variables - they're already defined in the header. + val memMapped = program.st.allMemMappedVariables() + .filterNot { it.name.startsWith("P8ZP_SCRATCH") || it.name.startsWith("prog8_lib.P8ZP_SCRATCH") } + .toList() + if (memMapped.isNotEmpty()) { + emitRaw("; Memory-mapped variables") + for (mm in memMapped) { + emitRaw("${mm.name} = ${"$"}${mm.address.toString(16)}") + } + emitRaw("") + } + + // External KERNAL entry points and other symbols used by inline assembly + // These should come from %asminclude in library source but aren't in the IR yet. + if (target.name == "cx16") { + emitRaw("; External KERNAL entry points (cx16)") + emitRaw("cx16.mouse_config = \$ff68") + emitRaw("cx16.audio_init = \$c09f") + emitRaw("cx16.screen_mode = \$ff5f") + emitRaw("cbm.CINT = \$ff81") + emitRaw("cbm.IOINIT = \$ff84") + emitRaw("cbm.RESTOR = \$ff8a") + emitRaw("cbm.CHROUT = \$ffd2") + emitRaw("") + } + } + + // === main code emission === + + private fun emitCode() { + emitLabel("run_global_inits") + translateChunk(program.globalInits) + emitLine("rts") + emitRaw("") + + for (block in program.blocks) { + val addr = block.options.address + if (addr != null) { + emitRaw("* = ${"$"}${addr.toString(16)}") + } + val scopeDirective = if (block.options.forceOutput) ".block" else ".proc" + emitRaw("${block.label} $scopeDirective") + emitRaw("; Block: ${block.label}") + for (element in block.children) { + when (element) { + is IRSubroutine -> emitSubroutine(element) + is IRAsmSubroutine -> emitAsmSubroutine(element) + is IRInlineAsmChunk -> { + // skip label if it matches the block label (already defined by .proc/.block) + val cl = element.label + if (cl != null && cl != block.label) emitLabel(cl) + emitRaw(element.assembly) + } + is IRInlineBinaryChunk -> { + val cl = element.label + if (cl != null && cl != block.label) emitLabel(cl) + emitRaw(" .byte ${element.data.joinToString(",") { "${it.toInt() and 0xff}" }}") + } + else -> {} + } + } + val endDirective = if (block.options.forceOutput) ".bend" else ".pend" + emitRaw(" $endDirective") + emitRaw("") + } + } + + private fun emitSubroutine(sub: IRSubroutine) { + emitRaw("; Subroutine: ${sub.label}") + val firstChunk = sub.chunks.filterIsInstance().firstOrNull() + if (firstChunk != null) emitSourceComment(firstChunk.sourceLinesPositions) + emitRaw("${sub.label} .proc") + for (chunk in sub.chunks) { + when (chunk) { + is IRCodeChunk -> { + // skip chunk label if it matches the subroutine name (already defined by .proc) + if (chunk.label != sub.label) { + val cl = chunk.label + if (cl != null) emitLabel(cl) + } + translateChunk(chunk) + } + is IRInlineAsmChunk -> { + val cl = chunk.label + if (cl != null) emitLabel(cl) + emitRaw(chunk.assembly) + } + is IRInlineBinaryChunk -> { + val cl = chunk.label + if (cl != null) emitLabel(cl) + emitRaw(" .byte ${chunk.data.joinToString(",") { "${it.toInt() and 0xff}" }}") + } + } + } + emitRaw(".pend") + emitRaw("") + } + + private fun emitAsmSubroutine(sub: IRAsmSubroutine) { + val addr = sub.address + if (addr != null) { + emitLine("* = $addr", "origin for asm subroutine ${sub.label}") + } + emitRaw("${sub.label} .proc") + emitRaw(sub.asmChunk.assembly) + emitRaw(".pend") + emitRaw("") + } + + private fun translateChunk(chunk: IRCodeChunk) { + emitSourceComment(chunk.sourceLinesPositions) + for (insn in chunk.instructions) { + translateInstruction(insn) + } + } + + // === instruction dispatch === + + private fun translateInstruction(insn: IRInstruction) { + when (insn.opcode) { + Opcode.NOP -> {} + Opcode.BREAKPOINT -> emitLine("brk") + + Opcode.LOAD, Opcode.LOADM, Opcode.LOADR, Opcode.LOADX, Opcode.LOADHR, Opcode.LOADI, + Opcode.STOREM, Opcode.STOREX, Opcode.STOREZM, Opcode.STOREZI, Opcode.STOREZX, Opcode.STOREHR, Opcode.STOREI, + Opcode.LOADHFACZERO, Opcode.LOADHFACONE, + Opcode.STOREHFACZERO, Opcode.STOREHFACONE -> translateLoadStore(insn) + + Opcode.INC, Opcode.INCM, Opcode.DEC, Opcode.DECM, + Opcode.NEG, Opcode.NEGM, + Opcode.ADDR, Opcode.ADD, Opcode.ADDM, + Opcode.SUBR, Opcode.SUB, Opcode.SUBM, + Opcode.MULR, Opcode.MUL, Opcode.MULM, + Opcode.MULSR, Opcode.MULS, Opcode.MULSM, + Opcode.DIVR, Opcode.DIV, Opcode.DIVM, + Opcode.DIVSR, Opcode.DIVS, Opcode.DIVSM, + Opcode.MODR, Opcode.MOD, Opcode.MODSR, Opcode.MODS, + Opcode.DIVMODR, Opcode.DIVMOD, Opcode.SDIVMODR, Opcode.SDIVMOD, + Opcode.CMP, Opcode.CMPI -> translateArithmetic(insn) + + Opcode.ANDR, Opcode.AND, Opcode.ANDM, + Opcode.ORR, Opcode.OR, Opcode.ORM, + Opcode.XORR, Opcode.XOR, Opcode.XORM, + Opcode.INV, Opcode.INVM, + Opcode.ASRN, Opcode.ASRNM, Opcode.LSRN, Opcode.LSRNM, Opcode.LSLN, Opcode.LSLNM, + Opcode.ASR, Opcode.ASRM, Opcode.LSR, Opcode.LSRM, Opcode.LSL, Opcode.LSLM, + Opcode.ROR, Opcode.RORM, Opcode.ROL, Opcode.ROLM, + Opcode.ROXR, Opcode.ROXRM, Opcode.ROXL, Opcode.ROXLM, + Opcode.BITTST, Opcode.BITSET, Opcode.BITCLR, Opcode.BITTOG -> translateBitwise(insn) + + Opcode.BSTCC, Opcode.BSTCS, Opcode.BSTEQ, Opcode.BSTNE, + Opcode.BSTNEG, Opcode.BSTPOS, Opcode.BSTVC, Opcode.BSTVS, + Opcode.BGTR, Opcode.BGT, Opcode.BLT, Opcode.BLE, + Opcode.BGTSR, Opcode.BGTS, Opcode.BLTS, Opcode.BGESR, Opcode.BGES, Opcode.BLES, + Opcode.BGER, Opcode.BGE -> translateBranch(insn) + + Opcode.JUMP, Opcode.JUMPI, + Opcode.CALL, Opcode.CALLI, Opcode.CALLFAR, Opcode.CALLFARVB, + Opcode.SYSCALL, + Opcode.RETURN, Opcode.RETURNR, Opcode.RETURNI, + Opcode.PUSH, Opcode.POP, + Opcode.PUSHST, Opcode.POPST, + Opcode.CLC, Opcode.SEC, Opcode.CLI, Opcode.SEI, + Opcode.ALIGN, + Opcode.LSIGB, Opcode.LSIGW, Opcode.MSIGB, Opcode.MSIGW, Opcode.BSIGB, + Opcode.MIDB, Opcode.CONCAT, + Opcode.EXT, Opcode.EXTS, + Opcode.SQRT, Opcode.SQUARE, Opcode.SGN, + Opcode.FFROMUB, Opcode.FFROMSB, Opcode.FFROMUW, Opcode.FFROMSW, Opcode.FFROMSL, + Opcode.FTOUB, Opcode.FTOSB, Opcode.FTOUW, Opcode.FTOSW, Opcode.FTOSL, + Opcode.FABS, Opcode.FSIN, Opcode.FCOS, Opcode.FTAN, Opcode.FATAN, + Opcode.FPOW, Opcode.FLN, Opcode.FLOG, + Opcode.FROUND, Opcode.FFLOOR, Opcode.FCEIL, + Opcode.FCOMP -> translateControl(insn) + } + } + + // === struct type definitions === + + private fun emitStructDefs() { + val structs = program.st.allStructDefs().toList() + if (structs.isEmpty()) return + emitRaw("; struct type definitions") + for (sd in structs) { + val paramFields = sd.fields.filter { it.arraySize == null } + val structargs = paramFields.indices.joinToString(",") { "f$it" } + emitRaw("${fixNameSymbols(sd.name)} .struct $structargs") + var paramIdx = 0 + for (field in sd.fields) { + val type = when { + field.type.isBool || field.type.isUnsignedByte -> ".byte" + field.type.isSignedByte -> ".char" + field.type.isUnsignedWord || field.type.isPointer -> ".word" + field.type.isSignedWord -> ".sint" + field.type.isLong -> ".dint" + field.type.isFloat -> ".byte" + else -> ".byte" + } + val fieldName = "p8v_${field.name}" + val arrSize = field.arraySize + if (arrSize != null) { + val anon = List(arrSize) { "?" }.joinToString(",") + emitRaw(" $fieldName $type $anon") + } else { + emitRaw(" $fieldName $type \\f$paramIdx") + paramIdx++ + } + } + emitRaw(" .endstruct") + emitRaw("") + } + emitRaw("") + } + + // === data section === + + private fun emitDataSection() { + emitStructDefs() + emitRaw("") + + // static variables with initialized values (non-BSS, inline with code) + val initdVars = program.st.allVariables().filter { !it.inBss }.toList() + if (initdVars.isNotEmpty()) { + emitRaw("; static variables with initial values") + for (v in initdVars) { + emitAlign(v.align) + emitInitializedVariable(v) + } + emitRaw("") + } + + // memory slabs in BSS_SLABS section + val slabs = program.st.allMemorySlabs().toList() + if (slabs.isNotEmpty()) { + emitRaw(" .section BSS_SLABS") + emitLabel("${REGFILE_LABEL}_slabs") + for (slab in slabs) { + emitAlign(slab.align) + val label = fixNameSymbols(slab.name) + emitLine("$label .fill ${slab.size}", "memory slab ${slab.name}") + } + emitRaw(" .send BSS_SLABS") + emitRaw("") + } + + // struct instances without init values -> BSS (zeroed at startup) + val allInstances = program.st.allStructInstances().toList() + val (instancesNoInit, instancesWithInit) = allInstances.partition { it.values.isEmpty() } + if (instancesNoInit.isNotEmpty()) { + emitRaw(" .section BSS") + emitRaw("${REGFILE_LABEL}_structinst_bss .block") + for (si in instancesNoInit) { + val label = fixNameSymbols(si.name) + emitLine("$label .fill ${si.size}", "struct instance ${si.name}") + } + emitRaw(" .bend") + emitRaw(" .send BSS") + emitRaw("") + } + + // struct instances with init values -> STRUCTINSTANCES section + if (instancesWithInit.isNotEmpty()) { + emitRaw(" .section STRUCTINSTANCES") + emitRaw("${REGFILE_LABEL}_structinst .block") + for (si in instancesWithInit) { + val label = fixNameSymbols(si.name) + val instName = label.substringAfter('.') + emitLabel(instName) + for (fieldValue in si.values) { + val fv = fieldValue.value + when (fv) { + is IRStSymbolicReference.Numeric -> { + val v = fv.value.toInt() + val size = when (fieldValue.dt) { + BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL -> ".byte" + BaseDataType.UWORD, BaseDataType.WORD -> ".word" + else -> ".word" + } + emitLine(" $size $v") + } + is IRStSymbolicReference.Symbol -> { + emitLine(" .word ${fixNameSymbols(fv.name)}") + } + is IRStSymbolicReference.BoolValue -> { + val v = if (fv.value) 1 else 0 + emitLine(" .byte $v") + } + } + } + } + emitRaw(" .bend") + emitRaw(" .send STRUCTINSTANCES") + emitRaw("") + } + } + + private fun emitAlign(align: UInt) { + if (align > 1u) { + emitLine(".align ${"$"}${align.toString(16)}") + } + } + + private fun emitInitializedVariable(v: IRStStaticVariable) { + val dt = v.dt + val label = fixNameSymbols(v.name) + val init = v.initializationValue + + when { + dt.isSplitWordArray -> { + // _lsb / _msb halves via helper array + val numElements = v.length?.toInt() ?: 0 + val halfBytes = numElements // each element is 1 byte per half + val values = when (init) { + is IRVariableInitializer.Array -> init.elements.map { + when (it) { + is IRStSymbolicReference.Numeric -> it.value.toInt() + is IRStSymbolicReference.BoolValue -> if (it.value) 1 else 0 + else -> 0 + } + } + else -> List(halfBytes) { 0 } + } + val parts = values.joinToString(",") + emitRaw("_array_$label := $parts") + emitLine("${label}_lsb .byte <_array_$label") + emitLine("${label}_msb .byte >_array_$label") + } + + dt.isString -> { + when (init) { + is IRVariableInitializer.Str -> { + emitLine("$label .null \"${init.text}\"") + } + else -> emitLine("$label .byte ?") + } + } + + dt.isArray -> { + val values = when (init) { + is IRVariableInitializer.Array -> init.elements.map { + when (it) { + is IRStSymbolicReference.Numeric -> "${it.value.toInt()}" + is IRStSymbolicReference.BoolValue -> if (it.value) "1" else "0" + is IRStSymbolicReference.Symbol -> asmSymbolRef(it.name) + } + } + else -> listOf("0") + } + val directive = when { + dt.elementType().isUnsignedByte || dt.elementType().isBool -> ".byte" + dt.elementType().isSignedByte -> ".char" + dt.elementType().isUnsignedWord || dt.elementType().isPointer -> ".word" + dt.elementType().isSignedWord -> ".sint" + dt.elementType().isLong -> ".dint" + else -> ".byte" + } + if (values.size <= 16) { + emitLine("$label $directive ${values.joinToString(",")}") + } else { + emitLabel(label) + for (chunk in values.chunked(16)) { + emitLine(" $directive ${chunk.joinToString(",")}") + } + } + } + + dt.isFloat -> { + val bytes = when (init) { + is IRVariableInitializer.Numeric -> target.getFloatAsmBytes(init.value) + else -> List(target.FLOAT_MEM_SIZE.toInt()) { 0 }.joinToString(",") { "\$00" } + } + emitLine("$label .byte $bytes") + } + + else -> { + // single numeric value (byte/word/long) + val v = when (init) { + is IRVariableInitializer.Numeric -> init.value.toInt() + else -> 0 + } + val directive = when { + dt.isByteOrBool -> ".byte" + dt.isWord -> ".word" + dt.isLong -> ".long" + else -> ".byte" + } + emitLine("$label $directive $v") + } + } + } + + // === BSS sections === + + private fun emitBssSection() { + val options = program.options + val bssVars = program.st.allVariables().filter { it.inBss }.toList() + + emitRaw("") + emitRaw("; bss sections") + emitRaw("PROG8_VARSHIGH_RAMBANK = ${options.varsHighBank ?: 1}") + + // Determine if BSS needs relocation + val relocateVars = options.varsGolden || options.varsHighBank != null + val relocateSlabs = options.slabsGolden || options.slabsHighBank != null + + if (relocateVars) { + emitNonrelocatableSections(relocateSlabs) + emitLabel("prog8_program_end") + val relocatedStart = if (options.varsGolden) options.compTarget.BSSGOLDENRAM_START + else options.compTarget.BSSHIGHRAM_START + emitRaw(" * = ${"$"}${relocatedStart.toString(16)}") + } + + emitLabel("prog8_bss_section_start") + emitBssVars(bssVars) + + if (relocateVars) { + val relocatedEnd = if (options.varsGolden) options.compTarget.BSSGOLDENRAM_END + else options.compTarget.BSSHIGHRAM_END + emitLine(" .cerror * > ${"$"}${relocatedEnd.toString(16)}", + "too many variables/data for BSS section") + } + + emitLabel("prog8_bss_section_end") + emitRaw("prog8_bss_section_size = prog8_bss_section_end - prog8_bss_section_start") + + emitRaw("") + emitAsmSymbols() + emitRaw("") + if (!relocateVars) { + emitLabel("prog8_program_end") + } + // memtop overflow check + if (options.memtopAddress > 0u) { + val memtopHex = "${"$"}${options.memtopAddress.toString(16)}" + emitLine(" .cerror * >= $memtopHex", + "Program too long, memtop=$memtopHex") + } + emitRaw("") + } + + private fun emitNonrelocatableSections(alsoSlabs: Boolean) { + // emit BSS_NOCLEAR and optionally BSS_SLABS before relocation + emitRaw(" .dsection BSS_NOCLEAR") + if (!alsoSlabs) { + emitRaw(" .dsection BSS_SLABS") + } + } + + private fun emitBssVars(vars: List) { + // BSS_NOCLEAR for dirty variables and float eval temporaries + val (dirty, clean) = vars.partition { it.dirty } + if (dirty.isNotEmpty()) { + emitRaw(" .section BSS_NOCLEAR") + emitRaw("; dirty variables (not cleared at subroutine entry)") + for (v in dirty) { + emitAlign(v.align) + emitUninitializedVariable(v) + } + emitRaw(" .send BSS_NOCLEAR") + emitRaw("") + } + + // Main BSS section (cleared at startup) + val regfileSize = NUM_REGISTERS * 2 + emitLabel(REGFILE_LABEL) + emitLine(".fill $regfileSize", "virtual register file (${NUM_REGISTERS} words)") + emitRaw("") + + if (clean.isNotEmpty()) { + emitRaw(" .section BSS") + for (v in clean) { + emitAlign(v.align) + emitUninitializedVariable(v) + } + emitRaw(" .send BSS") + emitRaw("") + } + + // emit BSS_NOCLEAR remainder (for library subroutines that may add vars) + emitRaw(" .dsection BSS_NOCLEAR") + emitRaw(" .dsection BSS_SLABS") + } + + private fun emitUninitializedVariable(v: IRStStaticVariable) { + val label = fixNameSymbols(v.name) + val dt = v.dt + + val (directive, count) = when { + dt.isSplitWordArray -> { + // two separate byte arrays: _lsb and _msb + val numElements = v.length?.toInt() ?: 1 + emitLine("${label}_lsb .fill $numElements") + emitLine("${label}_msb .fill $numElements") + return + } + dt.isBool || dt.isUnsignedByte -> ".byte" to 1 + dt.isSignedByte -> ".char" to 1 + dt.isUnsignedWord || dt.isPointer -> ".word" to 1 + dt.isSignedWord -> ".sint" to 1 + dt.isLong -> ".dint" to 1 + dt.isFloat -> ".fill" to target.FLOAT_MEM_SIZE.toInt() + dt.isArray || dt.isString -> { + val sz = (v.length ?: 1u).toInt() + ".fill" to sz + } + else -> ".byte" to 1 + } + + if (directive == ".fill") { + emitLine("$label .fill $count") + } else { + emitLine("$label $directive ?") + } + } + + private fun emitAsmSymbols() { + for ((name, value) in program.asmSymbols) { + if (!name.startsWith(".")) { + emitLabel(name) + emitLine("= $value") + } + } + for ((name, value) in program.st.getAsmSymbols()) { + if (!name.startsWith(".")) { + emitLabel(name) + emitLine("= $value") + } + } + } + + private val compTarget: ICompilationTarget get() = target +} diff --git a/new6502gen/src/codegen/ICodeGenerator.kt b/new6502gen/src/codegen/ICodeGenerator.kt index a65bc9cb6..ebf5b080e 100644 --- a/new6502gen/src/codegen/ICodeGenerator.kt +++ b/new6502gen/src/codegen/ICodeGenerator.kt @@ -1,5 +1,5 @@ package codegen interface ICodeGenerator { - fun generate() + fun generate(): Boolean } diff --git a/new6502gen/src/codegen/InstrArithmetic.kt b/new6502gen/src/codegen/InstrArithmetic.kt new file mode 100644 index 000000000..b410e770c --- /dev/null +++ b/new6502gen/src/codegen/InstrArithmetic.kt @@ -0,0 +1,857 @@ +/* + * Arithmetic IR instruction translations for the new6502gen code generator. + * + * Handles: INC/DEC, NEG, ADD/SUB, MUL/DIV/MOD (signed and unsigned), + * DIVMOD (combined division and modulo), and CMP/CMPI. + * + * All operations work on virtual registers (r0-r199 in the register file). + * BYTE and WORD types are fully implemented; LONG and FLOAT are partial/TODO. + * + * Multiply/divide/modulo operations use helper subroutines (math_mul8, math_div16, etc.) + * that must be provided externally (e.g. in the standard library or runtime). + * + * Important notes: + * - 6502 flag discipline: ADC needs CLC before, SBC needs SEC before + * - 16-bit addition/subtraction is done low byte first with carry propagation + * - Signed multiply/divide currently fall through to unsigned versions + */ + +package codegen + +import prog8.intermediate.* + +fun CodeGenerator.translateArithmetic(insn: IRInstruction) { + val type = insn.type ?: IRDataType.BYTE + val r1 = insn.reg1 // nullable - INCM/DECM/NEGM have no reg1 + val r2 = insn.reg2 + val imm = insn.immediate + val addr = insn.address + val label = insn.labelSymbol + + when (insn.opcode) { + Opcode.INC -> incrementRegister(r1 ?: error("INC needs reg1"), type) + Opcode.INCM -> incrementMemory(resolveAddress(addr, label), type) + Opcode.DEC -> decrementRegister(r1 ?: error("DEC needs reg1"), type) + Opcode.DECM -> decrementMemory(resolveAddress(addr, label), type) + Opcode.NEG -> negateRegister(r1 ?: error("NEG needs reg1"), type) + Opcode.NEGM -> negateMemory(resolveAddress(addr, label), type) + + Opcode.ADDR -> { + val r2val = r2 ?: error("ADDR needs reg2") + addRegisters(r1 ?: error("ADDR needs reg1"), r2val, type) + } + Opcode.ADD -> { + val value = imm ?: error("ADD needs immediate") + addImmediate(r1 ?: error("ADD needs reg1"), value, type) + } + Opcode.ADDM -> { + val target = resolveAddress(addr, label) + addMemory(r1 ?: error("ADDM needs reg1"), target, type) + } + + Opcode.SUBR -> { + val r2val = r2 ?: error("SUBR needs reg2") + subRegisters(r1 ?: error("SUBR needs reg1"), r2val, type) + } + Opcode.SUB -> { + val value = imm ?: error("SUB needs immediate") + subImmediate(r1 ?: error("SUB needs reg1"), value, type) + } + Opcode.SUBM -> { + val target = resolveAddress(addr, label) + subMemory(r1 ?: error("SUBM needs reg1"), target, type) + } + + Opcode.MULR -> { + val r2val = r2 ?: error("MULR needs reg2") + mulRegisters(r1 ?: error("MULR needs reg1"), r2val, type) + } + Opcode.MUL -> { + val value = imm ?: error("MUL needs immediate") + mulImmediate(r1 ?: error("MUL needs reg1"), value, type) + } + Opcode.MULM -> { + val target = resolveAddress(addr, label) + mulMemory(r1 ?: error("MULM needs reg1"), target, type) + } + Opcode.MULSR -> { + val r2val = r2 ?: error("MULSR needs reg2") + mulSignedRegisters(r1 ?: error("MULSR needs reg1"), r2val, type) + } + Opcode.MULS -> { + val value = imm ?: error("MULS needs immediate") + mulSignedImmediate(r1 ?: error("MULS needs reg1"), value, type) + } + Opcode.MULSM -> { + val target = resolveAddress(addr, label) + mulSignedMemory(r1 ?: error("MULSM needs reg1"), target, type) + } + + Opcode.DIVR -> { + val r2val = r2 ?: error("DIVR needs reg2") + divRegisters(r1 ?: error("DIVR needs reg1"), r2val, type) + } + Opcode.DIV -> { + val value = imm ?: error("DIV needs immediate") + divImmediate(r1 ?: error("DIV needs reg1"), value, type) + } + Opcode.DIVM -> { + val target = resolveAddress(addr, label) + divMemory(r1 ?: error("DIVM needs reg1"), target, type) + } + Opcode.DIVSR -> { + val r2val = r2 ?: error("DIVSR needs reg2") + divSignedRegisters(r1 ?: error("DIVSR needs reg1"), r2val, type) + } + Opcode.DIVS -> { + val value = imm ?: error("DIVS needs immediate") + divSignedImmediate(r1 ?: error("DIVS needs reg1"), value, type) + } + Opcode.DIVSM -> { + val target = resolveAddress(addr, label) + divSignedMemory(r1 ?: error("DIVSM needs reg1"), target, type) + } + + Opcode.MODR -> { + val r2val = r2 ?: error("MODR needs reg2") + modRegisters(r1 ?: error("MODR needs reg1"), r2val, type) + } + Opcode.MOD -> { + val value = imm ?: error("MOD needs immediate") + modImmediate(r1 ?: error("MOD needs reg1"), value, type) + } + Opcode.MODSR -> { + val r2val = r2 ?: error("MODSR needs reg2") + modSignedRegisters(r1 ?: error("MODSR needs reg1"), r2val, type) + } + Opcode.MODS -> { + val value = imm ?: error("MODS needs immediate") + modSignedImmediate(r1 ?: error("MODS needs reg1"), value, type) + } + + Opcode.DIVMODR -> { + val r2val = r2 ?: error("DIVMODR needs reg2") + divModRegisters(r1 ?: error("DIVMODR needs reg1"), r2val, type) + } + Opcode.DIVMOD -> { + val value = imm ?: error("DIVMOD needs immediate") + divModImmediate(r1 ?: error("DIVMOD needs reg1"), value, type) + } + Opcode.SDIVMODR -> { + val r2val = r2 ?: error("SDIVMODR needs reg2") + sdivModRegisters(r1 ?: error("SDIVMODR needs reg1"), r2val, type) + } + Opcode.SDIVMOD -> { + val value = imm ?: error("SDIVMOD needs immediate") + sdivModImmediate(r1 ?: error("SDIVMOD needs reg1"), value, type) + } + + Opcode.CMP -> { + val r2val = r2 ?: error("CMP needs reg2") + compareRegisters(r1 ?: error("CMP needs reg1"), r2val, type) + } + Opcode.CMPI -> { + val value = imm ?: error("CMPI needs immediate") + compareImmediate(r1 ?: error("CMPI needs reg1"), value, type) + } + + else -> error("Unknown arithmetic opcode: ${insn.opcode}") + } +} + +// === Increment/Decrement === + +private fun CodeGenerator.incrementRegister(reg: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("inc ${regAddrLo(reg)}") + } + IRDataType.WORD -> { + emitLine("inc ${regAddrLo(reg)}") + emitLine("bne +") + emitLine("inc ${regAddrHi(reg)}") + emitLabel("+") + } + IRDataType.LONG -> { + emitLine("inc ${regAddrLo(reg)}") + emitLine("bne +") + emitLine("inc ${regAddrHi(reg)}") + emitLine("bne +") + emitLine("inc ${regAddrLo(reg) + 2}") + emitLine("bne +") + emitLine("inc ${regAddrLo(reg) + 3}") + emitLabel("+") + } + IRDataType.FLOAT -> emitLine("; FLOAT INC r$reg (not implemented)") + } +} + +private fun CodeGenerator.decrementRegister(reg: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("dec ${regAddrLo(reg)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(reg)}") + emitLine("bne +") + emitLine("dec ${regAddrHi(reg)}") + emitLabel("+") + emitLine("dec ${regAddrLo(reg)}") + } + IRDataType.LONG -> { + emitLine("lda ${regAddrLo(reg)}") + emitLine("bne +") + emitLine("lda ${regAddrHi(reg)}") + emitLine("bne +") + emitLine("lda ${regAddrLo(reg) + 2}") + emitLine("bne +") + emitLine("dec ${regAddrLo(reg) + 3}") + emitLabel("+") + emitLine("dec ${regAddrLo(reg) + 2}") + emitLine("bne +") + emitLine("dec ${regAddrHi(reg)}") + emitLabel("+") + emitLine("dec ${regAddrLo(reg)}") + // Note: this decrement logic is simplified and may not handle all cases correctly + } + IRDataType.FLOAT -> emitLine("; FLOAT DEC r$reg (not implemented)") + } +} + +private fun CodeGenerator.incrementMemory(target: String, type: IRDataType) { + when (type) { + IRDataType.BYTE -> emitLine("inc $target") + IRDataType.WORD -> { + emitLine("inc $target") + emitLine("bne +") + emitLine("inc $target+1") + emitLabel("+") + } + else -> emitLine("; INCM $target ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.decrementMemory(target: String, type: IRDataType) { + when (type) { + IRDataType.BYTE -> emitLine("dec $target") + IRDataType.WORD -> { + emitLine("lda $target") + emitLine("bne +") + emitLine("dec $target+1") + emitLabel("+") + emitLine("dec $target") + } + else -> emitLine("; DECM $target ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.negateRegister(reg: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("sec") + emitLine("lda #0") + emitLine("sbc ${regAddrLo(reg)}") + emitLine("sta ${regAddrLo(reg)}") + } + IRDataType.WORD -> { + emitLine("sec") + emitLine("lda #0") + emitLine("sbc ${regAddrLo(reg)}") + emitLine("sta ${regAddrLo(reg)}") + emitLine("lda #0") + emitLine("sbc ${regAddrHi(reg)}") + emitLine("sta ${regAddrHi(reg)}") + } + else -> emitLine("; NEG r$reg ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.negateMemory(target: String, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("sec") + emitLine("lda #0") + emitLine("sbc $target") + emitLine("sta $target") + } + IRDataType.WORD -> { + emitLine("sec") + emitLine("lda #0") + emitLine("sbc $target") + emitLine("sta $target") + emitLine("lda #0") + emitLine("sbc $target+1") + emitLine("sta $target+1") + } + else -> emitLine("; NEGM $target ${type.name} (not implemented)") + } +} + +// === Addition === + +private fun CodeGenerator.addRegisters(dst: Int, src: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("clc") + emitLine("lda ${regAddrLo(dst)}") + emitLine("adc ${regAddrLo(src)}") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("clc") + emitLine("lda ${regAddrLo(dst)}") + emitLine("adc ${regAddrLo(src)}") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda ${regAddrHi(dst)}") + emitLine("adc ${regAddrHi(src)}") + emitLine("sta ${regAddrHi(dst)}") + } + IRDataType.LONG -> { + emitLine("clc") + emitLine("lda ${regAddrLo(dst)}") + emitLine("adc ${regAddrLo(src)}") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda ${regAddrHi(dst)}") + emitLine("adc ${regAddrHi(src)}") + emitLine("sta ${regAddrHi(dst)}") + emitLine("lda ${regAddrLo(dst) + 2}") + emitLine("adc ${regAddrLo(src) + 2}") + emitLine("sta ${regAddrLo(dst) + 2}") + emitLine("lda ${regAddrLo(dst) + 3}") + emitLine("adc ${regAddrLo(src) + 3}") + emitLine("sta ${regAddrLo(dst) + 3}") + } + IRDataType.FLOAT -> emitLine("; FLOAT ADDR r$dst, r$src (not implemented)") + } +} + +private fun CodeGenerator.addImmediate(dst: Int, value: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("clc") + emitLine("lda ${regAddrLo(dst)}") + emitLine("adc #${value and 0xff}") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("clc") + emitLine("lda ${regAddrLo(dst)}") + emitLine("adc #<${value and 0xffff}") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda ${regAddrHi(dst)}") + emitLine("adc #>${value and 0xffff}") + emitLine("sta ${regAddrHi(dst)}") + } + else -> emitLine("; ADD r$dst, #$value ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.addMemory(dst: Int, source: String, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("clc") + emitLine("lda ${regAddrLo(dst)}") + emitLine("adc $source") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("clc") + emitLine("lda ${regAddrLo(dst)}") + emitLine("adc $source") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda ${regAddrHi(dst)}") + emitLine("adc $source+1") + emitLine("sta ${regAddrHi(dst)}") + } + else -> emitLine("; ADDM r$dst, $source ${type.name} (not implemented)") + } +} + +// === Subtraction === + +private fun CodeGenerator.subRegisters(dst: Int, src: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("sec") + emitLine("lda ${regAddrLo(dst)}") + emitLine("sbc ${regAddrLo(src)}") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("sec") + emitLine("lda ${regAddrLo(dst)}") + emitLine("sbc ${regAddrLo(src)}") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda ${regAddrHi(dst)}") + emitLine("sbc ${regAddrHi(src)}") + emitLine("sta ${regAddrHi(dst)}") + } + IRDataType.LONG -> { + emitLine("sec") + emitLine("lda ${regAddrLo(dst)}") + emitLine("sbc ${regAddrLo(src)}") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda ${regAddrHi(dst)}") + emitLine("sbc ${regAddrHi(src)}") + emitLine("sta ${regAddrHi(dst)}") + emitLine("lda ${regAddrLo(dst) + 2}") + emitLine("sbc ${regAddrLo(src) + 2}") + emitLine("sta ${regAddrLo(dst) + 2}") + emitLine("lda ${regAddrLo(dst) + 3}") + emitLine("sbc ${regAddrLo(src) + 3}") + emitLine("sta ${regAddrLo(dst) + 3}") + } + IRDataType.FLOAT -> emitLine("; FLOAT SUBR r$dst, r$src (not implemented)") + } +} + +private fun CodeGenerator.subImmediate(dst: Int, value: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("sec") + emitLine("lda ${regAddrLo(dst)}") + emitLine("sbc #${value and 0xff}") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("sec") + emitLine("lda ${regAddrLo(dst)}") + emitLine("sbc #<${value and 0xffff}") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda ${regAddrHi(dst)}") + emitLine("sbc #>${value and 0xffff}") + emitLine("sta ${regAddrHi(dst)}") + } + else -> emitLine("; SUB r$dst, #$value ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.subMemory(dst: Int, source: String, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("sec") + emitLine("lda ${regAddrLo(dst)}") + emitLine("sbc $source") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("sec") + emitLine("lda ${regAddrLo(dst)}") + emitLine("sbc $source") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda ${regAddrHi(dst)}") + emitLine("sbc $source+1") + emitLine("sta ${regAddrHi(dst)}") + } + else -> emitLine("; SUBM r$dst, $source ${type.name} (not implemented)") + } +} + +// === Multiplication === + +private fun CodeGenerator.mulRegisters(dst: Int, src: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("sta math_tmp") + emitLine("lda ${regAddrLo(src)}") + emitLine("jsr math_mul8") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("sta math_tmp") + emitLine("lda ${regAddrHi(dst)}") + emitLine("sta math_tmp+1") + emitLine("lda ${regAddrLo(src)}") + emitLine("sta math_tmp+2") + emitLine("lda ${regAddrHi(src)}") + emitLine("sta math_tmp+3") + emitLine("jsr math_mul16") + emitLine("lda math_tmp") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda math_tmp+1") + emitLine("sta ${regAddrHi(dst)}") + } + IRDataType.LONG -> emitLine("; MULR LONG r$dst, r$src (not implemented)") + IRDataType.FLOAT -> emitLine("; MULR FLOAT r$dst, r$src (not implemented)") + } +} + +private fun CodeGenerator.mulImmediate(dst: Int, value: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("sta math_tmp") + emitLine("lda #${value and 0xff}") + emitLine("jsr math_mul8") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("sta math_tmp") + emitLine("lda ${regAddrHi(dst)}") + emitLine("sta math_tmp+1") + emitLine("lda #<${value and 0xffff}") + emitLine("sta math_tmp+2") + emitLine("lda #>${value and 0xffff}") + emitLine("sta math_tmp+3") + emitLine("jsr math_mul16") + emitLine("lda math_tmp") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda math_tmp+1") + emitLine("sta ${regAddrHi(dst)}") + } + else -> emitLine("; MUL r$dst, #$value ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.mulMemory(dst: Int, source: String, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("sta math_tmp") + emitLine("lda $source") + emitLine("jsr math_mul8") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("sta math_tmp") + emitLine("lda ${regAddrHi(dst)}") + emitLine("sta math_tmp+1") + emitLine("lda $source") + emitLine("sta math_tmp+2") + emitLine("lda $source+1") + emitLine("sta math_tmp+3") + emitLine("jsr math_mul16") + emitLine("lda math_tmp") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda math_tmp+1") + emitLine("sta ${regAddrHi(dst)}") + } + else -> emitLine("; MULM r$dst, $source ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.mulSignedRegisters(dst: Int, src: Int, type: IRDataType) { + emitLine("; MULSR r$dst, r$src (signed) - using unsigned for now") + mulRegisters(dst, src, type) +} + +private fun CodeGenerator.mulSignedImmediate(dst: Int, value: Int, type: IRDataType) { + emitLine("; MULS r$dst, #$value (signed) - using unsigned for now") + mulImmediate(dst, value, type) +} + +private fun CodeGenerator.mulSignedMemory(dst: Int, source: String, type: IRDataType) { + emitLine("; MULSM r$dst, $source (signed) - using unsigned for now") + mulMemory(dst, source, type) +} + +// === Division === + +private fun CodeGenerator.divRegisters(dst: Int, src: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("sta math_tmp") + emitLine("lda ${regAddrLo(src)}") + emitLine("sta math_tmp+1") + emitLine("jsr math_div8") + emitLine("lda math_tmp") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("sta math_tmp") + emitLine("lda ${regAddrHi(dst)}") + emitLine("sta math_tmp+1") + emitLine("lda ${regAddrLo(src)}") + emitLine("sta math_tmp+2") + emitLine("lda ${regAddrHi(src)}") + emitLine("sta math_tmp+3") + emitLine("jsr math_div16") + emitLine("lda math_tmp") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda math_tmp+1") + emitLine("sta ${regAddrHi(dst)}") + } + else -> emitLine("; DIVR r$dst, r$src ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.divImmediate(dst: Int, value: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("sta math_tmp") + emitLine("lda #${value and 0xff}") + emitLine("sta math_tmp+1") + emitLine("jsr math_div8") + emitLine("lda math_tmp") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("sta math_tmp") + emitLine("lda ${regAddrHi(dst)}") + emitLine("sta math_tmp+1") + emitLine("lda #<${value and 0xffff}") + emitLine("sta math_tmp+2") + emitLine("lda #>${value and 0xffff}") + emitLine("sta math_tmp+3") + emitLine("jsr math_div16") + emitLine("lda math_tmp") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda math_tmp+1") + emitLine("sta ${regAddrHi(dst)}") + } + else -> emitLine("; DIV r$dst, $value ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.divMemory(dst: Int, source: String, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("sta math_tmp") + emitLine("lda $source") + emitLine("sta math_tmp+1") + emitLine("jsr math_div8") + emitLine("lda math_tmp") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("sta math_tmp") + emitLine("lda ${regAddrHi(dst)}") + emitLine("sta math_tmp+1") + emitLine("lda $source") + emitLine("sta math_tmp+2") + emitLine("lda $source+1") + emitLine("sta math_tmp+3") + emitLine("jsr math_div16") + emitLine("lda math_tmp") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda math_tmp+1") + emitLine("sta ${regAddrHi(dst)}") + } + else -> emitLine("; DIVM r$dst, $source ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.divSignedRegisters(dst: Int, src: Int, type: IRDataType) { + emitLine("; DIVSR r$dst, r$src (signed) - using unsigned for now") + divRegisters(dst, src, type) +} + +private fun CodeGenerator.divSignedImmediate(dst: Int, value: Int, type: IRDataType) { + emitLine("; DIVS r$dst, $value (signed, using unsigned)") + divImmediate(dst, value, type) +} + +private fun CodeGenerator.divSignedMemory(dst: Int, source: String, type: IRDataType) { + emitLine("; DIVSM r$dst, $source (signed, not implemented)") +} + +// === Modulo === + +private fun CodeGenerator.modRegisters(dst: Int, src: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("sta math_tmp") + emitLine("lda ${regAddrLo(src)}") + emitLine("sta math_tmp+1") + emitLine("jsr math_mod8") + emitLine("lda math_tmp") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("sta math_tmp") + emitLine("lda ${regAddrHi(dst)}") + emitLine("sta math_tmp+1") + emitLine("lda ${regAddrLo(src)}") + emitLine("sta math_tmp+2") + emitLine("lda ${regAddrHi(src)}") + emitLine("sta math_tmp+3") + emitLine("jsr math_mod16") + emitLine("lda math_tmp") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda math_tmp+1") + emitLine("sta ${regAddrHi(dst)}") + } + else -> emitLine("; MODR r$dst, r$src ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.modImmediate(dst: Int, value: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("sta math_tmp") + emitLine("lda #${value and 0xff}") + emitLine("sta math_tmp+1") + emitLine("jsr math_mod8") + emitLine("lda math_tmp") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("sta math_tmp") + emitLine("lda ${regAddrHi(dst)}") + emitLine("sta math_tmp+1") + emitLine("lda #<${value and 0xffff}") + emitLine("sta math_tmp+2") + emitLine("lda #>${value and 0xffff}") + emitLine("sta math_tmp+3") + emitLine("jsr math_mod16") + emitLine("lda math_tmp") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda math_tmp+1") + emitLine("sta ${regAddrHi(dst)}") + } + else -> emitLine("; MOD r$dst, $value ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.modSignedRegisters(dst: Int, src: Int, type: IRDataType) { + emitLine("; MODSR r$dst, r$src (signed, not implemented)") + modRegisters(dst, src, type) +} + +private fun CodeGenerator.modSignedImmediate(dst: Int, value: Int, type: IRDataType) { + emitLine("; MODS r$dst, $value (signed, using unsigned)") + modImmediate(dst, value, type) +} + +// === DivMod === + +private fun CodeGenerator.divModRegisters(dst: Int, src: Int, type: IRDataType) { + // division and modulo combined: dst = dst/src, dst+1 = dst%src + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("sta math_tmp") + emitLine("lda ${regAddrLo(src)}") + emitLine("sta math_tmp+1") + emitLine("jsr math_divmod8") + emitLine("lda math_tmp") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda math_tmp+1") + emitLine("sta ${regAddrLo(dst) + 1}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("sta math_tmp") + emitLine("lda ${regAddrHi(dst)}") + emitLine("sta math_tmp+1") + emitLine("lda ${regAddrLo(src)}") + emitLine("sta math_tmp+2") + emitLine("lda ${regAddrHi(src)}") + emitLine("sta math_tmp+3") + emitLine("jsr math_divmod16") + emitLine("lda math_tmp") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda math_tmp+1") + emitLine("sta ${regAddrHi(dst)}") + emitLine("lda math_tmp+2") + emitLine("sta ${regAddrLo(dst) + 2}") + emitLine("lda math_tmp+3") + emitLine("sta ${regAddrLo(dst) + 3}") + } + else -> emitLine("; DIVMODR r$dst, r$src ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.sdivModRegisters(dst: Int, src: Int, type: IRDataType) { + emitLine("; SDIVMODR r$dst, r$src (signed, not implemented)") + divModRegisters(dst, src, type) +} + +private fun CodeGenerator.divModImmediate(dst: Int, value: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("sta math_tmp") + emitLine("lda #${value and 0xff}") + emitLine("sta math_tmp+1") + emitLine("jsr math_divmod8") + emitLine("lda math_tmp") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda math_tmp+1") + emitLine("sta ${regAddrLo(dst) + 1}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("sta math_tmp") + emitLine("lda ${regAddrHi(dst)}") + emitLine("sta math_tmp+1") + emitLine("lda #<${value and 0xffff}") + emitLine("sta math_tmp+2") + emitLine("lda #>${value and 0xffff}") + emitLine("sta math_tmp+3") + emitLine("jsr math_divmod16") + emitLine("lda math_tmp") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda math_tmp+1") + emitLine("sta ${regAddrHi(dst)}") + emitLine("lda math_tmp+2") + emitLine("sta ${regAddrLo(dst) + 2}") + emitLine("lda math_tmp+3") + emitLine("sta ${regAddrLo(dst) + 3}") + } + else -> emitLine("; DIVMOD r$dst, $value ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.sdivModImmediate(dst: Int, value: Int, type: IRDataType) { + emitLine("; SDIVMOD r$dst, $value (signed, using unsigned)") + divModImmediate(dst, value, type) +} + +// === Compare === + +private fun CodeGenerator.compareRegisters(r1: Int, r2: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(r1)}") + emitLine("cmp ${regAddrLo(r2)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(r1)}") + emitLine("cmp ${regAddrLo(r2)}") + emitLine("lda ${regAddrHi(r1)}") + emitLine("sbc ${regAddrHi(r2)}") + } + IRDataType.LONG -> { + emitLine("lda ${regAddrLo(r1)}") + emitLine("cmp ${regAddrLo(r2)}") + emitLine("lda ${regAddrHi(r1)}") + emitLine("sbc ${regAddrHi(r2)}") + emitLine("lda ${regAddrLo(r1) + 2}") + emitLine("sbc ${regAddrLo(r2) + 2}") + emitLine("lda ${regAddrLo(r1) + 3}") + emitLine("sbc ${regAddrLo(r2) + 3}") + } + IRDataType.FLOAT -> emitLine("; FLOAT CMP r$r1, r$r2 (not implemented)") + } +} + +private fun CodeGenerator.compareImmediate(r1: Int, value: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(r1)}") + emitLine("cmp #${value and 0xff}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(r1)}") + emitLine("cmp #<${value and 0xffff}") + emitLine("lda ${regAddrHi(r1)}") + emitLine("sbc #>${value and 0xffff}") + } + else -> emitLine("; CMPI r$r1, #$value ${type.name} (not implemented)") + } +} diff --git a/new6502gen/src/codegen/InstrBitwise.kt b/new6502gen/src/codegen/InstrBitwise.kt new file mode 100644 index 000000000..476d2482f --- /dev/null +++ b/new6502gen/src/codegen/InstrBitwise.kt @@ -0,0 +1,674 @@ +/* + * Bitwise and logical IR instruction translations for the new6502gen code generator. + * + * Handles: AND, OR, XOR (register/imm/memory), INV (bitwise not), + * shifts (LSL, LSR, ASR) by constant or variable count, + * rotates (ROL, ROR, ROXL, ROXR) and bit manipulation (BITTST, BITSET, BITCLR, BITTOG). + * + * Constant-count shifts are unrolled (for loop in the generator). + * Variable-count shifts use a loop with X as the counter. + * Arithmetic shifts use CMP #128 + ROR to preserve the sign bit. + * + * Note: ROL/ROR without carry prefix (clc) are rotate-through-carry, + * while ROXL/ROXR explicitly use carry for multi-byte rotates. + */ + +package codegen + +import prog8.intermediate.* + +fun CodeGenerator.translateBitwise(insn: IRInstruction) { + val type = insn.type ?: IRDataType.BYTE + val r1 = insn.reg1 // nullable - memory-only ops (INVM, ASRM, etc.) have no reg1 + val r2 = insn.reg2 + val imm = insn.immediate + val addr = insn.address + val label = insn.labelSymbol + + when (insn.opcode) { + Opcode.ANDR -> { + val r2val = r2 ?: error("ANDR needs reg2") + andRegisters(r1 ?: error("ANDR needs reg1"), r2val, type) + } + Opcode.AND -> { + val value = imm ?: error("AND needs immediate") + andImmediate(r1 ?: error("AND needs reg1"), value, type) + } + Opcode.ANDM -> { + val source = resolveAddress(addr, label) + andMemory(r1 ?: error("ANDM needs reg1"), source, type) + } + + Opcode.ORR -> { + val r2val = r2 ?: error("ORR needs reg2") + orRegisters(r1 ?: error("ORR needs reg1"), r2val, type) + } + Opcode.OR -> { + val value = imm ?: error("OR needs immediate") + orImmediate(r1 ?: error("OR needs reg1"), value, type) + } + Opcode.ORM -> { + val source = resolveAddress(addr, label) + orMemory(r1 ?: error("ORM needs reg1"), source, type) + } + + Opcode.XORR -> { + val r2val = r2 ?: error("XORR needs reg2") + xorRegisters(r1 ?: error("XORR needs reg1"), r2val, type) + } + Opcode.XOR -> { + val value = imm ?: error("XOR needs immediate") + xorImmediate(r1 ?: error("XOR needs reg1"), value, type) + } + Opcode.XORM -> { + val source = resolveAddress(addr, label) + xorMemory(r1 ?: error("XORM needs reg1"), source, type) + } + + Opcode.INV -> invertRegister(r1 ?: error("INV needs reg1"), type) + Opcode.INVM -> invertMemory(resolveAddress(addr, label), type) + + // Shift by register count (ASRN/LSRN/LSLN: r2 = shift count register) + Opcode.ASRN -> { + val countReg = r2 ?: error("ASRN needs reg2 for shift count") + arithmeticShiftRightVar(r1 ?: error("ASRN needs reg1"), countReg, type) + } + Opcode.LSRN -> { + val countReg = r2 ?: error("LSRN needs reg2 for shift count") + logicalShiftRightVar(r1 ?: error("LSRN needs reg1"), countReg, type) + } + Opcode.LSLN -> { + val countReg = r2 ?: error("LSLN needs reg2 for shift count") + logicalShiftLeftVar(r1 ?: error("LSLN needs reg1"), countReg, type) + } + + // Shift memory by register count (ASRNM/LSRNM/LSLNM: r1 = count reg, addr = target) + Opcode.ASRNM -> emitLine("; ASRNM (not implemented)") + Opcode.LSRNM -> emitLine("; LSRNM (not implemented)") + Opcode.LSLNM -> emitLine("; LSLNM (not implemented)") + + // Shift by 1 (ASR/LSR/LSL: single-bit shift, no count operand) + Opcode.ASR -> arithmeticShiftRight(r1 ?: error("ASR needs reg1"), 1, type) + Opcode.ASRM -> emitLine("; ASRM (not implemented)") + Opcode.LSR -> logicalShiftRight(r1 ?: error("LSR needs reg1"), 1, type) + Opcode.LSRM -> emitLine("; LSRM (not implemented)") + Opcode.LSL -> logicalShiftLeft(r1 ?: error("LSL needs reg1"), 1, type) + Opcode.LSLM -> emitLine("; LSLM (not implemented)") + + Opcode.ROR -> rotateRight(r1 ?: error("ROR needs reg1"), type) + Opcode.RORM -> rotateRightMemory(resolveAddress(addr, label), type) + Opcode.ROL -> rotateLeft(r1 ?: error("ROL needs reg1"), type) + Opcode.ROLM -> rotateLeftMemory(resolveAddress(addr, label), type) + Opcode.ROXR -> rotateRightThroughCarry(r1 ?: error("ROXR needs reg1"), type) + Opcode.ROXRM -> emitLine("; ROXRM (not implemented)") + Opcode.ROXL -> rotateLeftThroughCarry(r1 ?: error("ROXL needs reg1"), type) + Opcode.ROXLM -> emitLine("; ROXLM (not implemented)") + + Opcode.BITTST -> { + val bit = imm ?: error("BITTST needs bit number") + bitTest(r1 ?: error("BITTST needs reg1"), bit, type) + } + Opcode.BITSET -> { + val bit = imm ?: error("BITSET needs bit number") + bitSet(r1 ?: error("BITSET needs reg1"), bit, type) + } + Opcode.BITCLR -> { + val bit = imm ?: error("BITCLR needs bit number") + bitClear(r1 ?: error("BITCLR needs reg1"), bit, type) + } + Opcode.BITTOG -> { + val bit = imm ?: error("BITTOG needs bit number") + bitToggle(r1 ?: error("BITTOG needs reg1"), bit, type) + } + + else -> error("Unknown bitwise opcode: ${insn.opcode}") + } +} + +// === AND === + +private fun CodeGenerator.andRegisters(dst: Int, src: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("and ${regAddrLo(src)}") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("and ${regAddrLo(src)}") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda ${regAddrHi(dst)}") + emitLine("and ${regAddrHi(src)}") + emitLine("sta ${regAddrHi(dst)}") + } + IRDataType.LONG -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("and ${regAddrLo(src)}") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda ${regAddrHi(dst)}") + emitLine("and ${regAddrHi(src)}") + emitLine("sta ${regAddrHi(dst)}") + emitLine("lda ${regAddrLo(dst) + 2}") + emitLine("and ${regAddrLo(src) + 2}") + emitLine("sta ${regAddrLo(dst) + 2}") + emitLine("lda ${regAddrLo(dst) + 3}") + emitLine("and ${regAddrLo(src) + 3}") + emitLine("sta ${regAddrLo(dst) + 3}") + } + IRDataType.FLOAT -> emitLine("; FLOAT ANDR r$dst, r$src (not implemented)") + } +} + +private fun CodeGenerator.andImmediate(dst: Int, value: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("and #${value and 0xff}") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("and #<${value and 0xffff}") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda ${regAddrHi(dst)}") + emitLine("and #>${value and 0xffff}") + emitLine("sta ${regAddrHi(dst)}") + } + IRDataType.LONG -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("and #<${value and 0xffff}") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda ${regAddrHi(dst)}") + emitLine("and #>${value and 0xffff}") + emitLine("sta ${regAddrHi(dst)}") + emitLine("lda ${regAddrLo(dst) + 2}") + emitLine("and #${(value ushr 16) and 0xff}") + emitLine("sta ${regAddrLo(dst) + 2}") + emitLine("lda ${regAddrLo(dst) + 3}") + emitLine("and #${(value ushr 24) and 0xff}") + emitLine("sta ${regAddrLo(dst) + 3}") + } + IRDataType.FLOAT -> emitLine("; FLOAT AND r$dst #$value (not implemented)") + } +} + +private fun CodeGenerator.andMemory(dst: Int, source: String, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("and $source") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("and $source") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda ${regAddrHi(dst)}") + emitLine("and $source+1") + emitLine("sta ${regAddrHi(dst)}") + } + else -> emitLine("; ANDM r$dst, $source ${type.name} (not implemented)") + } +} + +// === OR === + +private fun CodeGenerator.orRegisters(dst: Int, src: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("ora ${regAddrLo(src)}") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("ora ${regAddrLo(src)}") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda ${regAddrHi(dst)}") + emitLine("ora ${regAddrHi(src)}") + emitLine("sta ${regAddrHi(dst)}") + } + else -> emitLine("; ORR r$dst, r$src ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.orImmediate(dst: Int, value: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("ora #${value and 0xff}") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("ora #<${value and 0xffff}") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda ${regAddrHi(dst)}") + emitLine("ora #>${value and 0xffff}") + emitLine("sta ${regAddrHi(dst)}") + } + else -> emitLine("; OR r$dst, #$value ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.orMemory(dst: Int, source: String, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("ora $source") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("ora $source") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda ${regAddrHi(dst)}") + emitLine("ora $source+1") + emitLine("sta ${regAddrHi(dst)}") + } + else -> emitLine("; ORM r$dst, $source ${type.name} (not implemented)") + } +} + +// === XOR === + +private fun CodeGenerator.xorRegisters(dst: Int, src: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("eor ${regAddrLo(src)}") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("eor ${regAddrLo(src)}") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda ${regAddrHi(dst)}") + emitLine("eor ${regAddrHi(src)}") + emitLine("sta ${regAddrHi(dst)}") + } + else -> emitLine("; XORR r$dst, r$src ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.xorImmediate(dst: Int, value: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("eor #${value and 0xff}") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("eor #<${value and 0xffff}") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda ${regAddrHi(dst)}") + emitLine("eor #>${value and 0xffff}") + emitLine("sta ${regAddrHi(dst)}") + } + else -> emitLine("; XOR r$dst, #$value ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.xorMemory(dst: Int, source: String, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("eor $source") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(dst)}") + emitLine("eor $source") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda ${regAddrHi(dst)}") + emitLine("eor $source+1") + emitLine("sta ${regAddrHi(dst)}") + } + else -> emitLine("; XORM r$dst, $source ${type.name} (not implemented)") + } +} + +// === Invert === + +private fun CodeGenerator.invertRegister(reg: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(reg)}") + emitLine("eor #255") + emitLine("sta ${regAddrLo(reg)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(reg)}") + emitLine("eor #255") + emitLine("sta ${regAddrLo(reg)}") + emitLine("lda ${regAddrHi(reg)}") + emitLine("eor #255") + emitLine("sta ${regAddrHi(reg)}") + } + else -> emitLine("; INV r$reg ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.invertMemory(target: String, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda $target") + emitLine("eor #255") + emitLine("sta $target") + } + IRDataType.WORD -> { + emitLine("lda $target") + emitLine("eor #255") + emitLine("sta $target") + emitLine("lda $target+1") + emitLine("eor #255") + emitLine("sta $target+1") + } + else -> emitLine("; INVM $target ${type.name} (not implemented)") + } +} + +// === Shifts === + +private fun CodeGenerator.logicalShiftLeft(reg: Int, count: Int, type: IRDataType) { + for (i in 1..count) { + when (type) { + IRDataType.BYTE -> { + emitLine("asl ${regAddrLo(reg)}") + } + IRDataType.WORD -> { + emitLine("asl ${regAddrLo(reg)}") + emitLine("rol ${regAddrHi(reg)}") + } + IRDataType.LONG -> { + emitLine("asl ${regAddrLo(reg)}") + emitLine("rol ${regAddrHi(reg)}") + emitLine("rol ${regAddrLo(reg) + 2}") + emitLine("rol ${regAddrLo(reg) + 3}") + } + IRDataType.FLOAT -> emitLine("; FLOAT LSLN r$reg, $count (not implemented)") + } + } +} + +private fun CodeGenerator.logicalShiftLeftMemory(target: String, count: Int, type: IRDataType) { + for (i in 1..count) { + when (type) { + IRDataType.BYTE -> emitLine("asl $target") + IRDataType.WORD -> { + emitLine("asl $target") + emitLine("rol $target+1") + } + else -> emitLine("; LSLNM $target, $count ${type.name} (not implemented)") + } + } +} + +private fun CodeGenerator.logicalShiftRight(reg: Int, count: Int, type: IRDataType) { + for (i in 1..count) { + when (type) { + IRDataType.BYTE -> { + emitLine("lsr ${regAddrLo(reg)}") + } + IRDataType.WORD -> { + emitLine("lsr ${regAddrHi(reg)}") + emitLine("ror ${regAddrLo(reg)}") + } + IRDataType.LONG -> { + emitLine("lsr ${regAddrLo(reg) + 3}") + emitLine("ror ${regAddrLo(reg) + 2}") + emitLine("ror ${regAddrHi(reg)}") + emitLine("ror ${regAddrLo(reg)}") + } + IRDataType.FLOAT -> emitLine("; FLOAT LSRN r$reg, $count (not implemented)") + } + } +} + +private fun CodeGenerator.logicalShiftRightMemory(target: String, count: Int, type: IRDataType) { + for (i in 1..count) { + when (type) { + IRDataType.BYTE -> emitLine("lsr $target") + IRDataType.WORD -> { + emitLine("lsr $target+1") + emitLine("ror $target") + } + else -> emitLine("; LSRNM $target, $count ${type.name} (not implemented)") + } + } +} + +private fun CodeGenerator.arithmeticShiftRight(reg: Int, count: Int, type: IRDataType) { + for (i in 1..count) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(reg)}") + emitLine("cmp #128", "sign extend for ASR") + emitLine("ror ${regAddrLo(reg)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrHi(reg)}") + emitLine("cmp #128") + emitLine("ror ${regAddrHi(reg)}") + emitLine("ror ${regAddrLo(reg)}") + } + else -> emitLine("; ASRN r$reg, $count ${type.name} (not implemented)") + } + } +} + +private fun CodeGenerator.arithmeticShiftRightMemory(target: String, count: Int, type: IRDataType) { + for (i in 1..count) { + emitLine("; ASRNM $target, $count (not implemented)") + } +} + +private fun CodeGenerator.logicalShiftLeftVar(reg: Int, countReg: Int, type: IRDataType) { + emitLine("ldx ${regAddrLo(countReg)}") + emitLine("beq +") + emitLabel("loop") + when (type) { + IRDataType.BYTE -> { + emitLine("asl ${regAddrLo(reg)}") + } + IRDataType.WORD -> { + emitLine("asl ${regAddrLo(reg)}") + emitLine("rol ${regAddrHi(reg)}") + } + else -> emitLine("; LSL r$reg, r$countReg ${type.name} (not implemented)") + } + emitLine("dex") + emitLine("bne loop") + emitLabel("+") +} + +private fun CodeGenerator.logicalShiftRightVar(reg: Int, countReg: Int, type: IRDataType) { + emitLine("ldx ${regAddrLo(countReg)}") + emitLine("beq +") + emitLabel("loop") + when (type) { + IRDataType.BYTE -> { + emitLine("lsr ${regAddrLo(reg)}") + } + IRDataType.WORD -> { + emitLine("lsr ${regAddrHi(reg)}") + emitLine("ror ${regAddrLo(reg)}") + } + else -> emitLine("; LSR r$reg, r$countReg ${type.name} (not implemented)") + } + emitLine("dex") + emitLine("bne loop") + emitLabel("+") +} + +private fun CodeGenerator.arithmeticShiftRightVar(reg: Int, countReg: Int, type: IRDataType) { + emitLine("ldx ${regAddrLo(countReg)}") + emitLine("beq +") + emitLabel("loop") + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(reg)}") + emitLine("cmp #128") + emitLine("ror ${regAddrLo(reg)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrHi(reg)}") + emitLine("cmp #128") + emitLine("ror ${regAddrHi(reg)}") + emitLine("ror ${regAddrLo(reg)}") + } + else -> emitLine("; ASR r$reg, r$countReg ${type.name} (not implemented)") + } + emitLine("dex") + emitLine("bne loop") + emitLabel("+") +} + +// === Rotates === + +private fun CodeGenerator.rotateLeft(reg: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("clc", "rotate left by 1 bit (no carry)") + emitLine("rol ${regAddrLo(reg)}") + } + IRDataType.WORD -> { + emitLine("clc") + emitLine("rol ${regAddrLo(reg)}") + emitLine("rol ${regAddrHi(reg)}") + } + else -> emitLine("; ROL r$reg ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.rotateLeftMemory(target: String, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("clc") + emitLine("rol $target") + } + IRDataType.WORD -> { + emitLine("clc") + emitLine("rol $target") + emitLine("rol $target+1") + } + else -> emitLine("; ROLM $target ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.rotateRight(reg: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("clc", "rotate right by 1 bit (no carry)") + emitLine("ror ${regAddrLo(reg)}") + } + IRDataType.WORD -> { + emitLine("clc") + emitLine("ror ${regAddrHi(reg)}") + emitLine("ror ${regAddrLo(reg)}") + } + else -> emitLine("; ROR r$reg ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.rotateRightMemory(target: String, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("clc") + emitLine("ror $target") + } + IRDataType.WORD -> { + emitLine("clc") + emitLine("ror $target+1") + emitLine("ror $target") + } + else -> emitLine("; RORM $target ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.rotateLeftThroughCarry(reg: Int, type: IRDataType) { + // rotate through carry (like the 6502 ROL instruction does) + when (type) { + IRDataType.BYTE -> { + emitLine("rol ${regAddrLo(reg)}") + } + IRDataType.WORD -> { + emitLine("rol ${regAddrLo(reg)}") + emitLine("rol ${regAddrHi(reg)}") + } + else -> emitLine("; ROXL r$reg ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.rotateRightThroughCarry(reg: Int, type: IRDataType) { + // rotate through carry (like the 6502 ROR instruction does) + when (type) { + IRDataType.BYTE -> { + emitLine("ror ${regAddrLo(reg)}") + } + IRDataType.WORD -> { + emitLine("ror ${regAddrHi(reg)}") + emitLine("ror ${regAddrLo(reg)}") + } + else -> emitLine("; ROXR r$reg ${type.name} (not implemented)") + } +} + +// === Bit manipulation === + +private fun CodeGenerator.bitTest(reg: Int, bit: Int, type: IRDataType) { + val mask = 1 shl bit + emitLine("lda ${regAddrLo(reg)}") + if (is65C02()) { + emitLine("bit #$mask", "test bit $bit of r$reg") + } else { + // 6502: bit only works with memory; use and to test + emitLine("and #$mask") + // Z flag set if result is zero (bit not set) + } +} + +private fun CodeGenerator.bitSet(reg: Int, bit: Int, type: IRDataType) { + val mask = 1 shl bit + if (mask <= 0xff) { + emitLine("lda ${regAddrLo(reg)}") + emitLine("ora #$mask") + emitLine("sta ${regAddrLo(reg)}") + } else { + // high byte bit + val hbit = bit - 8 + emitLine("lda ${regAddrHi(reg)}") + emitLine("ora #${1 shl hbit}") + emitLine("sta ${regAddrHi(reg)}") + } +} + +private fun CodeGenerator.bitClear(reg: Int, bit: Int, type: IRDataType) { + val mask = 1 shl bit + if (mask <= 0xff) { + emitLine("lda ${regAddrLo(reg)}") + emitLine("and #${0xff xor mask}") + emitLine("sta ${regAddrLo(reg)}") + } else { + val hbit = bit - 8 + emitLine("lda ${regAddrHi(reg)}") + emitLine("and #${0xff xor (1 shl hbit)}") + emitLine("sta ${regAddrHi(reg)}") + } +} + +private fun CodeGenerator.bitToggle(reg: Int, bit: Int, type: IRDataType) { + val mask = 1 shl bit + if (mask <= 0xff) { + emitLine("lda ${regAddrLo(reg)}") + emitLine("eor #$mask") + emitLine("sta ${regAddrLo(reg)}") + } else { + val hbit = bit - 8 + emitLine("lda ${regAddrHi(reg)}") + emitLine("eor #${1 shl hbit}") + emitLine("sta ${regAddrHi(reg)}") + } +} diff --git a/new6502gen/src/codegen/InstrBranch.kt b/new6502gen/src/codegen/InstrBranch.kt new file mode 100644 index 000000000..874f25c4a --- /dev/null +++ b/new6502gen/src/codegen/InstrBranch.kt @@ -0,0 +1,114 @@ +/* + * Branch/compare IR instruction translations for the new6502gen code generator. + * + * Two categories of branches: + * + * 1. Simple status flag branches (BSTCC/BSTCS/BSTEQ/BSTNE/BSTNEG/BSTPOS/BSTVC/BSTVS): + * These map directly to 6502 branch instructions (bcc/bcs/beq/bne/bmi/bpl/bvc/bvs) + * and branch based on flags set by the most recent comparison or arithmetic operation. + * + * 2. Comparison branches (BGT/BGE/BLT/BLE, BGTS/BGES/BLTS/BLES): + * These expect the flags to have been set by a preceding CMP instruction. + * Unsigned variants use simple bcc/bcs patterns. + * Signed variants need to check both N and V flags. + * + * BGT, BLE need two branches (e.g. bne skip + bcs target) because the 6502 + * doesn't have a single "branch if greater than" instruction. + * + * Signed branch logic (N flag vs V flag): + * - gt: Z=0 and (N==V) + * - ge: N==V + * - lt: N!=V + * - le: Z=1 or (N!=V) + */ + +package codegen + +import prog8.intermediate.* + +fun CodeGenerator.translateBranch(insn: IRInstruction) { + val target = insn.branchTarget + val ls = insn.labelSymbol + + val label: String = when { + target != null && target.label != null -> target.label!! + ls != null -> ls + else -> "unknown_target" + } + + when (insn.opcode) { + Opcode.BSTCC -> emitLine("bcc $label") + Opcode.BSTCS -> emitLine("bcs $label") + Opcode.BSTEQ -> emitLine("beq $label") + Opcode.BSTNE -> emitLine("bne $label") + Opcode.BSTNEG -> emitLine("bmi $label") + Opcode.BSTPOS -> emitLine("bpl $label") + Opcode.BSTVC -> emitLine("bvc $label") + Opcode.BSTVS -> emitLine("bvs $label") + + Opcode.BGTR -> { + emitLine("beq +") // equal → skip + emitLine("bcs $label") // unsigned above → branch + emitLabel("+") + } + Opcode.BGE -> emitLine("bcs $label") + Opcode.BLT -> emitLine("bcc $label") + Opcode.BLE -> { + emitLine("beq $label") + emitLine("bcc $label") + } + + Opcode.BGTSR -> emitSignedBranch("gt", label) + Opcode.BGTS -> emitSignedBranch("gt", label) + Opcode.BGER -> emitSignedBranch("ge", label) + Opcode.BGESR -> emitSignedBranch("ge", label) + Opcode.BGES -> emitSignedBranch("ge", label) + Opcode.BLTS -> emitSignedBranch("lt", label) + Opcode.BLES -> emitSignedBranch("le", label) + + else -> error("Unknown branch opcode: ${insn.opcode}") + } +} + +private fun CodeGenerator.emitSignedBranch(cond: String, label: String) { + // Standard 6502 signed comparison patterns after CMP. + // Flags: N = sign of result, V = overflow, Z = zero. + // Branch on (N == V) for signed >= and >, (N != V) for < and <=. + // 64tass local labels: + = forward, - = backward, ++ = next-forward, etc. + when (cond) { + "gt" -> { + emitLine("beq ++") // equal → skip (NOT gt) + emitLine("bvc +") // V=0: N is sign + emitLine("bmi $label") // V=1,N=1: inverted → positive (gt) + emitLine("jmp ++") // V=1,N=0: not gt + emitLabel("+") // V=0 landing: check N flag + emitLine("bpl $label") // V=0,N=0: positive → gt + emitLabel("+") // skip point + } + "ge" -> { + emitLine("bvc +") // V=0: N is sign + emitLine("bmi $label") // V=1,N=1: inverted → positive + emitLine("jmp ++") // V=1,N=0: not ge + emitLabel("+") // first + label + emitLine("bpl $label") // V=0,N=0: positive → ge + emitLabel("+") // second + label + } + "lt" -> { + emitLine("bvc +") // V=0: N is sign + emitLine("bpl $label") // V=1,N=0: inverted → negative + emitLine("jmp ++") // V=1,N=1: not lt + emitLabel("+") // first + label + emitLine("bmi $label") // V=0,N=1: negative → lt + emitLabel("+") // second + label + } + "le" -> { + emitLine("beq $label") // equal → branch + emitLine("bvc +") // V=0: N is sign + emitLine("bpl $label") // V=1,N=0: inverted → negative + emitLine("jmp ++") // V=1,N=1: not lt + emitLabel("+") // first + label + emitLine("bmi $label") // V=0,N=1: negative → lt + emitLabel("+") // second + label + } + } +} diff --git a/new6502gen/src/codegen/InstrControl.kt b/new6502gen/src/codegen/InstrControl.kt new file mode 100644 index 000000000..2a1a7cb0a --- /dev/null +++ b/new6502gen/src/codegen/InstrControl.kt @@ -0,0 +1,393 @@ +/* + * Control flow IR instruction translations for the new6502gen code generator. + * + * Handles: JUMP, JUMPI, CALL, CALLI, CALLFAR, CALLFARVB, SYSCALL, + * RETURN, RETURNR, RETURNI, PUSH, POP, PUSHST, POPST, + * CLC, SEC, CLI, SEI, ALIGN, + * sign extension (LSIGB, LSIGW, EXT, EXTS), + * and stubs for float math and conversions. + * + * CALL instruction translation: + * 1. Load arguments into the appropriate calling convention slots (s0-s5) + * 2. JSR to the subroutine + * 3. Save return values from slots back to virtual registers + * + * Argument/return value passing: + * - s0 (A, byte), s1 (X, byte), s2 (Y, byte) + * - s3 (AX, word), s4 (AY, word), s5 (XY, word) + * - Status flags (Pc, Pz, Pv, Pn) for boolean returns + * + * Not yet implemented: CALLI, CALLFAR, CALLFARVB, all floating point ops. + */ + +package codegen + +import prog8.code.core.Statusflag +import prog8.intermediate.* + +fun CodeGenerator.translateControl(insn: IRInstruction) { + val r1 = insn.reg1 + val r2 = insn.reg2 + val imm = insn.immediate + val addr = insn.address + val label = insn.labelSymbol + + when (insn.opcode) { + Opcode.JUMP -> { + val target = label ?: addr?.let { "${it.value}" } ?: error("JUMP needs target") + emitLine("jmp $target") + } + + Opcode.JUMPI -> { + val reg = r1 ?: error("JUMPI needs reg1") + emitLine("jmp (${regAddr(reg)})", "indirect jump via r$reg") + } + + Opcode.CALL -> { + val fnLabel = label ?: addr?.let { "${it.value}" } ?: error("CALL needs label or address") + val args = insn.fcallArgs + translateCall(fnLabel, args) + } + + Opcode.CALLI -> { + val reg = r1 ?: error("CALLI needs reg1") + emitLine("; CALLI via r$reg (not implemented)") + } + + Opcode.CALLFAR -> { + val target = label ?: "${addr?.value ?: 0}" + val bank = imm ?: 0 + emitLine("; CALLFAR $target, bank=$bank (not implemented)") + } + + Opcode.CALLFARVB -> { + val target = label ?: "${addr?.value ?: 0}" + val bankReg = r1 ?: error("CALLFARVB needs reg1") + emitLine("; CALLFARVB $target, bank in r$bankReg (not implemented)") + } + + Opcode.SYSCALL -> { + val args = insn.fcallArgs + translateSyscall(args) + } + + Opcode.RETURN -> { + emitLine("rts") + } + + Opcode.RETURNR -> { + val reg = r1 ?: error("RETURNR needs reg1") + val type = insn.type ?: IRDataType.BYTE + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(reg)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(reg)}") + emitLine("ldx ${regAddrHi(reg)}") + } + else -> emitLine("; RETURNR r$reg ${type.name} (not implemented)") + } + emitLine("rts") + } + + Opcode.RETURNI -> { + val value = imm ?: error("RETURNI needs immediate") + val type = insn.type ?: IRDataType.BYTE + when (type) { + IRDataType.BYTE -> { + emitLine("lda #${value and 0xff}") + } + IRDataType.WORD -> { + emitLine("lda #<${value and 0xffff}") + emitLine("ldx #>${value and 0xffff}") + } + else -> emitLine("; RETURNI #$value ${type.name} (not implemented)") + } + emitLine("rts") + } + + Opcode.PUSH -> { + val reg = r1 ?: error("PUSH needs reg1") + val type = insn.type ?: IRDataType.BYTE + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(reg)}") + emitLine("pha") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrHi(reg)}") + emitLine("pha") + emitLine("lda ${regAddrLo(reg)}") + emitLine("pha") + } + else -> emitLine("; PUSH r$reg ${type.name} (not implemented)") + } + } + + Opcode.POP -> { + val reg = r1 ?: error("POP needs reg1") + val type = insn.type ?: IRDataType.BYTE + when (type) { + IRDataType.BYTE -> { + emitLine("pla") + emitLine("sta ${regAddrLo(reg)}") + } + IRDataType.WORD -> { + emitLine("pla") + emitLine("sta ${regAddrLo(reg)}") + emitLine("pla") + emitLine("sta ${regAddrHi(reg)}") + } + else -> emitLine("; POP r$reg ${type.name} (not implemented)") + } + } + + Opcode.PUSHST -> { + emitLine("php") + } + + Opcode.POPST -> { + emitLine("plp") + } + + Opcode.CLC -> emitLine("clc") + Opcode.SEC -> emitLine("sec") + Opcode.CLI -> emitLine("cli") + Opcode.SEI -> emitLine("sei") + + Opcode.ALIGN -> { + val alignment = imm ?: 256 + emitLine("; ALIGN to $alignment bytes") + } + + Opcode.LSIGB -> { + val reg = r1 ?: error("LSIGB needs reg1") + emitLine("lda ${regAddrLo(reg)}") + emitLine("and #128") + emitLine("beq +") + emitLine("lda #255") + emitLine("jmp ++") + emitLabel("+") // first + label + emitLine("lda #0") + emitLabel("+") // second + label + emitLine("sta ${regAddrHi(reg)}") + emitStoreZero("${regAddrLo(reg) + 2}") + emitStoreZero("${regAddrLo(reg) + 3}") + } + + Opcode.LSIGW -> { + val reg = r1 ?: error("LSIGW needs reg1") + emitLine("lda ${regAddrHi(reg)}") + emitLine("and #128") + emitLine("beq +") + emitLine("lda #255") + emitLine("jmp ++") + emitLabel("+") // first + label + emitLine("lda #0") + emitLabel("+") // second + label + emitStoreZero("${regAddrLo(reg) + 2}") + emitStoreZero("${regAddrLo(reg) + 3}") + } + + Opcode.MSIGB, Opcode.MSIGW -> { + emitLine("; ${insn.opcode} (not implemented)") + } + + Opcode.BSIGB -> { + emitLine("; BSIGB (not implemented)") + } + + Opcode.MIDB -> { + emitLine("; MIDB (not implemented)") + } + + Opcode.CONCAT -> { + emitLine("; CONCAT (not implemented)") + } + + Opcode.EXT -> { + val reg = r1 ?: error("EXT needs reg1") + emitStoreZero("${regAddrHi(reg)}", "zero extend r$reg") + } + + Opcode.EXTS -> { + val reg = r1 ?: error("EXTS needs reg1") + emitLine("lda ${regAddrLo(reg)}") + emitLine("and #128") + emitLine("beq +") + emitLine("lda #255") + emitLine("sta ${regAddrHi(reg)}") + emitLine("jmp ++") + emitLabel("+") + emitStoreZero("${regAddrHi(reg)}") + emitLabel("+") + } + + Opcode.SQRT -> emitLine("; SQRT (not implemented)") + Opcode.SQUARE -> emitLine("; SQUARE (not implemented)") + Opcode.SGN -> emitLine("; SGN (not implemented)") + + Opcode.FFROMUB, Opcode.FFROMSB, Opcode.FFROMUW, Opcode.FFROMSW, Opcode.FFROMSL -> { + emitLine("; ${insn.opcode} (float conversion, not implemented)") + } + + Opcode.FTOUB, Opcode.FTOSB, Opcode.FTOUW, Opcode.FTOSW, Opcode.FTOSL -> { + emitLine("; ${insn.opcode} (float conversion, not implemented)") + } + + Opcode.FABS -> emitLine("; FABS (not implemented)") + Opcode.FSIN -> emitLine("; FSIN (not implemented)") + Opcode.FCOS -> emitLine("; FCOS (not implemented)") + Opcode.FTAN -> emitLine("; FTAN (not implemented)") + Opcode.FATAN -> emitLine("; FATAN (not implemented)") + Opcode.FPOW -> emitLine("; FPOW (not implemented)") + Opcode.FLN -> emitLine("; FLN (not implemented)") + Opcode.FLOG -> emitLine("; FLOG (not implemented)") + Opcode.FROUND -> emitLine("; FROUND (not implemented)") + Opcode.FFLOOR -> emitLine("; FFLOOR (not implemented)") + Opcode.FCEIL -> emitLine("; FCEIL (not implemented)") + Opcode.FCOMP -> emitLine("; FCOMP (not implemented)") + + else -> error("Unknown control opcode: ${insn.opcode}") + } +} + +// === Call handling === + +private fun CodeGenerator.translateCall(fnLabel: String, args: FunctionCallArgs?) { + if (args != null) { + for (arg in args.arguments) { + translateArgument(arg) + } + } + + emitLine("jsr $fnLabel") + + if (args != null) { + for (ret in args.returns) { + translateReturnValue(ret) + } + } +} + +private fun CodeGenerator.translateArgument(arg: FunctionCallArgs.ArgumentSpec) { + val regSpec = arg.reg + val slot = regSpec.callingConventionSlot + val regNum = regSpec.registerNum.value + + when (slot?.value) { + 0 -> { + emitLine("lda ${regAddrLo(regNum)}", "arg to s0 (A)") + } + 1 -> { + emitLine("ldx ${regAddrLo(regNum)}", "arg to s1 (X)") + } + 2 -> { + emitLine("ldy ${regAddrLo(regNum)}", "arg to s2 (Y)") + } + 3 -> { + emitLine("lda ${regAddrLo(regNum)}", "arg to s3 (AX)") + emitLine("ldx ${regAddrHi(regNum)}") + } + 4 -> { + emitLine("lda ${regAddrLo(regNum)}", "arg to s4 (AY)") + emitLine("ldy ${regAddrHi(regNum)}") + } + 5 -> { + emitLine("ldx ${regAddrLo(regNum)}", "arg to s5 (XY)") + emitLine("ldy ${regAddrHi(regNum)}") + } + null -> { + val address = arg.address + if (address != null) { + emitLine("; arg passed via address $address") + } else { + emitLine("; arg passed via register $regNum (slot not set)") + } + } + } +} + +private fun CodeGenerator.translateReturnValue(ret: FunctionCallArgs.RegSpec) { + val slot = ret.callingConventionSlot + val regNum = ret.registerNum.value + + when (slot?.value) { + 0 -> { + emitLine("sta ${regAddrLo(regNum)}", "return from s0 (A) -> r$regNum") + } + 1 -> { + emitLine("stx ${regAddrLo(regNum)}", "return from s1 (X) -> r$regNum") + } + 2 -> { + emitLine("sty ${regAddrLo(regNum)}", "return from s2 (Y) -> r$regNum") + } + 3 -> { + emitLine("sta ${regAddrLo(regNum)}", "return from s3 (AX) -> r$regNum") + emitLine("stx ${regAddrHi(regNum)}") + } + 4 -> { + emitLine("sta ${regAddrLo(regNum)}", "return from s4 (AY) -> r$regNum") + emitLine("sty ${regAddrHi(regNum)}") + } + 5 -> { + emitLine("stx ${regAddrLo(regNum)}", "return from s5 (XY) -> r$regNum") + emitLine("sty ${regAddrHi(regNum)}") + } + null -> { + val flag = ret.statusflag + if (flag != null) { + when (flag) { + Statusflag.Pc -> { + emitLine("lda #0") + emitLine("rol a", "carry flag -> bit 0") + emitLine("and #1") + emitLine("sta ${regAddrLo(regNum)}") + emitStoreZero("${regAddrHi(regNum)}") + } + Statusflag.Pz -> { + emitLine("lda #0") + emitLine("rol a", "zero flag -> bit 0") + emitLine("and #1") + emitLine("sta ${regAddrLo(regNum)}") + emitStoreZero("${regAddrHi(regNum)}") + } + Statusflag.Pv -> { + emitLine("lda #0") + emitLine("rol a", "overflow flag -> bit 0") + emitLine("and #1") + emitLine("sta ${regAddrLo(regNum)}") + emitStoreZero("${regAddrHi(regNum)}") + } + Statusflag.Pn -> { + emitLine("lda #0") + emitLine("rol a", "negative flag -> bit 0") + emitLine("and #1") + emitLine("sta ${regAddrLo(regNum)}") + emitStoreZero("${regAddrHi(regNum)}") + } + } + } else { + emitLine("; return value to r$regNum (slot/flag not set)") + } + } + } +} + +// === Syscall handling === + +private fun CodeGenerator.translateSyscall(args: FunctionCallArgs?) { + emitLine("; SYSCALL") + if (args != null) { + for (arg in args.arguments) { + translateArgument(arg) + } + } + emitLine("jsr p8_syscall_handler") + if (args != null) { + for (ret in args.returns) { + translateReturnValue(ret) + } + } +} diff --git a/new6502gen/src/codegen/InstrLoadStore.kt b/new6502gen/src/codegen/InstrLoadStore.kt new file mode 100644 index 000000000..0689e66a1 --- /dev/null +++ b/new6502gen/src/codegen/InstrLoadStore.kt @@ -0,0 +1,649 @@ +/* + * LOAD and STORE IR instruction translations for the new6502gen code generator. + * + * Translates all variants of LOAD/STORE opcodes into 6502/65C02 assembly. + * Key variants: + * LOAD - immediate value -> virtual register + * LOADM - memory address -> virtual register + * LOADR - virtual register -> virtual register (copy) + * LOADX - memory[address + index_reg] -> virtual register (indexed load) + * LOADHR - physical CPU register (slot 0-5) -> virtual register + * LOADI - memory[base_reg + offset] -> virtual register (indirect load) + * STOREM - virtual register -> memory address + * STOREX - exchange virtual register with memory + * STOREZM - zero memory at address + * STOREZI - zero memory at [base_reg + offset] + * STOREZX - zero memory at [address + index_reg] + * STOREHR - virtual register -> physical CPU register (slot 0-5) + * STOREI - virtual register -> memory[base_reg + offset] (indirect store) + * + * Slot mapping (for LOADHR/STOREHR): + * 0 = A (byte), 1 = X (byte), 2 = Y (byte), + * 3 = AX (word), 4 = AY (word), 5 = XY (word) + * + * Zero page temporary location $22-$23 is used for address computation. + */ + +package codegen + +import prog8.intermediate.* + +fun CodeGenerator.translateLoadStore(insn: IRInstruction) { + val type = insn.type ?: IRDataType.BYTE + val r1 = insn.reg1 // nullable - STOREZM and float ops have no reg1 + val r2 = insn.reg2 + val imm = insn.immediate + val addr = insn.address + val label = insn.labelSymbol + + when (insn.opcode) { + Opcode.LOAD -> { + val value = insn.immediate + val sym = insn.labelSymbol + when { + value != null -> loadImmediate(r1 ?: error("LOAD needs reg1"), value, type) + sym != null -> loadSymbolAddress(r1 ?: error("LOAD needs reg1"), sym, insn.labelSymbolOffset, type) + else -> error("LOAD needs immediate or labelSymbol") + } + } + + Opcode.LOADM -> { + val target = resolveAddress(addr, label) + loadFromMemory(r1 ?: error("LOADM needs reg1"), target, type) + } + + Opcode.LOADR -> { + val src = r2 ?: error("LOADR needs reg2") + copyRegister(r1 ?: error("LOADR needs reg1"), src, type) + } + + Opcode.LOADX -> { + val idxReg = r2 ?: error("LOADX needs reg2") + val base = resolveAddress(addr, label) + indexedLoad(r1 ?: error("LOADX needs reg1"), idxReg, base, type) + } + + Opcode.LOADHR -> { + loadFromHardwareReg(r1 ?: error("LOADHR needs reg1"), imm ?: error("LOADHR needs slot"), type) + } + + Opcode.LOADI -> { + val baseReg = r2 ?: error("LOADI needs reg2") + val offset = insn.immediate ?: 0 + indirectLoad(r1 ?: error("LOADI needs reg1"), baseReg, offset, type) + } + + Opcode.STOREM -> { + val target = resolveAddress(addr, label) + storeToMemory(r1 ?: error("STOREM needs reg1"), target, type) + } + + Opcode.STOREX -> { + val r2val = r2 ?: error("STOREX needs reg2") + val target = resolveAddress(addr, label) + storeExchange(r1 ?: error("STOREX needs reg1"), r2val, target, type) + } + + Opcode.STOREZM -> { + val target = resolveAddress(addr, label) + zeroMemory(target, type) + } + + Opcode.STOREZI -> { + val offset = insn.immediate ?: 0 + zeroIndexed(r1 ?: error("STOREZI needs reg1"), offset, type) + } + + Opcode.STOREZX -> { + val target = resolveAddress(addr, label) + zeroMemoryIndexed(r1 ?: error("STOREZX needs reg1"), target, type) + } + + Opcode.STOREHR -> { + storeToHardwareReg(r1 ?: error("STOREHR needs reg1"), imm ?: error("STOREHR needs slot"), type) + } + + Opcode.STOREI -> { + val baseReg = r2 ?: error("STOREI needs reg2") + val offset = insn.immediate ?: 0 + indirectStore(r1 ?: error("STOREI needs reg1"), baseReg, offset, type) + } + + Opcode.LOADHFACZERO -> emitLine("; LOADHFACZERO (float, not implemented)") + Opcode.LOADHFACONE -> emitLine("; LOADHFACONE (float, not implemented)") + Opcode.STOREHFACZERO -> emitLine("; STOREHFACZERO (float, not implemented)") + Opcode.STOREHFACONE -> emitLine("; STOREHFACONE (float, not implemented)") + + else -> error("Unknown load/store opcode: ${insn.opcode}") + } +} + +// === Hardware register (slot) operations === + +private fun CodeGenerator.loadFromHardwareReg(virtualReg: Int, slot: Int, type: IRDataType) { + // LOADHR: move value FROM physical CPU register (slot) INTO virtual register file + when (slot) { + 0 -> { + // slot s0 = A (byte) + emitLine("sta ${regAddrLo(virtualReg)}", "LOADHR: A -> r$virtualReg") + if (type == IRDataType.WORD) { + emitLine("stx ${regAddrHi(virtualReg)}", "should not happen: AX pair through s0") + } + } + 1 -> { + // slot s1 = X (byte) + emitLine("stx ${regAddrLo(virtualReg)}", "LOADHR: X -> r$virtualReg") + } + 2 -> { + // slot s2 = Y (byte) + emitLine("sty ${regAddrLo(virtualReg)}", "LOADHR: Y -> r$virtualReg") + } + 3 -> { + // slot s3 = AX (word) + emitLine("sta ${regAddrLo(virtualReg)}", "LOADHR: AX -> r$virtualReg") + emitLine("stx ${regAddrHi(virtualReg)}") + } + 4 -> { + // slot s4 = AY (word) + emitLine("sta ${regAddrLo(virtualReg)}", "LOADHR: AY -> r$virtualReg") + emitLine("sty ${regAddrHi(virtualReg)}") + } + 5 -> { + // slot s5 = XY (word) + emitLine("stx ${regAddrLo(virtualReg)}", "LOADHR: XY -> r$virtualReg") + emitLine("sty ${regAddrHi(virtualReg)}") + } + else -> emitLine("; LOADHR: unknown slot $slot") + } +} + +private fun CodeGenerator.storeToHardwareReg(virtualReg: Int, slot: Int, type: IRDataType) { + // STOREHR: move value FROM virtual register file INTO physical CPU register (slot) + when (slot) { + 0 -> { + // slot s0 = A (byte) + emitLine("lda ${regAddrLo(virtualReg)}", "STOREHR: r$virtualReg -> A") + } + 1 -> { + // slot s1 = X (byte) + emitLine("ldx ${regAddrLo(virtualReg)}", "STOREHR: r$virtualReg -> X") + } + 2 -> { + // slot s2 = Y (byte) + emitLine("ldy ${regAddrLo(virtualReg)}", "STOREHR: r$virtualReg -> Y") + } + 3 -> { + // slot s3 = AX (word) + emitLine("lda ${regAddrLo(virtualReg)}", "STOREHR: r$virtualReg -> AX") + emitLine("ldx ${regAddrHi(virtualReg)}") + } + 4 -> { + // slot s4 = AY (word) + emitLine("lda ${regAddrLo(virtualReg)}", "STOREHR: r$virtualReg -> AY") + emitLine("ldy ${regAddrHi(virtualReg)}") + } + 5 -> { + // slot s5 = XY (word) + emitLine("ldx ${regAddrLo(virtualReg)}", "STOREHR: r$virtualReg -> XY") + emitLine("ldy ${regAddrHi(virtualReg)}") + } + else -> emitLine("; STOREHR: unknown slot $slot") + } +} + +// === helper implementations === + +private fun CodeGenerator.loadSymbolAddress(reg: Int, sym: String, offset: Int?, type: IRDataType) { + val resolved = resolveSymbolRef(sym) + val symWithOffset = if (offset != null) "$resolved+$offset" else resolved + when (type) { + IRDataType.BYTE -> { + emitLine("lda #<${symWithOffset}") + emitLine("sta ${regAddrLo(reg)}") + } + IRDataType.WORD -> { + emitLine("lda #<${symWithOffset}") + emitLine("sta ${regAddrLo(reg)}") + emitLine("lda #>${symWithOffset}") + emitLine("sta ${regAddrHi(reg)}") + } + IRDataType.LONG -> { + emitLine("lda #<${symWithOffset}") + emitLine("sta ${regAddrLo(reg)}") + emitLine("lda #>${symWithOffset}") + emitLine("sta ${regAddrHi(reg)}") + emitLine("lda #0") + emitLine("sta ${regAddrLo(reg) + 2}") + emitLine("sta ${regAddrLo(reg) + 3}") + } + else -> emitLine("; SYMLOAD r$reg, $symWithOffset ${type.name} (not implemented)") + } +} + +private fun CodeGenerator.loadImmediate(reg: Int, value: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda #${value and 0xff}") + emitLine("sta ${regAddrLo(reg)}") + } + IRDataType.WORD -> { + emitLine("lda #<${value and 0xffff}") + emitLine("sta ${regAddrLo(reg)}") + emitLine("lda #>${value and 0xffff}") + emitLine("sta ${regAddrHi(reg)}") + } + IRDataType.LONG -> { + emitLine("lda #<${value and 0xffff}") + emitLine("sta ${regAddrLo(reg)}") + emitLine("lda #>${value and 0xffff}") + emitLine("sta ${regAddrHi(reg)}") + emitLine("lda #${(value ushr 16) and 0xff}") + emitLine("sta ${regAddrLo(reg) + 2}") + emitLine("lda #${(value ushr 24) and 0xff}") + emitLine("sta ${regAddrLo(reg) + 3}") + } + IRDataType.FLOAT -> { + emitLine("; FLOAT LOAD immediate r$reg = $value (not implemented)") + } + } +} + +private fun CodeGenerator.loadFromMemory(reg: Int, source: String, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda $source") + emitLine("sta ${regAddrLo(reg)}") + } + IRDataType.WORD -> { + emitLine("lda $source") + emitLine("sta ${regAddrLo(reg)}") + emitLine("lda $source+1") + emitLine("sta ${regAddrHi(reg)}") + } + IRDataType.LONG -> { + emitLine("lda $source") + emitLine("sta ${regAddrLo(reg)}") + emitLine("lda $source+1") + emitLine("sta ${regAddrHi(reg)}") + emitLine("lda $source+2") + emitLine("sta ${regAddrLo(reg) + 2}") + emitLine("lda $source+3") + emitLine("sta ${regAddrLo(reg) + 3}") + } + IRDataType.FLOAT -> { + emitLine("; FLOAT LOADM r$reg from $source (not implemented)") + } + } +} + +private fun CodeGenerator.copyRegister(dst: Int, src: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(src)}") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(src)}") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda ${regAddrHi(src)}") + emitLine("sta ${regAddrHi(dst)}") + } + IRDataType.LONG -> { + emitLine("lda ${regAddrLo(src)}") + emitLine("sta ${regAddrLo(dst)}") + emitLine("lda ${regAddrHi(src)}") + emitLine("sta ${regAddrHi(dst)}") + emitLine("lda ${regAddrLo(src) + 2}") + emitLine("sta ${regAddrLo(dst) + 2}") + emitLine("lda ${regAddrHi(src) + 2}") + emitLine("sta ${regAddrLo(dst) + 3}") + } + IRDataType.FLOAT -> { + emitLine("; FLOAT LOADR r$dst = r$src (not implemented)") + } + } +} + +private fun CodeGenerator.storeToMemory(reg: Int, target: String, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(reg)}") + emitLine("sta $target") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(reg)}") + emitLine("sta $target") + emitLine("lda ${regAddrHi(reg)}") + emitLine("sta $target+1") + } + IRDataType.LONG -> { + emitLine("lda ${regAddrLo(reg)}") + emitLine("sta $target") + emitLine("lda ${regAddrHi(reg)}") + emitLine("sta $target+1") + emitLine("lda ${regAddrLo(reg) + 2}") + emitLine("sta $target+2") + emitLine("lda ${regAddrLo(reg) + 3}") + emitLine("sta $target+3") + } + IRDataType.FLOAT -> { + emitLine("; FLOAT STOREM r$reg -> $target (not implemented)") + } + } +} + +private fun CodeGenerator.indexedLoad(dst: Int, idxReg: Int, base: String, type: IRDataType) { + val ptr = ZP_TEMP + emitLine("lda #<$base") + emitLine("sta $ptr") + emitLine("lda #>$base") + emitLine("sta ${ptr}+1") + emitLine("lda ${regAddrLo(idxReg)}") + emitLine("clc") + emitLine("adc $ptr") + emitLine("sta $ptr") + emitLine("lda ${regAddrHi(idxReg)}") + emitLine("adc ${ptr}+1") + emitLine("sta ${ptr}+1") + when (type) { + IRDataType.BYTE -> { + emitLine("ldy #0") + emitLine("lda ($ptr),y") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("ldy #0") + emitLine("lda ($ptr),y") + emitLine("sta ${regAddrLo(dst)}") + emitLine("ldy #1") + emitLine("lda ($ptr),y") + emitLine("sta ${regAddrHi(dst)}") + } + IRDataType.LONG -> { + emitLine("ldy #0") + emitLine("lda ($ptr),y") + emitLine("sta ${regAddrLo(dst)}") + emitLine("ldy #1") + emitLine("lda ($ptr),y") + emitLine("sta ${regAddrHi(dst)}") + emitLine("ldy #2") + emitLine("lda ($ptr),y") + emitLine("sta ${regAddrLo(dst) + 2}") + emitLine("ldy #3") + emitLine("lda ($ptr),y") + emitLine("sta ${regAddrLo(dst) + 3}") + } + IRDataType.FLOAT -> { + emitLine("; FLOAT LOADX r$dst (not implemented)") + } + } +} + +private fun CodeGenerator.indirectLoad(dst: Int, baseReg: Int, offset: Int, type: IRDataType) { + val ptr = ZP_TEMP + emitLine("lda ${regAddrLo(baseReg)}") + emitLine("clc") + emitLine("adc #<${offset and 0xffff}") + emitLine("sta $ptr") + emitLine("lda ${regAddrHi(baseReg)}") + emitLine("adc #>${offset and 0xffff}") + emitLine("sta ${ptr}+1") + when (type) { + IRDataType.BYTE -> { + emitLine("ldy #0") + emitLine("lda ($ptr),y") + emitLine("sta ${regAddrLo(dst)}") + } + IRDataType.WORD -> { + emitLine("ldy #0") + emitLine("lda ($ptr),y") + emitLine("sta ${regAddrLo(dst)}") + emitLine("ldy #1") + emitLine("lda ($ptr),y") + emitLine("sta ${regAddrHi(dst)}") + } + IRDataType.LONG -> { + emitLine("ldy #0") + emitLine("lda ($ptr),y") + emitLine("sta ${regAddrLo(dst)}") + emitLine("ldy #1") + emitLine("lda ($ptr),y") + emitLine("sta ${regAddrHi(dst)}") + emitLine("ldy #2") + emitLine("lda ($ptr),y") + emitLine("sta ${regAddrLo(dst) + 2}") + emitLine("ldy #3") + emitLine("lda ($ptr),y") + emitLine("sta ${regAddrLo(dst) + 3}") + } + IRDataType.FLOAT -> { + emitLine("; FLOAT LOADI r$dst (not implemented)") + } + } +} + +private fun CodeGenerator.storeExchange(reg: Int, reg2: Int, target: String, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda $target") + emitLine("sta ${regAddrLo(reg2)}") + emitLine("lda ${regAddrLo(reg)}") + emitLine("sta $target") + } + IRDataType.WORD -> { + emitLine("lda $target") + emitLine("sta ${regAddrLo(reg2)}") + emitLine("lda ${regAddrLo(reg)}") + emitLine("sta $target") + emitLine("lda $target+1") + emitLine("sta ${regAddrHi(reg2)}") + emitLine("lda ${regAddrHi(reg)}") + emitLine("sta $target+1") + } + IRDataType.LONG -> { + emitLine("lda $target") + emitLine("sta ${regAddrLo(reg2)}") + emitLine("lda ${regAddrLo(reg)}") + emitLine("sta $target") + emitLine("lda $target+1") + emitLine("sta ${regAddrHi(reg2)}") + emitLine("lda ${regAddrHi(reg)}") + emitLine("sta $target+1") + emitLine("lda $target+2") + emitLine("sta ${regAddrLo(reg2) + 2}") + emitLine("lda ${regAddrLo(reg) + 2}") + emitLine("sta $target+2") + emitLine("lda $target+3") + emitLine("sta ${regAddrLo(reg2) + 3}") + emitLine("lda ${regAddrLo(reg) + 3}") + emitLine("sta $target+3") + } + else -> { + emitLine("; STOREX r$reg, r$reg2, $target ${type.name} (not implemented)") + } + } +} + +private fun CodeGenerator.zeroMemory(target: String, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitStoreZero(target) + } + IRDataType.WORD -> { + emitStoreZero(target) + emitStoreZero("$target+1") + } + IRDataType.LONG -> { + emitStoreZero(target) + emitStoreZero("$target+1") + emitStoreZero("$target+2") + emitStoreZero("$target+3") + } + IRDataType.FLOAT -> { + emitLine("; FLOAT STOREZM $target (not implemented)") + } + } +} + +private fun CodeGenerator.zeroIndexed(baseReg: Int, offset: Int, type: IRDataType) { + val ptr = ZP_TEMP + emitLine("lda ${regAddrLo(baseReg)}") + emitLine("clc") + emitLine("adc #<${offset and 0xffff}") + emitLine("sta $ptr") + emitLine("lda ${regAddrHi(baseReg)}") + emitLine("adc #>${offset and 0xffff}") + emitLine("sta ${ptr}+1") + when (type) { + IRDataType.BYTE -> { + emitLine("ldy #0") + if (is65C02()) + emitLine("stz ($ptr),y") + else { + emitLine("lda #0") + emitLine("sta ($ptr),y") + } + } + IRDataType.WORD -> { + emitLine("ldy #0") + if (is65C02()) + emitLine("stz ($ptr),y") + else { + emitLine("lda #0") + emitLine("sta ($ptr),y") + } + emitLine("ldy #1") + if (is65C02()) + emitLine("stz ($ptr),y") + else { + emitLine("lda #0") + emitLine("sta ($ptr),y") + } + } + IRDataType.LONG -> { + emitLine("ldy #0") + if (is65C02()) + emitLine("stz ($ptr),y") + else { + emitLine("lda #0") + emitLine("sta ($ptr),y") + } + emitLine("ldy #1") + if (is65C02()) + emitLine("stz ($ptr),y") + else { + emitLine("lda #0") + emitLine("sta ($ptr),y") + } + emitLine("ldy #2") + if (is65C02()) + emitLine("stz ($ptr),y") + else { + emitLine("lda #0") + emitLine("sta ($ptr),y") + } + emitLine("ldy #3") + if (is65C02()) + emitLine("stz ($ptr),y") + else { + emitLine("lda #0") + emitLine("sta ($ptr),y") + } + } + IRDataType.FLOAT -> { + emitLine("; FLOAT STOREZI r$baseReg + $offset (not implemented)") + } + } +} + +private fun CodeGenerator.zeroMemoryIndexed(reg: Int, base: String, type: IRDataType) { + emitLine("ldx ${regAddrLo(reg)}") + when (type) { + IRDataType.BYTE -> { + if (is65C02()) + emitLine("stz $base,x") + else { + emitLine("lda #0") + emitLine("sta $base,x") + } + } + IRDataType.WORD -> { + if (is65C02()) + emitLine("stz $base,x") + else { + emitLine("lda #0") + emitLine("sta $base,x") + } + emitLine("ldx ${regAddrHi(reg)}") + if (is65C02()) + emitLine("stz ${base}+1,x") + else { + emitLine("lda #0") + emitLine("sta ${base}+1,x") + } + } + IRDataType.LONG -> { + emitLine("; STOREZX LONG not fully implemented") + if (is65C02()) + emitLine("stz $base,x") + else { + emitLine("lda #0") + emitLine("sta $base,x") + } + } + IRDataType.FLOAT -> { + emitLine("; FLOAT STOREZX (not implemented)") + } + } +} + +private fun CodeGenerator.indirectStore(reg: Int, baseReg: Int, offset: Int, type: IRDataType) { + val ptr = ZP_TEMP + emitLine("lda ${regAddrLo(baseReg)}") + emitLine("clc") + emitLine("adc #<${offset and 0xffff}") + emitLine("sta $ptr") + emitLine("lda ${regAddrHi(baseReg)}") + emitLine("adc #>${offset and 0xffff}") + emitLine("sta ${ptr}+1") + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(reg)}") + emitLine("ldy #0") + emitLine("sta ($ptr),y") + } + IRDataType.WORD -> { + emitLine("lda ${regAddrLo(reg)}") + emitLine("ldy #0") + emitLine("sta ($ptr),y") + emitLine("lda ${regAddrHi(reg)}") + emitLine("ldy #1") + emitLine("sta ($ptr),y") + } + IRDataType.LONG -> { + emitLine("lda ${regAddrLo(reg)}") + emitLine("ldy #0") + emitLine("sta ($ptr),y") + emitLine("lda ${regAddrHi(reg)}") + emitLine("ldy #1") + emitLine("sta ($ptr),y") + emitLine("lda ${regAddrLo(reg) + 2}") + emitLine("ldy #2") + emitLine("sta ($ptr),y") + emitLine("lda ${regAddrLo(reg) + 3}") + emitLine("ldy #3") + emitLine("sta ($ptr),y") + } + IRDataType.FLOAT -> { + emitLine("; FLOAT STOREI r$reg (not implemented)") + } + } +} + +internal fun CodeGenerator.resolveAddress(addr: MemoryAddress?, label: String?): String { + return when { + label != null -> resolveSymbolRef(label) + addr != null -> "${addr.value}" + else -> "0" + } +} diff --git a/new6502gen/src/newgen/Main.kt b/new6502gen/src/newgen/Main.kt index 6e7acb31e..1b2fc8b21 100644 --- a/new6502gen/src/newgen/Main.kt +++ b/new6502gen/src/newgen/Main.kt @@ -22,16 +22,20 @@ fun main(args: Array) { val source = Path(inputFile).readText() val program = reader.read(source) val target = program.options.compTarget - println("Loaded IR program: ${program.name}") - println("Target system and CPU: ${target.name} / ${target.cpu}") + System.err.println("Loaded IR program: ${program.name}") + System.err.println("Target system and CPU: ${target.name} / ${target.cpu}") val gen = when(target.cpu) { - CpuType.CPU6502, CpuType.CPU65C02 -> CodeGenerator(program, target.cpu) + CpuType.CPU6502, CpuType.CPU65C02 -> CodeGenerator(program, target) CpuType.VIRTUAL -> { println("This code generator only works for 6502 and 65C02 CPUs.") exitProcess(1) } } - gen.generate() + val ok = gen.generate() + if(!ok) { + System.err.println("Assembly failed.") + exitProcess(1) + } }