diff --git a/2026-09-08-structured-ir-operands.md b/2026-09-08-structured-ir-operands.md new file mode 100644 index 000000000..4a9f9cfd1 --- /dev/null +++ b/2026-09-08-structured-ir-operands.md @@ -0,0 +1,1307 @@ +# Structured IR Operands Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace Prog8's positional, nullable IR operands with typed structured operands across the IR generator, serialization, VM, optimizers, and IR-based CPU backends without changing opcode semantics. + +**Architecture:** Introduce typed operand and call models plus one `OpcodeSchema` source of truth. Adapt the existing `IRInstruction` to expose structured views first, migrate every consumer and producer in buildable stages, then make structured operands canonical and enable the breaking `.p8ir` format. + +**Tech Stack:** Kotlin 2.4, Java 17, KoTest/JUnit 5, Gradle, XML `.p8ir` serialization. + +**Repository constraint:** Do not run `git add`, `git commit`, `git mv`, `git rm`, or any other git write operation. Commit steps normally required by the planning workflow are intentionally omitted. + +--- + +## File structure + +### New production files + +- `intermediate/src/prog8/intermediate/IROperands.kt` + - Virtual register identities, register accesses, immediates, memory + references, code references, hardware slots, and effect enums. +- `intermediate/src/prog8/intermediate/IRCalls.kt` + - `CallSite`, targets, locations, arguments, results, and call effects. +- `intermediate/src/prog8/intermediate/OpcodeSchema.kt` + - Complete opcode/type schema and schema validation. +- `intermediate/src/prog8/intermediate/IRInstructionsFactory.kt` + - Typed construction helpers used by all producers and tests. +- `intermediate/src/prog8/intermediate/IRTextCodec.kt` + - Canonical instruction parser and printer for the new format. + +### New tests + +- `intermediate/test/TestStructuredOperands.kt` +- `intermediate/test/TestOpcodeSchema.kt` +- `intermediate/test/TestIRTextCodec.kt` +- `intermediate/test/TestStructuredCalls.kt` + +### Existing files changed by responsibility + +- `intermediate/src/prog8/intermediate/IRInstructions.kt` + - Temporary structured adapter, then canonical structured + `IRInstruction`; removal of legacy fields and `instructionFormats`. +- `intermediate/src/prog8/intermediate/IRProgram.kt` + - Register accounting and code-reference linking. +- `intermediate/src/prog8/intermediate/Utils.kt` + - Delegate instruction parsing to `IRTextCodec`. +- `intermediate/src/prog8/intermediate/IRFileReader.kt` + - Require the new `IRFORMAT` and read new instruction text. +- `intermediate/src/prog8/intermediate/IRFileWriter.kt` + - Write `IRFORMAT` and new instruction text. +- `intermediate/test/TestInstructions.kt` + - Replace positional-field assertions with structured assertions. +- `intermediate/test/TestIRFileInOut.kt` + - New format-version and round-trip coverage. +- `intermediate/test/TestIRTraversal.kt` + - Structured register and target traversal. +- `codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt` + - Structured matching and transformations. +- `codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt` + - Structured uses, definitions, and targets. +- `codeGenIntermediate/src/prog8/codegen/intermediate/RegisterPacker.kt` + - Distinct integer/float register identities and structured accesses. +- `codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt` +- `codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt` +- `codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt` +- `codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt` + - Replace direct constructors with typed factories. +- `codeGenIntermediate/test/TestIRPeepholeOpt.kt` +- `codeGenIntermediate/test/TestRegisterPacker.kt` +- `codeGenIntermediate/test/TestVmCodeGen.kt` + - Structured optimizer, allocator-analysis, and generation coverage. +- `virtualmachine/src/prog8/vm/VirtualMachine.kt` +- `virtualmachine/src/prog8/vm/VmArithmetic.kt` +- `virtualmachine/src/prog8/vm/VmProgramLoader.kt` +- `virtualmachine/test/TestVm.kt` +- `virtualmachine/test/TestRegisters.kt` + - Consume structured operands and calls. +- `codeGenM68k/src/prog8/codegen/m68k/AsmGen.kt` +- `codeGenM68k/src/prog8/codegen/m68k/InstrArithmetic.kt` +- `codeGenM68k/src/prog8/codegen/m68k/InstrBitwise.kt` +- `codeGenM68k/src/prog8/codegen/m68k/InstrBranch.kt` +- `codeGenM68k/src/prog8/codegen/m68k/InstrControl.kt` +- `codeGenM68k/src/prog8/codegen/m68k/InstrLoadStore.kt` +- `codeGenM68k/src/prog8/codegen/m68k/InstrSyscall.kt` +- `codeGenM68k/test/TestInstructionSelectionOptimizations.kt` + - Consume semantic operands without physical allocation changes. +- `codeGenNew6502/src/prog8/codegen/new6502/AsmGen.kt` +- `codeGenNew6502/src/prog8/codegen/new6502/InstrArithmetic.kt` +- `codeGenNew6502/src/prog8/codegen/new6502/InstrBitwise.kt` +- `codeGenNew6502/src/prog8/codegen/new6502/InstrBranch.kt` +- `codeGenNew6502/src/prog8/codegen/new6502/InstrControl.kt` +- `codeGenNew6502/src/prog8/codegen/new6502/InstrLoadStore.kt` +- `codeGenNew6502/test/TestAsmsubReturns.kt` +- `codeGenNew6502/test/TestInlineAsmSub.kt` +- `codeGenNew6502/test/TestLoopCodegen.kt` + - Consume semantic operands without changing generated behavior. +- `compiler/res/prog8lib/virtual/*.p8` + - Migrate all embedded `%ir` instruction text. +- `docs/CODEBASE-KNOWLEDGE-GRAPH.md` + - Document the structured operand contract. + +## Task 1: Lock down current operand semantics + +**Files:** +- Modify: `intermediate/test/TestInstructions.kt` +- Modify: `intermediate/test/TestIRFileInOut.kt` +- Modify: `codeGenIntermediate/test/TestVmCodeGen.kt` + +- [ ] **Step 1: Add a failing round-trip test for two-result instruction order** + +Add to `TestInstructions.kt`: + +```kotlin +test("DIVMOD preserves quotient and remainder register order") { + val original = IRInstruction( + Opcode.DIVMOD, + IRDataType.WORD, + reg1 = 1, + reg2 = 2, + immediate = 7 + ) + val parsed = (parseIRCodeLine(original.toString()) as ParsedIRLine.Instruction).value + parsed.reg1 shouldBe 1 + parsed.reg2 shouldBe 2 + parsed shouldBe original +} +``` + +- [ ] **Step 2: Run the intermediate test to expose the current ambiguity** + +Run: + +```bash +gradle :intermediate:test --tests "TestInstructions" --console=plain +``` + +Expected: the new `DIVMOD` round-trip test fails by swapping or otherwise +misidentifying the two destination registers. + +- [ ] **Step 3: Add representative behavior baselines** + +Add explicit construction and `toString()` assertions for: + +```kotlin +IRInstruction(Opcode.LOADX, IRDataType.WORD, reg1 = 1, reg2 = 2, + labelSymbol = "main.items", symbolOffset = 4, scale = 2) +IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = 3, reg2 = 4, immediate = 8) +IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1 = 5, immediate = 10) +IRInstruction(Opcode.CALLI, reg1 = 6) +``` + +In `TestIRFileInOut.kt`, add one file round trip containing a normal call, an +asmsub hardware-slot return, and a status-flag return. + +- [ ] **Step 4: Run baseline module tests** + +Run: + +```bash +gradle :intermediate:test :codeGenIntermediate:test --console=plain +``` + +Expected: only the intentionally exposed `DIVMOD` ambiguity fails. Record its +failure text in the task notes; do not weaken the assertion. + +## Task 2: Add typed structured operand values + +**Files:** +- Create: `intermediate/src/prog8/intermediate/IROperands.kt` +- Create: `intermediate/test/TestStructuredOperands.kt` + +- [ ] **Step 1: Write failing tests for register identity and operand validation** + +Create `TestStructuredOperands.kt` with tests equivalent to: + +```kotlin +class TestStructuredOperands : FunSpec({ + fun testIndexOperand() = RegisterOperand( + VirtualRegister.IntReg(RegisterNum(3)), + IRDataType.WORD, + OperandRole.INDEX, + OperandDirection.USE, + AllocationHint.PREFER_DATA + ) + + test("integer and float registers are distinct") { + val intReg: VirtualRegister = VirtualRegister.IntReg(RegisterNum(5)) + val floatReg: VirtualRegister = VirtualRegister.FloatReg(RegisterNum(5)) + intReg shouldNotBe floatReg + } + + test("indexed memory keeps base index scale and displacement") { + val index = RegisterOperand( + VirtualRegister.IntReg(RegisterNum(3)), + IRDataType.WORD, + OperandRole.INDEX, + OperandDirection.USE, + AllocationHint.PREFER_DATA + ) + val ref = MemoryReference.Indexed( + AddressBase.Symbol("main.items"), + index, + scale = 6, + displacement = 4 + ) + ref.scale shouldBe 6 + ref.displacement shouldBe 4 + } + + test("indexed scale must be in range") { + shouldThrow { + MemoryReference.Indexed( + AddressBase.Symbol("main.items"), + testIndexOperand(), + scale = 0 + ) + } + } +}) +``` + +- [ ] **Step 2: Run the new test and verify it does not compile** + +Run: + +```bash +gradle :intermediate:test --tests "TestStructuredOperands" --console=plain +``` + +Expected: compilation fails because the structured operand types do not exist. + +- [ ] **Step 3: Implement the operand hierarchy** + +Implement in `IROperands.kt` the exact types from the approved specification: + +```kotlin +sealed interface VirtualRegister { + val number: RegisterNum + + @JvmInline + value class IntReg(override val number: RegisterNum) : VirtualRegister + + @JvmInline + value class FloatReg(override val number: RegisterNum) : VirtualRegister +} + +enum class OperandDirection { USE, DEF, USE_DEF } +enum class OperandRole { + RESULT, SECONDARY_RESULT, VALUE, LEFT, RIGHT, INDEX, POINTER_BASE, + INDIRECT_TARGET, CALL_ARGUMENT, CALL_RESULT +} +enum class AllocationHint { NONE, PREFER_DATA, PREFER_ADDRESS } + +data class RegisterOperand( + val register: VirtualRegister, + val type: IRDataType, + val role: OperandRole, + val direction: OperandDirection, + val allocationHint: AllocationHint = AllocationHint.NONE +) { + init { + require((register is VirtualRegister.FloatReg) == (type == IRDataType.FLOAT)) + } +} +``` + +Add `ImmediateOperand`, `HardwareSlotOperand`, `MemoryReference`, +`AddressBase`, `CodeReference`, `MemoryEffect`, and `StatusEffect` exactly as +specified. Enforce `scale in 1..65535`, non-negative indirect displacement in +the current LOADI/STOREI range, and valid symbol names in constructors. + +- [ ] **Step 4: Run structured operand tests** + +Run: + +```bash +gradle :intermediate:test --tests "TestStructuredOperands" --console=plain +``` + +Expected: all tests in `TestStructuredOperands` pass. + +## Task 3: Add the structured call model + +**Files:** +- Create: `intermediate/src/prog8/intermediate/IRCalls.kt` +- Create: `intermediate/test/TestStructuredCalls.kt` +- Modify: `intermediate/src/prog8/intermediate/IRInstructions.kt` + +- [ ] **Step 1: Write failing call-model tests** + +Cover a normal parameter-memory call, an asmsub call using D0/D2, a +multi-result call, a status-flag result, an indirect call, an Amiga LVO call, +and a syscall: + +```kotlin +test("call result has exactly one explicit location") { + val destination = RegisterOperand( + VirtualRegister.IntReg(RegisterNum(4)), + IRDataType.WORD, + OperandRole.CALL_RESULT, + OperandDirection.DEF + ) + val result = CallResult( + destination, + CallLocation.HardwareRegister(CallingConventionSlot(12)) + ) + result.location shouldBe CallLocation.HardwareRegister(CallingConventionSlot(12)) +} + +test("Amiga LVO retains signed offset") { + val target = CallTarget.AmigaLibrary(2, -30, "Open") + target.lvo shouldBe -30 +} +``` + +- [ ] **Step 2: Run the call test and verify it does not compile** + +Run: + +```bash +gradle :intermediate:test --tests "TestStructuredCalls" --console=plain +``` + +Expected: compilation fails because `CallSite` and related types do not exist. + +- [ ] **Step 3: Implement calls and legacy conversion** + +Implement `CallTarget`, `CallLocation`, `CallArgument`, `CallResult`, +`CallEffects`, and `CallSite` from the specification. Add conversion helpers +without changing canonical storage yet: + +```kotlin +fun FunctionCallArgs.toCallSite( + opcode: Opcode, + target: CallTarget, + effects: CallEffects +): CallSite + +fun CallSite.toLegacyFunctionCallArgs(): FunctionCallArgs +``` + +Map a missing slot/flag plus a named parameter to +`CallLocation.ParameterMemory`; map an unannotated value to +`CallLocation.Default`; never infer a hardware slot from a register number. + +- [ ] **Step 4: Run call and existing instruction tests** + +Run: + +```bash +gradle :intermediate:test --tests "TestStructuredCalls" --tests "TestInstructions" --console=plain +``` + +Expected: structured call tests pass; only the intentionally failing DIVMOD +round-trip test remains. + +## Task 4: Introduce `OpcodeSchema` + +**Files:** +- Create: `intermediate/src/prog8/intermediate/OpcodeSchema.kt` +- Create: `intermediate/test/TestOpcodeSchema.kt` +- Modify: `intermediate/src/prog8/intermediate/IRInstructions.kt` + +- [ ] **Step 1: Write schema completeness and representative-shape tests** + +Add tests that assert: + +```kotlin +Opcode.entries.forEach { opcode -> + OpcodeSchemas.schemasFor(opcode).shouldNotBeEmpty() +} + +OpcodeSchemas.get(Opcode.ADDR, IRDataType.WORD).slots shouldBe listOf( + RegisterSlotSchema(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.USE_DEF, + RegisterFile.INTEGER, TypeRule.InstructionType), + RegisterSlotSchema(InstructionSlot.SRC_A, OperandRole.RIGHT, OperandDirection.USE, + RegisterFile.INTEGER, TypeRule.InstructionType) +) + +OpcodeSchemas.get(Opcode.DIVMOD, IRDataType.WORD).slots.map { it.slot } shouldBe + listOf(InstructionSlot.DEST, InstructionSlot.DEST_B, InstructionSlot.IMMEDIATE) +``` + +Also test float `LOADX`, pointer `LOADI`, `LOADHR`, `CONCAT`, branch, return, +and every call opcode. + +- [ ] **Step 2: Run schema tests and verify they fail** + +Run: + +```bash +gradle :intermediate:test --tests "TestOpcodeSchema" --console=plain +``` + +Expected: compilation fails because `OpcodeSchema` does not exist. + +- [ ] **Step 3: Implement schema types and complete opcode coverage** + +Implement: + +```kotlin +enum class InstructionSlot { + DEST, DEST_B, SRC_A, SRC_B, IMMEDIATE, HARDWARE_SLOT, MEMORY, TARGET, CALL_SITE +} + +enum class RegisterFile { INTEGER, FLOAT } + +sealed interface TypeRule { + data object InstructionType : TypeRule + data object IndexType : TypeRule + data object PointerType : TypeRule + data class Fixed(val type: IRDataType) : TypeRule +} + +data class RegisterSlotSchema( + val slot: InstructionSlot, + val role: OperandRole, + val direction: OperandDirection, + val registerFile: RegisterFile, + val typeRule: TypeRule +) +``` + +Port every existing `instructionFormats` entry into `OpcodeSchemas`. Use named +slots in semantic order, not old field order. Encode special forms explicitly: + +- `DIVMOD`/`SDIVMOD`: `dest`, `destB`, `immediate`. +- `CONCAT`: `dest`, `srcA`, `srcB`. +- Float indexed operations: float destination/value plus integer index. +- `LOADI`/`STOREI`: pointer base through `memory`. +- `LOADHR`/`STOREHR`: `hardwareSlot`. +- Calls: only `callSite`. +- Branches/jumps: `target`, plus comparison sources where applicable. + +- [ ] **Step 4: Add schema equivalence checks during migration** + +While `instructionFormats` still exists, add a test that each old format and +new schema agree on register count, register file, direction, immediate +presence, address presence, and call kind. This temporary test is removed with +`instructionFormats`. + +- [ ] **Step 5: Run schema and instruction tests** + +Run: + +```bash +gradle :intermediate:test --tests "TestOpcodeSchema" --tests "TestInstructions" --console=plain +``` + +Expected: schema tests pass; the known old parser round-trip failure remains. + +## Task 5: Add structured views and factories to the legacy instruction + +**Files:** +- Create: `intermediate/src/prog8/intermediate/IRInstructionsFactory.kt` +- Modify: `intermediate/src/prog8/intermediate/IRInstructions.kt` +- Modify: `intermediate/test/TestInstructions.kt` +- Modify: `intermediate/test/TestStructuredOperands.kt` + +- [ ] **Step 1: Write failing tests for structured views** + +For representative integer, float, indexed-memory, branch, hardware-slot, and +call instructions, assert: + +```kotlin +val add = IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1 = 1, reg2 = 2) +add.dest shouldBe RegisterOperand( + VirtualRegister.IntReg(RegisterNum(1)), + IRDataType.WORD, + OperandRole.RESULT, + OperandDirection.USE_DEF +) +add.srcA shouldBe RegisterOperand( + VirtualRegister.IntReg(RegisterNum(2)), + IRDataType.WORD, + OperandRole.RIGHT, + OperandDirection.USE +) +add.uses shouldBe setOf( + VirtualRegister.IntReg(RegisterNum(1)), + VirtualRegister.IntReg(RegisterNum(2)) +) +add.definitions shouldBe setOf(VirtualRegister.IntReg(RegisterNum(1))) +``` + +- [ ] **Step 2: Run the tests and verify they fail** + +Run: + +```bash +gradle :intermediate:test --tests "TestStructuredOperands" --tests "TestInstructions" --console=plain +``` + +Expected: compilation fails because the structured views do not exist. + +- [ ] **Step 3: Implement a schema-driven legacy adapter** + +Keep the existing fields canonical temporarily. Add derived properties: + +```kotlin +val IRInstruction.dest: RegisterOperand? +val IRInstruction.destB: RegisterOperand? +val IRInstruction.srcA: RegisterOperand? +val IRInstruction.srcB: RegisterOperand? +val IRInstruction.structuredImmediate: ImmediateOperand? +val IRInstruction.hardwareSlot: HardwareSlotOperand? +val IRInstruction.memory: MemoryReference? +val IRInstruction.target: CodeReference? +val IRInstruction.callSite: CallSite? +val IRInstruction.registerAccesses: List +val IRInstruction.uses: Set +val IRInstruction.definitions: Set +``` + +Resolve each property through `OpcodeSchema`; do not duplicate opcode switches +in the individual getters. + +- [ ] **Step 4: Add typed factories** + +Implement factory methods that produce the current instruction fields from +structured input during the migration: + +```kotlin +object IRInstructions { + fun load(destination: RegisterOperand, immediate: ImmediateOperand): IRInstruction + fun loadMemory(destination: RegisterOperand, memory: MemoryReference): IRInstruction + fun move(destination: RegisterOperand, source: RegisterOperand): IRInstruction + fun binary(opcode: Opcode, destination: RegisterOperand, source: RegisterOperand): IRInstruction + fun branch(opcode: Opcode, target: CodeReference, srcA: RegisterOperand? = null, + srcB: RegisterOperand? = null, immediate: ImmediateOperand? = null): IRInstruction + fun call(opcode: Opcode, site: CallSite): IRInstruction +} +``` + +All factories call one schema validator before constructing the legacy form. + +- [ ] **Step 5: Run all intermediate tests except the known parser failure** + +Run: + +```bash +gradle :intermediate:test --console=plain +``` + +Expected: structured view and factory tests pass; the recorded DIVMOD +round-trip test is the only failure. + +## Task 6: Replace instruction text parsing and printing + +**Files:** +- Create: `intermediate/src/prog8/intermediate/IRTextCodec.kt` +- Create: `intermediate/test/TestIRTextCodec.kt` +- Modify: `intermediate/src/prog8/intermediate/Utils.kt` +- Modify: `intermediate/src/prog8/intermediate/IRInstructions.kt` +- Modify: `intermediate/test/TestInstructions.kt` + +- [ ] **Step 1: Write failing canonical text tests** + +Cover every token category from the specification: + +```kotlin +IRTextCodec.parse("load.w r1.w,#$1234.w").print() shouldBe + "load.w r1.w,#$1234.w" +IRTextCodec.parse("loadi.b r1.b,[r2.p+8]").print() shouldBe + "loadi.b r1.b,[r2.p+8]" +IRTextCodec.parse("loadx.w r1.w,[main.items+4+r2.w*2]").print() shouldBe + "loadx.w r1.w,[main.items+4+r2.w*2]" +IRTextCodec.parse("loadhr.w r1.w,s10.w").print() shouldBe + "loadhr.w r1.w,s10.w" +``` + +Add explicit tests for `DIVMOD`, `SDIVMOD`, every branch target form, and all +four call opcodes. + +- [ ] **Step 2: Run text-codec tests and verify they fail** + +Run: + +```bash +gradle :intermediate:test --tests "TestIRTextCodec" --console=plain +``` + +Expected: compilation fails because `IRTextCodec` does not exist. + +- [ ] **Step 3: Implement token parsing and printing** + +Implement a tokenizer with explicit token classes rather than comma splitting: + +```kotlin +sealed interface IRToken { + data class Register(val text: String) : IRToken + data class Immediate(val text: String) : IRToken + data class Memory(val text: String) : IRToken + data class CodeTarget(val text: String) : IRToken + data class HardwareSlot(val text: String) : IRToken + data class StatusFlag(val text: String) : IRToken +} +``` + +Parsing flow: + +1. Parse opcode and optional type suffix. +2. Resolve `OpcodeSchema`. +3. Tokenize balanced `[]` and `()` expressions. +4. Parse each token according to the schema's canonical slot order. +5. Construct through `IRInstructions` factories. +6. Reject extra, missing, or wrong-kind tokens with `IRParseException`. + +- [ ] **Step 4: Route existing public helpers through the codec** + +Make `parseIRCodeLine` delegate instruction lines to `IRTextCodec.parse`. +Make `IRInstruction.toString()` delegate to `IRTextCodec.print`. Keep label-line +parsing in `Utils.kt`. + +- [ ] **Step 5: Run all intermediate tests** + +Run: + +```bash +gradle :intermediate:test --console=plain +``` + +Expected: all intermediate tests pass, including the original DIVMOD +round-trip regression. + +## Task 7: Version and round-trip the `.p8ir` format + +**Files:** +- Modify: `intermediate/src/prog8/intermediate/IRFileWriter.kt` +- Modify: `intermediate/src/prog8/intermediate/IRFileReader.kt` +- Modify: `intermediate/test/TestIRFileInOut.kt` + +- [ ] **Step 1: Write failing format-version tests** + +Change the expected root to: + +```xml + +``` + +Add: + +```kotlin +shouldThrowWithMessage("unsupported IR format: 1") { + IRFileReader().read(irFileWithRoot("""""")) +} + +shouldThrowWithMessage("missing IRFORMAT") { + IRFileReader().read(irFileWithoutFormatAttribute()) +} +``` + +- [ ] **Step 2: Run file I/O tests and verify they fail** + +Run: + +```bash +gradle :intermediate:test --tests "TestIRFileInOut" --console=plain +``` + +Expected: root-attribute and rejection tests fail. + +- [ ] **Step 3: Implement strict format versioning** + +Add: + +```kotlin +const val IR_FORMAT_VERSION = 2 +``` + +Write `IRFORMAT="2"` in `IRFileWriter`. Require exactly `2` in +`IRFileReader` before reading options or code. Do not retain a legacy parser. + +- [ ] **Step 4: Convert all fixture instruction lines in `TestIRFileInOut.kt`** + +Use typed register and explicit memory syntax, for example: + +```text +load.b r1.b,#42.b +loadm.w r0.w,[sys.wait.jiffies] +``` + +- [ ] **Step 5: Run intermediate tests** + +Run: + +```bash +gradle :intermediate:test --console=plain +``` + +Expected: all intermediate tests pass. + +## Task 8: Migrate generic analyses and linking + +**Files:** +- Modify: `intermediate/src/prog8/intermediate/IRProgram.kt` +- Modify: `intermediate/test/TestIRTraversal.kt` +- Modify: `codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt` +- Modify: `codeGenIntermediate/src/prog8/codegen/intermediate/RegisterPacker.kt` +- Modify: `codeGenIntermediate/test/TestRegisterPacker.kt` + +- [ ] **Step 1: Write failing mixed-register and linkage tests** + +Add a traversal test in which `r5` and `fr5` both occur and assert that two +different `VirtualRegister` values are reported. Add a linking test asserting: + +```kotlin +program.resolveCodeTarget(CodeReference.Label("main.target")) shouldBe targetChunk +``` + +Add RegisterPacker coverage showing integer and float register 5 never share +one graph node. + +- [ ] **Step 2: Run focused tests and verify failure** + +Run: + +```bash +gradle :intermediate:test --tests "TestIRTraversal" --console=plain +gradle :codeGenIntermediate:test --tests "TestRegisterPacker" --console=plain +``` + +Expected: the tests fail because analyses still use integer field numbers. + +- [ ] **Step 3: Rebuild register accounting on structured accesses** + +Replace enumeration of `reg1..reg3` and `fpReg1..fpReg2` with: + +```kotlin +for (access in instruction.registerAccesses) { + when (access.direction) { + OperandDirection.USE -> addRead(access) + OperandDirection.DEF -> addWrite(access) + OperandDirection.USE_DEF -> { + addRead(access) + addWrite(access) + } + } +} +``` + +Change internal maps that can contain both register files to use +`VirtualRegister` keys. + +- [ ] **Step 4: Replace mutable branch targets with program linkage** + +Build a `Map` in `IRProgram.linkChunks()` and expose: + +```kotlin +fun resolveCodeTarget(reference: CodeReference): IRCodeChunkBase? = when (reference) { + is CodeReference.Label -> linkedCodeTargets[reference.name] + is CodeReference.Absolute, is CodeReference.Indirect -> null +} +``` + +Update validation and unused-code traversal to use `instruction.target` and +this resolver. Remove writes to `IRInstruction.branchTarget` only after all +consumers have migrated. + +- [ ] **Step 5: Update RegisterPacker access extraction** + +Return `Set` for written and read values. Keep integer and +float interference separate. Preserve the packer's documented prototype +limitations; do not turn this task into the M68k allocator. + +- [ ] **Step 6: Run focused module tests** + +Run: + +```bash +gradle :intermediate:test :codeGenIntermediate:test --console=plain +``` + +Expected: both modules pass. + +## Task 9: Migrate IR optimizers to structured matching + +**Files:** +- Modify: `codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt` +- Modify: `codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt` +- Modify: `codeGenIntermediate/test/TestIRPeepholeOpt.kt` + +- [ ] **Step 1: Add tests for transformations that previously reconstructed instructions** + +Cover at least: + +- `LOADX`/`STOREX` retaining scale and symbol displacement. +- Calls retaining all arguments, results, locations, and effects. +- Branch rewrites retaining `CodeReference`. +- Register replacement changing only the selected virtual register. + +Use structured assertions rather than old field assertions. + +- [ ] **Step 2: Run optimizer tests before migration** + +Run: + +```bash +gradle :codeGenIntermediate:test --tests "TestIRPeepholeOpt" --console=plain +``` + +Expected: new structured assertions fail at legacy reconstruction sites. + +- [ ] **Step 3: Replace positional matches** + +Use semantic accessors: + +```kotlin +val destination = instruction.dest ?: return noMatch +val source = instruction.srcA ?: return noMatch +val memory = instruction.memory +``` + +Replace direct `.copy(reg1 = ...)` and full constructor rebuilding with: + +```kotlin +instruction.mapRegisters { register -> + replacements[register] ?: register +} +``` + +Use `withTarget` and `mapMemoryReferences` for their respective changes. + +- [ ] **Step 4: Remove duplicate direction/type helpers** + +Delete optimizer-local helpers that reconstruct destination order, float +register roles, index roles, or register types when equivalent information is +available through `OpcodeSchema`. + +- [ ] **Step 5: Run optimizer and packer tests** + +Run: + +```bash +gradle :codeGenIntermediate:test --tests "TestIRPeepholeOpt" --tests "TestRegisterPacker" --console=plain +``` + +Expected: all selected tests pass. + +## Task 10: Migrate the virtual machine + +**Files:** +- Modify: `virtualmachine/src/prog8/vm/VirtualMachine.kt` +- Modify: `virtualmachine/src/prog8/vm/VmArithmetic.kt` +- Modify: `virtualmachine/src/prog8/vm/VmProgramLoader.kt` +- Modify: `virtualmachine/test/TestVm.kt` +- Modify: `virtualmachine/test/TestRegisters.kt` + +- [ ] **Step 1: Add structured execution tests** + +Construct instructions with `IRInstructions` factories and cover: + +- Integer and float register number equality without identity collision. +- `LOADI` and `LOADX` memory addressing. +- `CALL`, `CALLI`, multi-result return, hardware-slot return, and flag return. +- Branch target resolution through `IRProgram`. + +- [ ] **Step 2: Run VM tests and verify adapter dependence** + +Run: + +```bash +gradle :virtualmachine:test --tests "TestVm" --tests "TestRegisters" --console=plain +``` + +Expected: tests using factories compile, but structured-only assertions expose +remaining field-based handlers. + +- [ ] **Step 3: Add typed operand extraction helpers** + +Use narrow helpers that fail with opcode context: + +```kotlin +fun IRInstruction.requireIntDest(): RegisterOperand +fun IRInstruction.requireFloatDest(): RegisterOperand +fun IRInstruction.requireIntSourceA(): RegisterOperand +fun IRInstruction.requireMemory(): MemoryReference +fun IRInstruction.requireCallSite(): CallSite +``` + +Convert `RegisterNum` to VM array indexes only at `Registers` access. + +- [ ] **Step 4: Migrate opcode handlers** + +Replace every `reg1!!`, `reg2!!`, `fpReg1!!`, `immediate!!`, `address!!`, +`labelSymbol`, and `fcallArgs` read with the semantic helper matching that +opcode. Resolve code labels through `IRProgram.resolveCodeTarget`. + +- [ ] **Step 5: Migrate loader transformations** + +Replace `.copy(address=...)`, `.copy(labelSymbol=...)`, and direct +`branchTarget` assignment in `VmProgramLoader.kt` with `withTarget`, +`mapMemoryReferences`, and program linkage. + +- [ ] **Step 6: Run the complete VM module** + +Run: + +```bash +gradle :virtualmachine:test --console=plain +``` + +Expected: all VM tests pass. + +## Task 11: Migrate the M68k backend + +**Files:** +- Modify: `codeGenM68k/src/prog8/codegen/m68k/AsmGen.kt` +- Modify: `codeGenM68k/src/prog8/codegen/m68k/InstrArithmetic.kt` +- Modify: `codeGenM68k/src/prog8/codegen/m68k/InstrBitwise.kt` +- Modify: `codeGenM68k/src/prog8/codegen/m68k/InstrBranch.kt` +- Modify: `codeGenM68k/src/prog8/codegen/m68k/InstrControl.kt` +- Modify: `codeGenM68k/src/prog8/codegen/m68k/InstrLoadStore.kt` +- Modify: `codeGenM68k/src/prog8/codegen/m68k/InstrSyscall.kt` +- Modify: `codeGenM68k/test/TestInstructionSelectionOptimizations.kt` + +- [ ] **Step 1: Convert test construction to factories** + +Replace positional `IRInstruction(...)` construction in +`TestInstructionSelectionOptimizations` with `IRInstructions` factories. +Add assertions that indexed and indirect references still emit their current +assembly forms. + +- [ ] **Step 2: Run the M68k test before backend migration** + +Run: + +```bash +gradle :codeGenM68k:test --tests "prog8tests.codegen.m68k.TestInstructionSelectionOptimizations" --console=plain +``` + +Expected: factory-based tests pass through the adapter, establishing unchanged +assembly before field removal. + +- [ ] **Step 3: Add backend-local typed accessors** + +Use structured inputs while preserving current memory-regfile code generation: + +```kotlin +fun RegisterOperand.intNumber(): Int = + (register as VirtualRegister.IntReg).number.value + +fun RegisterOperand.floatNumber(): RegisterNum = + (register as VirtualRegister.FloatReg).number +``` + +Do not add allocation, operand-location selection, or new peepholes in this +task. + +- [ ] **Step 4: Migrate instruction translators** + +Replace all old operand-field reads with `dest`, `destB`, `srcA`, `srcB`, +`immediate`, `memory`, `target`, `hardwareSlot`, and `callSite`. Centralize +`MemoryReference` lowering in one `AsmGen` helper so `LOADX`, `STOREX`, +`LOADI`, `STOREI`, and float variants do not decode address fields separately. + +- [ ] **Step 5: Migrate call translation** + +In `InstrControl.kt`, translate: + +- `CallTarget.Direct(CodeReference.Label/Absolute/Indirect)`. +- `CallTarget.AmigaLibrary`. +- `CallTarget.SystemCall`. +- `CallLocation.ParameterMemory`, `HardwareRegister`, `StatusFlag`, and + `Default`. + +Preserve current argument ordering, multi-return behavior, and +`ImmediateCallOptimization`; do not yet enforce the proposed allocator ABI. + +- [ ] **Step 6: Run the M68k module** + +Run: + +```bash +gradle :codeGenM68k:test --console=plain +``` + +Expected: all M68k tests pass with unchanged assembly assertions. + +## Task 12: Migrate the new6502 backend + +**Files:** +- Modify: `codeGenNew6502/src/prog8/codegen/new6502/AsmGen.kt` +- Modify: `codeGenNew6502/src/prog8/codegen/new6502/InstrArithmetic.kt` +- Modify: `codeGenNew6502/src/prog8/codegen/new6502/InstrBitwise.kt` +- Modify: `codeGenNew6502/src/prog8/codegen/new6502/InstrBranch.kt` +- Modify: `codeGenNew6502/src/prog8/codegen/new6502/InstrControl.kt` +- Modify: `codeGenNew6502/src/prog8/codegen/new6502/InstrLoadStore.kt` +- Modify: `codeGenNew6502/test/TestAsmsubReturns.kt` +- Modify: `codeGenNew6502/test/TestInlineAsmSub.kt` +- Modify: `codeGenNew6502/test/TestLoopCodegen.kt` + +- [ ] **Step 1: Convert backend tests to factories and structured calls** + +Replace `FunctionCallArgs.RegSpec` construction with `CallResult` and +`CallLocation`. Keep all expected assembly unchanged. + +- [ ] **Step 2: Run focused backend tests** + +Run: + +```bash +gradle :codeGenNew6502:test --tests "prog8tests.codegen.new6502.TestAsmsubReturns" --tests "prog8tests.codegen.new6502.TestInlineAsmSub" --tests "prog8tests.codegen.new6502.TestLoopCodegen" --console=plain +``` + +Expected: tests pass through the adapter before old fields are removed. + +- [ ] **Step 3: Migrate translators and register scans** + +Replace old fields with semantic slots and structured memory/call targets. +Convert to 6502 register-file indexes only at the backend boundary. Treat +unknown call or inline-assembly effects conservatively. + +- [ ] **Step 4: Run the complete new6502 module** + +Run: + +```bash +gradle :codeGenNew6502:test --console=plain +``` + +Expected: all new6502 tests pass. + +## Task 13: Migrate IR producers + +**Files:** +- Modify: `codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt` +- Modify: `codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt` +- Modify: `codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt` +- Modify: `codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt` +- Modify: `codeGenIntermediate/test/TestVmCodeGen.kt` +- Modify: `codeGenIntermediate/test/TestIRPeepholeOpt.kt` + +- [ ] **Step 1: Add generation tests for every structured operand category** + +Compile small Simple AST inputs and assert the resulting instruction has: + +- `ImmediateOperand.SymbolAddress` for address-of. +- `MemoryReference.Indirect` for pointer dereference. +- `MemoryReference.Indexed` for array indexing. +- `HardwareSlotOperand` for LOADHR/STOREHR. +- `CallSite` for normal calls, asmsubs, extsubs, and syscalls. + +- [ ] **Step 2: Run generator tests before migration** + +Run: + +```bash +gradle :codeGenIntermediate:test --tests "TestVmCodeGen" --console=plain +``` + +Expected: structured assertions pass through the legacy adapter. + +- [ ] **Step 3: Replace direct constructors in the smallest producer** + +Migrate `BuiltinFuncGen.kt` to `IRInstructions` factories. Compile: + +```bash +gradle :codeGenIntermediate:compileKotlin --console=plain +``` + +Expected: compilation succeeds. + +- [ ] **Step 4: Migrate expression and main IR generation** + +Migrate `ExpressionGen.kt` and `IRCodeGen.kt`. Build call sites directly as: + +```kotlin +IRInstructions.call( + Opcode.CALL, + CallSite(target, arguments, results, effects) +) +``` + +Fix `replaceMemoryMappedVars` by using `mapMemoryReferences`; it must preserve +index scale, displacement, calls, and targets. + +- [ ] **Step 5: Migrate assignment generation** + +Replace all direct constructors in `AssignmentGen.kt`, preserving evaluation +order and current temporary-register allocation. + +- [ ] **Step 6: Prove no production producer uses the legacy constructor** + +Run: + +```bash +rg -n "IRInstruction\\(" codeGenIntermediate/src --glob '*.kt' +``` + +Expected: only `IRInstructionsFactory.kt` or explicitly approved low-level +factory internals match; generator files contain no direct construction. + +- [ ] **Step 7: Run codeGenIntermediate tests** + +Run: + +```bash +gradle :codeGenIntermediate:test --console=plain +``` + +Expected: all tests pass. + +## Task 14: Make structured operands canonical + +**Files:** +- Modify: `intermediate/src/prog8/intermediate/IRInstructions.kt` +- Modify: `intermediate/src/prog8/intermediate/IRInstructionsFactory.kt` +- Modify: `intermediate/src/prog8/intermediate/OpcodeSchema.kt` +- Modify: all remaining test files that construct `IRInstruction` directly + +- [ ] **Step 1: Search for legacy field and constructor dependencies** + +Run: + +```bash +rg -n "\\.(reg[123]|fpReg[12]|immediateFp|labelSymbol|labelSymbolOffset|branchTarget|fcallArgs|scale)\\b|IRInstruction\\(" \ + intermediate codeGenIntermediate virtualmachine codeGenM68k codeGenNew6502 \ + --glob '*.kt' +``` + +Expected: matches are limited to the legacy implementation and tests awaiting +factory conversion. + +- [ ] **Step 2: Convert remaining tests and low-level helpers** + +Use `IRInstructions` factories and structured assertions. Do not add +test-only compatibility getters. + +- [ ] **Step 3: Flip canonical storage** + +Replace the old `IRInstruction` fields with: + +```kotlin +data class IRInstruction( + val opcode: Opcode, + val type: IRDataType? = null, + val dest: RegisterOperand? = null, + val destB: RegisterOperand? = null, + val srcA: RegisterOperand? = null, + val srcB: RegisterOperand? = null, + val immediate: ImmediateOperand? = null, + val hardwareSlot: HardwareSlotOperand? = null, + val memory: MemoryReference? = null, + val target: CodeReference? = null, + val callSite: CallSite? = null +) { + init { + OpcodeSchemas.validate(this) + } +} +``` + +Implement `registerAccesses`, `uses`, `definitions`, `memoryEffect`, and +`statusEffect` directly from the canonical fields and schema. + +- [ ] **Step 4: Remove legacy implementation** + +Delete: + +- `InstructionFormat` and `instructionFormats`. +- `FunctionCallArgs`. +- Positional register/address/immediate fields. +- `determineReg1Type`, `determineReg2Type`, and `determineReg3Type`. +- Legacy structured adapters and conversion helpers. +- Mutable `branchTarget` and non-serialized `extSubName`. + +Store the external function name in `CallTarget.AmigaLibrary` or the direct +call metadata where required. + +- [ ] **Step 5: Compile all IR consumers** + +Run: + +```bash +gradle :intermediate:compileKotlin :codeGenIntermediate:compileKotlin \ + :virtualmachine:compileKotlin :codeGenM68k:compileKotlin \ + :codeGenNew6502:compileKotlin --console=plain +``` + +Expected: compilation succeeds with no legacy API. + +## Task 15: Migrate embedded IR and end-to-end fixtures + +**Files:** +- Modify: `compiler/res/prog8lib/virtual/*.p8` files containing `%ir` +- Modify: compiler tests containing literal old-format IR + +- [ ] **Step 1: Locate all old embedded IR** + +Run: + +```bash +rg -n "%ir|\\b(load|store|call|return|jump|add|sub|cmp)[.]?[bwlfp]?\\s+r[0-9]" \ + compiler/res/prog8lib compiler/test examples --glob '*.{p8,kt,p8ir}' +``` + +Expected: a finite list of standard-library blocks and fixtures to migrate. + +- [ ] **Step 2: Convert each `%ir` block through the new codec** + +For every instruction, add typed registers and structured memory, slot, and +target syntax. Example: + +```text +loadm.w r0.w,[sys.wait.jiffies] +sub.w r0.w,#1.w +storem.w r0.w,[sys.wait.jiffies] +``` + +Do not change surrounding Prog8 source or assembly formatting. + +- [ ] **Step 3: Rebuild and reinstall the compiler** + +Run: + +```bash +gradle installdist installshadowdist --console=plain +``` + +Expected: build and installation succeed. + +- [ ] **Step 4: Compile representative virtual programs** + +Run the existing virtual-target examples or compiler tests that import each +module containing `%ir`. Use: + +```bash +gradle :compiler:test --console=plain +``` + +Expected: all compiler tests pass and no old-format parse errors occur. + +## Task 16: Remove migration scaffolding and document the contract + +**Files:** +- Modify: `intermediate/src/prog8/intermediate/IRInstructions.kt` +- Modify: `intermediate/src/prog8/intermediate/IRInstructionsFactory.kt` +- Modify: `intermediate/src/prog8/intermediate/OpcodeSchema.kt` +- Modify: `docs/CODEBASE-KNOWLEDGE-GRAPH.md` + +- [ ] **Step 1: Prove legacy symbols are gone** + +Run: + +```bash +rg -n "InstructionFormat|instructionFormats|FunctionCallArgs|reg1direction|reg2direction|fpReg1direction|labelSymbolOffset" \ + intermediate codeGenIntermediate virtualmachine codeGenM68k codeGenNew6502 \ + --glob '*.kt' +``` + +Expected: no matches. + +- [ ] **Step 2: Add final schema invariants** + +At startup in tests, validate: + +```kotlin +require(OpcodeSchemas.coveredOpcodes == Opcode.entries.toSet()) +require(OpcodeSchemas.all().groupBy { it.opcode to it.type }.values.all { it.size == 1 }) +``` + +Ensure every schema has canonical print order and declared memory, status, and +control-flow effects. + +- [ ] **Step 3: Update architecture documentation** + +Document in `docs/CODEBASE-KNOWLEDGE-GRAPH.md` that: + +- `IRInstruction` uses structured operands. +- `OpcodeSchema` is the semantic source of truth. +- Machine backends consume typed uses/definitions and lower structured memory + and calls. +- `.p8ir` format 2 is intentionally incompatible with older files. + +- [ ] **Step 4: Run focused module verification** + +Run: + +```bash +gradle :intermediate:test :codeGenIntermediate:test :virtualmachine:test \ + :codeGenM68k:test :codeGenNew6502:test --console=plain +``` + +Expected: all selected module tests pass. + +- [ ] **Step 5: Run full repository verification** + +Run: + +```bash +gradle build --console=plain +``` + +Expected: `BUILD SUCCESSFUL` with no failed tests. + +- [ ] **Step 6: Check the final diff** + +Run: + +```bash +git --no-pager diff --check +git --no-pager status --short +``` + +Expected: no whitespace errors; only files required by this migration are +modified or newly created. diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt index e66b51904..11cebf9bf 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt @@ -117,22 +117,22 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen else null to null val m68kSlot3 = slot3?.takeIf { it.value >= 10 } if (m68kSlot3 != null) { - addInstr(result, IRInstruction(Opcode.LOADHR, codeGen.irType(returns.type), reg1=regNum, immediate=m68kSlot3.value), null) + addInstr(result, IRInstructions.hardwareLoad(codeGen.irType(returns.type), regNum, m68kSlot3), null) } else when(returns.register.registerOrPair) { - RegisterOrPair.A -> addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.BYTE, reg1=regNum, immediate=0), null) - RegisterOrPair.X -> addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.BYTE, reg1=regNum, immediate=1), null) - RegisterOrPair.Y -> addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.BYTE, reg1=regNum, immediate=2), null) - RegisterOrPair.AX -> addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=regNum, immediate=3), null) - RegisterOrPair.AY -> addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=regNum, immediate=4), null) - RegisterOrPair.XY -> addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=regNum, immediate=5), null) - in Cx16VirtualRegisters -> addInstr(result, IRInstruction(Opcode.LOADM, codeGen.irType(returns.type), reg1=regNum, labelSymbol = "cx16.${returns.register.registerOrPair.toString().lowercase()}"), null) + RegisterOrPair.A -> addInstr(result, IRInstructions.hardwareLoad(IRDataType.BYTE, regNum, CallingConventionSlot(0)), null) + RegisterOrPair.X -> addInstr(result, IRInstructions.hardwareLoad(IRDataType.BYTE, regNum, CallingConventionSlot(1)), null) + RegisterOrPair.Y -> addInstr(result, IRInstructions.hardwareLoad(IRDataType.BYTE, regNum, CallingConventionSlot(2)), null) + RegisterOrPair.AX -> addInstr(result, IRInstructions.hardwareLoad(IRDataType.WORD, regNum, CallingConventionSlot(3)), null) + RegisterOrPair.AY -> addInstr(result, IRInstructions.hardwareLoad(IRDataType.WORD, regNum, CallingConventionSlot(4)), null) + RegisterOrPair.XY -> addInstr(result, IRInstructions.hardwareLoad(IRDataType.WORD, regNum, CallingConventionSlot(5)), null) + in Cx16VirtualRegisters -> addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, codeGen.irType(returns.type), regNum, IRMemory.direct("cx16.${returns.register.registerOrPair.toString().lowercase()}")), null) in CombinedLongRegisters -> { require(returns.type.isLong) val startreg = returns.register.registerOrPair!!.startregname() - addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.LONG, reg1=regNum, labelSymbol = "cx16.${startreg}"), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, IRDataType.LONG, regNum, IRMemory.direct("cx16.${startreg}")), null) } - RegisterOrPair.FAC1 -> addInstr(result, IRInstruction(Opcode.LOADHFACZERO, IRDataType.FLOAT, fpReg1 = RegisterNum(regNum)), null) - RegisterOrPair.FAC2 -> addInstr(result, IRInstruction(Opcode.LOADHFACONE, IRDataType.FLOAT, fpReg1 = RegisterNum(regNum)), null) + RegisterOrPair.FAC1 -> addInstr(result, IRInstructions.unary(Opcode.LOADHFACZERO, IRDataType.FLOAT, regNum), null) + RegisterOrPair.FAC2 -> addInstr(result, IRInstructions.unary(Opcode.LOADHFACONE, IRDataType.FLOAT, regNum), null) null -> if(returns.register.statusflag!=null) result += assignCpuStatusFlagReturnvalue(returns.register.statusflag!!, regNum) else @@ -155,20 +155,20 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen when(statusflag) { Statusflag.Pc -> { result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=regNum, immediate = 0) - it += IRInstruction(Opcode.ROXL, IRDataType.BYTE, reg1=regNum) + it += IRInstructions.load(IRDataType.BYTE, regNum, 0) + it += IRInstructions.unary(Opcode.ROXL, IRDataType.BYTE, regNum) } } Statusflag.Pz -> { val setLabel = codeGen.createLabelName() val endLabel = codeGen.createLabelName() result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.BSTEQ, labelSymbol = setLabel) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=regNum, immediate = 0) - it += IRInstruction(Opcode.JUMP, labelSymbol = endLabel) + it += IRInstructions.branch(Opcode.BSTEQ, codeLabel(setLabel)) + it += IRInstructions.load(IRDataType.BYTE, regNum, 0) + it += IRInstructions.jump(codeLabel(endLabel)) } result += IRCodeChunk(setLabel, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=regNum, immediate = 1) + it += IRInstructions.load(IRDataType.BYTE, regNum, 1) } result += IRCodeChunk(endLabel, null) } @@ -176,21 +176,21 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val setLabel = codeGen.createLabelName() val endLabel = codeGen.createLabelName() result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.BSTNEG, labelSymbol = setLabel) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=regNum, immediate = 0) - it += IRInstruction(Opcode.JUMP, labelSymbol = endLabel) + it += IRInstructions.branch(Opcode.BSTNEG, codeLabel(setLabel)) + it += IRInstructions.load(IRDataType.BYTE, regNum, 0) + it += IRInstructions.jump(codeLabel(endLabel)) } result += IRCodeChunk(setLabel, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=regNum, immediate = 1) + it += IRInstructions.load(IRDataType.BYTE, regNum, 1) } result += IRCodeChunk(endLabel, null) } Statusflag.Pv -> { val skipLabel = codeGen.createLabelName() result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=regNum, immediate = 0) - it += IRInstruction(Opcode.BSTVC, labelSymbol = skipLabel) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=regNum, immediate = 1) + it += IRInstructions.load(IRDataType.BYTE, regNum, 0) + it += IRInstructions.branch(Opcode.BSTVC, codeLabel(skipLabel)) + it += IRInstructions.load(IRDataType.BYTE, regNum, 1) } result += IRCodeChunk(skipLabel, null) } @@ -228,7 +228,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen loadfield(inplaceInstrs, addressReg, fieldOffset, targetDt, oldvalueReg) val instr = if(augAssign.operator=="+=") Opcode.INC else Opcode.DEC repeat(value.asConstInteger()!!) { - addInstr(inplaceInstrs, IRInstruction(instr, targetDt, reg1 = oldvalueReg), null) + addInstr(inplaceInstrs, IRInstructions.unary(instr, targetDt, oldvalueReg), null) } } else { @@ -244,11 +244,11 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen loadfield(inplaceInstrs, addressReg, fieldOffset, targetDt, oldvalueReg) inplaceInstrs += operandTr.chunks when(augAssign.operator) { - "+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, fpReg1 = RegisterNum(oldvalueReg), fpReg2 = RegisterNum(operandTr.resultFpReg)), null) - "-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, fpReg1 = RegisterNum(oldvalueReg), fpReg2 = RegisterNum(operandTr.resultFpReg)), null) - "*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, fpReg1 = RegisterNum(oldvalueReg), fpReg2 = RegisterNum(operandTr.resultFpReg)), null) - "/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVSR, targetDt, fpReg1 = RegisterNum(oldvalueReg), fpReg2 = RegisterNum(operandTr.resultFpReg)), null) - "%=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MODR, targetDt, fpReg1 = RegisterNum(oldvalueReg), fpReg2 = RegisterNum(operandTr.resultFpReg)), null) + "+=" -> addInstr(inplaceInstrs, IRInstructions.binary(Opcode.ADDR, targetDt, oldvalueReg, operandTr.resultFpReg), null) + "-=" -> addInstr(inplaceInstrs, IRInstructions.binary(Opcode.SUBR, targetDt, oldvalueReg, operandTr.resultFpReg), null) + "*=" -> addInstr(inplaceInstrs, IRInstructions.binary(Opcode.MULR, targetDt, oldvalueReg, operandTr.resultFpReg), null) + "/=" -> addInstr(inplaceInstrs, IRInstructions.binary(Opcode.DIVSR, targetDt, oldvalueReg, operandTr.resultFpReg), null) + "%=" -> addInstr(inplaceInstrs, IRInstructions.binary(Opcode.MODR, targetDt, oldvalueReg, operandTr.resultFpReg), null) "+" -> { /* inplace + is a no-op */ } else -> throw AssemblyError("invalid augmented assign operator for floats ${augAssign.operator}") } @@ -258,58 +258,58 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen loadfield(inplaceInstrs, addressReg, fieldOffset, targetDt, oldvalueReg) inplaceInstrs += operandTr.chunks when(augAssign.operator) { - "+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null) - "-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null) - "*=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.MULR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null) - "/=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.DIVR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null) + "+=" -> addInstr(inplaceInstrs, IRInstructions.binary(Opcode.ADDR, targetDt, oldvalueReg, operandTr.resultReg), null) + "-=" -> addInstr(inplaceInstrs, IRInstructions.binary(Opcode.SUBR, targetDt, oldvalueReg, operandTr.resultReg), null) + "*=" -> addInstr(inplaceInstrs, IRInstructions.binary(Opcode.MULR, targetDt, oldvalueReg, operandTr.resultReg), null) + "/=" -> addInstr(inplaceInstrs, IRInstructions.binary(Opcode.DIVR, targetDt, oldvalueReg, operandTr.resultReg), null) "%=" -> { val opc = if(signed) Opcode.MODSR else Opcode.MODR - addInstr(inplaceInstrs, IRInstruction(opc, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null) + addInstr(inplaceInstrs, IRInstructions.binary(opc, targetDt, oldvalueReg, operandTr.resultReg), null) } - "|=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null) - "&=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null) - "^=", "xor=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XORR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null) + "|=" -> addInstr(inplaceInstrs, IRInstructions.binary(Opcode.ORR, targetDt, oldvalueReg, operandTr.resultReg), null) + "&=" -> addInstr(inplaceInstrs, IRInstructions.binary(Opcode.ANDR, targetDt, oldvalueReg, operandTr.resultReg), null) + "^=", "xor=" -> addInstr(inplaceInstrs, IRInstructions.binary(Opcode.XORR, targetDt, oldvalueReg, operandTr.resultReg), null) "<<=" -> { val constCount = value.asConstInteger() if(constCount != null) { - addInstr(inplaceInstrs, IRInstruction(Opcode.LSLI, targetDt, reg1 = oldvalueReg, immediate = constCount), null) + addInstr(inplaceInstrs, IRInstructions.binaryImmediate(Opcode.LSLI, targetDt, oldvalueReg, constCount), null) } else { - addInstr(inplaceInstrs, IRInstruction(Opcode.LSLN, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null) + addInstr(inplaceInstrs, IRInstructions.binary(Opcode.LSLN, targetDt, oldvalueReg, operandTr.resultReg), null) } } ">>=" -> { val constCount = value.asConstInteger() if(constCount != null) { val opc = if (signed) Opcode.ASRI else Opcode.LSRI - addInstr(inplaceInstrs, IRInstruction(opc, targetDt, reg1 = oldvalueReg, immediate = constCount), null) + addInstr(inplaceInstrs, IRInstructions.binaryImmediate(opc, targetDt, oldvalueReg, constCount), null) } else { val opc = if (signed) Opcode.ASRN else Opcode.LSRN - addInstr(inplaceInstrs, IRInstruction(opc, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null) + addInstr(inplaceInstrs, IRInstructions.binary(opc, targetDt, oldvalueReg, operandTr.resultReg), null) } } "or=" -> { val shortcutLabel = codeGen.createLabelName() if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps) - addInstr(inplaceInstrs, IRInstruction(Opcode.CMPI, targetDt, reg1 = oldvalueReg, immediate = 0), null) - addInstr(inplaceInstrs, IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel), null) + addInstr(inplaceInstrs, IRInstructions.compareImmediate(targetDt, oldvalueReg, 0), null) + addInstr(inplaceInstrs, IRInstructions.branch(Opcode.BSTNE, codeLabel(shortcutLabel)), null) val valueTr = exprGen.translateExpression(value) inplaceInstrs += valueTr.chunks - addInstr(inplaceInstrs, IRInstruction(Opcode.ORR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null) + addInstr(inplaceInstrs, IRInstructions.binary(Opcode.ORR, targetDt, oldvalueReg, valueTr.resultReg), null) inplaceInstrs += IRCodeChunk(shortcutLabel, null) } "and=" -> { val shortcutLabel = codeGen.createLabelName() if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps) - addInstr(inplaceInstrs, IRInstruction(Opcode.CMPI, targetDt, reg1 = oldvalueReg, immediate = 0), null) - addInstr(inplaceInstrs, IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel), null) + addInstr(inplaceInstrs, IRInstructions.compareImmediate(targetDt, oldvalueReg, 0), null) + addInstr(inplaceInstrs, IRInstructions.branch(Opcode.BSTEQ, codeLabel(shortcutLabel)), null) val valueTr = exprGen.translateExpression(value) inplaceInstrs += valueTr.chunks - addInstr(inplaceInstrs, IRInstruction(Opcode.ANDR, targetDt, reg1=oldvalueReg, reg2=valueTr.resultReg), null) + addInstr(inplaceInstrs, IRInstructions.binary(Opcode.ANDR, targetDt, oldvalueReg, valueTr.resultReg), null) inplaceInstrs += IRCodeChunk(shortcutLabel, null) } - "-" -> addInstr(inplaceInstrs, IRInstruction(Opcode.NEG, targetDt, reg1 = oldvalueReg), null) - "~" -> addInstr(inplaceInstrs, IRInstruction(Opcode.INV, targetDt, reg1 = oldvalueReg), null) - "not" -> addInstr(inplaceInstrs, IRInstruction(Opcode.XOR, targetDt, reg1 = oldvalueReg, immediate = 1), null) + "-" -> addInstr(inplaceInstrs, IRInstructions.unary(Opcode.NEG, targetDt, oldvalueReg), null) + "~" -> addInstr(inplaceInstrs, IRInstructions.unary(Opcode.INV, targetDt, oldvalueReg), null) + "not" -> addInstr(inplaceInstrs, IRInstructions.binaryImmediate(Opcode.XOR, targetDt, oldvalueReg, 1), null) "+" -> { /* inplace + is a no-op */ } else -> throw AssemblyError("invalid augmented assign operator ${augAssign.operator}") } @@ -353,13 +353,13 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen if (targetDt == IRDataType.FLOAT) { addInstr( inplaceInstrs, - IRInstruction(Opcode.LOADI, targetDt, fpReg1 = RegisterNum(oldvalueReg), reg1 = addressReg, immediate = fieldOffset.toInt()), + IRInstructions.loadMemory(Opcode.LOADI, targetDt, oldvalueReg, IRMemory.indirect(addressReg, fieldOffset.toInt())), null ) } else { addInstr( inplaceInstrs, - IRInstruction(Opcode.LOADI, targetDt, reg1 = oldvalueReg, reg2 = addressReg, immediate = fieldOffset.toInt()), + IRInstructions.loadMemory(Opcode.LOADI, targetDt, oldvalueReg, IRMemory.indirect(addressReg, fieldOffset.toInt())), null ) } @@ -404,28 +404,29 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen when(operator) { "-" -> { result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, vmDt, reg1=register, reg2=tr.resultReg, immediate = 0) - it += IRInstruction(Opcode.NEG, vmDt, reg1=register) - it += IRInstruction(Opcode.STOREI, vmDt, reg1=register, reg2=tr.resultReg, immediate = 0) + it += IRInstructions.loadMemory(Opcode.LOADI, vmDt, register, IRMemory.indirect(tr.resultReg, 0)) + it += IRInstructions.unary(Opcode.NEG, vmDt, register) + it += IRInstructions.storeMemory(Opcode.STOREI, vmDt, register, IRMemory.indirect(tr.resultReg, 0)) } } "~" -> { result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, vmDt, reg1=register, reg2=tr.resultReg, immediate = 0) - it += IRInstruction(Opcode.INV, vmDt, reg1=register) - it += IRInstruction(Opcode.STOREI, vmDt, reg1=register, reg2=tr.resultReg, immediate = 0) + it += IRInstructions.loadMemory(Opcode.LOADI, vmDt, register, IRMemory.indirect(tr.resultReg, 0)) + it += IRInstructions.unary(Opcode.INV, vmDt, register) + it += IRInstructions.storeMemory(Opcode.STOREI, vmDt, register, IRMemory.indirect(tr.resultReg, 0)) } } } } else { + val directMem = if(constAddress!=null) IRMemory.direct(constAddress) else IRMemory.direct(symbol!!) when (operator) { - "-" -> addInstr(result, IRInstruction(Opcode.NEGM, vmDt, address = constAddress?.toAddress(), labelSymbol = symbol), null) - "~" -> addInstr(result, IRInstruction(Opcode.INVM, vmDt, address = constAddress?.toAddress(), labelSymbol = symbol), null) + "-" -> addInstr(result, IRInstructions.memoryOp(Opcode.NEGM, vmDt, directMem), null) + "~" -> addInstr(result, IRInstructions.memoryOp(Opcode.INVM, vmDt, directMem), null) "not" -> { val regMask = codeGen.registers.next(vmDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, vmDt, reg1=regMask, immediate = 1) - it += IRInstruction(Opcode.XORM, vmDt, reg1=regMask, address = constAddress?.toAddress(), labelSymbol = symbol) + it += IRInstructions.load(vmDt, regMask, 1) + it += IRInstructions.memoryOp(Opcode.XORM, vmDt, directMem, regMask) } } else -> throw AssemblyError("weird prefix operator") @@ -456,47 +457,47 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val skipCarryLabel = codeGen.createLabelName() if(constIndex!=null) { val negLsbReg = codeGen.registers.next(IRDataType.BYTE) - addInstr(result, IRInstruction(Opcode.NEGM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex), null) - addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = negLsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex), null) + addInstr(result, IRInstructions.memoryOp(Opcode.NEGM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_lsb", constIndex)), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, negLsbReg, IRMemory.direct(arrayVariableName+"_lsb", constIndex)), null) if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps) - addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = negLsbReg, immediate = 0), null) - addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = skipCarryLabel), null) - addInstr(result, IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex), null) - addInstr(result, IRInstruction(Opcode.NEGM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex), skipCarryLabel) + addInstr(result, IRInstructions.compareImmediate(IRDataType.BYTE, negLsbReg, 0), null) + addInstr(result, IRInstructions.branch(Opcode.BSTEQ, codeLabel(skipCarryLabel)), null) + addInstr(result, IRInstructions.memoryOp(Opcode.INCM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_msb", constIndex)), null) + addInstr(result, IRInstructions.memoryOp(Opcode.NEGM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_msb", constIndex)), skipCarryLabel) } else { val indexReg = loadIndex() val registerLsb = codeGen.registers.next(IRDataType.BYTE) val registerMsb = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1 = registerLsb, reg2 = indexReg, labelSymbol = arrayVariableName+"_lsb") - it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1 = registerLsb) - it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1 = registerLsb, reg2 = indexReg, labelSymbol = arrayVariableName+"_lsb") - it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1 = registerMsb, reg2 = indexReg, labelSymbol = arrayVariableName+"_msb") - it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1 = registerMsb) - it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = registerLsb, immediate = 0) - it += IRInstruction(Opcode.BSTEQ, labelSymbol = skipCarryLabel) - it += IRInstruction(Opcode.DEC, IRDataType.BYTE, reg1 = registerMsb) + it += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, registerLsb, IRMemory.indexed(arrayVariableName+"_lsb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.unary(Opcode.NEG, IRDataType.BYTE, registerLsb) + it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, registerLsb, IRMemory.indexed(arrayVariableName+"_lsb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, registerMsb, IRMemory.indexed(arrayVariableName+"_msb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.unary(Opcode.NEG, IRDataType.BYTE, registerMsb) + it += IRInstructions.compareImmediate(IRDataType.BYTE, registerLsb, 0) + it += IRInstructions.branch(Opcode.BSTEQ, codeLabel(skipCarryLabel)) + it += IRInstructions.unary(Opcode.DEC, IRDataType.BYTE, registerMsb) } result += IRCodeChunk(skipCarryLabel, null).also { - it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1 = registerMsb, reg2 = indexReg, labelSymbol = arrayVariableName+"_msb") + it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, registerMsb, IRMemory.indexed(arrayVariableName+"_msb", indexReg, codeGen.options.compTarget.indexRegType)) } } } "~" -> { val arrayVariableName = array.variable!!.name if(constIndex!=null) { - addInstr(result, IRInstruction(Opcode.INVM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex), null) - addInstr(result, IRInstruction(Opcode.INVM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex), null) + addInstr(result, IRInstructions.memoryOp(Opcode.INVM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_lsb", constIndex)), null) + addInstr(result, IRInstructions.memoryOp(Opcode.INVM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_msb", constIndex)), null) } else { val indexReg = loadIndex() val register = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1 = register, reg2 = indexReg, labelSymbol = arrayVariableName+"_lsb") - it += IRInstruction(Opcode.INV, IRDataType.BYTE, reg1 = register) - it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1 = register, reg2 = indexReg, labelSymbol = arrayVariableName+"_lsb") - it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1 = register, reg2 = indexReg, labelSymbol = arrayVariableName+"_msb") - it += IRInstruction(Opcode.INV, IRDataType.BYTE, reg1 = register) - it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1 = register, reg2 = indexReg, labelSymbol = arrayVariableName+"_msb") + it += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, register, IRMemory.indexed(arrayVariableName+"_lsb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.unary(Opcode.INV, IRDataType.BYTE, register) + it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, register, IRMemory.indexed(arrayVariableName+"_lsb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, register, IRMemory.indexed(arrayVariableName+"_msb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.unary(Opcode.INV, IRDataType.BYTE, register) + it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, register, IRMemory.indexed(arrayVariableName+"_msb", indexReg, codeGen.options.compTarget.indexRegType)) } } } @@ -512,28 +513,28 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen "-" -> { val arrayVariableName = array.variable!!.name if(constIndex!=null) { - addInstr(result, IRInstruction(Opcode.NEGM, vmDt, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null) + addInstr(result, IRInstructions.memoryOp(Opcode.NEGM, vmDt, IRMemory.direct(arrayVariableName, constIndex*eltSize)), null) } else { val indexReg = loadIndex() val register = codeGen.registers.next(vmDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, vmDt, reg1 = register, reg2 = indexReg, labelSymbol = arrayVariableName, scale = eltSize) - it += IRInstruction(Opcode.NEG, vmDt, reg1 = register) - it += IRInstruction(Opcode.STOREX, vmDt, reg1 = register, reg2 = indexReg, labelSymbol = arrayVariableName, scale = eltSize) + it += IRInstructions.loadMemory(Opcode.LOADX, vmDt, register, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType, scale=eltSize)) + it += IRInstructions.unary(Opcode.NEG, vmDt, register) + it += IRInstructions.storeMemory(Opcode.STOREX, vmDt, register, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType, scale=eltSize)) } } } "~" -> { val arrayVariableName = array.variable!!.name if(constIndex!=null) { - addInstr(result, IRInstruction(Opcode.INVM, vmDt, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null) + addInstr(result, IRInstructions.memoryOp(Opcode.INVM, vmDt, IRMemory.direct(arrayVariableName, constIndex*eltSize)), null) } else { val indexReg = loadIndex() val register = codeGen.registers.next(vmDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, vmDt, reg1 = register, reg2 = indexReg, labelSymbol = arrayVariableName, scale = eltSize) - it += IRInstruction(Opcode.INV, vmDt, reg1 = register) - it += IRInstruction(Opcode.STOREX, vmDt, reg1 = register, reg2 = indexReg, labelSymbol = arrayVariableName, scale = eltSize) + it += IRInstructions.loadMemory(Opcode.LOADX, vmDt, register, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType, scale=eltSize)) + it += IRInstructions.unary(Opcode.INV, vmDt, register) + it += IRInstructions.storeMemory(Opcode.STOREX, vmDt, register, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType, scale=eltSize)) } } } @@ -542,15 +543,15 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val register = codeGen.registers.next(vmDt) if(constIndex!=null) { result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, vmDt, reg1=register, immediate = 1) - it += IRInstruction(Opcode.XORM, vmDt, reg1=register, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize) + it += IRInstructions.load(vmDt, register, 1) + it += IRInstructions.memoryOp(Opcode.XORM, vmDt, IRMemory.direct(arrayVariableName, constIndex*eltSize), register) } } else { val indexReg = loadIndex() result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, vmDt, reg1 = register, reg2 = indexReg, labelSymbol = arrayVariableName) - it += IRInstruction(Opcode.XOR, vmDt, reg1 = register, immediate = 1) - it += IRInstruction(Opcode.STOREX, vmDt, reg1 = register, reg2 = indexReg, labelSymbol = arrayVariableName) + it += IRInstructions.loadMemory(Opcode.LOADX, vmDt, register, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.binaryImmediate(Opcode.XOR, vmDt, register, 1) + it += IRInstructions.storeMemory(Opcode.STOREX, vmDt, register, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) } } } @@ -632,7 +633,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen if(extendSourceDt != null) { val opcode = extOpcode(extendSourceDt, extendDestDt!!, extendSigned) valueRegister = codeGen.registers.next(extendDestDt) - addInstr(result, IRInstruction(opcode, extendSourceDt, reg1=valueRegister, reg2=(assignment.value as PtIrRegister).register), null) + addInstr(result, IRInstructions.binary(opcode, extendSourceDt, valueRegister, (assignment.value as PtIrRegister).register), null) } } else { val tr = exprGen.translateExpression(assignment.value) @@ -641,7 +642,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen if(extendSourceDt != null) { val opcode = extOpcode(extendSourceDt, extendDestDt!!, extendSigned) valueRegister = codeGen.registers.next(extendDestDt) - addInstr(result, IRInstruction(opcode, extendSourceDt, reg1=valueRegister, reg2=tr.resultReg), null) + addInstr(result, IRInstructions.binary(opcode, extendSourceDt, valueRegister, tr.resultReg), null) } } } @@ -651,7 +652,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen when { identifier != null -> { val instruction = when { - zero -> IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = identifier!!.name) + zero -> IRInstructions.storeZero(Opcode.STOREZM, targetDt, IRMemory.direct(identifier!!.name)) constInt != null -> { val v = when(targetDt) { IRDataType.BYTE -> constInt and 0xff @@ -659,18 +660,18 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen IRDataType.LONG -> constInt else -> throw AssemblyError("invalid target dt $targetDt for const store") } - IRInstruction(Opcode.STOREIM, targetDt, immediate = v, labelSymbol = identifier!!.name) + IRInstructions.storeImmediate(targetDt, v, IRMemory.direct(identifier!!.name)) } constFloat != null -> { - IRInstruction(Opcode.STOREIM, targetDt, immediateFp = constFloat, labelSymbol = identifier!!.name) + IRInstructions.storeImmediateFloat(constFloat, IRMemory.direct(identifier!!.name)) } targetDt == IRDataType.FLOAT -> { require(valueFpRegister>=0) - IRInstruction(Opcode.STOREM, targetDt, fpReg1 = RegisterNum(valueFpRegister), labelSymbol = identifier!!.name) + IRInstructions.storeMemory(Opcode.STOREM, targetDt, valueFpRegister, IRMemory.direct(identifier!!.name)) } else -> { require(valueRegister>=0) - IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = identifier!!.name) + IRInstructions.storeMemory(Opcode.STOREM, targetDt, valueRegister, IRMemory.direct(identifier!!.name)) } } result += IRCodeChunk(null, null).also { it += instruction } @@ -681,18 +682,18 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen require(targetDt == IRDataType.BYTE) { "must be byte type ${memory!!.position}"} if(zero) { if(memory!!.address is PtNumber) { - val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, targetDt, address = (memory!!.address as PtNumber).number.toUInt().toAddress()) } + val chunk = IRCodeChunk(null, null).also { it += IRInstructions.storeZero(Opcode.STOREZM, targetDt, IRMemory.direct((memory!!.address as PtNumber).number.toUInt().toAddress())) } result += chunk } else { val (address, offset) = exprGen.getAddressAndOffset(memory!!.address) if(address!=null) { val tr = exprGen.translateExpression(address) addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.STOREZI, targetDt, reg1 = tr.resultReg, immediate = offset!!), null) + addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, targetDt, IRMemory.indirect(tr.resultReg, offset!!)), null) } else { val tr = exprGen.translateExpression(memory!!.address) addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.STOREZI, targetDt, reg1 = tr.resultReg, immediate = 0), null) + addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, targetDt, IRMemory.indirect(tr.resultReg, 0)), null) } } } else { @@ -705,10 +706,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen IRDataType.WORD, IRDataType.POINTER -> constInt and 0xffff IRDataType.LONG -> constInt } - IRInstruction(Opcode.STOREIM, targetDt, immediate = v, address=constAddress.number.toUInt().toAddress()) + IRInstructions.storeImmediate(targetDt, v, IRMemory.direct(constAddress.number.toUInt().toAddress())) } else -> null - } ?: IRInstruction(Opcode.STOREM, targetDt, reg1=valueRegister, address=constAddress.number.toUInt().toAddress()) + } ?: IRInstructions.storeMemory(Opcode.STOREM, targetDt, valueRegister, IRMemory.direct(constAddress.number.toUInt().toAddress())) addInstr(result, storeIns, null) return result } @@ -722,8 +723,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen IRDataType.POINTER else IRDataType.WORD val pointerReg = codeGen.registers.next(dt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADM, dt, reg1=pointerReg, labelSymbol = ptrIdentifier.name) - it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=valueRegister, reg2=pointerReg, immediate = constOffset) + it += IRInstructions.loadMemory(Opcode.LOADM, dt, pointerReg, IRMemory.direct(ptrIdentifier.name)) + it += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, valueRegister, IRMemory.indirect(pointerReg, constOffset!!)) } return result } @@ -733,7 +734,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val tr = exprGen.translateExpression(memory!!.address) val addressReg = tr.resultReg addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.STOREI, targetDt, reg1=valueRegister, reg2=addressReg, immediate = 0), null) + addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, targetDt, valueRegister, IRMemory.indirect(addressReg, 0)), null) return result } @@ -782,25 +783,25 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen if(zeroValue) { if(constIndex!=null) { val offset = eltSize * constIndex - addInstr(result, IRInstruction(Opcode.ADD, IRDataType.POINTER, reg1=pointerReg, immediate = offset), null) + addInstr(result, IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.POINTER, pointerReg, offset), null) } else { val (code, indexReg) = codeGen.loadIndexReg(targetArray.index, eltSize, true, targetArray.splitWords) result += code if(eltSize!=1) result += codeGen.multiplyByConst(DataType.UWORD, indexReg, eltSize) - addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.POINTER, reg1=pointerReg, reg2=indexReg), null) + addInstr(result, IRInstructions.binary(Opcode.ADDR, IRDataType.POINTER, pointerReg, indexReg), null) } codeGen.storeValueAtPointersLocation(result, pointerReg, 0u, targetIdent.type.dereference(), true, -1) } else { if(constIndex!=null) { val offset = eltSize * constIndex - addInstr(result, IRInstruction(Opcode.ADD, IRDataType.POINTER, reg1=pointerReg, immediate = offset), null) + addInstr(result, IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.POINTER, pointerReg, offset), null) } else { val (code, indexReg) = codeGen.loadIndexReg(targetArray.index, eltSize, true, targetArray.splitWords) result += code if(eltSize!=1) result += codeGen.multiplyByConst(DataType.UWORD, indexReg, eltSize) - addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.POINTER, reg1=pointerReg, reg2=indexReg), null) + addInstr(result, IRInstructions.binary(Opcode.ADDR, IRDataType.POINTER, pointerReg, indexReg), null) } val realValueReg = if(targetDt == IRDataType.FLOAT) valueFpRegister else valueRegister codeGen.storeValueAtPointersLocation(result, pointerReg, 0u, targetIdent.type.dereference(), false, realValueReg) @@ -823,11 +824,11 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen if(fixedIndex!=null) { val chunk = IRCodeChunk(null, null).also { if(targetArray.splitWords) { - it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, immediate = arrayLength, labelSymbol = "${variable}_lsb", symbolOffset = fixedIndex) - it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, immediate = arrayLength, labelSymbol = "${variable}_msb", symbolOffset = fixedIndex) + it += IRInstructions.storeZero(Opcode.STOREZM, IRDataType.BYTE, IRMemory.direct("${variable}_lsb", fixedIndex)) + it += IRInstructions.storeZero(Opcode.STOREZM, IRDataType.BYTE, IRMemory.direct("${variable}_msb", fixedIndex)) } else - it += IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = variable, symbolOffset = fixedIndex*eltSize) + it += IRInstructions.storeZero(Opcode.STOREZM, targetDt, IRMemory.direct(variable, fixedIndex*eltSize)) } result += chunk } else { @@ -836,11 +837,11 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val scaleZX = if(targetArray.splitWords) 1 else eltSize result += IRCodeChunk(null, null).also { if(targetArray.splitWords) { - it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1 = indexReg, labelSymbol = variable+"_lsb") - it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1 = indexReg, labelSymbol = variable+"_msb") + it += IRInstructions.storeZero(Opcode.STOREZX, IRDataType.BYTE, IRMemory.indexed(variable+"_lsb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.storeZero(Opcode.STOREZX, IRDataType.BYTE, IRMemory.indexed(variable+"_msb", indexReg, codeGen.options.compTarget.indexRegType)) } else - it += IRInstruction(Opcode.STOREZX, targetDt, reg1=indexReg, labelSymbol = variable, scale = scaleZX) + it += IRInstructions.storeZero(Opcode.STOREZX, targetDt, IRMemory.indexed(variable, indexReg, codeGen.options.compTarget.indexRegType, scale=scaleZX)) } } } else { @@ -848,14 +849,14 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen if(fixedIndex!=null) { val offset = fixedIndex*eltSize val chunk = IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.STOREM, targetDt, fpReg1 = RegisterNum(valueFpRegister), labelSymbol = variable, symbolOffset = offset) + it += IRInstructions.storeMemory(Opcode.STOREM, targetDt, valueFpRegister, IRMemory.direct(variable, offset)) } result += chunk } else { val (code, indexReg) = codeGen.loadIndexReg(targetArray.index, eltSize, codeGen.wordArrayIndex, targetArray.splitWords) result += code result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.STOREX, targetDt, reg1 = indexReg, fpReg1 = RegisterNum(valueFpRegister), labelSymbol = variable, scale = eltSize) + it += IRInstructions.storeMemory(Opcode.STOREX, targetDt, valueFpRegister, IRMemory.indexed(variable, indexReg, codeGen.options.compTarget.indexRegType, scale=eltSize)) } } } else { @@ -863,13 +864,13 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val chunk = IRCodeChunk(null, null).also { if(targetArray.splitWords) { val lsbmsbReg = codeGen.registers.next(IRDataType.BYTE) - it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = lsbmsbReg, reg2 = valueRegister) - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = lsbmsbReg, immediate = arrayLength, labelSymbol = "${variable}_lsb", symbolOffset = fixedIndex) - it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = lsbmsbReg, reg2 = valueRegister) - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = lsbmsbReg, immediate = arrayLength, labelSymbol = "${variable}_msb", symbolOffset = fixedIndex) + it += IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, lsbmsbReg, valueRegister) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, lsbmsbReg, IRMemory.direct("${variable}_lsb", fixedIndex)) + it += IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, lsbmsbReg, valueRegister) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, lsbmsbReg, IRMemory.direct("${variable}_msb", fixedIndex)) } else - it += IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = variable, symbolOffset = fixedIndex*eltSize) + it += IRInstructions.storeMemory(Opcode.STOREM, targetDt, valueRegister, IRMemory.direct(variable, fixedIndex*eltSize)) } result += chunk } else { @@ -878,13 +879,13 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen result += IRCodeChunk(null, null).also { if(targetArray.splitWords) { val lsbmsbReg = codeGen.registers.next(IRDataType.BYTE) - it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = lsbmsbReg, reg2 = valueRegister) - it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1 = lsbmsbReg, reg2=indexReg, immediate = arrayLength, labelSymbol = "${variable}_lsb") - it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = lsbmsbReg, reg2 = valueRegister) - it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1 = lsbmsbReg, reg2=indexReg, immediate = arrayLength, labelSymbol = "${variable}_msb") + it += IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, lsbmsbReg, valueRegister) + it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, lsbmsbReg, IRMemory.indexed("${variable}_lsb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, lsbmsbReg, valueRegister) + it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, lsbmsbReg, IRMemory.indexed("${variable}_msb", indexReg, codeGen.options.compTarget.indexRegType)) } else - it += IRInstruction(Opcode.STOREX, targetDt, reg1 = valueRegister, reg2=indexReg, labelSymbol = variable, scale = eltSize) + it += IRInstructions.storeMemory(Opcode.STOREX, targetDt, valueRegister, IRMemory.indexed(variable, indexReg, codeGen.options.compTarget.indexRegType, scale=eltSize)) } } } @@ -909,14 +910,14 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen if(fixedIndex!=null) { val offset = fixedIndex*eltSize if(zero) { - addInstr(result, IRInstruction(Opcode.STOREZI, targetDt, reg1 = pointerReg, immediate = offset), null) + addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, targetDt, IRMemory.indirect(pointerReg, offset)), null) } else { addInstr( result, if (targetDt == IRDataType.FLOAT) - IRInstruction(Opcode.STOREI, IRDataType.FLOAT, fpReg1 = RegisterNum(valueFpRegister), reg1 = pointerReg, immediate = offset) + IRInstructions.storeMemory(Opcode.STOREI, IRDataType.FLOAT, valueFpRegister, IRMemory.indirect(pointerReg, offset)) else - IRInstruction(Opcode.STOREI, targetDt, reg1 = valueRegister, reg2 = pointerReg, immediate = offset), null + IRInstructions.storeMemory(Opcode.STOREI, targetDt, valueRegister, IRMemory.indirect(pointerReg, offset)), null ) } } else { @@ -925,14 +926,14 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen result += code if(eltSize!=1) result += codeGen.multiplyByConst(DataType.UWORD, indexReg, eltSize) - addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.POINTER, reg1 = pointerReg, reg2 = indexReg), null) + addInstr(result, IRInstructions.binary(Opcode.ADDR, IRDataType.POINTER, pointerReg, indexReg), null) if(zero) { - addInstr(result, IRInstruction(Opcode.STOREZI, targetDt, reg1 = pointerReg, immediate = 0), null) + addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, targetDt, IRMemory.indirect(pointerReg, 0)), null) } else { addInstr(result, if(targetDt== IRDataType.FLOAT) - IRInstruction(Opcode.STOREI, IRDataType.FLOAT, fpReg1 = RegisterNum(valueFpRegister), reg1 = pointerReg, immediate = 0) + IRInstructions.storeMemory(Opcode.STOREI, IRDataType.FLOAT, valueFpRegister, IRMemory.indirect(pointerReg, 0)) else - IRInstruction(Opcode.STOREI, targetDt, reg1 = valueRegister, reg2 = pointerReg, immediate = 0) + IRInstructions.storeMemory(Opcode.STOREI, targetDt, valueRegister, IRMemory.indirect(pointerReg, 0)) , null) } } @@ -950,16 +951,16 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val valueRegLsb = codeGen.registers.next(IRDataType.BYTE) val valueRegMsb = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueRegLsb, immediate=constValue and 255) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueRegMsb, immediate=constValue shr 8) - it += IRInstruction(Opcode.ANDM, IRDataType.BYTE, reg1=valueRegLsb, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) - it += IRInstruction(Opcode.ANDM, IRDataType.BYTE, reg1=valueRegMsb, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + it += IRInstructions.load(IRDataType.BYTE, valueRegLsb, constValue and 255) + it += IRInstructions.load(IRDataType.BYTE, valueRegMsb, constValue shr 8) + it += IRInstructions.memoryOp(Opcode.ANDM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_lsb", constIndex), valueRegLsb) + it += IRInstructions.memoryOp(Opcode.ANDM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_msb", constIndex), valueRegMsb) } } else { val valueReg = codeGen.registers.next(vmDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, vmDt, reg1=valueReg, immediate=constValue) - it += IRInstruction(Opcode.ANDM, vmDt, reg1=valueReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize) + it += IRInstructions.load(vmDt, valueReg, constValue) + it += IRInstructions.memoryOp(Opcode.ANDM, vmDt, IRMemory.direct(arrayVariableName, constIndex*eltSize), valueReg) } } return result @@ -975,10 +976,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val lsbReg = codeGen.registers.next(IRDataType.BYTE) val msbReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=lsbReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1=msbReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.ANDM, IRDataType.BYTE, reg1=lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) - it += IRInstruction(Opcode.ANDM, IRDataType.BYTE, reg1=msbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + it += IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, lsbReg, valueTr.resultReg) + it += IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, msbReg, valueTr.resultReg) + it += IRInstructions.memoryOp(Opcode.ANDM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_lsb", constIndex), lsbReg) + it += IRInstructions.memoryOp(Opcode.ANDM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_msb", constIndex), msbReg) } return result } @@ -994,14 +995,14 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val valLsbReg = codeGen.registers.next(IRDataType.BYTE) val valMsbReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=valLsbReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1=valMsbReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=lsbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_lsb") - it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=msbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_msb") - it += IRInstruction(Opcode.ANDR, IRDataType.BYTE, reg1=lsbReg, reg2=valLsbReg) - it += IRInstruction(Opcode.ANDR, IRDataType.BYTE, reg1=msbReg, reg2=valMsbReg) - it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=lsbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_lsb") - it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=msbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_msb") + it += IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, valLsbReg, valueTr.resultReg) + it += IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, valMsbReg, valueTr.resultReg) + it += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, lsbReg, IRMemory.indexed(arrayVariableName+"_lsb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, msbReg, IRMemory.indexed(arrayVariableName+"_msb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.binary(Opcode.ANDR, IRDataType.BYTE, lsbReg, valLsbReg) + it += IRInstructions.binary(Opcode.ANDR, IRDataType.BYTE, msbReg, valMsbReg) + it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, lsbReg, IRMemory.indexed(arrayVariableName+"_lsb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, msbReg, IRMemory.indexed(arrayVariableName+"_msb", indexReg, codeGen.options.compTarget.indexRegType)) } return result } @@ -1011,7 +1012,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val arrayVariableName = array.variable!!.name val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.ANDM, vmDt, reg1=valueTr.resultReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null) + addInstr(result, IRInstructions.memoryOp(Opcode.ANDM, vmDt, IRMemory.direct(arrayVariableName, constIndex*eltSize), valueTr.resultReg), null) return result } // Non-constant index - use LOADX/ANDR/STOREX @@ -1027,19 +1028,19 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val loadReg = codeGen.registers.next(vmDt) val constReg = codeGen.registers.next(vmDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName) - it += IRInstruction(Opcode.LOAD, vmDt, reg1=constReg, immediate = constValue) - it += IRInstruction(Opcode.ANDR, vmDt, reg1=loadReg, reg2=constReg) - it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName) + it += IRInstructions.loadMemory(Opcode.LOADX, vmDt, loadReg, IRMemory.indexed(arrayVarName, indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.load(vmDt, constReg, constValue) + it += IRInstructions.binary(Opcode.ANDR, vmDt, loadReg, constReg) + it += IRInstructions.storeMemory(Opcode.STOREX, vmDt, loadReg, IRMemory.indexed(arrayVarName, indexReg, codeGen.options.compTarget.indexRegType)) } } else { val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) val loadReg = codeGen.registers.next(vmDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName) - it += IRInstruction(Opcode.ANDR, vmDt, reg1=loadReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName) + it += IRInstructions.loadMemory(Opcode.LOADX, vmDt, loadReg, IRMemory.indexed(arrayVarName, indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.binary(Opcode.ANDR, vmDt, loadReg, valueTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREX, vmDt, loadReg, IRMemory.indexed(arrayVarName, indexReg, codeGen.options.compTarget.indexRegType)) } } return result @@ -1057,18 +1058,18 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen addToResult(result, valueTr, valueTr.resultReg, -1) val loadReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) - it += IRInstruction(Opcode.ANDR, IRDataType.BYTE, reg1=loadReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) + it += IRInstructions.binary(Opcode.ANDR, IRDataType.BYTE, loadReg, valueTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) } } else { val loadReg = codeGen.registers.next(IRDataType.BYTE) val valueReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueReg, immediate = operandConstValue.toInt()) - it += IRInstruction(Opcode.ANDR, IRDataType.BYTE, reg1=loadReg, reg2=valueReg) - it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) + it += IRInstructions.load(IRDataType.BYTE, valueReg, operandConstValue.toInt()) + it += IRInstructions.binary(Opcode.ANDR, IRDataType.BYTE, loadReg, valueReg) + it += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) } } return result @@ -1078,9 +1079,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val tr = exprGen.translateExpression(operand) addToResult(result, tr, tr.resultReg, -1) addInstr(result, if(constAddress!=null) - IRInstruction(Opcode.ANDM, vmDt, reg1=tr.resultReg, address = constAddress.toAddress()) + IRInstructions.memoryOp(Opcode.ANDM, vmDt, IRMemory.direct(constAddress.toAddress()), tr.resultReg) else - IRInstruction(Opcode.ANDM, vmDt, reg1=tr.resultReg, labelSymbol = symbol),null) + IRInstructions.memoryOp(Opcode.ANDM, vmDt, IRMemory.direct(symbol!!), tr.resultReg),null) return result } @@ -1097,8 +1098,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } else { val valueReg = codeGen.registers.next(vmDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, vmDt, reg1=valueReg, immediate=constValue) - it += IRInstruction(Opcode.ANDM, vmDt, reg1=valueReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize) + it += IRInstructions.load(vmDt, valueReg, constValue) + it += IRInstructions.memoryOp(Opcode.ANDM, vmDt, IRMemory.direct(arrayVariableName, constIndex*eltSize), valueReg) } } return result @@ -1108,7 +1109,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen // Constant index, non-constant value val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.ANDM, vmDt, reg1=valueTr.resultReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null) + addInstr(result, IRInstructions.memoryOp(Opcode.ANDM, vmDt, IRMemory.direct(arrayVariableName, constIndex*eltSize), valueTr.resultReg), null) return result } // Non-constant index @@ -1119,19 +1120,19 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val loadReg = codeGen.registers.next(vmDt) val constReg = codeGen.registers.next(vmDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) - it += IRInstruction(Opcode.LOAD, vmDt, reg1=constReg, immediate = constValue) - it += IRInstruction(Opcode.ANDR, vmDt, reg1=loadReg, reg2=constReg) - it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + it += IRInstructions.loadMemory(Opcode.LOADX, vmDt, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.load(vmDt, constReg, constValue) + it += IRInstructions.binary(Opcode.ANDR, vmDt, loadReg, constReg) + it += IRInstructions.storeMemory(Opcode.STOREX, vmDt, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) } } else { val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) val loadReg = codeGen.registers.next(vmDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) - it += IRInstruction(Opcode.ANDR, vmDt, reg1=loadReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + it += IRInstructions.loadMemory(Opcode.LOADX, vmDt, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.binary(Opcode.ANDR, vmDt, loadReg, valueTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREX, vmDt, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) } } return result @@ -1149,18 +1150,18 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen addToResult(memResult, valueTr, valueTr.resultReg, -1) val loadReg = codeGen.registers.next(IRDataType.BYTE) memResult += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) - it += IRInstruction(Opcode.ANDR) - it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) + it += IRInstructions.simple(Opcode.ANDR) + it += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) } } else { val loadReg = codeGen.registers.next(IRDataType.BYTE) val valueReg = codeGen.registers.next(IRDataType.BYTE) memResult += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueReg, immediate = operandConstValue.toInt()) - it += IRInstruction(Opcode.ANDR, IRDataType.BYTE, reg1=loadReg, reg2=valueReg) - it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) + it += IRInstructions.load(IRDataType.BYTE, valueReg, operandConstValue.toInt()) + it += IRInstructions.binary(Opcode.ANDR, IRDataType.BYTE, loadReg, valueReg) + it += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) } } return memResult @@ -1174,26 +1175,26 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val shortcutLabel = codeGen.createLabelName() result += IRCodeChunk(null, null).also { it += if(constAddress!=null) - IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, address = constAddress.toAddress()) + IRInstructions.loadMemory(Opcode.LOADM, vmDt, inplaceReg, IRMemory.direct(constAddress.toAddress())) else - IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, labelSymbol = symbol) + IRInstructions.loadMemory(Opcode.LOADM, vmDt, inplaceReg, IRMemory.direct(symbol!!)) if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps) - it += IRInstruction(Opcode.CMPI, vmDt, reg1=inplaceReg, immediate = 0) - it += IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel) + it += IRInstructions.compareImmediate(vmDt, inplaceReg, 0) + it += IRInstructions.branch(Opcode.BSTEQ, codeLabel(shortcutLabel)) } addToResult(result, tr, tr.resultReg, -1) addInstr(result, if(constAddress!=null) - IRInstruction(Opcode.STOREM, vmDt, reg1=tr.resultReg, address = constAddress.toAddress()) + IRInstructions.storeMemory(Opcode.STOREM, vmDt, tr.resultReg, IRMemory.direct(constAddress.toAddress())) else - IRInstruction(Opcode.STOREM, vmDt, reg1=tr.resultReg, labelSymbol = symbol), null) + IRInstructions.storeMemory(Opcode.STOREM, vmDt, tr.resultReg, IRMemory.direct(symbol!!)), null) result += IRCodeChunk(shortcutLabel, null) } else { // normal evaluation, it is *likely* shorter and faster because of the simple operands. addToResult(result, tr, tr.resultReg, -1) addInstr(result, if(constAddress!=null) - IRInstruction(Opcode.ANDM, vmDt, reg1=tr.resultReg, address = constAddress.toAddress()) + IRInstructions.memoryOp(Opcode.ANDM, vmDt, IRMemory.direct(constAddress.toAddress()), tr.resultReg) else - IRInstruction(Opcode.ANDM, vmDt, reg1=tr.resultReg, labelSymbol = symbol),null) + IRInstructions.memoryOp(Opcode.ANDM, vmDt, IRMemory.direct(symbol!!), tr.resultReg),null) } return result } @@ -1210,16 +1211,16 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val valueRegLsb = codeGen.registers.next(IRDataType.BYTE) val valueRegMsb = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueRegLsb, immediate=constValue and 255) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueRegMsb, immediate=constValue shr 8) - it += IRInstruction(Opcode.ORM, IRDataType.BYTE, reg1=valueRegLsb, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) - it += IRInstruction(Opcode.ORM, IRDataType.BYTE, reg1=valueRegMsb, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + it += IRInstructions.load(IRDataType.BYTE, valueRegLsb, constValue and 255) + it += IRInstructions.load(IRDataType.BYTE, valueRegMsb, constValue shr 8) + it += IRInstructions.memoryOp(Opcode.ORM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_lsb", constIndex), valueRegLsb) + it += IRInstructions.memoryOp(Opcode.ORM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_msb", constIndex), valueRegMsb) } } else { val valueReg = codeGen.registers.next(vmDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, vmDt, reg1=valueReg, immediate=constValue) - it += IRInstruction(Opcode.ORM, vmDt, reg1=valueReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize) + it += IRInstructions.load(vmDt, valueReg, constValue) + it += IRInstructions.memoryOp(Opcode.ORM, vmDt, IRMemory.direct(arrayVariableName, constIndex*eltSize), valueReg) } } return result @@ -1234,10 +1235,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val lsbReg = codeGen.registers.next(IRDataType.BYTE) val msbReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=lsbReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1=msbReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.ORM, IRDataType.BYTE, reg1=lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) - it += IRInstruction(Opcode.ORM, IRDataType.BYTE, reg1=msbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + it += IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, lsbReg, valueTr.resultReg) + it += IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, msbReg, valueTr.resultReg) + it += IRInstructions.memoryOp(Opcode.ORM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_lsb", constIndex), lsbReg) + it += IRInstructions.memoryOp(Opcode.ORM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_msb", constIndex), msbReg) } return result } @@ -1252,14 +1253,14 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val valLsbReg = codeGen.registers.next(IRDataType.BYTE) val valMsbReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=valLsbReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1=valMsbReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=lsbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_lsb") - it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=msbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_msb") - it += IRInstruction(Opcode.ORR, IRDataType.BYTE, reg1=lsbReg, reg2=valLsbReg) - it += IRInstruction(Opcode.ORR, IRDataType.BYTE, reg1=msbReg, reg2=valMsbReg) - it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=lsbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_lsb") - it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=msbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_msb") + it += IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, valLsbReg, valueTr.resultReg) + it += IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, valMsbReg, valueTr.resultReg) + it += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, lsbReg, IRMemory.indexed(arrayVariableName+"_lsb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, msbReg, IRMemory.indexed(arrayVariableName+"_msb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.binary(Opcode.ORR, IRDataType.BYTE, lsbReg, valLsbReg) + it += IRInstructions.binary(Opcode.ORR, IRDataType.BYTE, msbReg, valMsbReg) + it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, lsbReg, IRMemory.indexed(arrayVariableName+"_lsb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, msbReg, IRMemory.indexed(arrayVariableName+"_msb", indexReg, codeGen.options.compTarget.indexRegType)) } return result } @@ -1268,7 +1269,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val arrayVarName = array.variable!!.name val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.ORM, vmDt, reg1=valueTr.resultReg, labelSymbol = arrayVarName, symbolOffset = constIndex*eltSize), null) + addInstr(result, IRInstructions.memoryOp(Opcode.ORM, vmDt, IRMemory.direct(arrayVarName, constIndex*eltSize), valueTr.resultReg), null) return result } // Non-constant index - use LOADX/ORR/STOREX @@ -1284,19 +1285,19 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val loadReg = codeGen.registers.next(vmDt) val constReg = codeGen.registers.next(vmDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) - it += IRInstruction(Opcode.LOAD, vmDt, reg1=constReg, immediate = constValue) - it += IRInstruction(Opcode.ORR, vmDt, reg1=loadReg, reg2=constReg) - it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) + it += IRInstructions.loadMemory(Opcode.LOADX, vmDt, loadReg, IRMemory.indexed(arrayVarName2, indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.load(vmDt, constReg, constValue) + it += IRInstructions.binary(Opcode.ORR, vmDt, loadReg, constReg) + it += IRInstructions.storeMemory(Opcode.STOREX, vmDt, loadReg, IRMemory.indexed(arrayVarName2, indexReg, codeGen.options.compTarget.indexRegType)) } } else { val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) val loadReg = codeGen.registers.next(vmDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) - it += IRInstruction(Opcode.ORR, vmDt, reg1=loadReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) + it += IRInstructions.loadMemory(Opcode.LOADX, vmDt, loadReg, IRMemory.indexed(arrayVarName2, indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.binary(Opcode.ORR, vmDt, loadReg, valueTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREX, vmDt, loadReg, IRMemory.indexed(arrayVarName2, indexReg, codeGen.options.compTarget.indexRegType)) } } return result @@ -1314,18 +1315,18 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen addToResult(memResult, valueTr, valueTr.resultReg, -1) val loadReg = codeGen.registers.next(IRDataType.BYTE) memResult += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) - it += IRInstruction(Opcode.ORR, IRDataType.BYTE, reg1=loadReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) + it += IRInstructions.binary(Opcode.ORR, IRDataType.BYTE, loadReg, valueTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) } } else { val loadReg = codeGen.registers.next(IRDataType.BYTE) val valueReg = codeGen.registers.next(IRDataType.BYTE) memResult += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueReg, immediate = operandConstValue.toInt()) - it += IRInstruction(Opcode.ORR, IRDataType.BYTE, reg1=loadReg, reg2=valueReg) - it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) + it += IRInstructions.load(IRDataType.BYTE, valueReg, operandConstValue.toInt()) + it += IRInstructions.binary(Opcode.ORR, IRDataType.BYTE, loadReg, valueReg) + it += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) } } return memResult @@ -1335,9 +1336,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val tr = exprGen.translateExpression(operand) addToResult(result, tr, tr.resultReg, -1) addInstr(result, if(constAddress!=null) - IRInstruction(Opcode.ORM, vmDt, reg1=tr.resultReg, address = constAddress.toAddress()) + IRInstructions.memoryOp(Opcode.ORM, vmDt, IRMemory.direct(constAddress.toAddress()), tr.resultReg) else - IRInstruction(Opcode.ORM, vmDt, reg1=tr.resultReg, labelSymbol = symbol), null) + IRInstructions.memoryOp(Opcode.ORM, vmDt, IRMemory.direct(symbol!!), tr.resultReg), null) return result } @@ -1354,8 +1355,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val arrayVariableName = array.variable!!.name val valueReg = codeGen.registers.next(vmDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, vmDt, reg1=valueReg, immediate=constValue) - it += IRInstruction(Opcode.ORM, vmDt, reg1=valueReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize) + it += IRInstructions.load(vmDt, valueReg, constValue) + it += IRInstructions.memoryOp(Opcode.ORM, vmDt, IRMemory.direct(arrayVariableName, constIndex*eltSize), valueReg) } } return result @@ -1369,7 +1370,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val arrayVarName = array.variable!!.name val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.ORM, vmDt, reg1=valueTr.resultReg, labelSymbol = arrayVarName, symbolOffset = constIndex*eltSize), null) + addInstr(result, IRInstructions.memoryOp(Opcode.ORM, vmDt, IRMemory.direct(arrayVarName, constIndex*eltSize), valueTr.resultReg), null) return result } // Non-constant index @@ -1381,19 +1382,19 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val loadReg = codeGen.registers.next(vmDt) val constReg = codeGen.registers.next(vmDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) - it += IRInstruction(Opcode.LOAD, vmDt, reg1=constReg, immediate = constValue) - it += IRInstruction(Opcode.ORR, vmDt, reg1=loadReg, reg2=constReg) - it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) + it += IRInstructions.loadMemory(Opcode.LOADX, vmDt, loadReg, IRMemory.indexed(arrayVarName2, indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.load(vmDt, constReg, constValue) + it += IRInstructions.binary(Opcode.ORR, vmDt, loadReg, constReg) + it += IRInstructions.storeMemory(Opcode.STOREX, vmDt, loadReg, IRMemory.indexed(arrayVarName2, indexReg, codeGen.options.compTarget.indexRegType)) } } else { val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) val loadReg = codeGen.registers.next(vmDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) - it += IRInstruction(Opcode.ORR, vmDt, reg1=loadReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) + it += IRInstructions.loadMemory(Opcode.LOADX, vmDt, loadReg, IRMemory.indexed(arrayVarName2, indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.binary(Opcode.ORR, vmDt, loadReg, valueTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREX, vmDt, loadReg, IRMemory.indexed(arrayVarName2, indexReg, codeGen.options.compTarget.indexRegType)) } } return result @@ -1411,18 +1412,18 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen addToResult(memResult, valueTr, valueTr.resultReg, -1) val loadReg = codeGen.registers.next(IRDataType.BYTE) memResult += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) - it += IRInstruction(Opcode.ORR, IRDataType.BYTE, reg1=loadReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) + it += IRInstructions.binary(Opcode.ORR, IRDataType.BYTE, loadReg, valueTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) } } else { val loadReg = codeGen.registers.next(IRDataType.BYTE) val valueReg = codeGen.registers.next(IRDataType.BYTE) memResult += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueReg, immediate = operandConstValue.toInt()) - it += IRInstruction(Opcode.ORR, IRDataType.BYTE, reg1=loadReg, reg2=valueReg) - it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) + it += IRInstructions.load(IRDataType.BYTE, valueReg, operandConstValue.toInt()) + it += IRInstructions.binary(Opcode.ORR, IRDataType.BYTE, loadReg, valueReg) + it += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) } } return memResult @@ -1436,26 +1437,26 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val shortcutLabel = codeGen.createLabelName() result += IRCodeChunk(null, null).also { it += if(constAddress!=null) - IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, address = constAddress.toAddress()) + IRInstructions.loadMemory(Opcode.LOADM, vmDt, inplaceReg, IRMemory.direct(constAddress.toAddress())) else - IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, labelSymbol = symbol) + IRInstructions.loadMemory(Opcode.LOADM, vmDt, inplaceReg, IRMemory.direct(symbol!!)) if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps) - it += IRInstruction(Opcode.CMPI, vmDt, reg1=inplaceReg, immediate = 0) - it += IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel) + it += IRInstructions.compareImmediate(vmDt, inplaceReg, 0) + it += IRInstructions.branch(Opcode.BSTNE, codeLabel(shortcutLabel)) } addToResult(result, tr, tr.resultReg, -1) addInstr(result, if(constAddress!=null) - IRInstruction(Opcode.STOREM, vmDt, reg1=tr.resultReg, address = constAddress.toAddress()) + IRInstructions.storeMemory(Opcode.STOREM, vmDt, tr.resultReg, IRMemory.direct(constAddress.toAddress())) else - IRInstruction(Opcode.STOREM, vmDt, reg1=tr.resultReg, labelSymbol = symbol), null) + IRInstructions.storeMemory(Opcode.STOREM, vmDt, tr.resultReg, IRMemory.direct(symbol!!)), null) result += IRCodeChunk(shortcutLabel, null) } else { // normal evaluation, it is *likely* shorter and faster because of the simple operands. addToResult(result, tr, tr.resultReg, -1) addInstr(result, if(constAddress!=null) - IRInstruction(Opcode.ORM, vmDt, reg1=tr.resultReg, address = constAddress.toAddress()) + IRInstructions.memoryOp(Opcode.ORM, vmDt, IRMemory.direct(constAddress.toAddress()), tr.resultReg) else - IRInstruction(Opcode.ORM, vmDt, reg1=tr.resultReg, labelSymbol = symbol), null) + IRInstructions.memoryOp(Opcode.ORM, vmDt, IRMemory.direct(symbol!!), tr.resultReg), null) } return result } @@ -1482,11 +1483,11 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen result += IRCodeChunk(null, null).also { val valueReg = if(constFloat!=null) { val reg = codeGen.registers.next(IRDataType.FLOAT) - it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1=RegisterNum(reg), immediateFp=constFloat) + it += IRInstructions.loadFloat(reg, constFloat) reg } else valueTr!!.resultFpReg - it += IRInstruction(divOpcode, IRDataType.FLOAT, fpReg1=RegisterNum(valueReg), labelSymbol=arrayVariableName, symbolOffset=constIndex*eltSize) + it += IRInstructions.memoryOp(divOpcode, IRDataType.FLOAT, IRMemory.direct(arrayVariableName, constIndex*eltSize), valueReg) } return result } @@ -1498,17 +1499,17 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen result += codeGen.multiplyByConst(indexDt, indexReg, eltSize) val loadReg = codeGen.registers.next(IRDataType.FLOAT) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, IRDataType.FLOAT, fpReg1=RegisterNum(loadReg), reg2=indexReg, labelSymbol=arrayVariableName) + it += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.FLOAT, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) } val valueTr = if(constFloat==null) exprGen.translateExpression(operand) else null if(valueTr!=null) addToResult(result, valueTr, -1, valueTr.resultFpReg) result += IRCodeChunk(null, null).also { if(constFloat!=null) - it += IRInstruction(divROpcode, IRDataType.FLOAT, fpReg1=RegisterNum(loadReg), immediateFp=constFloat) + it += IRInstructions.binaryImmediateFloat(divROpcode, loadReg, constFloat) else - it += IRInstruction(divROpcode, IRDataType.FLOAT, fpReg1=RegisterNum(loadReg), fpReg2=RegisterNum(valueTr!!.resultFpReg)) - it += IRInstruction(Opcode.STOREX, IRDataType.FLOAT, fpReg1=RegisterNum(loadReg), reg2=indexReg, labelSymbol=arrayVariableName) + it += IRInstructions.binary(divROpcode, IRDataType.FLOAT, loadReg, valueTr!!.resultFpReg) + it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.FLOAT, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) } return result } @@ -1516,8 +1517,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen if(constValue!=1) { val valueReg = codeGen.registers.next(eltDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, eltDt, reg1=valueReg, immediate = constValue) - it += IRInstruction(divOpcode, eltDt, reg1=valueReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize) + it += IRInstructions.load(eltDt, valueReg, constValue) + it += IRInstructions.memoryOp(divOpcode, eltDt, IRMemory.direct(arrayVariableName, constIndex*eltSize), valueReg) } } return result @@ -1526,7 +1527,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen // Constant index, non-constant value val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) - addInstr(result, IRInstruction(divOpcode, eltDt, reg1=valueTr.resultReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null) + addInstr(result, IRInstructions.memoryOp(divOpcode, eltDt, IRMemory.direct(arrayVariableName, constIndex*eltSize), valueTr.resultReg), null) return result } // Non-constant index - use LOADX/DIVR/STOREX @@ -1538,23 +1539,23 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } val loadReg = codeGen.registers.next(eltDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, eltDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + it += IRInstructions.loadMemory(Opcode.LOADX, eltDt, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) } if(constValue!=null) { val constReg = codeGen.registers.next(eltDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, eltDt, reg1=constReg, immediate = constValue) - it += IRInstruction(divROpcode, eltDt, reg1=loadReg, reg2=constReg) + it += IRInstructions.load(eltDt, constReg, constValue) + it += IRInstructions.binary(divROpcode, eltDt, loadReg, constReg) } } else { val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) result += IRCodeChunk(null, null).also { - it += IRInstruction(divROpcode, eltDt, reg1=loadReg, reg2=valueTr.resultReg) + it += IRInstructions.binary(divROpcode, eltDt, loadReg, valueTr.resultReg) } } result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.STOREX, eltDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + it += IRInstructions.storeMemory(Opcode.STOREX, eltDt, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) } return result } @@ -1568,31 +1569,31 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen addToResult(resultVar, tr, -1, tr.resultFpReg) val loadReg = codeGen.registers.next(IRDataType.FLOAT) resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, vmDt, fpReg1=RegisterNum(loadReg), reg1=addressReg, immediate=0) - it += IRInstruction(Opcode.DIVR, vmDt, fpReg1=RegisterNum(loadReg), fpReg2=RegisterNum(tr.resultFpReg)) - it += IRInstruction(Opcode.STOREI, vmDt, fpReg1=RegisterNum(loadReg), reg1=addressReg, immediate=0) + it += IRInstructions.loadMemory(Opcode.LOADI, vmDt, loadReg, IRMemory.indirect(addressReg, 0)) + it += IRInstructions.binary(Opcode.DIVR, vmDt, loadReg, tr.resultFpReg) + it += IRInstructions.storeMemory(Opcode.STOREI, vmDt, loadReg, IRMemory.indirect(addressReg, 0)) } } else { val loadReg = codeGen.registers.next(vmDt) resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, vmDt, reg1=loadReg, reg2=addressReg, immediate=0) + it += IRInstructions.loadMemory(Opcode.LOADI, vmDt, loadReg, IRMemory.indirect(addressReg, 0)) } val constVal = (operand as? PtNumber)?.number?.toInt() if(constVal!=null) { val constReg = codeGen.registers.next(vmDt) resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, vmDt, reg1=constReg, immediate=constVal) + it += IRInstructions.load(vmDt, constReg, constVal) val opc = if(signed) Opcode.DIVSR else Opcode.DIVR - it += IRInstruction(opc, vmDt, reg1=loadReg, reg2=constReg) - it += IRInstruction(Opcode.STOREI, vmDt, reg1=loadReg, reg2=addressReg, immediate=0) + it += IRInstructions.binary(opc, vmDt, loadReg, constReg) + it += IRInstructions.storeMemory(Opcode.STOREI, vmDt, loadReg, IRMemory.indirect(addressReg, 0)) } } else { val opTr = exprGen.translateExpression(operand) addToResult(resultVar, opTr, opTr.resultReg, -1) resultVar += IRCodeChunk(null, null).also { val opc = if(signed) Opcode.DIVSR else Opcode.DIVR - it += IRInstruction(opc, vmDt, reg1=loadReg, reg2=opTr.resultReg) - it += IRInstruction(Opcode.STOREI, vmDt, reg1=loadReg, reg2=addressReg, immediate=0) + it += IRInstructions.binary(opc, vmDt, loadReg, opTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREI, vmDt, loadReg, IRMemory.indirect(addressReg, 0)) } } } @@ -1610,15 +1611,15 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen addToResult(result, tr, -1, tr.resultFpReg) val ins = if(signed) { if(constAddress!=null) - IRInstruction(Opcode.DIVSM, vmDt, fpReg1 = RegisterNum(tr.resultFpReg), address = constAddress.toAddress()) + IRInstructions.memoryOp(Opcode.DIVSM, vmDt, IRMemory.direct(constAddress.toAddress()), tr.resultFpReg) else - IRInstruction(Opcode.DIVSM, vmDt, fpReg1 = RegisterNum(tr.resultFpReg), labelSymbol = symbol) + IRInstructions.memoryOp(Opcode.DIVSM, vmDt, IRMemory.direct(symbol!!), tr.resultFpReg) } else { if(constAddress!=null) - IRInstruction(Opcode.DIVM, vmDt, fpReg1 = RegisterNum(tr.resultFpReg), address = constAddress.toAddress()) + IRInstructions.memoryOp(Opcode.DIVM, vmDt, IRMemory.direct(constAddress.toAddress()), tr.resultFpReg) else - IRInstruction(Opcode.DIVM, vmDt, fpReg1 = RegisterNum(tr.resultFpReg), labelSymbol = symbol) + IRInstructions.memoryOp(Opcode.DIVM, vmDt, IRMemory.direct(symbol!!), tr.resultFpReg) } addInstr(result, ins, null) } @@ -1631,15 +1632,15 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen addToResult(result, tr, tr.resultReg, -1) val ins = if(signed) { if(constAddress!=null) - IRInstruction(Opcode.DIVSM, vmDt, reg1 = tr.resultReg, address = constAddress.toAddress()) + IRInstructions.memoryOp(Opcode.DIVSM, vmDt, IRMemory.direct(constAddress.toAddress()), tr.resultReg) else - IRInstruction(Opcode.DIVSM, vmDt, reg1 = tr.resultReg, labelSymbol = symbol) + IRInstructions.memoryOp(Opcode.DIVSM, vmDt, IRMemory.direct(symbol!!), tr.resultReg) } else { if(constAddress!=null) - IRInstruction(Opcode.DIVM, vmDt, reg1 = tr.resultReg, address = constAddress.toAddress()) + IRInstructions.memoryOp(Opcode.DIVM, vmDt, IRMemory.direct(constAddress.toAddress()), tr.resultReg) else - IRInstruction(Opcode.DIVM, vmDt, reg1 = tr.resultReg, labelSymbol = symbol) + IRInstructions.memoryOp(Opcode.DIVM, vmDt, IRMemory.direct(symbol!!), tr.resultReg) } addInstr(result, ins, null) } @@ -1661,9 +1662,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val arrayVariableName = array.variable!!.name val valueReg=codeGen.registers.next(eltDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, eltDt, reg1=valueReg, immediate = constValue) + it += IRInstructions.load(eltDt, valueReg, constValue) val opcode = if(signed) Opcode.MULSM else Opcode.MULM - it += IRInstruction(opcode, eltDt, reg1=valueReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize) + it += IRInstructions.memoryOp(opcode, eltDt, IRMemory.direct(arrayVariableName, constIndex*eltSize), valueReg) } } return result @@ -1675,7 +1676,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) val opcode = if(signed) Opcode.MULSM else Opcode.MULM - addInstr(result, IRInstruction(opcode, eltDt, reg1=valueTr.resultReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null) + addInstr(result, IRInstructions.memoryOp(opcode, eltDt, IRMemory.direct(arrayVariableName, constIndex*eltSize), valueTr.resultReg), null) return result } // Non-constant index - use LOADX/MULR/STOREX @@ -1692,10 +1693,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val constReg = codeGen.registers.next(eltDt) val opcode = if(signed) Opcode.MULSR else Opcode.MULR result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, eltDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) - it += IRInstruction(Opcode.LOAD, eltDt, reg1=constReg, immediate = constValue) - it += IRInstruction(opcode, eltDt, reg1=loadReg, reg2=constReg) - it += IRInstruction(Opcode.STOREX, eltDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + it += IRInstructions.loadMemory(Opcode.LOADX, eltDt, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.load(eltDt, constReg, constValue) + it += IRInstructions.binary(opcode, eltDt, loadReg, constReg) + it += IRInstructions.storeMemory(Opcode.STOREX, eltDt, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) } } else { val valueTr = exprGen.translateExpression(operand) @@ -1703,9 +1704,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val loadReg = codeGen.registers.next(eltDt) val opcode = if(signed) Opcode.MULSR else Opcode.MULR result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, eltDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) - it += IRInstruction(opcode, eltDt, reg1=loadReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.STOREX, eltDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + it += IRInstructions.loadMemory(Opcode.LOADX, eltDt, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.binary(opcode, eltDt, loadReg, valueTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREX, eltDt, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) } } return result @@ -1720,31 +1721,31 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen addToResult(resultVar, tr, -1, tr.resultFpReg) val loadReg = codeGen.registers.next(IRDataType.FLOAT) resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, vmDt, fpReg1=RegisterNum(loadReg), reg1=addressReg, immediate=0) - it += IRInstruction(Opcode.MULR, vmDt, fpReg1=RegisterNum(loadReg), fpReg2=RegisterNum(tr.resultFpReg)) - it += IRInstruction(Opcode.STOREI, vmDt, fpReg1=RegisterNum(loadReg), reg1=addressReg, immediate=0) + it += IRInstructions.loadMemory(Opcode.LOADI, vmDt, loadReg, IRMemory.indirect(addressReg, 0)) + it += IRInstructions.binary(Opcode.MULR, vmDt, loadReg, tr.resultFpReg) + it += IRInstructions.storeMemory(Opcode.STOREI, vmDt, loadReg, IRMemory.indirect(addressReg, 0)) } } else { val loadReg = codeGen.registers.next(vmDt) resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, vmDt, reg1=loadReg, reg2=addressReg, immediate=0) + it += IRInstructions.loadMemory(Opcode.LOADI, vmDt, loadReg, IRMemory.indirect(addressReg, 0)) } val constVal = (operand as? PtNumber)?.number?.toInt() if(constVal!=null) { val constReg = codeGen.registers.next(vmDt) resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, vmDt, reg1=constReg, immediate=constVal) + it += IRInstructions.load(vmDt, constReg, constVal) val opc = if(signed) Opcode.MULSR else Opcode.MULR - it += IRInstruction(opc, vmDt, reg1=loadReg, reg2=constReg) - it += IRInstruction(Opcode.STOREI, vmDt, reg1=loadReg, reg2=addressReg, immediate=0) + it += IRInstructions.binary(opc, vmDt, loadReg, constReg) + it += IRInstructions.storeMemory(Opcode.STOREI, vmDt, loadReg, IRMemory.indirect(addressReg, 0)) } } else { val opTr = exprGen.translateExpression(operand) addToResult(resultVar, opTr, opTr.resultReg, -1) resultVar += IRCodeChunk(null, null).also { val opc = if(signed) Opcode.MULSR else Opcode.MULR - it += IRInstruction(opc, vmDt, reg1=loadReg, reg2=opTr.resultReg) - it += IRInstruction(Opcode.STOREI, vmDt, reg1=loadReg, reg2=addressReg, immediate=0) + it += IRInstructions.binary(opc, vmDt, loadReg, opTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREI, vmDt, loadReg, IRMemory.indirect(addressReg, 0)) } } } @@ -1761,9 +1762,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val tr = exprGen.translateExpression(operand) addToResult(result, tr, -1, tr.resultFpReg) addInstr(result, if(constAddress!=null) - IRInstruction(Opcode.MULSM, vmDt, fpReg1 = RegisterNum(tr.resultFpReg), address = constAddress.toAddress()) + IRInstructions.memoryOp(Opcode.MULSM, vmDt, IRMemory.direct(constAddress.toAddress()), tr.resultFpReg) else - IRInstruction(Opcode.MULSM, vmDt, fpReg1 = RegisterNum(tr.resultFpReg), labelSymbol = symbol) + IRInstructions.memoryOp(Opcode.MULSM, vmDt, IRMemory.direct(symbol!!), tr.resultFpReg) , null) } } else { @@ -1775,9 +1776,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen addToResult(result, tr, tr.resultReg, -1) val opcode = if(signed) Opcode.MULSM else Opcode.MULM addInstr(result, if(constAddress!=null) - IRInstruction(opcode, vmDt, reg1=tr.resultReg, address = constAddress.toAddress()) + IRInstructions.memoryOp(opcode, vmDt, IRMemory.direct(constAddress.toAddress()), tr.resultReg) else - IRInstruction(opcode, vmDt, reg1=tr.resultReg, labelSymbol = symbol) + IRInstructions.memoryOp(opcode, vmDt, IRMemory.direct(symbol!!), tr.resultReg) , null) } } @@ -1797,9 +1798,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val arrayVariableName = array.variable!!.name if(constValue==1) { - addInstr(result, IRInstruction(Opcode.DECM, eltDt, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null) + addInstr(result, IRInstructions.memoryOp(Opcode.DECM, eltDt, IRMemory.direct(arrayVariableName, constIndex*eltSize)), null) } else { - addInstr(result, IRInstruction(Opcode.SUBIM, eltDt, immediate = constValue, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null) + addInstr(result, IRInstructions.memoryOpImmediate(Opcode.SUBIM, eltDt, IRMemory.direct(arrayVariableName, constIndex*eltSize), constValue), null) } return result } @@ -1809,7 +1810,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val arrayVariableName = array.variable!!.name val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.SUBM, eltDt, reg1=valueTr.resultReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null) + addInstr(result, IRInstructions.memoryOp(Opcode.SUBM, eltDt, IRMemory.direct(arrayVariableName, constIndex*eltSize), valueTr.resultReg), null) return result } // Non-constant index - use LOADX/SUBR/STOREX @@ -1825,11 +1826,11 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen // Non-constant index, constant value val loadReg = codeGen.registers.next(eltDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, eltDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + it += IRInstructions.loadMemory(Opcode.LOADX, eltDt, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) val constReg = codeGen.registers.next(eltDt) - it += IRInstruction(Opcode.LOAD, eltDt, reg1=constReg, immediate = constValue) - it += IRInstruction(Opcode.SUBR, eltDt, reg1=loadReg, reg2=constReg) - it += IRInstruction(Opcode.STOREX, eltDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + it += IRInstructions.load(eltDt, constReg, constValue) + it += IRInstructions.binary(Opcode.SUBR, eltDt, loadReg, constReg) + it += IRInstructions.storeMemory(Opcode.STOREX, eltDt, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) } } else { // Non-constant index, non-constant value @@ -1837,9 +1838,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen addToResult(result, valueTr, valueTr.resultReg, -1) val loadReg = codeGen.registers.next(eltDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, eltDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) - it += IRInstruction(Opcode.SUBR, eltDt, reg1=loadReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.STOREX, eltDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + it += IRInstructions.loadMemory(Opcode.LOADX, eltDt, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.binary(Opcode.SUBR, eltDt, loadReg, valueTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREX, eltDt, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) } } return result @@ -1858,20 +1859,20 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen addToResult(memResult, valueTr, valueTr.resultReg, -1) val loadReg = codeGen.registers.next(IRDataType.BYTE) memResult += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=loadReg, address = addr.toAddress()) - it += IRInstruction(Opcode.SUBR, IRDataType.BYTE, reg1=loadReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=loadReg, address = addr.toAddress()) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, loadReg, IRMemory.direct(addr.toAddress())) + it += IRInstructions.binary(Opcode.SUBR, IRDataType.BYTE, loadReg, valueTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, loadReg, IRMemory.direct(addr.toAddress())) } } else if(operandConstValue==1.0) { - addInstr(memResult, IRInstruction(Opcode.DECM, IRDataType.BYTE, address = addr.toAddress()), null) + addInstr(memResult, IRInstructions.memoryOp(Opcode.DECM, IRDataType.BYTE, IRMemory.direct(addr.toAddress())), null) } else { val loadReg = codeGen.registers.next(IRDataType.BYTE) val valueReg = codeGen.registers.next(IRDataType.BYTE) memResult += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=loadReg, address = addr.toAddress()) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueReg, immediate = operandConstValue.toInt()) - it += IRInstruction(Opcode.SUBR, IRDataType.BYTE, reg1=loadReg, reg2=valueReg) - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=loadReg, address = addr.toAddress()) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, loadReg, IRMemory.direct(addr.toAddress())) + it += IRInstructions.load(IRDataType.BYTE, valueReg, operandConstValue.toInt()) + it += IRInstructions.binary(Opcode.SUBR, IRDataType.BYTE, loadReg, valueReg) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, loadReg, IRMemory.direct(addr.toAddress())) } } return memResult @@ -1887,27 +1888,27 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen addToResult(memResult, valueTr, valueTr.resultReg, -1) val loadReg = codeGen.registers.next(IRDataType.BYTE) memResult += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) - it += IRInstruction(Opcode.SUBR, IRDataType.BYTE, reg1=loadReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) + it += IRInstructions.binary(Opcode.SUBR, IRDataType.BYTE, loadReg, valueTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) } } else { // Constant value, non-const address if(operandConstValue==1.0) { val loadReg = codeGen.registers.next(IRDataType.BYTE) memResult += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) - it += IRInstruction(Opcode.DEC, IRDataType.BYTE, reg1=loadReg) - it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) + it += IRInstructions.unary(Opcode.DEC, IRDataType.BYTE, loadReg) + it += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) } } else { val loadReg = codeGen.registers.next(IRDataType.BYTE) val valueReg = codeGen.registers.next(IRDataType.BYTE) memResult += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueReg, immediate = operandConstValue.toInt()) - it += IRInstruction(Opcode.SUBR, IRDataType.BYTE, reg1=loadReg, reg2=valueReg) - it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) + it += IRInstructions.load(IRDataType.BYTE, valueReg, operandConstValue.toInt()) + it += IRInstructions.binary(Opcode.SUBR, IRDataType.BYTE, loadReg, valueReg) + it += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) } } } @@ -1919,40 +1920,40 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen if(vmDt==IRDataType.FLOAT) { if(constValue==1.0) { addInstr(result, if(constAddress!=null) - IRInstruction(Opcode.DECM, vmDt, address = constAddress.toAddress()) + IRInstructions.memoryOp(Opcode.DECM, vmDt, IRMemory.direct(constAddress.toAddress())) else - IRInstruction(Opcode.DECM, vmDt, labelSymbol = symbol), null) + IRInstructions.memoryOp(Opcode.DECM, vmDt, IRMemory.direct(symbol!!)), null) } else if(constValue!=null) { addInstr(result, if(constAddress!=null) - IRInstruction(Opcode.SUBIM, vmDt, immediateFp = constValue, address = constAddress.toAddress()) + IRInstructions.memoryOpImmediateFloat(Opcode.SUBIM, IRMemory.direct(constAddress.toAddress()), constValue) else - IRInstruction(Opcode.SUBIM, vmDt, immediateFp = constValue, labelSymbol = symbol), null) + IRInstructions.memoryOpImmediateFloat(Opcode.SUBIM, IRMemory.direct(symbol!!), constValue), null) } else { val tr = exprGen.translateExpression(operand) addToResult(result, tr, -1, tr.resultFpReg) addInstr(result, if(constAddress!=null) - IRInstruction(Opcode.SUBM, vmDt, fpReg1=RegisterNum(tr.resultFpReg), address = constAddress.toAddress()) + IRInstructions.memoryOp(Opcode.SUBM, vmDt, IRMemory.direct(constAddress.toAddress()), tr.resultFpReg) else - IRInstruction(Opcode.SUBM, vmDt, fpReg1=RegisterNum(tr.resultFpReg), labelSymbol = symbol), null) + IRInstructions.memoryOp(Opcode.SUBM, vmDt, IRMemory.direct(symbol!!), tr.resultFpReg), null) } } else { if(constValue==1.0) { addInstr(result, if(constAddress!=null) - IRInstruction(Opcode.DECM, vmDt, address = constAddress.toAddress()) + IRInstructions.memoryOp(Opcode.DECM, vmDt, IRMemory.direct(constAddress.toAddress())) else - IRInstruction(Opcode.DECM, vmDt, labelSymbol = symbol), null) + IRInstructions.memoryOp(Opcode.DECM, vmDt, IRMemory.direct(symbol!!)), null) } else if(constValue!=null) { addInstr(result, if(constAddress!=null) - IRInstruction(Opcode.SUBIM, vmDt, immediate = constValue.toInt(), address = constAddress.toAddress()) + IRInstructions.memoryOpImmediate(Opcode.SUBIM, vmDt, IRMemory.direct(constAddress.toAddress()), constValue.toInt()) else - IRInstruction(Opcode.SUBIM, vmDt, immediate = constValue.toInt(), labelSymbol = symbol), null) + IRInstructions.memoryOpImmediate(Opcode.SUBIM, vmDt, IRMemory.direct(symbol!!), constValue.toInt()), null) } else { val tr = exprGen.translateExpression(operand) addToResult(result, tr, tr.resultReg, -1) addInstr(result, if(constAddress!=null) - IRInstruction(Opcode.SUBM, vmDt, reg1=tr.resultReg, address = constAddress.toAddress()) + IRInstructions.memoryOp(Opcode.SUBM, vmDt, IRMemory.direct(constAddress.toAddress()), tr.resultReg) else - IRInstruction(Opcode.SUBM, vmDt, reg1=tr.resultReg, labelSymbol = symbol), null) + IRInstructions.memoryOp(Opcode.SUBM, vmDt, IRMemory.direct(symbol!!), tr.resultReg), null) } } return result @@ -1976,20 +1977,20 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen result += IRCodeChunk(null, null).also { // Load current LSB and MSB - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = msbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, lsbReg, IRMemory.direct(arrayVariableName+"_lsb", constIndex)) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, msbReg, IRMemory.direct(arrayVariableName+"_msb", constIndex)) // Concatenate into word register - it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = wordReg, reg2 = msbReg, reg3 = lsbReg) + it += IRInstructions.concat(IRDataType.BYTE, wordReg, msbReg, lsbReg) // Multiply by constant value - it += IRInstruction(Opcode.MUL, IRDataType.WORD, reg1 = wordReg, immediate = constValue) + it += IRInstructions.binaryImmediate(Opcode.MUL, IRDataType.WORD, wordReg, constValue) // Extract bytes back into NEW registers val newLsbReg = codeGen.registers.next(IRDataType.BYTE) val newMsbReg = codeGen.registers.next(IRDataType.BYTE) - it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = newLsbReg, reg2 = wordReg) - it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = newMsbReg, reg2 = wordReg) + it += IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, newLsbReg, wordReg) + it += IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, newMsbReg, wordReg) // Store back - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newLsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newMsbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, newLsbReg, IRMemory.direct(arrayVariableName+"_lsb", constIndex)) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, newMsbReg, IRMemory.direct(arrayVariableName+"_msb", constIndex)) } return result } else { @@ -2005,20 +2006,20 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen result += IRCodeChunk(null, null).also { // Load current LSB and MSB from array - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = msbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, lsbReg, IRMemory.direct(arrayVariableName+"_lsb", constIndex)) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, msbReg, IRMemory.direct(arrayVariableName+"_msb", constIndex)) // Concatenate into word register - it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = wordReg, reg2 = msbReg, reg3 = lsbReg) + it += IRInstructions.concat(IRDataType.BYTE, wordReg, msbReg, lsbReg) // Multiply by operand value (use MULR for register-to-register) - it += IRInstruction(Opcode.MULR, IRDataType.WORD, reg1 = wordReg, reg2 = tr.resultReg) + it += IRInstructions.binary(Opcode.MULR, IRDataType.WORD, wordReg, tr.resultReg) // Extract bytes back into NEW registers val newLsbReg = codeGen.registers.next(IRDataType.BYTE) val newMsbReg = codeGen.registers.next(IRDataType.BYTE) - it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = newLsbReg, reg2 = wordReg) - it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = newMsbReg, reg2 = wordReg) + it += IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, newLsbReg, wordReg) + it += IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, newMsbReg, wordReg) // Store back - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newLsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newMsbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, newLsbReg, IRMemory.direct(arrayVariableName+"_lsb", constIndex)) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, newMsbReg, IRMemory.direct(arrayVariableName+"_msb", constIndex)) } return result } @@ -2037,14 +2038,14 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val skip = codeGen.createLabelName() val lsbReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, lsbReg, IRMemory.direct(arrayVariableName+"_lsb", constIndex)) if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps) - it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = lsbReg, immediate = 0) - it += IRInstruction(Opcode.BSTNE, labelSymbol = skip) - it += IRInstruction(Opcode.DECM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + it += IRInstructions.compareImmediate(IRDataType.BYTE, lsbReg, 0) + it += IRInstructions.branch(Opcode.BSTNE, codeLabel(skip)) + it += IRInstructions.memoryOp(Opcode.DECM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_msb", constIndex)) } result += IRCodeChunk(skip, null).also { - it += IRInstruction(Opcode.DECM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) + it += IRInstructions.memoryOp(Opcode.DECM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_lsb", constIndex)) } return result } else if(constValue!=null) { @@ -2055,20 +2056,20 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen result += IRCodeChunk(null, null).also { // Load current LSB and MSB - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = msbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, lsbReg, IRMemory.direct(arrayVariableName+"_lsb", constIndex)) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, msbReg, IRMemory.direct(arrayVariableName+"_msb", constIndex)) // Concatenate into word register - it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = wordReg, reg2 = msbReg, reg3 = lsbReg) + it += IRInstructions.concat(IRDataType.BYTE, wordReg, msbReg, lsbReg) // Subtract the constant value - it += IRInstruction(Opcode.SUB, IRDataType.WORD, reg1 = wordReg, immediate = constValue) + it += IRInstructions.binaryImmediate(Opcode.SUB, IRDataType.WORD, wordReg, constValue) // Extract bytes back into NEW registers val newLsbReg = codeGen.registers.next(IRDataType.BYTE) val newMsbReg = codeGen.registers.next(IRDataType.BYTE) - it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = newLsbReg, reg2 = wordReg) - it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = newMsbReg, reg2 = wordReg) + it += IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, newLsbReg, wordReg) + it += IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, newMsbReg, wordReg) // Store back - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newLsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newMsbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, newLsbReg, IRMemory.direct(arrayVariableName+"_lsb", constIndex)) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, newMsbReg, IRMemory.direct(arrayVariableName+"_msb", constIndex)) } return result } else { @@ -2084,20 +2085,20 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen result += IRCodeChunk(null, null).also { // Load current LSB and MSB from array - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = msbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, lsbReg, IRMemory.direct(arrayVariableName+"_lsb", constIndex)) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, msbReg, IRMemory.direct(arrayVariableName+"_msb", constIndex)) // Concatenate into word register - it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = wordReg, reg2 = msbReg, reg3 = lsbReg) + it += IRInstructions.concat(IRDataType.BYTE, wordReg, msbReg, lsbReg) // Subtract the operand value (use SUBR for register-to-register) - it += IRInstruction(Opcode.SUBR, IRDataType.WORD, reg1 = wordReg, reg2 = tr.resultReg) + it += IRInstructions.binary(Opcode.SUBR, IRDataType.WORD, wordReg, tr.resultReg) // Extract bytes back into NEW registers val newLsbReg = codeGen.registers.next(IRDataType.BYTE) val newMsbReg = codeGen.registers.next(IRDataType.BYTE) - it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = newLsbReg, reg2 = wordReg) - it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = newMsbReg, reg2 = wordReg) + it += IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, newLsbReg, wordReg) + it += IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, newMsbReg, wordReg) // Store back - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newLsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newMsbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, newLsbReg, IRMemory.direct(arrayVariableName+"_lsb", constIndex)) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, newMsbReg, IRMemory.direct(arrayVariableName+"_msb", constIndex)) } return result } @@ -2119,9 +2120,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val arrayVariableName = array.variable!!.name if(constValue==1) { - addInstr(result, IRInstruction(Opcode.INCM, elementDt, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null) + addInstr(result, IRInstructions.memoryOp(Opcode.INCM, elementDt, IRMemory.direct(arrayVariableName, constIndex*eltSize)), null) } else { - addInstr(result, IRInstruction(Opcode.ADDIM, elementDt, immediate = constValue, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null) + addInstr(result, IRInstructions.memoryOpImmediate(Opcode.ADDIM, elementDt, IRMemory.direct(arrayVariableName, constIndex*eltSize), constValue), null) } return result } @@ -2131,7 +2132,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val arrayVariableName = array.variable!!.name val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.ADDM, elementDt, reg1=valueTr.resultReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null) + addInstr(result, IRInstructions.memoryOp(Opcode.ADDM, elementDt, IRMemory.direct(arrayVariableName, constIndex*eltSize), valueTr.resultReg), null) return result } // Non-constant index - use LOADX/ADDR/STOREX @@ -2147,11 +2148,11 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen // Non-constant index, constant value val loadReg = codeGen.registers.next(elementDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, elementDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + it += IRInstructions.loadMemory(Opcode.LOADX, elementDt, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) val constReg = codeGen.registers.next(elementDt) - it += IRInstruction(Opcode.LOAD, elementDt, reg1=constReg, immediate = constValue) - it += IRInstruction(Opcode.ADDR, elementDt, reg1=loadReg, reg2=constReg) - it += IRInstruction(Opcode.STOREX, elementDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + it += IRInstructions.load(elementDt, constReg, constValue) + it += IRInstructions.binary(Opcode.ADDR, elementDt, loadReg, constReg) + it += IRInstructions.storeMemory(Opcode.STOREX, elementDt, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) } } else { // Non-constant index, non-constant value @@ -2159,9 +2160,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen addToResult(result, valueTr, valueTr.resultReg, -1) val loadReg = codeGen.registers.next(elementDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, elementDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) - it += IRInstruction(Opcode.ADDR, elementDt, reg1=loadReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.STOREX, elementDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + it += IRInstructions.loadMemory(Opcode.LOADX, elementDt, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.binary(Opcode.ADDR, elementDt, loadReg, valueTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREX, elementDt, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) } } return result @@ -2181,20 +2182,20 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen addToResult(memResult, valueTr, valueTr.resultReg, -1) val loadReg = codeGen.registers.next(IRDataType.BYTE) memResult += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=loadReg, address = addr.toAddress()) - it += IRInstruction(Opcode.ADDR, IRDataType.BYTE, reg1=loadReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=loadReg, address = addr.toAddress()) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, loadReg, IRMemory.direct(addr.toAddress())) + it += IRInstructions.binary(Opcode.ADDR, IRDataType.BYTE, loadReg, valueTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, loadReg, IRMemory.direct(addr.toAddress())) } } else if(operandConstValue==1.0) { - addInstr(memResult, IRInstruction(Opcode.INCM, IRDataType.BYTE, address = addr.toAddress()), null) + addInstr(memResult, IRInstructions.memoryOp(Opcode.INCM, IRDataType.BYTE, IRMemory.direct(addr.toAddress())), null) } else { val loadReg = codeGen.registers.next(IRDataType.BYTE) val valueReg = codeGen.registers.next(IRDataType.BYTE) memResult += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=loadReg, address = addr.toAddress()) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueReg, immediate = operandConstValue.toInt()) - it += IRInstruction(Opcode.ADDR, IRDataType.BYTE, reg1=loadReg, reg2=valueReg) - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=loadReg, address = addr.toAddress()) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, loadReg, IRMemory.direct(addr.toAddress())) + it += IRInstructions.load(IRDataType.BYTE, valueReg, operandConstValue.toInt()) + it += IRInstructions.binary(Opcode.ADDR, IRDataType.BYTE, loadReg, valueReg) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, loadReg, IRMemory.direct(addr.toAddress())) } } return memResult @@ -2211,27 +2212,27 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen // LOADI current value, ADD, STOREI back val loadReg = codeGen.registers.next(IRDataType.BYTE) memResult += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) - it += IRInstruction(Opcode.ADDR, IRDataType.BYTE, reg1=loadReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) + it += IRInstructions.binary(Opcode.ADDR, IRDataType.BYTE, loadReg, valueTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) } } else { // Constant value, non-const address if(operandConstValue==1.0) { val loadReg = codeGen.registers.next(IRDataType.BYTE) memResult += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) - it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=loadReg) - it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) + it += IRInstructions.unary(Opcode.INC, IRDataType.BYTE, loadReg) + it += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) } } else { val loadReg = codeGen.registers.next(IRDataType.BYTE) val valueReg = codeGen.registers.next(IRDataType.BYTE) memResult += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueReg, immediate = operandConstValue.toInt()) - it += IRInstruction(Opcode.ADDR, IRDataType.BYTE, reg1=loadReg, reg2=valueReg) - it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) + it += IRInstructions.load(IRDataType.BYTE, valueReg, operandConstValue.toInt()) + it += IRInstructions.binary(Opcode.ADDR, IRDataType.BYTE, loadReg, valueReg) + it += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) } } } @@ -2244,50 +2245,51 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen if(vmDt==IRDataType.FLOAT) { if(constValue==1.0) { addInstr(result, if (constAddress != null) - IRInstruction(Opcode.INCM, vmDt, address = constAddress.toAddress()) + IRInstructions.memoryOp(Opcode.INCM, vmDt, IRMemory.direct(constAddress.toAddress())) else - IRInstruction(Opcode.INCM, vmDt, labelSymbol = symbol) , null) + IRInstructions.memoryOp(Opcode.INCM, vmDt, IRMemory.direct(symbol!!)) , null) } else if(constValue!=null) { addInstr(result, if (constAddress != null) - IRInstruction(Opcode.ADDIM, vmDt, immediateFp = constValue, address = constAddress.toAddress()) + IRInstructions.memoryOpImmediateFloat(Opcode.ADDIM, IRMemory.direct(constAddress.toAddress()), constValue) else - IRInstruction(Opcode.ADDIM, vmDt, immediateFp = constValue, labelSymbol = symbol) , null) + IRInstructions.memoryOpImmediateFloat(Opcode.ADDIM, IRMemory.direct(symbol!!), constValue) , null) } else { val tr = exprGen.translateExpression(operand) addToResult(result, tr, -1, tr.resultFpReg) addInstr(result, if (constAddress != null) - IRInstruction(Opcode.ADDM, vmDt, fpReg1 = RegisterNum(tr.resultFpReg), address = constAddress.toAddress()) + IRInstructions.memoryOp(Opcode.ADDM, vmDt, IRMemory.direct(constAddress.toAddress()), tr.resultFpReg) else - IRInstruction(Opcode.ADDM, vmDt, fpReg1 = RegisterNum(tr.resultFpReg), labelSymbol = symbol) , null) + IRInstructions.memoryOp(Opcode.ADDM, vmDt, IRMemory.direct(symbol!!), tr.resultFpReg) , null) } } else { if(constValue==1.0) { addInstr(result, if (constAddress != null) - IRInstruction(Opcode.INCM, vmDt, address = constAddress.toAddress()) + IRInstructions.memoryOp(Opcode.INCM, vmDt, IRMemory.direct(constAddress.toAddress())) else - IRInstruction(Opcode.INCM, vmDt, labelSymbol = symbol) , null) + IRInstructions.memoryOp(Opcode.INCM, vmDt, IRMemory.direct(symbol!!)) , null) } else if(constValue==2.0 && codeGen.options.compTarget.cpu.is6502) { result += IRCodeChunk(null, null).also { if (constAddress != null) { - it += IRInstruction(Opcode.INCM, vmDt, address = constAddress.toAddress()) - it += IRInstruction(Opcode.INCM, vmDt, address = constAddress.toAddress()) + it += IRInstructions.memoryOp(Opcode.INCM, vmDt, IRMemory.direct(constAddress.toAddress())) + it += IRInstructions.memoryOp(Opcode.INCM, vmDt, IRMemory.direct(constAddress.toAddress())) } else { - it += IRInstruction(Opcode.INCM, vmDt, labelSymbol = symbol) - it += IRInstruction(Opcode.INCM, vmDt, labelSymbol = symbol) + val symbolName = symbol!! + it += IRInstructions.memoryOp(Opcode.INCM, vmDt, IRMemory.direct(symbolName)) + it += IRInstructions.memoryOp(Opcode.INCM, vmDt, IRMemory.direct(symbolName)) } } } else if(constValue!=null) { addInstr(result, if (constAddress != null) - IRInstruction(Opcode.ADDIM, vmDt, immediate = constValue.toInt(), address = constAddress.toAddress()) + IRInstructions.memoryOpImmediate(Opcode.ADDIM, vmDt, IRMemory.direct(constAddress.toAddress()), constValue.toInt()) else - IRInstruction(Opcode.ADDIM, vmDt, immediate = constValue.toInt(), labelSymbol = symbol), null) + IRInstructions.memoryOpImmediate(Opcode.ADDIM, vmDt, IRMemory.direct(symbol!!), constValue.toInt()), null) } else { val tr = exprGen.translateExpression(operand) addToResult(result, tr, tr.resultReg, -1) if (constAddress != null) - addInstr(result, IRInstruction(Opcode.ADDM, vmDt, reg1 = tr.resultReg, address = constAddress.toAddress()), null) + addInstr(result, IRInstructions.memoryOp(Opcode.ADDM, vmDt, IRMemory.direct(constAddress.toAddress()), tr.resultReg), null) else - addInstr(result, IRInstruction(Opcode.ADDM, vmDt, reg1 = tr.resultReg, labelSymbol = symbol) , null) + addInstr(result, IRInstructions.memoryOp(Opcode.ADDM, vmDt, IRMemory.direct(symbol!!), tr.resultReg) , null) } } return result @@ -2304,12 +2306,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val skip = codeGen.createLabelName() val lsbReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) + it += IRInstructions.memoryOp(Opcode.INCM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_lsb", constIndex)) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, lsbReg, IRMemory.direct(arrayVariableName+"_lsb", constIndex)) if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps) - it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = lsbReg, immediate = 0) - it += IRInstruction(Opcode.BSTNE, labelSymbol = skip) - it += IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + it += IRInstructions.compareImmediate(IRDataType.BYTE, lsbReg, 0) + it += IRInstructions.branch(Opcode.BSTNE, codeLabel(skip)) + it += IRInstructions.memoryOp(Opcode.INCM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_msb", constIndex)) } result += IRCodeChunk(skip, null) return result @@ -2321,20 +2323,20 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen result += IRCodeChunk(null, null).also { // Load current LSB and MSB - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = msbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, lsbReg, IRMemory.direct(arrayVariableName+"_lsb", constIndex)) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, msbReg, IRMemory.direct(arrayVariableName+"_msb", constIndex)) // Concatenate into word register - it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = wordReg, reg2 = msbReg, reg3 = lsbReg) + it += IRInstructions.concat(IRDataType.BYTE, wordReg, msbReg, lsbReg) // Add the constant value - it += IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = wordReg, immediate = constValue) + it += IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.WORD, wordReg, constValue) // Extract bytes back into NEW registers val newLsbReg = codeGen.registers.next(IRDataType.BYTE) val newMsbReg = codeGen.registers.next(IRDataType.BYTE) - it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = newLsbReg, reg2 = wordReg) - it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = newMsbReg, reg2 = wordReg) + it += IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, newLsbReg, wordReg) + it += IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, newMsbReg, wordReg) // Store back - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newLsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newMsbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, newLsbReg, IRMemory.direct(arrayVariableName+"_lsb", constIndex)) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, newMsbReg, IRMemory.direct(arrayVariableName+"_msb", constIndex)) } return result } else { @@ -2350,20 +2352,20 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen result += IRCodeChunk(null, null).also { // Load current LSB and MSB from array - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = msbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, lsbReg, IRMemory.direct(arrayVariableName+"_lsb", constIndex)) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, msbReg, IRMemory.direct(arrayVariableName+"_msb", constIndex)) // Concatenate into word register - it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = wordReg, reg2 = msbReg, reg3 = lsbReg) + it += IRInstructions.concat(IRDataType.BYTE, wordReg, msbReg, lsbReg) // Add the operand value (use ADDR for register-to-register) - it += IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1 = wordReg, reg2 = tr.resultReg) + it += IRInstructions.binary(Opcode.ADDR, IRDataType.WORD, wordReg, tr.resultReg) // Extract bytes back into NEW registers val newLsbReg = codeGen.registers.next(IRDataType.BYTE) val newMsbReg = codeGen.registers.next(IRDataType.BYTE) - it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = newLsbReg, reg2 = wordReg) - it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = newMsbReg, reg2 = wordReg) + it += IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, newLsbReg, wordReg) + it += IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, newMsbReg, wordReg) // Store back - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newLsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newMsbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, newLsbReg, IRMemory.direct(arrayVariableName+"_lsb", constIndex)) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, newMsbReg, IRMemory.direct(arrayVariableName+"_msb", constIndex)) } return result } @@ -2383,21 +2385,21 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen if(array.splitWords) { repeat(constValue) { result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LSRM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) - it += IRInstruction(Opcode.ROXRM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) + it += IRInstructions.memoryOp(Opcode.LSRM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_msb", constIndex)) + it += IRInstructions.memoryOp(Opcode.ROXRM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_lsb", constIndex)) } } } else { val eltSize = codeGen.program.memsizer.memorySize(array.type, null) if(constValue==1) { result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LSRM, vmDt, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize) + it += IRInstructions.memoryOp(Opcode.LSRM, vmDt, IRMemory.direct(arrayVariableName, constIndex*eltSize)) } } else { val valueReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueReg, immediate=constValue and 255) - it += IRInstruction(Opcode.LSRNM, vmDt, reg1=valueReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize) + it += IRInstructions.load(IRDataType.BYTE, valueReg, constValue and 255) + it += IRInstructions.memoryOp(Opcode.LSRNM, vmDt, IRMemory.direct(arrayVariableName, constIndex*eltSize), valueReg) } } } @@ -2414,23 +2416,23 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen resultVar += codeGen.multiplyByConst(indexDt, indexReg, eltSize) val loadReg = codeGen.registers.next(vmDt) resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol=array.variable!!.name) + it += IRInstructions.loadMemory(Opcode.LOADX, vmDt, loadReg, IRMemory.indexed(array.variable!!.name, indexReg, codeGen.options.compTarget.indexRegType)) } val constShift = operand.asConstInteger() if(constShift!=null) { if(constShift==1) { val opc = if(signed) Opcode.ASR else Opcode.LSR resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(opc, vmDt, reg1=loadReg) - it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol=array.variable!!.name) + it += IRInstructions.unary(opc, vmDt, loadReg) + it += IRInstructions.storeMemory(Opcode.STOREX, vmDt, loadReg, IRMemory.indexed(array.variable!!.name, indexReg, codeGen.options.compTarget.indexRegType)) } } else { val shiftReg = codeGen.registers.next(IRDataType.BYTE) resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=shiftReg, immediate=constShift and 255) + it += IRInstructions.load(IRDataType.BYTE, shiftReg, constShift and 255) val opc = if(signed) Opcode.ASRN else Opcode.LSRN - it += IRInstruction(opc, vmDt, reg1=loadReg, reg2=shiftReg) - it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol=array.variable!!.name) + it += IRInstructions.binary(opc, vmDt, loadReg, shiftReg) + it += IRInstructions.storeMemory(Opcode.STOREX, vmDt, loadReg, IRMemory.indexed(array.variable!!.name, indexReg, codeGen.options.compTarget.indexRegType)) } } } else { @@ -2438,8 +2440,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen addToResult(resultVar, shiftTr, shiftTr.resultReg, -1) resultVar += IRCodeChunk(null, null).also { val opc = if(signed) Opcode.ASRN else Opcode.LSRN - it += IRInstruction(opc, vmDt, reg1=loadReg, reg2=shiftTr.resultReg) - it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol=array.variable!!.name) + it += IRInstructions.binary(opc, vmDt, loadReg, shiftTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREX, vmDt, loadReg, IRMemory.indexed(array.variable!!.name, indexReg, codeGen.options.compTarget.indexRegType)) } } return resultVar @@ -2455,20 +2457,20 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val loadReg = codeGen.registers.next(vmDt) val constShift = operand.asConstInteger() resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, vmDt, reg1=loadReg, reg2=addressReg, immediate=0) + it += IRInstructions.loadMemory(Opcode.LOADI, vmDt, loadReg, IRMemory.indirect(addressReg, 0)) } if(constShift!=null) { if(constShift==1) { val opc = if(signed) Opcode.ASR else Opcode.LSR resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(opc, vmDt, reg1=loadReg) + it += IRInstructions.unary(opc, vmDt, loadReg) } } else { val shiftReg = codeGen.registers.next(IRDataType.BYTE) resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=shiftReg, immediate=constShift and 255) + it += IRInstructions.load(IRDataType.BYTE, shiftReg, constShift and 255) val opc = if(signed) Opcode.ASRN else Opcode.LSRN - it += IRInstruction(opc, vmDt, reg1=loadReg, reg2=shiftReg) + it += IRInstructions.binary(opc, vmDt, loadReg, shiftReg) } } } else { @@ -2476,11 +2478,11 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen addToResult(resultVar, shiftTr, shiftTr.resultReg, -1) resultVar += IRCodeChunk(null, null).also { val opc = if(signed) Opcode.ASRN else Opcode.LSRN - it += IRInstruction(opc, vmDt, reg1=loadReg, reg2=shiftTr.resultReg) + it += IRInstructions.binary(opc, vmDt, loadReg, shiftTr.resultReg) } } resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.STOREI, vmDt, reg1=loadReg, reg2=addressReg, immediate=0) + it += IRInstructions.storeMemory(Opcode.STOREI, vmDt, loadReg, IRMemory.indirect(addressReg, 0)) } return resultVar } @@ -2489,9 +2491,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen if(codeGen.isOne(operand)) { val opc = if (signed) Opcode.ASRM else Opcode.LSRM val ins = if(constAddress!=null) - IRInstruction(opc, vmDt, address = constAddress.toAddress()) + IRInstructions.memoryOp(opc, vmDt, IRMemory.direct(constAddress.toAddress())) else - IRInstruction(opc, vmDt, labelSymbol = symbol) + IRInstructions.memoryOp(opc, vmDt, IRMemory.direct(symbol!!)) addInstr(result, ins, null) } else { val shiftTr = exprGen.translateExpression(operand) @@ -2499,9 +2501,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val shiftReg = shiftTr.resultReg val opc = if (signed) Opcode.ASRNM else Opcode.LSRNM val ins = if(constAddress!=null) - IRInstruction(opc, vmDt, reg1 = shiftReg, address = constAddress.toAddress()) + IRInstructions.memoryOp(opc, vmDt, IRMemory.direct(constAddress.toAddress()), shiftReg) else - IRInstruction(opc, vmDt, reg1 = shiftReg, labelSymbol = symbol) + IRInstructions.memoryOp(opc, vmDt, IRMemory.direct(symbol!!), shiftReg) addInstr(result, ins, null) } return result @@ -2519,21 +2521,21 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen if(array.splitWords) { repeat(constValue) { result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LSLM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) - it += IRInstruction(Opcode.ROXLM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + it += IRInstructions.memoryOp(Opcode.LSLM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_lsb", constIndex)) + it += IRInstructions.memoryOp(Opcode.ROXLM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_msb", constIndex)) } } } else { val eltSize = codeGen.program.memsizer.memorySize(array.type, null) if(constValue==1) { result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LSLM, vmDt, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize) + it += IRInstructions.memoryOp(Opcode.LSLM, vmDt, IRMemory.direct(arrayVariableName, constIndex*eltSize)) } } else { val valueReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueReg, immediate=constValue and 255) - it += IRInstruction(Opcode.LSLNM, vmDt, reg1=valueReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize) + it += IRInstructions.load(IRDataType.BYTE, valueReg, constValue and 255) + it += IRInstructions.memoryOp(Opcode.LSLNM, vmDt, IRMemory.direct(arrayVariableName, constIndex*eltSize), valueReg) } } } @@ -2549,29 +2551,29 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen resultVar += codeGen.multiplyByConst(indexDt, indexReg, eltSize) val loadReg = codeGen.registers.next(vmDt) resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol=array.variable!!.name) + it += IRInstructions.loadMemory(Opcode.LOADX, vmDt, loadReg, IRMemory.indexed(array.variable!!.name, indexReg, codeGen.options.compTarget.indexRegType)) } val constShift = operand.asConstInteger() if(constShift!=null) { if(constShift==1) { resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LSL, vmDt, reg1=loadReg) - it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol=array.variable!!.name) + it += IRInstructions.unary(Opcode.LSL, vmDt, loadReg) + it += IRInstructions.storeMemory(Opcode.STOREX, vmDt, loadReg, IRMemory.indexed(array.variable!!.name, indexReg, codeGen.options.compTarget.indexRegType)) } } else { val shiftReg = codeGen.registers.next(IRDataType.BYTE) resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=shiftReg, immediate=constShift and 255) - it += IRInstruction(Opcode.LSLN, vmDt, reg1=loadReg, reg2=shiftReg) - it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol=array.variable!!.name) + it += IRInstructions.load(IRDataType.BYTE, shiftReg, constShift and 255) + it += IRInstructions.binary(Opcode.LSLN, vmDt, loadReg, shiftReg) + it += IRInstructions.storeMemory(Opcode.STOREX, vmDt, loadReg, IRMemory.indexed(array.variable!!.name, indexReg, codeGen.options.compTarget.indexRegType)) } } } else { val shiftTr = exprGen.translateExpression(operand) addToResult(resultVar, shiftTr, shiftTr.resultReg, -1) resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LSLN, vmDt, reg1=loadReg, reg2=shiftTr.resultReg) - it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol=array.variable!!.name) + it += IRInstructions.binary(Opcode.LSLN, vmDt, loadReg, shiftTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREX, vmDt, loadReg, IRMemory.indexed(array.variable!!.name, indexReg, codeGen.options.compTarget.indexRegType)) } } return resultVar @@ -2586,29 +2588,29 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val loadReg = codeGen.registers.next(vmDt) val constShift = operand.asConstInteger() resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, vmDt, reg1=loadReg, reg2=addressReg, immediate=0) + it += IRInstructions.loadMemory(Opcode.LOADI, vmDt, loadReg, IRMemory.indirect(addressReg, 0)) } if(constShift!=null) { if(constShift==1) { resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LSL, vmDt, reg1=loadReg) + it += IRInstructions.unary(Opcode.LSL, vmDt, loadReg) } } else { val shiftReg = codeGen.registers.next(IRDataType.BYTE) resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=shiftReg, immediate=constShift and 255) - it += IRInstruction(Opcode.LSLN, vmDt, reg1=loadReg, reg2=shiftReg) + it += IRInstructions.load(IRDataType.BYTE, shiftReg, constShift and 255) + it += IRInstructions.binary(Opcode.LSLN, vmDt, loadReg, shiftReg) } } } else { val shiftTr = exprGen.translateExpression(operand) addToResult(resultVar, shiftTr, shiftTr.resultReg, -1) resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LSLN, vmDt, reg1=loadReg, reg2=shiftTr.resultReg) + it += IRInstructions.binary(Opcode.LSLN, vmDt, loadReg, shiftTr.resultReg) } } resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.STOREI, vmDt, reg1=loadReg, reg2=addressReg, immediate=0) + it += IRInstructions.storeMemory(Opcode.STOREI, vmDt, loadReg, IRMemory.indirect(addressReg, 0)) } return resultVar } @@ -2616,18 +2618,18 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val result = mutableListOf() if(codeGen.isOne(operand)){ addInstr(result, if(constAddress!=null) - IRInstruction(Opcode.LSLM, vmDt, address = constAddress.toAddress()) + IRInstructions.memoryOp(Opcode.LSLM, vmDt, IRMemory.direct(constAddress.toAddress())) else - IRInstruction(Opcode.LSLM, vmDt, labelSymbol = symbol) + IRInstructions.memoryOp(Opcode.LSLM, vmDt, IRMemory.direct(symbol!!)) , null) } else { val shiftTr = exprGen.translateExpression(operand) addToResult(result, shiftTr, shiftTr.resultReg, -1) val shiftReg = shiftTr.resultReg addInstr(result, if(constAddress!=null) - IRInstruction(Opcode.LSLNM, vmDt, reg1=shiftReg, address = constAddress.toAddress()) + IRInstructions.memoryOp(Opcode.LSLNM, vmDt, IRMemory.direct(constAddress.toAddress()), shiftReg) else - IRInstruction(Opcode.LSLNM, vmDt, reg1=shiftReg, labelSymbol = symbol) + IRInstructions.memoryOp(Opcode.LSLNM, vmDt, IRMemory.direct(symbol!!), shiftReg) ,null) } return result @@ -2646,16 +2648,16 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val valueRegLsb = codeGen.registers.next(IRDataType.BYTE) val valueRegMsb = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueRegLsb, immediate=constValue and 255) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueRegMsb, immediate=constValue shr 8) - it += IRInstruction(Opcode.XORM, IRDataType.BYTE, reg1=valueRegLsb, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) - it += IRInstruction(Opcode.XORM, IRDataType.BYTE, reg1=valueRegMsb, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + it += IRInstructions.load(IRDataType.BYTE, valueRegLsb, constValue and 255) + it += IRInstructions.load(IRDataType.BYTE, valueRegMsb, constValue shr 8) + it += IRInstructions.memoryOp(Opcode.XORM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_lsb", constIndex), valueRegLsb) + it += IRInstructions.memoryOp(Opcode.XORM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_msb", constIndex), valueRegMsb) } } else { val valueReg = codeGen.registers.next(vmDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, vmDt, reg1=valueReg, immediate=constValue) - it += IRInstruction(Opcode.XORM, vmDt, reg1=valueReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize) + it += IRInstructions.load(vmDt, valueReg, constValue) + it += IRInstructions.memoryOp(Opcode.XORM, vmDt, IRMemory.direct(arrayVariableName, constIndex*eltSize), valueReg) } } return result @@ -2669,10 +2671,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val lsbReg = codeGen.registers.next(IRDataType.BYTE) val msbReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=lsbReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1=msbReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.XORM, IRDataType.BYTE, reg1=lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) - it += IRInstruction(Opcode.XORM, IRDataType.BYTE, reg1=msbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + it += IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, lsbReg, valueTr.resultReg) + it += IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, msbReg, valueTr.resultReg) + it += IRInstructions.memoryOp(Opcode.XORM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_lsb", constIndex), lsbReg) + it += IRInstructions.memoryOp(Opcode.XORM, IRDataType.BYTE, IRMemory.direct(arrayVariableName+"_msb", constIndex), msbReg) } return result } @@ -2687,14 +2689,14 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val valLsbReg = codeGen.registers.next(IRDataType.BYTE) val valMsbReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=valLsbReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1=valMsbReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=lsbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_lsb") - it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=msbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_msb") - it += IRInstruction(Opcode.XORR, IRDataType.BYTE, reg1=lsbReg, reg2=valLsbReg) - it += IRInstruction(Opcode.XORR, IRDataType.BYTE, reg1=msbReg, reg2=valMsbReg) - it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=lsbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_lsb") - it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=msbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_msb") + it += IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, valLsbReg, valueTr.resultReg) + it += IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, valMsbReg, valueTr.resultReg) + it += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, lsbReg, IRMemory.indexed(arrayVariableName+"_lsb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, msbReg, IRMemory.indexed(arrayVariableName+"_msb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.binary(Opcode.XORR, IRDataType.BYTE, lsbReg, valLsbReg) + it += IRInstructions.binary(Opcode.XORR, IRDataType.BYTE, msbReg, valMsbReg) + it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, lsbReg, IRMemory.indexed(arrayVariableName+"_lsb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, msbReg, IRMemory.indexed(arrayVariableName+"_msb", indexReg, codeGen.options.compTarget.indexRegType)) } return result } @@ -2703,7 +2705,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val arrayVarName = array.variable!!.name val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.XORM, vmDt, reg1=valueTr.resultReg, labelSymbol = arrayVarName, symbolOffset = constIndex*eltSize), null) + addInstr(result, IRInstructions.memoryOp(Opcode.XORM, vmDt, IRMemory.direct(arrayVarName, constIndex*eltSize), valueTr.resultReg), null) return result } // Non-constant index - use LOADX/XORR/STOREX @@ -2715,19 +2717,19 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val loadReg = codeGen.registers.next(vmDt) val constReg = codeGen.registers.next(vmDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) - it += IRInstruction(Opcode.LOAD, vmDt, reg1=constReg, immediate = constValue) - it += IRInstruction(Opcode.XORR, vmDt, reg1=loadReg, reg2=constReg) - it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) + it += IRInstructions.loadMemory(Opcode.LOADX, vmDt, loadReg, IRMemory.indexed(arrayVarName2, indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.load(vmDt, constReg, constValue) + it += IRInstructions.binary(Opcode.XORR, vmDt, loadReg, constReg) + it += IRInstructions.storeMemory(Opcode.STOREX, vmDt, loadReg, IRMemory.indexed(arrayVarName2, indexReg, codeGen.options.compTarget.indexRegType)) } } else { val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) val loadReg = codeGen.registers.next(vmDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) - it += IRInstruction(Opcode.XORR, vmDt, reg1=loadReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) + it += IRInstructions.loadMemory(Opcode.LOADX, vmDt, loadReg, IRMemory.indexed(arrayVarName2, indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.binary(Opcode.XORR, vmDt, loadReg, valueTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREX, vmDt, loadReg, IRMemory.indexed(arrayVarName2, indexReg, codeGen.options.compTarget.indexRegType)) } } return result @@ -2745,18 +2747,18 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen addToResult(memResult, valueTr, valueTr.resultReg, -1) val loadReg = codeGen.registers.next(IRDataType.BYTE) memResult += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) - it += IRInstruction(Opcode.XORR, IRDataType.BYTE, reg1=loadReg, reg2=valueTr.resultReg) - it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) + it += IRInstructions.binary(Opcode.XORR, IRDataType.BYTE, loadReg, valueTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) } } else { val loadReg = codeGen.registers.next(IRDataType.BYTE) val valueReg = codeGen.registers.next(IRDataType.BYTE) memResult += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueReg, immediate = operandConstValue.toInt()) - it += IRInstruction(Opcode.XORR, IRDataType.BYTE, reg1=loadReg, reg2=valueReg) - it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) + it += IRInstructions.load(IRDataType.BYTE, valueReg, operandConstValue.toInt()) + it += IRInstructions.binary(Opcode.XORR, IRDataType.BYTE, loadReg, valueReg) + it += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, loadReg, IRMemory.indirect(addressReg, 0)) } } return memResult @@ -2766,9 +2768,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val tr = exprGen.translateExpression(operand) addToResult(result, tr, tr.resultReg, -1) addInstr(result, if(constAddress!=null) - IRInstruction(Opcode.XORM, vmDt, reg1=tr.resultReg, address = constAddress.toAddress()) + IRInstructions.memoryOp(Opcode.XORM, vmDt, IRMemory.direct(constAddress.toAddress()), tr.resultReg) else - IRInstruction(Opcode.XORM, vmDt, reg1=tr.resultReg, labelSymbol = symbol) + IRInstructions.memoryOp(Opcode.XORM, vmDt, IRMemory.direct(symbol!!), tr.resultReg) ,null) return result } @@ -2795,16 +2797,16 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val msbReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { if(constIndex!=null) { - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=lsbReg, labelSymbol=arrayName+"_lsb", symbolOffset=constIndex) - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=msbReg, labelSymbol=arrayName+"_msb", symbolOffset=constIndex) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, lsbReg, IRMemory.direct(arrayName+"_lsb", constIndex)) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, msbReg, IRMemory.direct(arrayName+"_msb", constIndex)) } else { - it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=lsbReg, reg2=indexReg, labelSymbol=arrayName+"_lsb") - it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=msbReg, reg2=indexReg, labelSymbol=arrayName+"_msb") + it += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, lsbReg, IRMemory.indexed(arrayName+"_lsb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, msbReg, IRMemory.indexed(arrayName+"_msb", indexReg, codeGen.options.compTarget.indexRegType)) } - it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=wordReg, reg2=msbReg, reg3=lsbReg) + it += IRInstructions.concat(IRDataType.BYTE, wordReg, msbReg, lsbReg) if(constValue!=null) { if(constValue!=1) - it += IRInstruction(opcode, IRDataType.WORD, reg1=wordReg, immediate=constValue) + it += IRInstructions.binaryImmediate(opcode, IRDataType.WORD, wordReg, constValue) } } @@ -2812,21 +2814,21 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) result += IRCodeChunk(null, null).also { - it += IRInstruction(regOpcode, IRDataType.WORD, reg1=wordReg, reg2=valueTr.resultReg) + it += IRInstructions.binary(regOpcode, IRDataType.WORD, wordReg, valueTr.resultReg) } } val newLsbReg = codeGen.registers.next(IRDataType.BYTE) val newMsbReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=newLsbReg, reg2=wordReg) - it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1=newMsbReg, reg2=wordReg) + it += IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, newLsbReg, wordReg) + it += IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, newMsbReg, wordReg) if(constIndex!=null) { - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=newLsbReg, labelSymbol=arrayName+"_lsb", symbolOffset=constIndex) - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=newMsbReg, labelSymbol=arrayName+"_msb", symbolOffset=constIndex) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, newLsbReg, IRMemory.direct(arrayName+"_lsb", constIndex)) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, newMsbReg, IRMemory.direct(arrayName+"_msb", constIndex)) } else { - it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=newLsbReg, reg2=indexReg, labelSymbol=arrayName+"_lsb") - it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=newMsbReg, reg2=indexReg, labelSymbol=arrayName+"_msb") + it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, newLsbReg, IRMemory.indexed(arrayName+"_lsb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, newMsbReg, IRMemory.indexed(arrayName+"_msb", indexReg, codeGen.options.compTarget.indexRegType)) } } return result @@ -2847,9 +2849,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen if(constIndex!=null && constValue!=null) { val resultReg = codeGen.registers.next(eltDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADM, eltDt, reg1=resultReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize) - it += IRInstruction(modOpcode, eltDt, reg1=resultReg, immediate = constValue) - it += IRInstruction(Opcode.STOREM, eltDt, reg1=resultReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize) + it += IRInstructions.loadMemory(Opcode.LOADM, eltDt, resultReg, IRMemory.direct(arrayVariableName, constIndex*eltSize)) + it += IRInstructions.binaryImmediate(modOpcode, eltDt, resultReg, constValue) + it += IRInstructions.storeMemory(Opcode.STOREM, eltDt, resultReg, IRMemory.direct(arrayVariableName, constIndex*eltSize)) } return result } @@ -2859,9 +2861,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADM, eltDt, reg1=resultReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize) - it += IRInstruction(modROpcode, eltDt, reg1=resultReg, reg2 = valueTr.resultReg) - it += IRInstruction(Opcode.STOREM, eltDt, reg1=resultReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize) + it += IRInstructions.loadMemory(Opcode.LOADM, eltDt, resultReg, IRMemory.direct(arrayVariableName, constIndex*eltSize)) + it += IRInstructions.binary(modROpcode, eltDt, resultReg, valueTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREM, eltDt, resultReg, IRMemory.direct(arrayVariableName, constIndex*eltSize)) } return result } @@ -2874,23 +2876,23 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } val loadReg = codeGen.registers.next(eltDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADX, eltDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + it += IRInstructions.loadMemory(Opcode.LOADX, eltDt, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) } if(constValue!=null) { val constReg = codeGen.registers.next(eltDt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, eltDt, reg1=constReg, immediate = constValue) - it += IRInstruction(modROpcode, eltDt, reg1=loadReg, reg2=constReg) + it += IRInstructions.load(eltDt, constReg, constValue) + it += IRInstructions.binary(modROpcode, eltDt, loadReg, constReg) } } else { val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) result += IRCodeChunk(null, null).also { - it += IRInstruction(modROpcode, eltDt, reg1=loadReg, reg2=valueTr.resultReg) + it += IRInstructions.binary(modROpcode, eltDt, loadReg, valueTr.resultReg) } } result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.STOREX, eltDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + it += IRInstructions.storeMemory(Opcode.STOREX, eltDt, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType)) } return result } @@ -2901,23 +2903,23 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val addressReg = addrTr.resultReg val loadReg = codeGen.registers.next(vmDt) resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, vmDt, reg1=loadReg, reg2=addressReg, immediate=0) + it += IRInstructions.loadMemory(Opcode.LOADI, vmDt, loadReg, IRMemory.indirect(addressReg, 0)) } val modROpcode = if(signed) Opcode.MODSR else Opcode.MODR val constVal = (operand as? PtNumber)?.number?.toInt() if(constVal!=null) { val constReg = codeGen.registers.next(vmDt) resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, vmDt, reg1=constReg, immediate=constVal) - it += IRInstruction(modROpcode, vmDt, reg1=loadReg, reg2=constReg) - it += IRInstruction(Opcode.STOREI, vmDt, reg1=loadReg, reg2=addressReg, immediate=0) + it += IRInstructions.load(vmDt, constReg, constVal) + it += IRInstructions.binary(modROpcode, vmDt, loadReg, constReg) + it += IRInstructions.storeMemory(Opcode.STOREI, vmDt, loadReg, IRMemory.indirect(addressReg, 0)) } } else { val opTr = exprGen.translateExpression(operand) addToResult(resultVar, opTr, opTr.resultReg, -1) resultVar += IRCodeChunk(null, null).also { - it += IRInstruction(modROpcode, vmDt, reg1=loadReg, reg2=opTr.resultReg) - it += IRInstruction(Opcode.STOREI, vmDt, reg1=loadReg, reg2=addressReg, immediate=0) + it += IRInstructions.binary(modROpcode, vmDt, loadReg, opTr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREI, vmDt, loadReg, IRMemory.indirect(addressReg, 0)) } } return resultVar @@ -2932,16 +2934,17 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen if (constAddress != null) { // @(address) = @(address) %= operand result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADM, vmDt, reg1 = resultReg, address = constAddress.toAddress()) - it += IRInstruction(modOpcode, vmDt, reg1 = resultReg, immediate = number) - it += IRInstruction(Opcode.STOREM, vmDt, reg1 = resultReg, address = constAddress.toAddress()) + it += IRInstructions.loadMemory(Opcode.LOADM, vmDt, resultReg, IRMemory.direct(constAddress.toAddress())) + it += IRInstructions.binaryImmediate(modOpcode, vmDt, resultReg, number) + it += IRInstructions.storeMemory(Opcode.STOREM, vmDt, resultReg, IRMemory.direct(constAddress.toAddress())) } } else { // symbol = symbol %= operand result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADM, vmDt, reg1 = resultReg, labelSymbol = symbol) - it += IRInstruction(modOpcode, vmDt, reg1 = resultReg, immediate = number) - it += IRInstruction(Opcode.STOREM, vmDt, reg1 = resultReg, labelSymbol = symbol) + val symbolName = symbol!! + it += IRInstructions.loadMemory(Opcode.LOADM, vmDt, resultReg, IRMemory.direct(symbolName)) + it += IRInstructions.binaryImmediate(modOpcode, vmDt, resultReg, number) + it += IRInstructions.storeMemory(Opcode.STOREM, vmDt, resultReg, IRMemory.direct(symbolName)) } } } else { @@ -2950,16 +2953,17 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen if (constAddress != null) { // @(address) = @(address) %= operand result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADM, vmDt, reg1 = resultReg, address = constAddress.toAddress()) - it += IRInstruction(modROpcode, vmDt, reg1 = resultReg, reg2 = tr.resultReg) - it += IRInstruction(Opcode.STOREM, vmDt, reg1 = resultReg, address = constAddress.toAddress()) + it += IRInstructions.loadMemory(Opcode.LOADM, vmDt, resultReg, IRMemory.direct(constAddress.toAddress())) + it += IRInstructions.binary(modROpcode, vmDt, resultReg, tr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREM, vmDt, resultReg, IRMemory.direct(constAddress.toAddress())) } } else { // symbol = symbol %= operand result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADM, vmDt, reg1 = resultReg, labelSymbol = symbol) - it += IRInstruction(modROpcode, vmDt, reg1 = resultReg, reg2 = tr.resultReg) - it += IRInstruction(Opcode.STOREM, vmDt, reg1 = resultReg, labelSymbol = symbol) + val symbolName = symbol!! + it += IRInstructions.loadMemory(Opcode.LOADM, vmDt, resultReg, IRMemory.direct(symbolName)) + it += IRInstructions.binary(modROpcode, vmDt, resultReg, tr.resultReg) + it += IRInstructions.storeMemory(Opcode.STOREM, vmDt, resultReg, IRMemory.direct(symbolName)) } } } @@ -2988,15 +2992,15 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val msbReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { if(constIndex!=null) { - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=lsbReg, labelSymbol=arrayName+"_lsb", symbolOffset=constIndex) - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=msbReg, labelSymbol=arrayName+"_msb", symbolOffset=constIndex) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, lsbReg, IRMemory.direct(arrayName+"_lsb", constIndex)) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, msbReg, IRMemory.direct(arrayName+"_msb", constIndex)) } else { - it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=lsbReg, reg2=indexReg, labelSymbol=arrayName+"_lsb") - it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=msbReg, reg2=indexReg, labelSymbol=arrayName+"_msb") + it += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, lsbReg, IRMemory.indexed(arrayName+"_lsb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, msbReg, IRMemory.indexed(arrayName+"_msb", indexReg, codeGen.options.compTarget.indexRegType)) } - it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=wordReg, reg2=msbReg, reg3=lsbReg) + it += IRInstructions.concat(IRDataType.BYTE, wordReg, msbReg, lsbReg) if(constValue!=null) { - it += IRInstruction(opcode, IRDataType.WORD, reg1=wordReg, immediate=constValue) + it += IRInstructions.binaryImmediate(opcode, IRDataType.WORD, wordReg, constValue) } } @@ -3004,21 +3008,21 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) result += IRCodeChunk(null, null).also { - it += IRInstruction(regOpcode, IRDataType.WORD, reg1=wordReg, reg2=valueTr.resultReg) + it += IRInstructions.binary(regOpcode, IRDataType.WORD, wordReg, valueTr.resultReg) } } val newLsbReg = codeGen.registers.next(IRDataType.BYTE) val newMsbReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=newLsbReg, reg2=wordReg) - it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1=newMsbReg, reg2=wordReg) + it += IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, newLsbReg, wordReg) + it += IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, newMsbReg, wordReg) if(constIndex!=null) { - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=newLsbReg, labelSymbol=arrayName+"_lsb", symbolOffset=constIndex) - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=newMsbReg, labelSymbol=arrayName+"_msb", symbolOffset=constIndex) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, newLsbReg, IRMemory.direct(arrayName+"_lsb", constIndex)) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, newMsbReg, IRMemory.direct(arrayName+"_msb", constIndex)) } else { - it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=newLsbReg, reg2=indexReg, labelSymbol=arrayName+"_lsb") - it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=newMsbReg, reg2=indexReg, labelSymbol=arrayName+"_msb") + it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, newLsbReg, IRMemory.indexed(arrayName+"_lsb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, newMsbReg, IRMemory.indexed(arrayName+"_msb", indexReg, codeGen.options.compTarget.indexRegType)) } } return result @@ -3030,14 +3034,14 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen result += idxCode if(zero) { result += IRCodeChunk(null, null).also { - if(info.fieldOffset==0) it += IRInstruction(Opcode.STOREZX, targetDt, reg1=indexReg, labelSymbol=info.arrayName, scale=info.structSize) - else it += IRInstruction(Opcode.STOREZX, targetDt, reg1=indexReg, labelSymbol=info.arrayName, symbolOffset=info.fieldOffset, scale=info.structSize) + if(info.fieldOffset==0) it += IRInstructions.storeZero(Opcode.STOREZX, targetDt, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize)) + else it += IRInstructions.storeZero(Opcode.STOREZX, targetDt, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset)) } } else { if(valueReg<0) return false result += IRCodeChunk(null, null).also { - if(info.fieldOffset==0) it += IRInstruction(Opcode.STOREX, targetDt, reg1=valueReg, reg2=indexReg, labelSymbol=info.arrayName, scale=info.structSize) - else it += IRInstruction(Opcode.STOREX, targetDt, reg1=valueReg, reg2=indexReg, labelSymbol=info.arrayName, symbolOffset=info.fieldOffset, scale=info.structSize) + if(info.fieldOffset==0) it += IRInstructions.storeMemory(Opcode.STOREX, targetDt, valueReg, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize)) + else it += IRInstructions.storeMemory(Opcode.STOREX, targetDt, valueReg, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset)) } } return true diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt index 87f2b2cb6..f459661c3 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt @@ -81,7 +81,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val result = mutableListOf() val tr = exprGen.translateExpression(call.args[0]) addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=tr.resultReg), null) + addInstr(result, IRInstructions.push(IRDataType.BYTE, tr.resultReg), null) return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) } @@ -89,7 +89,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val result = mutableListOf() val tr = exprGen.translateExpression(call.args[0]) addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.WORD, reg1=tr.resultReg), null) + addInstr(result, IRInstructions.push(IRDataType.WORD, tr.resultReg), null) return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) } @@ -97,7 +97,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val result = mutableListOf() val tr = exprGen.translateExpression(call.args[0]) addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.LONG, reg1=tr.resultReg), null) + addInstr(result, IRInstructions.push(IRDataType.LONG, tr.resultReg), null) return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) } @@ -105,35 +105,35 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val result = mutableListOf() val tr = exprGen.translateExpression(call.args[0]) addToResult(result, tr, -1, tr.resultFpReg) - addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.FLOAT, fpReg1 = RegisterNum(tr.resultFpReg)), null) + addInstr(result, IRInstructions.push(IRDataType.FLOAT, tr.resultFpReg), null) return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) } private fun funcPop(): ExpressionCodeResult { val result = mutableListOf() val resultRegister = codeGen.registers.next(IRDataType.BYTE) - addInstr(result, IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=resultRegister), null) + addInstr(result, IRInstructions.pop(IRDataType.BYTE, resultRegister), null) return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1) } private fun funcPopW(): ExpressionCodeResult { val result = mutableListOf() val resultRegister = codeGen.registers.next(IRDataType.WORD) - addInstr(result, IRInstruction(Opcode.POP, IRDataType.WORD, reg1=resultRegister), null) + addInstr(result, IRInstructions.pop(IRDataType.WORD, resultRegister), null) return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1) } private fun funcPopL(): ExpressionCodeResult { val result = mutableListOf() val resultRegister = codeGen.registers.next(IRDataType.LONG) - addInstr(result, IRInstruction(Opcode.POP, IRDataType.LONG, reg1=resultRegister), null) + addInstr(result, IRInstructions.pop(IRDataType.LONG, resultRegister), null) return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1) } private fun funcPopF(): ExpressionCodeResult { val result = mutableListOf() val resultRegister = codeGen.registers.next(IRDataType.FLOAT) - addInstr(result, IRInstruction(Opcode.POP, IRDataType.FLOAT, fpReg1 = RegisterNum(resultRegister)), null) + addInstr(result, IRInstructions.pop(IRDataType.FLOAT, resultRegister), null) return ExpressionCodeResult(result, IRDataType.BYTE, -1, resultRegister) } @@ -147,15 +147,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe IRDataType.LONG -> { val intermediateReg = codeGen.registers.next(IRDataType.LONG) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, type, reg1=intermediateReg, reg2=trSourceAddr.resultReg, immediate = 0) - it += IRInstruction(Opcode.STOREI, type, reg1=intermediateReg, reg2=trTargetAddr.resultReg, immediate = 0) + it += IRInstructions.loadMemory(Opcode.LOADI, type, intermediateReg, IRMemory.indirect(trSourceAddr.resultReg)) + it += IRInstructions.storeMemory(Opcode.STOREI, type, intermediateReg, IRMemory.indirect(trTargetAddr.resultReg)) } } IRDataType.FLOAT -> { val intermediateReg = codeGen.registers.next(IRDataType.FLOAT) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADI, type, fpReg1=RegisterNum(intermediateReg), reg1=trSourceAddr.resultReg, immediate = 0) - it += IRInstruction(Opcode.STOREI, type, fpReg1=RegisterNum(intermediateReg), reg1=trTargetAddr.resultReg, immediate = 0) + it += IRInstructions.loadMemory(Opcode.LOADI, type, intermediateReg, IRMemory.indirect(trSourceAddr.resultReg)) + it += IRInstructions.storeMemory(Opcode.STOREI, type, intermediateReg, IRMemory.indirect(trTargetAddr.resultReg)) } } else -> throw AssemblyError("invalid type $type") @@ -169,12 +169,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg) return if(resultType==IRDataType.FLOAT) { val resultFpReg = codeGen.registers.next(IRDataType.FLOAT) - addInstr(result, IRInstruction(Opcode.SQUARE, resultType, fpReg1 = RegisterNum(resultFpReg), fpReg2 = RegisterNum(valueTr.resultFpReg)), null) + addInstr(result, IRInstructions.binary(Opcode.SQUARE, resultType, resultFpReg, valueTr.resultFpReg), null) ExpressionCodeResult(result, resultType, -1, resultFpReg) } else { val resultReg = codeGen.registers.next(resultType) - addInstr(result, IRInstruction(Opcode.SQUARE, resultType, reg1 = resultReg, reg2 = valueTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.SQUARE, resultType, resultReg, valueTr.resultReg), null) ExpressionCodeResult(result, resultType, resultReg, -1) } } @@ -183,13 +183,13 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val result = mutableListOf() val addressTr = exprGen.translateExpression(call.args[0]) addToResult(result, addressTr, addressTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.CALLI, reg1 = addressTr.resultReg), null) + addInstr(result, IRInstructions.call(Opcode.CALLI, CallSite(CallTarget.Direct(codeIndirect(addressTr.resultReg)))), null) return if(call.void) ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) else { val resultvalueReg = codeGen.registers.next(IRDataType.WORD) val slot = if(codeGen.options.compTarget.cpu.usesM68kConvention) 10 else 4 - addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=resultvalueReg, immediate=slot), null) + addInstr(result, IRInstructions.hardwareLoad(IRDataType.WORD, resultvalueReg, CallingConventionSlot(slot)), null) ExpressionCodeResult(result, IRDataType.WORD, resultvalueReg, -1) } } @@ -202,8 +202,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe addToResult(result, bankTr, bankTr.resultReg, -1) addToResult(result, addressTr, addressTr.resultReg, -1) addToResult(result, argumentwordTr, argumentwordTr.resultReg, -1) - result += codeGen.makeSyscall(IMSyscall.CALLFAR, listOf(IRDataType.BYTE to bankTr.resultReg, codeGen.addressDt to addressTr.resultReg, IRDataType.WORD to argumentwordTr.resultReg), IRDataType.WORD to argumentwordTr.resultReg) - return ExpressionCodeResult(result, IRDataType.WORD, argumentwordTr.resultReg, -1) + val resultReg = codeGen.registers.next(IRDataType.WORD) + result += codeGen.makeSyscall(IMSyscall.CALLFAR, listOf(IRDataType.BYTE to bankTr.resultReg, codeGen.addressDt to addressTr.resultReg, IRDataType.WORD to argumentwordTr.resultReg), IRDataType.WORD to resultReg) + return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1) } private fun funcCallfar2(call: PtFunctionCall): ExpressionCodeResult { @@ -220,12 +221,13 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe addToResult(result, argumentX, argumentX.resultReg, -1) addToResult(result, argumentY, argumentY.resultReg, -1) addToResult(result, argumentCarry, argumentCarry.resultReg, -1) + val resultReg = codeGen.registers.next(IRDataType.WORD) result += codeGen.makeSyscall(IMSyscall.CALLFAR2, listOf(IRDataType.BYTE to bankTr.resultReg, codeGen.addressDt to addressTr.resultReg, IRDataType.BYTE to argumentA.resultReg, IRDataType.BYTE to argumentX.resultReg, IRDataType.BYTE to argumentY.resultReg, - IRDataType.BYTE to argumentCarry.resultReg), IRDataType.WORD to addressTr.resultReg) - return ExpressionCodeResult(result, IRDataType.WORD, addressTr.resultReg, -1) + IRDataType.BYTE to argumentCarry.resultReg), IRDataType.WORD to resultReg) + return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1) } private fun funcDivmod(call: PtFunctionCall, type: IRDataType, signed: Boolean): ExpressionCodeResult { @@ -244,7 +246,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val tr = exprGen.translateExpression(number) addToResult(result, tr, tr.resultReg, -1) remainderReg = codeGen.registers.next(type) - addInstr(result, IRInstruction(divmodOpcode, type, reg1 = tr.resultReg, reg2 = remainderReg, immediate = divident.number.toInt()), null) + addInstr(result, IRInstructions.divmodImmediate(divmodOpcode, type, tr.resultReg, remainderReg, divident.number.toInt()), null) divisionReg = tr.resultReg } else { val numTr = exprGen.translateExpression(number) @@ -252,7 +254,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val dividentTr = exprGen.translateExpression(divident) addToResult(result, dividentTr, dividentTr.resultReg, -1) remainderReg = dividentTr.resultReg - addInstr(result, IRInstruction(divmodrOpcode, type, reg1 = numTr.resultReg, reg2 = dividentTr.resultReg), null) + addInstr(result, IRInstructions.divmodRegister(divmodrOpcode, type, numTr.resultReg, dividentTr.resultReg), null) divisionReg = numTr.resultReg } // DIVMOD result convention: quotient in reg1, remainder in reg2. @@ -261,13 +263,13 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe // m68k: first return value in D0 (slot 10), second in D1 (slot 11) if(type==IRDataType.BYTE) { result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1=remainderReg, immediate=11) - it += IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1=divisionReg, immediate=10) + it += IRInstructions.hardwareStore(IRDataType.BYTE, remainderReg, CallingConventionSlot(11)) + it += IRInstructions.hardwareStore(IRDataType.BYTE, divisionReg, CallingConventionSlot(10)) } } else if(type==IRDataType.WORD) { result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.STOREHR, IRDataType.WORD, reg1=remainderReg, immediate=11) - it += IRInstruction(Opcode.STOREHR, IRDataType.WORD, reg1=divisionReg, immediate=10) + it += IRInstructions.hardwareStore(IRDataType.WORD, remainderReg, CallingConventionSlot(11)) + it += IRInstructions.hardwareStore(IRDataType.WORD, divisionReg, CallingConventionSlot(10)) } } else throw AssemblyError("invalid type for DIVMOD") } else { @@ -275,13 +277,13 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe // Note: STOREM must come before STOREHR, otherwise STOREM clobbers A before LOADHR reads it. if(type==IRDataType.BYTE) { result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=remainderReg, labelSymbol = "cx16.r15") - it += IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1=divisionReg, immediate=0) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, remainderReg, IRMemory.direct("cx16.r15")) + it += IRInstructions.hardwareStore(IRDataType.BYTE, divisionReg, CallingConventionSlot(0)) } } else if(type==IRDataType.WORD) { result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=remainderReg, labelSymbol = "cx16.r15") - it += IRInstruction(Opcode.STOREHR, IRDataType.WORD, reg1=divisionReg, immediate=4) + it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.WORD, remainderReg, IRMemory.direct("cx16.r15")) + it += IRInstructions.hardwareStore(IRDataType.WORD, divisionReg, CallingConventionSlot(4)) } } else throw AssemblyError("invalid type for DIVMOD") } @@ -308,7 +310,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe addToResult(result, rightTr, rightTr.resultReg, -1) val dt = codeGen.irType(call.args[0].type) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.CMP, dt, reg1=leftTr.resultReg, reg2=rightTr.resultReg) + it += IRInstructions.compare(dt, leftTr.resultReg, rightTr.resultReg) } return ExpressionCodeResult(result, dt, leftTr.resultReg, -1) } @@ -327,11 +329,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val notNegativeLabel = codeGen.createLabelName() val compareReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=compareReg, reg2=tr.resultReg) + it += IRInstructions.move(IRDataType.BYTE, compareReg, tr.resultReg) if(needsExplicitCmpi) - it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=compareReg, immediate = 0) - it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel) - it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=tr.resultReg) + it += IRInstructions.compareImmediate(IRDataType.BYTE, compareReg, 0) + it += IRInstructions.branch(Opcode.BSTPOS, codeLabel(notNegativeLabel)) + it += IRInstructions.unary(Opcode.NEG, IRDataType.BYTE, tr.resultReg) } result += IRCodeChunk(notNegativeLabel, null) return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1) @@ -340,11 +342,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val notNegativeLabel = codeGen.createLabelName() val compareReg = codeGen.registers.next(IRDataType.WORD) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADR, IRDataType.WORD, reg1=compareReg, reg2=tr.resultReg) + it += IRInstructions.move(IRDataType.WORD, compareReg, tr.resultReg) if(needsExplicitCmpi) - it += IRInstruction(Opcode.CMPI, IRDataType.WORD, reg1=compareReg, immediate = 0) - it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel) - it += IRInstruction(Opcode.NEG, IRDataType.WORD, reg1=tr.resultReg) + it += IRInstructions.compareImmediate(IRDataType.WORD, compareReg, 0) + it += IRInstructions.branch(Opcode.BSTPOS, codeLabel(notNegativeLabel)) + it += IRInstructions.unary(Opcode.NEG, IRDataType.WORD, tr.resultReg) } result += IRCodeChunk(notNegativeLabel, null) return ExpressionCodeResult(result, IRDataType.WORD, tr.resultReg, -1) @@ -353,18 +355,18 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val notNegativeLabel = codeGen.createLabelName() val compareReg = codeGen.registers.next(IRDataType.LONG) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADR, IRDataType.LONG, reg1=compareReg, reg2=tr.resultReg) + it += IRInstructions.move(IRDataType.LONG, compareReg, tr.resultReg) if(needsExplicitCmpi) - it += IRInstruction(Opcode.CMPI, IRDataType.LONG, reg1=compareReg, immediate = 0) - it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel) - it += IRInstruction(Opcode.NEG, IRDataType.LONG, reg1=tr.resultReg) + it += IRInstructions.compareImmediate(IRDataType.LONG, compareReg, 0) + it += IRInstructions.branch(Opcode.BSTPOS, codeLabel(notNegativeLabel)) + it += IRInstructions.unary(Opcode.NEG, IRDataType.LONG, tr.resultReg) } result += IRCodeChunk(notNegativeLabel, null) return ExpressionCodeResult(result, IRDataType.LONG, tr.resultReg, -1) } BaseDataType.FLOAT -> { val resultFpReg = codeGen.registers.next(IRDataType.FLOAT) - addInstr(result, IRInstruction(Opcode.FABS, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpReg), fpReg2 = RegisterNum(tr.resultFpReg)), null) + addInstr(result, IRInstructions.binary(Opcode.FABS, IRDataType.FLOAT, resultFpReg, tr.resultFpReg), null) return ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg) } BaseDataType.POINTER -> { @@ -383,12 +385,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe if(tr.dt==IRDataType.FLOAT) { addToResult(result, tr, -1, tr.resultFpReg) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.SGN, tr.dt, reg1 = resultReg, fpReg1 = RegisterNum(tr.resultFpReg)) + it += IRInstructions.binary(Opcode.SGN, tr.dt, resultReg, tr.resultFpReg) } } else { addToResult(result, tr, tr.resultReg, -1) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.SGN, tr.dt, reg1 = resultReg, reg2 = tr.resultReg) + it += IRInstructions.binary(Opcode.SGN, tr.dt, resultReg, tr.resultReg) } } return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1) @@ -403,7 +405,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe addToResult(result, tr, tr.resultReg, -1) val resultReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.SQRT, IRDataType.BYTE, reg1=resultReg, reg2=tr.resultReg) + it += IRInstructions.binary(Opcode.SQRT, IRDataType.BYTE, resultReg, tr.resultReg) } return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1) } @@ -411,7 +413,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe addToResult(result, tr, tr.resultReg, -1) val resultReg = codeGen.registers.next(IRDataType.BYTE) // sqrt of a word still produces just a byte result result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.SQRT, IRDataType.WORD, reg1=resultReg, reg2=tr.resultReg) + it += IRInstructions.binary(Opcode.SQRT, IRDataType.WORD, resultReg, tr.resultReg) } return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1) } @@ -419,7 +421,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe addToResult(result, tr, tr.resultReg, -1) val resultReg = codeGen.registers.next(IRDataType.WORD) // sqrt of a long still produces just a word result result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.SQRT, IRDataType.LONG, reg1=resultReg, reg2=tr.resultReg) + it += IRInstructions.binary(Opcode.SQRT, IRDataType.LONG, resultReg, tr.resultReg) } return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1) } @@ -427,7 +429,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe addToResult(result, tr, -1, tr.resultFpReg) val resultFpReg = codeGen.registers.next(IRDataType.FLOAT) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.SQRT, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpReg), fpReg2 = RegisterNum(tr.resultFpReg)) + it += IRInstructions.binary(Opcode.SQRT, IRDataType.FLOAT, resultFpReg, tr.resultFpReg) } return ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg) } @@ -442,13 +444,13 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe // msb is 0, use EXT val lsbTr = exprGen.translateExpression(call.args[1]) addToResult(result, lsbTr, lsbTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=resultReg, reg2 = lsbTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.EXT, IRDataType.BYTE, resultReg, lsbTr.resultReg), null) } else { val msbTr = exprGen.translateExpression(call.args[0]) addToResult(result, msbTr, msbTr.resultReg, -1) val lsbTr = exprGen.translateExpression(call.args[1]) addToResult(result, lsbTr, lsbTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=resultReg, reg2 = msbTr.resultReg, reg3 = lsbTr.resultReg), null) + addInstr(result, IRInstructions.concat(IRDataType.BYTE, resultReg, msbTr.resultReg, lsbTr.resultReg), null) } return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1) } @@ -462,13 +464,13 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe // msw is 0, use EXT val lswTr = exprGen.translateExpression(call.args[1]) addToResult(result, lswTr, lswTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.EXT, IRDataType.WORD, reg1=resultReg, reg2 = lswTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.EXT, IRDataType.WORD, resultReg, lswTr.resultReg), null) } else { val mswTr = exprGen.translateExpression(call.args[0]) addToResult(result, mswTr, mswTr.resultReg, -1) val lswTr = exprGen.translateExpression(call.args[1]) addToResult(result, lswTr, lswTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.WORD, reg1=resultReg, reg2 = mswTr.resultReg, reg3 = lswTr.resultReg), null) + addInstr(result, IRInstructions.concat(IRDataType.WORD, resultReg, mswTr.resultReg, lswTr.resultReg), null) } } else { // mklong(msb, b3, b2, lsb) @@ -476,7 +478,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe // use EXTL.b: zero-extend byte directly to long val lsbTr = exprGen.translateExpression(call.args[3]) addToResult(result, lsbTr, lsbTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.EXTL, IRDataType.BYTE, reg1=resultReg, reg2 = lsbTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.EXTL, IRDataType.BYTE, resultReg, lsbTr.resultReg), null) } else { val msbTr = exprGen.translateExpression(call.args[0]) val b2Tr = exprGen.translateExpression(call.args[1]) @@ -488,9 +490,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe addToResult(result, lsbTr, lsbTr.resultReg, -1) val lswReg = codeGen.registers.next(IRDataType.WORD) val mswReg = codeGen.registers.next(IRDataType.WORD) - addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=mswReg, reg2 = msbTr.resultReg, reg3 = b2Tr.resultReg), null) - addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=lswReg, reg2 = b1Tr.resultReg, reg3 = lsbTr.resultReg), null) - addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.WORD, reg1=resultReg, reg2 = mswReg, reg3 = lswReg), null) + addInstr(result, IRInstructions.concat(IRDataType.BYTE, mswReg, msbTr.resultReg, b2Tr.resultReg), null) + addInstr(result, IRInstructions.concat(IRDataType.BYTE, lswReg, b1Tr.resultReg, lsbTr.resultReg), null) + addInstr(result, IRInstructions.concat(IRDataType.WORD, resultReg, mswReg, lswReg), null) } } @@ -515,13 +517,14 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe BaseDataType.LONG -> IMSyscall.CLAMP_LONG else -> throw AssemblyError("invalid dt") } + val resultReg = codeGen.registers.next(type) result += codeGen.makeSyscall(syscall, listOf( valueTr.dt to valueTr.resultReg, minimumTr.dt to minimumTr.resultReg, maximumTr.dt to maximumTr.resultReg, - ), type to valueTr.resultReg + ), type to resultReg ) - return ExpressionCodeResult(result, type, valueTr.resultReg, -1) + return ExpressionCodeResult(result, type, resultReg, -1) } private fun funcMin(call: PtFunctionCall): ExpressionCodeResult { @@ -534,10 +537,10 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val comparisonOpcode = if(call.type.isSigned) Opcode.BGTSR else Opcode.BGTR val after = codeGen.createLabelName() result += IRCodeChunk(null, null).also { - it += IRInstruction(comparisonOpcode, type, reg1 = rightTr.resultReg, reg2 = leftTr.resultReg, labelSymbol = after) + it += IRInstructions.branchRegister(comparisonOpcode, type, rightTr.resultReg, leftTr.resultReg, codeLabel(after)) // right <= left, take right - it += IRInstruction(Opcode.LOADR, type, reg1=leftTr.resultReg, reg2=rightTr.resultReg) - it += IRInstruction(Opcode.JUMP, labelSymbol = after) + it += IRInstructions.move(type, leftTr.resultReg, rightTr.resultReg) + it += IRInstructions.jump(codeLabel(after)) } result += IRCodeChunk(after, null) return ExpressionCodeResult(result, type, leftTr.resultReg, -1) @@ -553,10 +556,10 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val comparisonOpcode = if(call.type.isSigned) Opcode.BGTSR else Opcode.BGTR val after = codeGen.createLabelName() result += IRCodeChunk(null, null).also { - it += IRInstruction(comparisonOpcode, type, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg, labelSymbol = after) + it += IRInstructions.branchRegister(comparisonOpcode, type, leftTr.resultReg, rightTr.resultReg, codeLabel(after)) // right >= left, take right - it += IRInstruction(Opcode.LOADR, type, reg1=leftTr.resultReg, reg2=rightTr.resultReg) - it += IRInstruction(Opcode.JUMP, labelSymbol = after) + it += IRInstructions.move(type, leftTr.resultReg, rightTr.resultReg) + it += IRInstructions.jump(codeLabel(after)) } result += IRCodeChunk(after, null) return ExpressionCodeResult(result, type, leftTr.resultReg, -1) @@ -570,18 +573,18 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe if (call.args[0] is PtNumber) { val address = (call.args[0] as PtNumber).number.toUInt() result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.STOREZM, dt, address = address.toAddress()) + it += IRInstructions.storeZero(Opcode.STOREZM, dt, IRMemory.direct(address.toAddress())) } } else { val (address, offset) = exprGen.getAddressAndOffset(call.args[0]) if(address!=null) { val tr = exprGen.translateExpression(address) addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.STOREZI, dt, reg1 = tr.resultReg, immediate = offset!!), null) + addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, dt, IRMemory.indirect(tr.resultReg, offset!!)), null) } else { val tr = exprGen.translateExpression(call.args[0]) addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.STOREZI, dt, reg1 = tr.resultReg, immediate = 0), null) + addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, dt, IRMemory.indirect(tr.resultReg)), null) } } } else { @@ -590,9 +593,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val address = (call.args[0] as PtNumber).number.toUInt() addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg) if(dt==IRDataType.FLOAT) { - addInstr(result, IRInstruction(Opcode.STOREM, dt, fpReg1 = RegisterNum(valueTr.resultFpReg), address = address.toAddress()), null) + addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, dt, valueTr.resultFpReg, IRMemory.direct(address.toAddress())), null) } else { - addInstr(result, IRInstruction(Opcode.STOREM, dt, reg1 = valueTr.resultReg, address = address.toAddress()), null) + addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, dt, valueTr.resultReg, IRMemory.direct(address.toAddress())), null) } } else { val (address, offset) = exprGen.getAddressAndOffset(call.args[0]) @@ -601,18 +604,18 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg) addToResult(result, addressTr, addressTr.resultReg, -1) if(dt==IRDataType.FLOAT) { - addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.FLOAT, reg1 = addressTr.resultReg, fpReg1 = RegisterNum(valueTr.resultFpReg), immediate = offset!!), null) + addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, IRDataType.FLOAT, valueTr.resultFpReg, IRMemory.indirect(addressTr.resultReg, offset!!)), null) } else { - addInstr(result, IRInstruction(Opcode.STOREI, dt, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg, immediate = offset!!), null) + addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, dt, valueTr.resultReg, IRMemory.indirect(addressTr.resultReg, offset!!)), null) } } else { val addressTr = exprGen.translateExpression(call.args[0]) addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg) addToResult(result, addressTr, addressTr.resultReg, -1) if(dt==IRDataType.FLOAT) { - addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.FLOAT, reg1 = addressTr.resultReg, fpReg1 = RegisterNum(valueTr.resultFpReg), immediate = 0), null) + addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, IRDataType.FLOAT, valueTr.resultFpReg, IRMemory.indirect(addressTr.resultReg)), null) } else { - addInstr(result, IRInstruction(Opcode.STOREI, dt, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg, immediate = 0), null) + addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, dt, valueTr.resultReg, IRMemory.indirect(addressTr.resultReg)), null) } } } @@ -626,26 +629,26 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe if(call.args[0] is PtNumber) { val resultFpRegister = codeGen.registers.next(IRDataType.FLOAT) val address = (call.args[0] as PtNumber).number.toUInt() - addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpRegister), address = address.toAddress()), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, IRDataType.FLOAT, resultFpRegister, IRMemory.direct(address.toAddress())), null) ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpRegister) } else { val tr = exprGen.translateExpression(call.args.single()) addToResult(result, tr, tr.resultReg, -1) val resultFpReg = codeGen.registers.next(IRDataType.FLOAT) - addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, reg1 = tr.resultReg, fpReg1 = RegisterNum(resultFpReg), immediate = 0), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, IRDataType.FLOAT, resultFpReg, IRMemory.indirect(tr.resultReg)), null) ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg) } } else { if (call.args[0] is PtNumber) { val resultRegister = codeGen.registers.next(dt) val address = (call.args[0] as PtNumber).number.toUInt() - addInstr(result, IRInstruction(Opcode.LOADM, dt, reg1 = resultRegister, address = address.toAddress()), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, dt, resultRegister, IRMemory.direct(address.toAddress())), null) ExpressionCodeResult(result, dt, resultRegister, -1) } else { val tr = exprGen.translateExpression(call.args.single()) addToResult(result, tr, tr.resultReg, -1) val resultReg = codeGen.registers.next(dt) - addInstr(result, IRInstruction(Opcode.LOADI, dt, reg1 = resultReg, reg2 = tr.resultReg, immediate = 0), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, dt, resultReg, IRMemory.indirect(tr.resultReg)), null) ExpressionCodeResult(result, dt, resultReg, -1) } } @@ -662,11 +665,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val addressNum = address.number.toUInt() val resultRegister = codeGen.registers.next(IRDataType.BYTE) if (newValueTr != null) addToResult(result, newValueTr, newValueTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, address = addressNum.toAddress()), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, resultRegister, IRMemory.direct(addressNum.toAddress())), null) if (newValueTr == null) { - addInstr(result, IRInstruction(Opcode.STOREZM, IRDataType.BYTE, address = addressNum.toAddress()), null) + addInstr(result, IRInstructions.storeZero(Opcode.STOREZM, IRDataType.BYTE, IRMemory.direct(addressNum.toAddress())), null) } else { - addInstr(result, IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newValueTr.resultReg, address = addressNum.toAddress()), null) + addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, newValueTr.resultReg, IRMemory.direct(addressNum.toAddress())), null) } ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1) } else { @@ -679,11 +682,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe if (newValueTr != null) addToResult(result, newValueTr, newValueTr.resultReg, -1) addToResult(result, addressTr, addressTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = addressTr.resultReg, immediate = finalOffset), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, resultReg, IRMemory.indirect(addressTr.resultReg, finalOffset)), null) if (newValueTr == null) { - addInstr(result, IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = addressTr.resultReg, immediate = finalOffset), null) + addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, IRDataType.BYTE, IRMemory.indirect(addressTr.resultReg, finalOffset)), null) } else { - addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = newValueTr.resultReg, reg2 = addressTr.resultReg, immediate = finalOffset), null) + addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, newValueTr.resultReg, IRMemory.indirect(addressTr.resultReg, finalOffset)), null) } ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1) } @@ -694,7 +697,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val code = IRCodeChunk(null, null) val resultReg = codeGen.registers.next(IRDataType.POINTER) val labelname = SymbolTable.labelnameForStructInstance(call) - code += IRInstruction(Opcode.LOAD, IRDataType.POINTER, reg1=resultReg, labelSymbol = "${StStructInstanceBlockName}.$labelname") + code += IRInstructions.loadAddress(IRDataType.POINTER, resultReg, "${StStructInstanceBlockName}.$labelname") return ExpressionCodeResult(code, IRDataType.POINTER, resultReg, -1) } @@ -704,9 +707,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe addToResult(result, tr, tr.resultReg, -1) val resultReg = codeGen.registers.next(IRDataType.BYTE) if(fromLong) - addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1 = resultReg, reg2 = tr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, resultReg, tr.resultReg), null) else - addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = resultReg, reg2 = tr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, resultReg, tr.resultReg), null) // note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here. return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1) } @@ -720,20 +723,20 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe if(codeGen.options.compTarget.cpu.usesM68kConvention) { // m68k: 3 return values in D0 (slot 10), D1 (slot 11), D2 (slot 12) // store high byte first, then mid, then low (to avoid clobbering) - addInstr(result, IRInstruction(Opcode.BSIGB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null) - addInstr(result, IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1 = byteReg, immediate = 12), null) - addInstr(result, IRInstruction(Opcode.MIDB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null) - addInstr(result, IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1 = byteReg, immediate = 11), null) - addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null) - addInstr(result, IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1 = byteReg, immediate = 10), null) + addInstr(result, IRInstructions.binary(Opcode.BSIGB, IRDataType.LONG, byteReg, tr.resultReg), null) + addInstr(result, IRInstructions.hardwareStore(IRDataType.BYTE, byteReg, CallingConventionSlot(12)), null) + addInstr(result, IRInstructions.binary(Opcode.MIDB, IRDataType.LONG, byteReg, tr.resultReg), null) + addInstr(result, IRInstructions.hardwareStore(IRDataType.BYTE, byteReg, CallingConventionSlot(11)), null) + addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, byteReg, tr.resultReg), null) + addInstr(result, IRInstructions.hardwareStore(IRDataType.BYTE, byteReg, CallingConventionSlot(10)), null) } else { // 6502/cx16: low byte in A (slot 0), mid in cx16.r15, high (bank) in cx16.r14 - addInstr(result, IRInstruction(Opcode.BSIGB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null) - addInstr(result, IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = byteReg, labelSymbol = "cx16.r14"), null) - addInstr(result, IRInstruction(Opcode.MIDB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null) - addInstr(result, IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = byteReg, labelSymbol = "cx16.r15"), null) - addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null) - addInstr(result, IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1 = byteReg, immediate = 0), null) + addInstr(result, IRInstructions.binary(Opcode.BSIGB, IRDataType.LONG, byteReg, tr.resultReg), null) + addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, byteReg, IRMemory.direct("cx16.r14")), null) + addInstr(result, IRInstructions.binary(Opcode.MIDB, IRDataType.LONG, byteReg, tr.resultReg), null) + addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, byteReg, IRMemory.direct("cx16.r15")), null) + addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, byteReg, tr.resultReg), null) + addInstr(result, IRInstructions.hardwareStore(IRDataType.BYTE, byteReg, CallingConventionSlot(0)), null) } return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) } @@ -743,7 +746,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val tr = exprGen.translateExpression(call.args.single()) addToResult(result, tr, tr.resultReg, -1) val resultReg = codeGen.registers.next(IRDataType.WORD) - addInstr(result, IRInstruction(Opcode.LSIGW, IRDataType.LONG, reg1 = resultReg, reg2 = tr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.LSIGW, IRDataType.LONG, resultReg, tr.resultReg), null) return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1) } @@ -753,9 +756,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe addToResult(result, tr, tr.resultReg, -1) val resultReg = codeGen.registers.next(IRDataType.BYTE) if(fromLong) - addInstr(result, IRInstruction(Opcode.MSIGB, IRDataType.LONG, reg1 = resultReg, reg2 = tr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.MSIGB, IRDataType.LONG, resultReg, tr.resultReg), null) else - addInstr(result, IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = resultReg, reg2 = tr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, resultReg, tr.resultReg), null) // note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here. return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1) } @@ -765,7 +768,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val tr = exprGen.translateExpression(call.args.single()) addToResult(result, tr, tr.resultReg, -1) val resultReg = codeGen.registers.next(IRDataType.WORD) - addInstr(result, IRInstruction(Opcode.MSIGW, IRDataType.LONG, reg1 = resultReg, reg2 = tr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.MSIGW, IRDataType.LONG, resultReg, tr.resultReg), null) return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1) } @@ -783,13 +786,13 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val ident = arg as? PtIdentifier if(ident!=null) { - addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, labelSymbol = ident.name), null) + addInstr(result, IRInstructions.memoryOp(opcodeMemAndReg.first, vmDt, IRMemory.direct(ident.name)), null) return ExpressionCodeResult(result, vmDt, -1, -1) } val memAddr: UInt? = (arg as? PtMemoryByte)?.address?.asConstInteger()?.toUInt() if(memAddr!=null) { - addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, address = memAddr.toAddress()), null) + addInstr(result, IRInstructions.memoryOp(opcodeMemAndReg.first, vmDt, IRMemory.direct(memAddr.toAddress())), null) return ExpressionCodeResult(result, vmDt, -1, -1) } @@ -801,19 +804,19 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe result += IRCodeChunk(null, null).also { when(opcodeMemAndReg.first) { Opcode.ROXRM, Opcode.RORM -> { - it += IRInstruction(opcodeMemAndReg.first, IRDataType.BYTE, labelSymbol = "${variable}_msb", symbolOffset = index) - it += IRInstruction(opcodeMemAndReg.first, IRDataType.BYTE, labelSymbol = "${variable}_lsb", symbolOffset = index) + it += IRInstructions.memoryOp(opcodeMemAndReg.first, IRDataType.BYTE, IRMemory.direct("${variable}_msb", index)) + it += IRInstructions.memoryOp(opcodeMemAndReg.first, IRDataType.BYTE, IRMemory.direct("${variable}_lsb", index)) } Opcode.ROXLM, Opcode.ROLM -> { - it += IRInstruction(opcodeMemAndReg.first, IRDataType.BYTE, labelSymbol = "${variable}_lsb", symbolOffset = index) - it += IRInstruction(opcodeMemAndReg.first, IRDataType.BYTE, labelSymbol = "${variable}_msb", symbolOffset = index) + it += IRInstructions.memoryOp(opcodeMemAndReg.first, IRDataType.BYTE, IRMemory.direct("${variable}_lsb", index)) + it += IRInstructions.memoryOp(opcodeMemAndReg.first, IRDataType.BYTE, IRMemory.direct("${variable}_msb", index)) } else -> throw AssemblyError("wrong rol/ror opcode") } } } else { val offset = codeGen.program.memsizer.memorySize(arr.type, index) - addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, labelSymbol = variable, symbolOffset = offset), null) + addInstr(result, IRInstructions.memoryOp(opcodeMemAndReg.first, vmDt, IRMemory.direct(variable, offset)), null) } return ExpressionCodeResult(result, vmDt, -1, -1) } @@ -821,17 +824,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val opcode = opcodeMemAndReg.second val saveCarry = opcode in OpcodesThatDependOnCarry && !arg.isSimple() if(saveCarry) - addInstr(result, IRInstruction(Opcode.PUSHST), null) // save Carry + addInstr(result, IRInstructions.simple(Opcode.PUSHST), null) // save Carry val tr = exprGen.translateExpression(arg) addToResult(result, tr, tr.resultReg, -1) if(saveCarry) - addInstr(result, IRInstruction(Opcode.POPST), null) - addInstr(result, IRInstruction(opcode, vmDt, reg1 = tr.resultReg), null) + addInstr(result, IRInstructions.simple(Opcode.POPST), null) + addInstr(result, IRInstructions.unary(opcode, vmDt, tr.resultReg), null) if(saveCarry) - addInstr(result, IRInstruction(Opcode.PUSHST), null) // save Carry + addInstr(result, IRInstructions.simple(Opcode.PUSHST), null) // save Carry result += codeGen.assignRegisterTo(arg, tr.resultReg) if(saveCarry) - addInstr(result, IRInstruction(Opcode.POPST), null) + addInstr(result, IRInstructions.simple(Opcode.POPST), null) return ExpressionCodeResult(result, vmDt, -1, -1) } @@ -847,16 +850,16 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe if(isConstZeroValue) { result += IRCodeChunk(null, null).also { val pointerReg = codeGen.registers.next(IRDataType.POINTER) - it += IRInstruction(Opcode.LOAD, IRDataType.POINTER, reg1 = pointerReg, labelSymbol = target.name) - it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = pointerReg, immediate = offset) + it += IRInstructions.loadAddress(IRDataType.POINTER, pointerReg, target.name) + it += IRInstructions.storeZero(Opcode.STOREZI, IRDataType.BYTE, IRMemory.indirect(pointerReg, offset)) } } else { val valueTr = exprGen.translateExpression(call.args[1]) addToResult(result, valueTr, valueTr.resultReg, -1) result += IRCodeChunk(null, null).also { val pointerReg = codeGen.registers.next(IRDataType.POINTER) - it += IRInstruction(Opcode.LOAD, IRDataType.POINTER, reg1 = pointerReg, labelSymbol = target.name) - it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = pointerReg, immediate = offset) + it += IRInstructions.loadAddress(IRDataType.POINTER, pointerReg, target.name) + it += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, valueTr.resultReg, IRMemory.indirect(pointerReg, offset)) } } } @@ -871,22 +874,22 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe if(constIndex != null) { val offset = eltSize * constIndex + elementByteOffset if(offset > 0) - addInstr(result, IRInstruction(Opcode.ADD, IRDataType.POINTER, reg1 = pointerTr.resultReg, immediate = offset), null) + addInstr(result, IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.POINTER, pointerTr.resultReg, offset), null) } else { val (code, indexWordReg) = codeGen.loadIndexReg(target.index, eltSize, true, false) result += code if(eltSize!=1) result += codeGen.multiplyByConst(DataType.UWORD, indexWordReg, eltSize) - addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.POINTER, reg1 = pointerTr.resultReg, reg2 = indexWordReg), null) + addInstr(result, IRInstructions.binary(Opcode.ADDR, IRDataType.POINTER, pointerTr.resultReg, indexWordReg), null) if(elementByteOffset > 0) - addInstr(result, IRInstruction(Opcode.ADD, IRDataType.POINTER, reg1 = pointerTr.resultReg, immediate = elementByteOffset), null) + addInstr(result, IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.POINTER, pointerTr.resultReg, elementByteOffset), null) } if(isConstZeroValue) { - addInstr(result, IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = pointerTr.resultReg, immediate = 0), null) + addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, IRDataType.BYTE, IRMemory.indirect(pointerTr.resultReg)), null) } else { val valueTr = exprGen.translateExpression(call.args[1]) addToResult(result, valueTr, valueTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = pointerTr.resultReg, immediate = 0), null) + addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, valueTr.resultReg, IRMemory.indirect(pointerTr.resultReg)), null) } } else if(target.splitWords) { @@ -898,15 +901,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val idxType = codeGen.options.compTarget.indexRegType val offsetReg = codeGen.registers.next(idxType) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, idxType, reg1=offsetReg, immediate = constIndex) - it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = varName) + it += IRInstructions.load(idxType, offsetReg, constIndex) + it += IRInstructions.storeZero(Opcode.STOREZX, IRDataType.BYTE, IRMemory.indexed(varName, offsetReg, codeGen.options.compTarget.indexRegType)) } } else { val indexTr = exprGen.translateExpression(target.index) addToResult(result, indexTr, indexTr.resultReg, -1) val indexReg = codeGen.canonicalizeIndexReg(result, indexTr) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexReg, labelSymbol = varName) + it += IRInstructions.storeZero(Opcode.STOREZX, IRDataType.BYTE, IRMemory.indexed(varName, indexReg, codeGen.options.compTarget.indexRegType)) } } } else { @@ -916,15 +919,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val idxType = codeGen.options.compTarget.indexRegType val offsetReg = codeGen.registers.next(idxType) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, idxType, reg1=offsetReg, immediate = constIndex) - it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = varName) + it += IRInstructions.load(idxType, offsetReg, constIndex) + it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, valueTr.resultReg, IRMemory.indexed(varName, offsetReg, codeGen.options.compTarget.indexRegType)) } } else { val indexTr = exprGen.translateExpression(target.index) addToResult(result, indexTr, indexTr.resultReg, -1) val indexReg = codeGen.canonicalizeIndexReg(result, indexTr) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexReg, labelSymbol = varName) + it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, valueTr.resultReg, IRMemory.indexed(varName, indexReg, codeGen.options.compTarget.indexRegType)) } } } @@ -939,15 +942,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val offsetReg = codeGen.registers.next(if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE) val offset = eltSize*constIndex + byteOffset(eltSize) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE, reg1=offsetReg, immediate = offset) - it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = targetVariable.name) + it += IRInstructions.load(if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE, offsetReg, offset) + it += IRInstructions.storeZero(Opcode.STOREZX, IRDataType.BYTE, IRMemory.indexed(targetVariable.name, offsetReg, codeGen.options.compTarget.indexRegType)) } } else { val (code, indexReg) = codeGen.loadIndexReg(target.index, eltSize, codeGen.wordArrayIndex, false) result += code result += IRCodeChunk(null, null).also { val offset = byteOffset(eltSize) - it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexReg, labelSymbol = targetVariable.name, symbolOffset = if(offset!=0) offset else null, scale = eltSize) + it += IRInstructions.storeZero(Opcode.STOREZX, IRDataType.BYTE, IRMemory.indexed(targetVariable.name, indexReg, codeGen.options.compTarget.indexRegType, scale = eltSize, displacement = offset)) } } } else { @@ -957,15 +960,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val offsetReg = codeGen.registers.next(if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE) val offset = eltSize*constIndex + byteOffset(eltSize) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE, reg1=offsetReg, immediate = offset) - it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = targetVariable.name) + it += IRInstructions.load(if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE, offsetReg, offset) + it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, valueTr.resultReg, IRMemory.indexed(targetVariable.name, offsetReg, codeGen.options.compTarget.indexRegType)) } } else { val (code, indexReg) = codeGen.loadIndexReg(target.index, eltSize, codeGen.wordArrayIndex, false) result += code result += IRCodeChunk(null, null).also { val offset = byteOffset(eltSize) - it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexReg, labelSymbol = targetVariable.name, symbolOffset = if(offset!=0) offset else null, scale = eltSize) + it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, valueTr.resultReg, IRMemory.indexed(targetVariable.name, indexReg, codeGen.options.compTarget.indexRegType, scale = eltSize, displacement = offset)) } } } @@ -986,10 +989,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe result += idxCode return if(isZero) { result += IRCodeChunk(null, null).also { - if(info.fieldOffset==0) - it += IRInstruction(Opcode.STOREZX, dt, reg1=indexReg, labelSymbol=info.arrayName, scale=info.structSize) - else - it += IRInstruction(Opcode.STOREZX, dt, reg1=indexReg, labelSymbol=info.arrayName, symbolOffset=info.fieldOffset, scale=info.structSize) + it += IRInstructions.storeZero(Opcode.STOREZX, dt, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset)) } ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) } else { @@ -997,15 +997,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg) result += IRCodeChunk(null, null).also { if(dt==IRDataType.FLOAT) { - if(info.fieldOffset==0) - it += IRInstruction(Opcode.STOREX, dt, reg1=indexReg, fpReg1=RegisterNum(valueTr.resultFpReg), labelSymbol=info.arrayName, scale=info.structSize) - else - it += IRInstruction(Opcode.STOREX, dt, reg1=indexReg, fpReg1=RegisterNum(valueTr.resultFpReg), labelSymbol=info.arrayName, symbolOffset=info.fieldOffset, scale=info.structSize) + it += IRInstructions.storeMemory(Opcode.STOREX, dt, valueTr.resultFpReg, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset)) } else { - if(info.fieldOffset==0) - it += IRInstruction(Opcode.STOREX, dt, reg1=valueTr.resultReg, reg2=indexReg, labelSymbol=info.arrayName, scale=info.structSize) - else - it += IRInstruction(Opcode.STOREX, dt, reg1=valueTr.resultReg, reg2=indexReg, labelSymbol=info.arrayName, symbolOffset=info.fieldOffset, scale=info.structSize) + it += IRInstructions.storeMemory(Opcode.STOREX, dt, valueTr.resultReg, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset)) } } ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt index cb6927f0b..00976615e 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt @@ -35,7 +35,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { is PtBool -> { val code = IRCodeChunk(null, null) val resultRegister = codeGen.registers.next(IRDataType.BYTE) - code += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultRegister, immediate = expr.asInt()) + code += IRInstructions.load(IRDataType.BYTE, resultRegister, expr.asInt()) ExpressionCodeResult(code, IRDataType.BYTE, resultRegister, -1) } is PtNumber -> { @@ -43,12 +43,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val code = IRCodeChunk(null, null) if(vmDt==IRDataType.FLOAT) { val resultFpRegister = codeGen.registers.next(IRDataType.FLOAT) - code += IRInstruction(Opcode.LOAD, vmDt, fpReg1 = RegisterNum(resultFpRegister), immediateFp = expr.number) + code += IRInstructions.loadFloat(resultFpRegister, expr.number) ExpressionCodeResult(code, vmDt,-1, resultFpRegister) } else { val resultRegister = codeGen.registers.next(vmDt) - code += IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, immediate = expr.number.toInt()) + code += IRInstructions.load(vmDt, resultRegister, expr.number.toInt()) ExpressionCodeResult(code, vmDt, resultRegister, -1) } } @@ -63,18 +63,18 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { if(vmDt==IRDataType.FLOAT) { val resultFpRegister = codeGen.registers.next(IRDataType.FLOAT) if (isConstant) { - code += IRInstruction(Opcode.LOAD, vmDt, fpReg1 = RegisterNum(resultFpRegister), labelSymbol = expr.name) + code += IRInstructions.loadAddress(IRDataType.FLOAT, resultFpRegister, expr.name) } else { - code += IRInstruction(Opcode.LOADM, vmDt, fpReg1 = RegisterNum(resultFpRegister), labelSymbol = expr.name) + code += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.FLOAT, resultFpRegister, IRMemory.direct(expr.name)) } ExpressionCodeResult(code, vmDt, -1, resultFpRegister) } else { val resultRegister = codeGen.registers.next(vmDt) if (isConstant) { - code += IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, labelSymbol = expr.name) + code += IRInstructions.loadAddress(vmDt, resultRegister, expr.name) } else { - code += IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = expr.name) + code += IRInstructions.loadMemory(Opcode.LOADM, vmDt, resultRegister, IRMemory.direct(expr.name)) } ExpressionCodeResult(code, vmDt, resultRegister, -1) } @@ -84,7 +84,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val vmDt = if(expr.type.isUndefined) IRDataType.WORD else codeGen.irType(expr.type) val resultRegister = codeGen.registers.next(vmDt) val labelsymbol = if(expr.type.isSplitWordArray(codeGen.options.compTarget)) expr.name+"_lsb" else expr.name - code += IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, labelSymbol = labelsymbol) + code += IRInstructions.loadAddress(vmDt, resultRegister, labelsymbol) ExpressionCodeResult(code, vmDt, resultRegister, -1) } } @@ -106,14 +106,14 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val slabDt = codeGen.irType(expr.type) val code = IRCodeChunk(null, null) val resultReg = codeGen.registers.next(slabDt) - code += IRInstruction(Opcode.LOAD, slabDt, reg1=resultReg, labelSymbol = "${StMemorySlabBlockName}.${slab.name}") + code += IRInstructions.loadAddress(slabDt, resultReg, "${StMemorySlabBlockName}.${slab.name}") ExpressionCodeResult(code, slabDt, resultReg, -1) } else if(expr.value != null) { TODO("didn't expect PtConstant to appear here ${expr.position}") // val vmDt = irType(expr.type) // val code = IRCodeChunk(null, null) // val resultRegister = codeGen.registers.next(vmDt) -// code += IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, immediate = expr.value!!.toInt()) +// code += IRInstructions.load(vmDt, resultRegister, expr.value!!.toInt()) // ExpressionCodeResult(code, vmDt, resultRegister, -1) } else { // Regular constant reference - look up in symbol table @@ -121,7 +121,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { // val vmDt = irType(expr.type) // val code = IRCodeChunk(null, null) // val resultRegister = codeGen.registers.next(vmDt) -// code += IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, labelSymbol = name) +// code += IRInstructions.loadAddress(vmDt, resultRegister, name) // ExpressionCodeResult(code, vmDt, resultRegister, -1) } } @@ -165,7 +165,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val constIndex = arrayIndexer.index.asConstInteger() if(constIndex!=null) { val offset = constIndex * struct.size.toInt() - addInstr(result, IRInstruction(Opcode.ADD, IRDataType.POINTER, reg1 = pointerReg, immediate = offset), null) + addInstr(result, IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.POINTER, pointerReg, offset), null) } else { val indexTr = translateExpression(arrayIndexer.index) result += indexTr.chunks @@ -176,21 +176,21 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { if(arrayIndexer.index.type.isByte) { // extend array index to word indexReg = codeGen.registers.next(IRDataType.WORD) - it += IRInstruction(Opcode.EXT, IRDataType.BYTE, indexReg, indexTr.resultReg) + it += IRInstructions.binary(Opcode.EXT, IRDataType.BYTE, indexReg, indexTr.resultReg) } else { indexReg = indexTr.resultReg } it += codeGen.multiplyByConst(DataType.UWORD, indexReg, struct.size.toInt()) - it += IRInstruction(Opcode.ADDR, IRDataType.POINTER, reg1 = pointerReg, reg2 = indexReg) + it += IRInstructions.binary(Opcode.ADDR, IRDataType.POINTER, pointerReg, indexReg) } } result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.ADD, IRDataType.POINTER, reg1 = pointerReg, immediate = firstField.second.toInt()) + it += IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.POINTER, pointerReg, firstField.second.toInt()) if (firstField.first.isPointer) { // get the address stored in the pointer and use that for the rest of the chain // LOADI has an exception to allo reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs - it += IRInstruction(Opcode.LOADI, IRDataType.POINTER, reg1 = pointerReg, reg2 = pointerReg) + it += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.POINTER, pointerReg, IRMemory.indirect(pointerReg)) } else { require(chain.isEmpty()) // it's a pointer to a simple value, so keep the pointer as-is @@ -215,18 +215,18 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { // For inline array fields (not pointer fields), return the address (ptr+offset) instead of loading from it if(deref.type.isArray && !deref.derefLast) { if(offset > 0u) - addInstr(result, IRInstruction(Opcode.ADD, IRDataType.POINTER, reg1 = pointerReg, immediate = offset.toInt()), null) + addInstr(result, IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.POINTER, pointerReg, offset.toInt()), null) return ExpressionCodeResult(result, IRDataType.POINTER, pointerReg, -1) } return if(deref.type.isFloat) { val resultReg = codeGen.registers.next(IRDataType.FLOAT) - addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1 = RegisterNum(resultReg), reg1 = pointerReg, immediate = offset.toInt()), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, IRDataType.FLOAT, resultReg, IRMemory.indirect(pointerReg, offset.toInt())), null) ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultReg) } else { val irdt = codeGen.irType(deref.type) val resultReg = codeGen.registers.next(irdt) - addInstr(result, IRInstruction(Opcode.LOADI, irdt, reg1 = resultReg, reg2 = pointerReg, immediate = offset.toInt()), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, irdt, resultReg, IRMemory.indirect(pointerReg, offset.toInt())), null) ExpressionCodeResult(result, irdt, resultReg, -1) } } @@ -251,13 +251,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val bitPos = Integer.numberOfTrailingZeros(bitmask) val tr = translateExpression(expr) addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.BITTST, tr.dt, reg1 = tr.resultReg, immediate = bitPos), null) + addInstr(result, bitTest(tr.dt, tr.resultReg, bitPos), null) if(testBitSet) - addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = falseLabel), null) + addInstr(result, IRInstructions.branch(Opcode.BSTEQ, codeLabel(falseLabel)), null) else - addInstr(result, IRInstruction(Opcode.BSTNE, labelSymbol = falseLabel), null) + addInstr(result, IRInstructions.branch(Opcode.BSTNE, codeLabel(falseLabel)), null) addToResult(result, trueTr, trueTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null) + addInstr(result, IRInstructions.jump(codeLabel(endLabel)), null) result += IRCodeChunk(falseLabel, null) addToResult(result, falseTr, trueTr.resultReg, -1) result += IRCodeChunk(endLabel, null) @@ -278,20 +278,20 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { && lastInstr != null && lastInstr.opcode in OpcodesThatSetZeroFlagOnM68k if (!skipCmpi) { - addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=condTr.resultReg, immediate = 0), null) + addInstr(result, IRInstructions.compareImmediate(IRDataType.BYTE, condTr.resultReg, 0), null) } - addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = falseLabel), null) + addInstr(result, IRInstructions.branch(Opcode.BSTEQ, codeLabel(falseLabel)), null) if (irDt != IRDataType.FLOAT) { addToResult(result, trueTr, trueTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null) + addInstr(result, IRInstructions.jump(codeLabel(endLabel)), null) result += IRCodeChunk(falseLabel, null) addToResult(result, falseTr, trueTr.resultReg, -1) result += IRCodeChunk(endLabel, null) return ExpressionCodeResult(result, irDt, trueTr.resultReg, -1) } else { addToResult(result, trueTr, -1, trueTr.resultFpReg) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null) + addInstr(result, IRInstructions.jump(codeLabel(endLabel)), null) result += IRCodeChunk(falseLabel, null) addToResult(result, falseTr, -1, trueTr.resultFpReg) result += IRCodeChunk(endLabel, null) @@ -310,16 +310,16 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { if(branchExpr.condition==BranchCondition.CC && irDt==IRDataType.BYTE) { if(branchExpr.truevalue.asConstInteger()==0 && branchExpr.falsevalue.asConstInteger()==1) { result.add(IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, irDt, reg1=trueTr.resultReg, immediate = 0) - it += IRInstruction(Opcode.ROXL, irDt, reg1=trueTr.resultReg) + it += IRInstructions.load(irDt, trueTr.resultReg, 0) + it += IRInstructions.unary(Opcode.ROXL, irDt, trueTr.resultReg) }) return ExpressionCodeResult(result, irDt, trueTr.resultReg, -1) } else if(branchExpr.truevalue.asConstInteger()==1 && branchExpr.falsevalue.asConstInteger()==0) { result.add(IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, irDt, reg1=trueTr.resultReg, immediate = 0) - it += IRInstruction(Opcode.ROXL, irDt, reg1=trueTr.resultReg) - it += IRInstruction(Opcode.XOR, irDt, reg1=trueTr.resultReg, immediate = 1) + it += IRInstructions.load(irDt, trueTr.resultReg, 0) + it += IRInstructions.unary(Opcode.ROXL, irDt, trueTr.resultReg) + it += IRInstructions.binaryImmediate(Opcode.XOR, irDt, trueTr.resultReg, 1) }) return ExpressionCodeResult(result, irDt, trueTr.resultReg, -1) } @@ -327,16 +327,16 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { else if(branchExpr.condition==BranchCondition.CS) { if(branchExpr.truevalue.asConstInteger()==0 && branchExpr.falsevalue.asConstInteger()==1) { result.add(IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, irDt, reg1=trueTr.resultReg, immediate = 0) - it += IRInstruction(Opcode.ROXL, irDt, reg1=trueTr.resultReg) - it += IRInstruction(Opcode.XOR, irDt, reg1=trueTr.resultReg, immediate = 1) + it += IRInstructions.load(irDt, trueTr.resultReg, 0) + it += IRInstructions.unary(Opcode.ROXL, irDt, trueTr.resultReg) + it += IRInstructions.binaryImmediate(Opcode.XOR, irDt, trueTr.resultReg, 1) }) return ExpressionCodeResult(result, irDt, trueTr.resultReg, -1) } else if(branchExpr.truevalue.asConstInteger()==1 && branchExpr.falsevalue.asConstInteger()==0) { result.add(IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, irDt, reg1=trueTr.resultReg, immediate = 0) - it += IRInstruction(Opcode.ROXL, irDt, reg1=trueTr.resultReg) + it += IRInstructions.load(irDt, trueTr.resultReg, 0) + it += IRInstructions.unary(Opcode.ROXL, irDt, trueTr.resultReg) }) return ExpressionCodeResult(result, irDt, trueTr.resultReg, -1) } @@ -347,14 +347,14 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { if (irDt != IRDataType.FLOAT) { addToResult(result, falseTr, trueTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null) + addInstr(result, IRInstructions.jump(codeLabel(endLabel)), null) result += IRCodeChunk(trueLabel, null) addToResult(result, trueTr, trueTr.resultReg, -1) result += IRCodeChunk(endLabel, null) return ExpressionCodeResult(result, irDt, trueTr.resultReg, -1) } else { addToResult(result, falseTr, -1, trueTr.resultFpReg) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null) + addInstr(result, IRInstructions.jump(codeLabel(endLabel)), null) result += IRCodeChunk(trueLabel, null) addToResult(result, trueTr, -1, trueTr.resultFpReg) result += IRCodeChunk(endLabel, null) @@ -370,12 +370,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { fun loadAddressOfArrayLabel(reg: Int) { if (expr.isMsbForSplitArray) { - addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = reg, labelSymbol = identifier!!.name + "_msb"), null) + addInstr(result, IRInstructions.loadAddress(vmDt, reg, identifier!!.name + "_msb"), null) } else if (identifier!!.type.isSplitWordArray(codeGen.options.compTarget)) { // the _lsb split array comes first in memory - addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = reg, labelSymbol = identifier.name + "_lsb"), null) + addInstr(result, IRInstructions.loadAddress(vmDt, reg, identifier.name + "_lsb"), null) } else - addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = reg, labelSymbol = identifier.name), null) + addInstr(result, IRInstructions.loadAddress(vmDt, reg, identifier.name), null) } if(expr.isFromArrayElement) { @@ -383,7 +383,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { addToResult(result, indexTr, indexTr.resultReg, -1) val indexWordReg = if(indexTr.dt==IRDataType.BYTE) { val ixWord = codeGen.registers.next(IRDataType.WORD) - addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=ixWord, reg2=indexTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.EXT, IRDataType.BYTE, ixWord, indexTr.resultReg), null) ixWord } else indexTr.resultReg val resultRegister = codeGen.registers.next(vmDt) @@ -393,13 +393,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val ptr = codeGen.symbolTable.lookup(identifier.name) it += if(ptr is StConstant) { if(ptr.value != null) - IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, immediate = ptr.value!!.toInt()) + IRInstructions.load(vmDt, resultRegister, ptr.value!!.toInt()) else TODO("LOAD memory() address? ${ptr}") } else - IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = identifier.name) - it += IRInstruction(Opcode.ADDR, IRDataType.POINTER, reg1=resultRegister, reg2=indexWordReg) + IRInstructions.loadMemory(Opcode.LOADM, vmDt, resultRegister, IRMemory.direct(identifier.name)) + it += IRInstructions.binary(Opcode.ADDR, IRDataType.POINTER, resultRegister, indexWordReg) } } else if(identifier.type.isPointer) { // apply pointer arithmetic for the array indexing @@ -408,11 +408,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { else identifier.type.subType!!.memsize(codeGen.program.memsizer) result += IRCodeChunk(null, null).also { - addInstr(result, IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = identifier.name), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, vmDt, resultRegister, IRMemory.direct(identifier.name)), null) if (eltSize > 1) { it += codeGen.multiplyByConst(DataType.UWORD, indexWordReg, eltSize) } - it += IRInstruction(Opcode.ADDR, IRDataType.POINTER, reg1 = resultRegister, reg2 = indexWordReg) + it += IRInstructions.binary(Opcode.ADDR, IRDataType.POINTER, resultRegister, indexWordReg) } } else { // regular array indexing @@ -422,7 +422,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { if (eltSize > 1 && !identifier.type.isSplitWordArray(codeGen.options.compTarget)) { it += codeGen.multiplyByConst(DataType.UWORD, indexWordReg, eltSize) } - it += IRInstruction(Opcode.ADDR, IRDataType.POINTER, reg1 = resultRegister, reg2 = indexWordReg) + it += IRInstructions.binary(Opcode.ADDR, IRDataType.POINTER, resultRegister, indexWordReg) } } return ExpressionCodeResult(result, vmDt, resultRegister, -1) @@ -436,7 +436,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { result += pointerTr.chunks val (instructions, offset) = traverseRestOfDerefChainToCalculateFinalAddress(expr.dereference!!, pointerTr.resultReg) result += instructions - addInstr(result, IRInstruction(Opcode.ADD, IRDataType.POINTER, reg1 = pointerTr.resultReg, immediate = offset.toInt()), null) + addInstr(result, IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.POINTER, pointerTr.resultReg, offset.toInt()), null) return ExpressionCodeResult(result, vmDt, pointerTr.resultReg, -1) } } @@ -447,7 +447,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val constAddress = mem.address as? PtNumber if(constAddress!=null) { - addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=resultRegister, address = constAddress.number.toUInt().toAddress()), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, resultRegister, IRMemory.direct(constAddress.number.toUInt().toAddress())), null) return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1) } @@ -455,14 +455,14 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { if(ptrWithOffset!=null) { if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) { val constOffset = (ptrWithOffset.right as? PtNumber)?.number?.toInt() - if(constOffset in 0..65535) { + if(constOffset!=null && constOffset in 0..65535) { val ptrIdentifier = ptrWithOffset.left as PtIdentifier val dt = if(codeGen.options.compTarget.POINTER_MEM_SIZE > 2u && ptrIdentifier.type.isLong) IRDataType.POINTER else IRDataType.WORD val pointerReg = codeGen.registers.next(dt) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADM, dt, reg1 = pointerReg, labelSymbol = ptrIdentifier.name) - it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=resultRegister, reg2=pointerReg, immediate = constOffset) + it += IRInstructions.loadMemory(Opcode.LOADM, dt, pointerReg, IRMemory.direct(ptrIdentifier.name)) + it += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, resultRegister, IRMemory.indirect(pointerReg, constOffset)) } return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1) } @@ -471,7 +471,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val tr = translateExpression(mem.address) addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=resultRegister, reg2=tr.resultReg, immediate = 0), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, resultRegister, IRMemory.indirect(tr.resultReg, 0)), null) return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1) } @@ -495,13 +495,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val boolResultRegister = if(elementDt.isByteOrBool) elementTr.resultReg else codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { for(value in haystack){ - it += IRInstruction(Opcode.CMPI, codeGen.irType(elementDt), elementTr.resultReg, immediate = value) - it += IRInstruction(Opcode.BSTEQ, labelSymbol = gottemLabel) + it += IRInstructions.compareImmediate(codeGen.irType(elementDt), elementTr.resultReg, value) + it += IRInstructions.branch(Opcode.BSTEQ, codeLabel(gottemLabel)) } - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, boolResultRegister, immediate = 0) - it += IRInstruction(Opcode.JUMP, labelSymbol = endLabel) + it += IRInstructions.load(IRDataType.BYTE, boolResultRegister, 0) + it += IRInstructions.jump(codeLabel(endLabel)) } - addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, boolResultRegister, immediate = 1), gottemLabel) + addInstr(result, IRInstructions.load(IRDataType.BYTE, boolResultRegister, 1), gottemLabel) result += IRCodeChunk(endLabel, null) return ExpressionCodeResult(result, IRDataType.BYTE, boolResultRegister, -1) } @@ -520,7 +520,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { addToResult(result, iterableTr, iterableTr.resultReg, -1) val resultReg = codeGen.registers.next(IRDataType.BYTE) result += codeGen.makeSyscall(IMSyscall.STRING_CONTAINS, listOf(IRDataType.BYTE to elementTr.resultReg, addressDt to iterableTr.resultReg), IRDataType.BYTE to resultReg) - addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=resultReg, immediate = 0), null) + addInstr(result, IRInstructions.compareImmediate(IRDataType.BYTE, resultReg, 0), null) return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1) } haystackVar.type.isByteArray -> { @@ -530,10 +530,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { addToResult(result, iterableTr, iterableTr.resultReg, -1) val lengthReg = codeGen.registers.next(IRDataType.BYTE) val iterableLength = codeGen.symbolTable.getLength(haystackVar.name) - addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null) + addInstr(result, IRInstructions.load(IRDataType.BYTE, lengthReg, iterableLength!!), null) val resultReg = codeGen.registers.next(IRDataType.BYTE) result += codeGen.makeSyscall(IMSyscall.BYTEARRAY_CONTAINS, listOf(IRDataType.BYTE to elementTr.resultReg, addressDt to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg) - addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=resultReg, immediate = 0), null) + addInstr(result, IRInstructions.compareImmediate(IRDataType.BYTE, resultReg, 0), null) return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1) } haystackVar.type.isWordArray -> { @@ -543,11 +543,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { addToResult(result, iterableTr, iterableTr.resultReg, -1) val lengthReg = codeGen.registers.next(IRDataType.BYTE) val iterableLength = codeGen.symbolTable.getLength(haystackVar.name) - addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null) + addInstr(result, IRInstructions.load(IRDataType.BYTE, lengthReg, iterableLength!!), null) val resultReg = codeGen.registers.next(IRDataType.BYTE) val syscall = if(haystackVar.type.isSplitWordArray(codeGen.options.compTarget)) IMSyscall.SPLIT_WORDARRAY_CONTAINS else IMSyscall.WORDARRAY_CONTAINS result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to elementTr.resultReg, addressDt to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg) - addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=resultReg, immediate = 0), null) + addInstr(result, IRInstructions.compareImmediate(IRDataType.BYTE, resultReg, 0), null) return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1) } haystackVar.type.isLongArray -> { @@ -558,9 +558,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val lengthReg = codeGen.registers.next(IRDataType.BYTE) val resultReg = codeGen.registers.next(IRDataType.BYTE) val iterableLength = codeGen.symbolTable.getLength(haystackVar.name) - addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null) + addInstr(result, IRInstructions.load(IRDataType.BYTE, lengthReg, iterableLength!!), null) result += codeGen.makeSyscall(IMSyscall.LONGARRAY_CONTAINS, listOf(IRDataType.LONG to elementTr.resultReg, addressDt to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg) - addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=resultReg, immediate = 0), null) + addInstr(result, IRInstructions.compareImmediate(IRDataType.BYTE, resultReg, 0), null) return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1) } haystackVar.type.isFloatArray -> { @@ -571,9 +571,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val lengthReg = codeGen.registers.next(IRDataType.BYTE) val resultReg = codeGen.registers.next(IRDataType.BYTE) val iterableLength = codeGen.symbolTable.getLength(haystackVar.name) - addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null) + addInstr(result, IRInstructions.load(IRDataType.BYTE, lengthReg, iterableLength!!), null) result += codeGen.makeSyscall(IMSyscall.FLOATARRAY_CONTAINS, listOf(IRDataType.FLOAT to elementTr.resultFpReg, addressDt to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg) - addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=resultReg, immediate = 0), null) + addInstr(result, IRInstructions.compareImmediate(IRDataType.BYTE, resultReg, 0), null) return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1) } else -> throw AssemblyError("weird iterable dt ${haystackVar.type} for ${haystackVar.name}") @@ -614,9 +614,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val memOffset = (arrayIx.index as PtNumber).number.toInt() result += IRCodeChunk(null, null).also { val tmpRegMsb = codeGen.registers.next(IRDataType.BYTE) - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=tmpRegMsb, labelSymbol= "${arrayVarSymbol}_msb", symbolOffset = memOffset) - it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=resultRegister, labelSymbol= "${arrayVarSymbol}_lsb", symbolOffset = memOffset) - it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=tmpRegMsb, reg3=resultRegister) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, tmpRegMsb, IRMemory.direct("${arrayVarSymbol}_msb", memOffset)) + it += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, resultRegister, IRMemory.direct("${arrayVarSymbol}_lsb", memOffset)) + it += IRInstructions.concat(IRDataType.BYTE, finalResultReg, tmpRegMsb, resultRegister) } } else { val tr = translateExpression(arrayIx.index) @@ -624,9 +624,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val indexReg = codeGen.canonicalizeIndexReg(result, tr) result += IRCodeChunk(null, null).also { val tmpRegMsb = codeGen.registers.next(IRDataType.BYTE) - it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2 = indexReg, labelSymbol= "${arrayVarSymbol}_msb") - it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=resultRegister, reg2 = indexReg, labelSymbol= "${arrayVarSymbol}_lsb") - it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=tmpRegMsb, reg3=resultRegister) + it += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, tmpRegMsb, IRMemory.indexed("${arrayVarSymbol}_msb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, resultRegister, IRMemory.indexed("${arrayVarSymbol}_lsb", indexReg, codeGen.options.compTarget.indexRegType)) + it += IRInstructions.concat(IRDataType.BYTE, finalResultReg, tmpRegMsb, resultRegister) } } return ExpressionCodeResult(result, vmDt, finalResultReg, -1) @@ -636,11 +636,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val memOffset = index * eltSize if(vmDt==IRDataType.FLOAT) { resultFpRegister = codeGen.registers.next(IRDataType.FLOAT) - addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1=RegisterNum(resultFpRegister), labelSymbol = arrayVarSymbol, symbolOffset = memOffset), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, IRDataType.FLOAT, resultFpRegister, IRMemory.direct(arrayVarSymbol, memOffset)), null) } else { resultRegister = codeGen.registers.next(vmDt) - addInstr(result, IRInstruction(Opcode.LOADM, vmDt, reg1=resultRegister, labelSymbol = arrayVarSymbol, symbolOffset = memOffset), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, vmDt, resultRegister, IRMemory.direct(arrayVarSymbol, memOffset)), null) } } @@ -650,11 +650,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val scale = if(arrayIx.splitWords) 1 else eltSize if(vmDt==IRDataType.FLOAT) { resultFpRegister = codeGen.registers.next(IRDataType.FLOAT) - addInstr(result, IRInstruction(Opcode.LOADX, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpRegister), reg1=indexReg, labelSymbol = arrayVarSymbol, scale = scale), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADX, IRDataType.FLOAT, resultFpRegister, IRMemory.indexed(arrayVarSymbol, indexReg, codeGen.options.compTarget.indexRegType, scale = scale)), null) } else { resultRegister = codeGen.registers.next(vmDt) - addInstr(result, IRInstruction(Opcode.LOADX, vmDt, reg1=resultRegister, reg2=indexReg, labelSymbol = arrayVarSymbol, scale = scale), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADX, vmDt, resultRegister, IRMemory.indexed(arrayVarSymbol, indexReg, codeGen.options.compTarget.indexRegType, scale = scale)), null) } } @@ -678,23 +678,23 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { if(index is PtNumber) { val memOffset = eltSize * index.number.toInt() if(memOffset>0) - addInstr(result, IRInstruction(Opcode.ADD, IRDataType.POINTER, reg1=pointerReg, immediate = memOffset), null) + addInstr(result, IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.POINTER, pointerReg, memOffset), null) } else { val (code, indexWordReg) = codeGen.loadIndexReg(index, eltSize, true, false) result += code if(eltSize!=1) result += codeGen.multiplyByConst(DataType.UWORD, indexWordReg, eltSize) - addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.POINTER, reg1=pointerReg, reg2=indexWordReg), null) + addInstr(result, IRInstructions.binary(Opcode.ADDR, IRDataType.POINTER, pointerReg, indexWordReg), null) } if(resultDt==IRDataType.FLOAT) { resultFpRegister = codeGen.registers.next(IRDataType.FLOAT) - addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1=RegisterNum(resultFpRegister), reg1=pointerReg, immediate = 0), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, IRDataType.FLOAT, resultFpRegister, IRMemory.indirect(pointerReg, 0)), null) } else { resultRegister = codeGen.registers.next(resultDt) - addInstr(result, IRInstruction(Opcode.LOADI, resultDt, reg1=resultRegister, reg2=pointerReg, immediate = 0), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, resultDt, resultRegister, IRMemory.indirect(pointerReg, 0)), null) } return ExpressionCodeResult(result, resultDt, resultRegister, resultFpRegister) @@ -709,15 +709,15 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { "+" -> { } "-" -> { if(vmDt==IRDataType.FLOAT) - addInstr(result, IRInstruction(Opcode.NEG, vmDt, fpReg1 = RegisterNum(tr.resultFpReg)), null) + addInstr(result, IRInstructions.unary(Opcode.NEG, vmDt, tr.resultFpReg), null) else - addInstr(result, IRInstruction(Opcode.NEG, vmDt, reg1 = tr.resultReg), null) + addInstr(result, IRInstructions.unary(Opcode.NEG, vmDt, tr.resultReg), null) } "~" -> { - addInstr(result, IRInstruction(Opcode.INV, vmDt, reg1 = tr.resultReg), null) + addInstr(result, IRInstructions.unary(Opcode.INV, vmDt, tr.resultReg), null) } "not" -> { - addInstr(result, IRInstruction(Opcode.XOR, vmDt, reg1 = tr.resultReg, immediate = 1), null) + addInstr(result, IRInstructions.binaryImmediate(Opcode.XOR, vmDt, tr.resultReg, 1), null) } else -> throw AssemblyError("weird prefix operator") } @@ -737,22 +737,22 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { BaseDataType.BOOL -> { when { valueDt.isByte -> { - addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=tr.resultReg, immediate = 0), null) + addInstr(result, IRInstructions.compareImmediate(IRDataType.BYTE, tr.resultReg, 0), null) actualResultReg2 = loadStatusAsBooleanResult(Opcode.BSTNE, result) } valueDt.isWord || valueDt.isPointer -> { - addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.WORD, reg1=tr.resultReg, immediate = 0), null) + addInstr(result, IRInstructions.compareImmediate(IRDataType.WORD, tr.resultReg, 0), null) actualResultReg2 = loadStatusAsBooleanResult(Opcode.BSTNE, result) } valueDt.isLong -> { - addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.LONG, reg1=tr.resultReg, immediate = 0), null) + addInstr(result, IRInstructions.compareImmediate(IRDataType.LONG, tr.resultReg, 0), null) actualResultReg2 = loadStatusAsBooleanResult(Opcode.BSTNE, result) } valueDt.isFloat -> { actualResultReg2 = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.SGN, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = RegisterNum(tr.resultFpReg)) - it += IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=actualResultReg2, immediate = 1) + it += IRInstructions.binary(Opcode.SGN, IRDataType.FLOAT, actualResultReg2, tr.resultFpReg) + it += IRInstructions.binaryImmediate(Opcode.AND, IRDataType.BYTE, actualResultReg2, 1) } } else -> throw AssemblyError("weird cast value type ${cast.position}") @@ -765,15 +765,15 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { } BaseDataType.UWORD, BaseDataType.WORD -> { actualResultReg2 = codeGen.registers.next(IRDataType.BYTE) - addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null) + addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, actualResultReg2, tr.resultReg), null) } BaseDataType.LONG -> { actualResultReg2 = codeGen.registers.next(IRDataType.BYTE) - addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null) + addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, actualResultReg2, tr.resultReg), null) } BaseDataType.FLOAT -> { actualResultReg2 = codeGen.registers.next(IRDataType.BYTE) - addInstr(result, IRInstruction(Opcode.FTOUB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = RegisterNum(tr.resultFpReg)), null) + addInstr(result, IRInstructions.binary(Opcode.FTOUB, IRDataType.FLOAT, actualResultReg2, tr.resultFpReg), null) } else -> throw AssemblyError("weird cast value type ${cast.position}") } @@ -785,15 +785,15 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { } BaseDataType.UWORD, BaseDataType.WORD -> { actualResultReg2 = codeGen.registers.next(IRDataType.BYTE) - addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null) + addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, actualResultReg2, tr.resultReg), null) } BaseDataType.LONG -> { actualResultReg2 = codeGen.registers.next(IRDataType.BYTE) - addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null) + addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, actualResultReg2, tr.resultReg), null) } BaseDataType.FLOAT -> { actualResultReg2 = codeGen.registers.next(IRDataType.BYTE) - addInstr(result, IRInstruction(Opcode.FTOSB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = RegisterNum(tr.resultFpReg)), null) + addInstr(result, IRInstructions.binary(Opcode.FTOSB, IRDataType.FLOAT, actualResultReg2, tr.resultFpReg), null) } else -> throw AssemblyError("weird cast value type ${cast.position}") } @@ -803,23 +803,23 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { BaseDataType.BYTE -> { // byte -> uword: sign extend actualResultReg2 = codeGen.registers.next(IRDataType.WORD) - addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.BYTE, reg1 = actualResultReg2, reg2 = tr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.EXTS, IRDataType.BYTE, actualResultReg2, tr.resultReg), null) } BaseDataType.BOOL, BaseDataType.UBYTE -> { // ubyte -> uword: sign extend actualResultReg2 = codeGen.registers.next(IRDataType.WORD) - addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.BYTE, reg1 = actualResultReg2, reg2 = tr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.EXT, IRDataType.BYTE, actualResultReg2, tr.resultReg), null) } BaseDataType.WORD -> { actualResultReg2 = tr.resultReg } BaseDataType.LONG -> { actualResultReg2 = codeGen.registers.next(IRDataType.WORD) - addInstr(result, IRInstruction(Opcode.LSIGW, IRDataType.LONG, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null) + addInstr(result, IRInstructions.binary(Opcode.LSIGW, IRDataType.LONG, actualResultReg2, tr.resultReg), null) } BaseDataType.FLOAT -> { actualResultReg2 = codeGen.registers.next(IRDataType.WORD) - addInstr(result, IRInstruction(Opcode.FTOUW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = RegisterNum(tr.resultFpReg)), null) + addInstr(result, IRInstructions.binary(Opcode.FTOUW, IRDataType.FLOAT, actualResultReg2, tr.resultFpReg), null) } BaseDataType.POINTER -> { actualResultReg2 = tr.resultReg @@ -832,23 +832,23 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { BaseDataType.BYTE -> { // byte -> word: sign extend actualResultReg2 = codeGen.registers.next(IRDataType.WORD) - addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.BYTE, reg1 = actualResultReg2, reg2=tr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.EXTS, IRDataType.BYTE, actualResultReg2, tr.resultReg), null) } BaseDataType.BOOL, BaseDataType.UBYTE -> { // byte -> word: sign extend actualResultReg2 = codeGen.registers.next(IRDataType.WORD) - addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.BYTE, reg1 = actualResultReg2, reg2=tr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.EXT, IRDataType.BYTE, actualResultReg2, tr.resultReg), null) } BaseDataType.UWORD -> { actualResultReg2 = tr.resultReg } BaseDataType.LONG -> { actualResultReg2 = codeGen.registers.next(IRDataType.WORD) - addInstr(result, IRInstruction(Opcode.LSIGW, IRDataType.LONG, reg1=actualResultReg2, reg2=tr.resultReg, immediate = 0), null) + addInstr(result, IRInstructions.binary(Opcode.LSIGW, IRDataType.LONG, actualResultReg2, tr.resultReg), null) } BaseDataType.FLOAT -> { actualResultReg2 = codeGen.registers.next(IRDataType.WORD) - addInstr(result, IRInstruction(Opcode.FTOSW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = RegisterNum(tr.resultFpReg)), null) + addInstr(result, IRInstructions.binary(Opcode.FTOSW, IRDataType.FLOAT, actualResultReg2, tr.resultFpReg), null) } else -> throw AssemblyError("weird cast value type ${cast.position}") } @@ -858,24 +858,24 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { BaseDataType.UBYTE, BaseDataType.BOOL -> { // ubyte to long: single zero-extend byte->long actualResultReg2 = codeGen.registers.next(IRDataType.LONG) - addInstr(result, IRInstruction(Opcode.EXTL, type = IRDataType.BYTE, reg1 = actualResultReg2, reg2=tr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.EXTL, IRDataType.BYTE, actualResultReg2, tr.resultReg), null) } BaseDataType.BYTE -> { // byte to long: single sign-extend byte->long actualResultReg2 = codeGen.registers.next(IRDataType.LONG) - addInstr(result, IRInstruction(Opcode.EXTLS, type = IRDataType.BYTE, reg1 = actualResultReg2, reg2=tr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.EXTLS, IRDataType.BYTE, actualResultReg2, tr.resultReg), null) } BaseDataType.UWORD -> { actualResultReg2 = codeGen.registers.next(IRDataType.LONG) - addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.WORD, reg1 = actualResultReg2, reg2=tr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.EXT, IRDataType.WORD, actualResultReg2, tr.resultReg), null) } BaseDataType.WORD -> { actualResultReg2 = codeGen.registers.next(IRDataType.LONG) - addInstr(result, IRInstruction(Opcode.EXTS, type = IRDataType.WORD, reg1 = actualResultReg2, reg2=tr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.EXTS, IRDataType.WORD, actualResultReg2, tr.resultReg), null) } BaseDataType.FLOAT -> { actualResultReg2 = codeGen.registers.next(IRDataType.LONG) - addInstr(result, IRInstruction(Opcode.FTOSL, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = RegisterNum(tr.resultFpReg)), null) + addInstr(result, IRInstructions.binary(Opcode.FTOSL, IRDataType.FLOAT, actualResultReg2, tr.resultFpReg), null) } BaseDataType.POINTER -> { actualResultReg2 = tr.resultReg @@ -887,19 +887,19 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { actualResultFpReg2 = codeGen.registers.next(IRDataType.FLOAT) when(valueDt.base) { BaseDataType.BOOL, BaseDataType.UBYTE -> { - addInstr(result, IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = RegisterNum(actualResultFpReg2)), null) + addInstr(result, IRInstructions.binary(Opcode.FFROMUB, IRDataType.FLOAT, actualResultFpReg2, tr.resultReg), null) } BaseDataType.BYTE -> { - addInstr(result, IRInstruction(Opcode.FFROMSB, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = RegisterNum(actualResultFpReg2)), null) + addInstr(result, IRInstructions.binary(Opcode.FFROMSB, IRDataType.FLOAT, actualResultFpReg2, tr.resultReg), null) } BaseDataType.UWORD -> { - addInstr(result, IRInstruction(Opcode.FFROMUW, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = RegisterNum(actualResultFpReg2)), null) + addInstr(result, IRInstructions.binary(Opcode.FFROMUW, IRDataType.FLOAT, actualResultFpReg2, tr.resultReg), null) } BaseDataType.WORD -> { - addInstr(result, IRInstruction(Opcode.FFROMSW, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = RegisterNum(actualResultFpReg2)), null) + addInstr(result, IRInstructions.binary(Opcode.FFROMSW, IRDataType.FLOAT, actualResultFpReg2, tr.resultReg), null) } BaseDataType.LONG -> { - addInstr(result, IRInstruction(Opcode.FFROMSL, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = RegisterNum(actualResultFpReg2)), null) + addInstr(result, IRInstructions.binary(Opcode.FFROMSL, IRDataType.FLOAT, actualResultFpReg2, tr.resultReg), null) } else -> throw AssemblyError("weird cast value type ${cast.position}") } @@ -910,17 +910,17 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { BaseDataType.BOOL, BaseDataType.UBYTE -> { actualResultReg2 = codeGen.registers.next(pointerIrDt) val op = if(pointerIrDt==IRDataType.LONG) Opcode.EXTL else Opcode.EXT - addInstr(result, IRInstruction(op, type = IRDataType.BYTE, reg1 = actualResultReg2, reg2=tr.resultReg), null) + addInstr(result, IRInstructions.binary(op, IRDataType.BYTE, actualResultReg2, tr.resultReg), null) } BaseDataType.BYTE -> { actualResultReg2 = codeGen.registers.next(pointerIrDt) val op = if(pointerIrDt==IRDataType.LONG) Opcode.EXTLS else Opcode.EXTS - addInstr(result, IRInstruction(op, type = IRDataType.BYTE, reg1 = actualResultReg2, reg2=tr.resultReg), null) + addInstr(result, IRInstructions.binary(op, IRDataType.BYTE, actualResultReg2, tr.resultReg), null) } BaseDataType.WORD -> { actualResultReg2 = if(pointerIrDt==IRDataType.LONG) { val r = codeGen.registers.next(IRDataType.LONG) - addInstr(result, IRInstruction(Opcode.EXT, type = IRDataType.WORD, reg1 = r, reg2=tr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.EXT, IRDataType.WORD, r, tr.resultReg), null) r } else tr.resultReg } @@ -979,22 +979,22 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { when(callTarget.scopedNameString) { "sys.clear_carry" -> { val chunk = mutableListOf() - addInstr(chunk, IRInstruction(Opcode.CLC), null) + addInstr(chunk, IRInstructions.simple(Opcode.CLC), null) return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1) } "sys.set_carry" -> { val chunk = mutableListOf() - addInstr(chunk, IRInstruction(Opcode.SEC), null) + addInstr(chunk, IRInstructions.simple(Opcode.SEC), null) return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1) } "sys.clear_irqd" -> { val chunk = mutableListOf() - addInstr(chunk, IRInstruction(Opcode.CLI), null) + addInstr(chunk, IRInstructions.simple(Opcode.CLI), null) return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1) } "sys.set_irqd" -> { val chunk = mutableListOf() - addInstr(chunk, IRInstruction(Opcode.SEI), null) + addInstr(chunk, IRInstructions.simple(Opcode.SEI), null) return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1) } } @@ -1003,7 +1003,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { is StSub -> { val result = mutableListOf() // assign the arguments - val argRegisters = mutableListOf() + val argRegisters = mutableListOf() for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) { val paramDt = codeGen.irType(parameter.type) if(parameter.register==null) { @@ -1013,9 +1013,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { // backend uses those to load the CPU registers directly. result += tr.chunks if(paramDt==IRDataType.FLOAT) - argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, RegisterNum(tr.resultFpReg), null, null))) + argRegisters.add(Calls.argument(tr.resultFpReg, IRDataType.FLOAT, CallLocation.ParameterMemory(parameter.name))) else - argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(paramDt, RegisterNum(tr.resultReg), null, null))) + argRegisters.add(Calls.argument(tr.resultReg, paramDt, CallLocation.ParameterMemory(parameter.name))) } else { require(parameter.register in Cx16VirtualRegisters || parameter.register in CombinedLongRegisters || parameter.register in M68kRegisters) { "can only use R0-R15, D0-D7, A0-A6, or FP0-FP7 'registers' here" } if(parameter.register in Cx16VirtualRegisters || parameter.register in CombinedLongRegisters) { @@ -1031,9 +1031,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val tr = translateExpression(arg) result += tr.chunks if(paramDt==IRDataType.FLOAT) - argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, RegisterNum(tr.resultFpReg), null, null))) + argRegisters.add(Calls.argument(tr.resultFpReg, IRDataType.FLOAT, CallLocation.ParameterMemory(parameter.name))) else - argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(paramDt, RegisterNum(tr.resultReg), null, null))) + argRegisters.add(Calls.argument(tr.resultReg, paramDt, CallLocation.ParameterMemory(parameter.name))) } } } @@ -1041,23 +1041,23 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { // TODO: for current implementation of the call convention in case of multiple return values, // a list of Ir virtual registers to hold the results is NOT correct (they're loaded into AY + R15..R0 instead!) // So we use an empty list to avoid confusion here. This may change in a future version. - val returnRegSpecs = if(fcall.void || callTarget.returns.size>1) emptyList() else { + val returnResults: List = if(fcall.void || callTarget.returns.size>1) emptyList() else { callTarget.returns.map { val returnIrType = codeGen.irType(it) - FunctionCallArgs.RegSpec(returnIrType, RegisterNum(codeGen.registers.next(returnIrType)), null, null) + Calls.result(codeGen.registers.next(returnIrType), returnIrType) } } // create the call - addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name, - fcallArgs = FunctionCallArgs(argRegisters, returnRegSpecs)), null) + val site = CallSite(CallTarget.Direct(codeLabel(fcall.name)), argRegisters, returnResults) + addInstr(result, IRInstructions.call(site), null) return if(fcall.void) ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) - else if(returnRegSpecs.size==1) { - val returnRegSpec = returnRegSpecs.single() + else if(returnResults.size==1) { + val returnResult = returnResults.single().destination!! if (fcall.type.isFloat) - ExpressionCodeResult(result, returnRegSpec.dt, -1, returnRegSpec.registerNum.value) + ExpressionCodeResult(result, returnResult.type, -1, returnResult.registerNumber) else - ExpressionCodeResult(result, returnRegSpec.dt, returnRegSpec.registerNum.value, -1) + ExpressionCodeResult(result, returnResult.type, returnResult.registerNumber, -1) } else { // note: multi-value returns are passed throug A or AY (for the first value) then cx16.R15 down to R0 // so the actual result of the expression here is 'void' (doesn't use IR virtual registers at all) @@ -1067,7 +1067,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { is StExtSub -> { val result = mutableListOf() // assign the arguments - val argRegisters = mutableListOf() + val argRegisters = mutableListOf() for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) { val paramDt = codeGen.irType(parameter.type) val tr = translateExpression(arg) @@ -1082,10 +1082,16 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { parameter.name } } else "" + val loc: CallLocation = when { + slot != null -> CallLocation.HardwareRegister(slot) + flag != null -> CallLocation.StatusFlag(flag) + argName.isNotEmpty() -> CallLocation.ParameterMemory(argName) + else -> CallLocation.Default + } if(paramDt==IRDataType.FLOAT) - argRegisters.add(FunctionCallArgs.ArgumentSpec(argName, null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, RegisterNum(tr.resultFpReg), slot, flag))) + argRegisters.add(Calls.argument(tr.resultFpReg, IRDataType.FLOAT, loc)) else - argRegisters.add(FunctionCallArgs.ArgumentSpec(argName, null, FunctionCallArgs.RegSpec(paramDt, RegisterNum(tr.resultReg), slot, flag))) + argRegisters.add(Calls.argument(tr.resultReg, paramDt, loc)) result += tr.chunks } @@ -1093,35 +1099,41 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { return callExtSubWithMultipleReturnValues(callTarget, fcall, argRegisters, result) // return a single value (or nothing) - val returnRegSpec = if(fcall.void) null else { + val returnResult: CallResult? = if(fcall.void) null else { if(callTarget.returns.isEmpty()) null else { val returns = callTarget.returns[0] val returnIrType = codeGen.irType(returns.type) val (retSlot, retFlag) = registerOrStatusflagToSlotAndFlag(returns.register) - FunctionCallArgs.RegSpec(returnIrType, RegisterNum(codeGen.registers.next(returnIrType)), retSlot, retFlag) + val loc: CallLocation = when { + retSlot != null -> CallLocation.HardwareRegister(retSlot) + retFlag != null -> CallLocation.StatusFlag(retFlag) + else -> CallLocation.Default + } + Calls.result(codeGen.registers.next(returnIrType), returnIrType, loc) } } // create the call - val returnRegs = if(returnRegSpec==null) emptyList() else listOf(returnRegSpec) - val call = emitExtSubCall(callTarget, fcall, FunctionCallArgs(argRegisters, returnRegs), result) + val returnResults = if(returnResult==null) emptyList() else listOf(returnResult) + val call = emitExtSubCall(callTarget, fcall, argRegisters, returnResults, result) addInstr(result, call, null) - var finalReturnRegister = returnRegSpec?.registerNum?.value ?: -1 + var finalReturnRegister = returnResult?.destination?.registerNumber ?: -1 if(resultUsedAsExpression) { // look if the status flag bit should actually be returned as a 0/1 byte value in a result register (so it can be assigned) - val statusFlagResult = returnRegSpec?.statusflag + val statusFlagResult = (returnResult?.location as? CallLocation.StatusFlag)?.flag if(statusFlagResult!=null) { // assign status flag bit to the return value register - finalReturnRegister = returnRegSpec.registerNum.value + val returnDt = returnResult.destination!!.type + finalReturnRegister = returnResult.destination!!.registerNumber if(finalReturnRegister<0) - finalReturnRegister = codeGen.registers.next(returnRegSpec.dt) + finalReturnRegister = codeGen.registers.next(returnDt) when(statusFlagResult) { Statusflag.Pc -> { result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, returnRegSpec.dt, reg1 = finalReturnRegister, immediate = 0) - it += IRInstruction(Opcode.ROXL, returnRegSpec.dt, reg1 = finalReturnRegister) + it += IRInstructions.load(returnDt, finalReturnRegister, 0) + it += IRInstructions.unary(Opcode.ROXL, returnDt, finalReturnRegister) } } else -> { @@ -1134,12 +1146,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val setLabel = codeGen.createLabelName() val endLabel = codeGen.createLabelName() result += IRCodeChunk(null, null).also { - it += IRInstruction(branchOpcode, labelSymbol = setLabel) - it += IRInstruction(Opcode.LOAD, returnRegSpec.dt, reg1=finalReturnRegister, immediate = 0) - it += IRInstruction(Opcode.JUMP, labelSymbol = endLabel) + it += IRInstructions.branch(branchOpcode, codeLabel(setLabel)) + it += IRInstructions.load(returnDt, finalReturnRegister, 0) + it += IRInstructions.jump(codeLabel(endLabel)) } result += IRCodeChunk(setLabel, null).also { - it += IRInstruction(Opcode.LOAD, returnRegSpec.dt, reg1=finalReturnRegister, immediate = 1) + it += IRInstructions.load(returnDt, finalReturnRegister, 1) } result += IRCodeChunk(endLabel, null) } @@ -1150,9 +1162,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { return if(fcall.void) ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) else if(fcall.type.isFloat) - ExpressionCodeResult(result, returnRegSpec!!.dt, -1, finalReturnRegister) + ExpressionCodeResult(result, returnResult!!.destination!!.type, -1, finalReturnRegister) else - ExpressionCodeResult(result, returnRegSpec!!.dt, finalReturnRegister, -1) + ExpressionCodeResult(result, returnResult!!.destination!!.type, finalReturnRegister, -1) } is StStruct -> { throw AssemblyError("stray struct constructor should have been removed (normally it can only occur as initialization expression for a pointer variable)") @@ -1161,8 +1173,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { if(callTarget.type == StNodeType.LABEL) { require(fcall.void) val result = mutableListOf() - val args = FunctionCallArgs(emptyList(), emptyList()) - addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = args), null) + val site = CallSite(CallTarget.Direct(codeLabel(fcall.name))) + addInstr(result, IRInstructions.call(site), null) return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) } else { @@ -1195,45 +1207,48 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { private fun callExtSubWithMultipleReturnValues( callTarget: StExtSub, fcall: PtFunctionCall, - argRegisters: MutableList, + argRegisters: List, result: MutableList ): ExpressionCodeResult { // return multiple values - val returnRegisters = callTarget.returns.map { + val returnResults = callTarget.returns.map { val regnum = codeGen.registers.next(codeGen.irType(it.type)) val (slot, flag) = registerOrStatusflagToSlotAndFlag(it.register) - FunctionCallArgs.RegSpec(codeGen.irType(it.type), RegisterNum(regnum), slot, flag) + val loc: CallLocation = when { + slot != null -> CallLocation.HardwareRegister(slot) + flag != null -> CallLocation.StatusFlag(flag) + else -> CallLocation.Default + } + Calls.result(regnum, codeGen.irType(it.type), loc) } // create the call - val call = emitExtSubCall(callTarget, fcall, FunctionCallArgs(argRegisters, returnRegisters), result) + val call = emitExtSubCall(callTarget, fcall, argRegisters, returnResults, result) addInstr(result, call, null) - val resultRegs = returnRegisters.filter{it.dt!=IRDataType.FLOAT}.map{it.registerNum.value} - val resultFpRegs = returnRegisters.filter{it.dt==IRDataType.FLOAT}.map{it.registerNum.value} + val resultRegs = returnResults.filter{it.destination!!.type!=IRDataType.FLOAT}.map{it.destination!!.registerNumber} + val resultFpRegs = returnResults.filter{it.destination!!.type==IRDataType.FLOAT}.map{it.destination!!.registerNumber} return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1, resultRegs, resultFpRegs) } private fun emitExtSubCall( callTarget: StExtSub, fcall: PtFunctionCall, - fcallArgs: FunctionCallArgs, + arguments: List, + results: List, result: MutableList ): IRInstruction { val address = callTarget.address return if (address == null) { - IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = fcallArgs) + IRInstructions.call(CallSite(CallTarget.Direct(codeLabel(fcall.name)), arguments, results)) } else if (address.constbank == null && address.varbank == null) { - IRInstruction( - Opcode.CALL, - address = address.address.toAddress(), - fcallArgs = fcallArgs - ) + IRInstructions.call(CallSite(CallTarget.Direct(codeAddress(address.address.toAddress())), arguments, results)) } else if (address.constbank != null) { - IRInstruction( - Opcode.CALLFAR, - address = address.address.toAddress(), - immediate = address.constbank!!.toInt(), - fcallArgs = fcallArgs - ).also { it.extSubName = callTarget.scopedNameString } + val bank = address.constbank!!.toInt() + val addr = address.address.toAddress() + val target = if (addr.value > 0x7fffffffu) + CallTarget.AmigaLibrary(bank, addr.value.toInt(), callTarget.scopedNameString) + else + CallTarget.Banked(bank, codeAddress(addr), callTarget.scopedNameString) + IRInstructions.call(Opcode.CALLFAR, CallSite(target, arguments, results)) } else { val varBank = address.varbank!! val target = codeGen.symbolTable.lookup(varBank.name) ?: codeGen.symbolTable.lookupUnscoped(varBank.name) @@ -1253,12 +1268,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { result += tr.chunks tr.resultReg } - IRInstruction( - Opcode.CALLFARVB, - address = address.address.toAddress(), - reg1 = bankReg, - fcallArgs = fcallArgs - ) + val bankRegOperand = RegisterOperand(VirtualRegister.int(bankReg), IRDataType.BYTE, OperandRole.VALUE, OperandDirection.USE) + val callTargetVb = CallTarget.BankedVariable(bankRegOperand, codeAddress(address.address.toAddress())) + IRInstructions.call(Opcode.CALLFARVB, CallSite(callTargetVb, arguments, results)) } } @@ -1275,7 +1287,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val rightTr = translateExpression(binExpr.right) addToResult(result, rightTr, -1, rightTr.resultFpReg) val resultRegister = codeGen.registers.next(IRDataType.BYTE) - addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=resultRegister, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2 = RegisterNum(rightTr.resultFpReg)), null) + addInstr(result, IRInstructions.floatCompare(resultRegister, leftTr.resultFpReg, rightTr.resultFpReg), null) val branch = if(greaterEquals) Opcode.BGES else Opcode.BGTS val resultReg = loadBooleanStatusAsByte(branch, resultRegister, result) return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1) @@ -1324,7 +1336,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val rightTr = translateExpression(binExpr.right) addToResult(result, rightTr, -1, rightTr.resultFpReg) val resultRegister = codeGen.registers.next(IRDataType.BYTE) - addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=resultRegister, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2 = RegisterNum(rightTr.resultFpReg)), null) + addInstr(result, IRInstructions.floatCompare(resultRegister, leftTr.resultFpReg, rightTr.resultFpReg), null) val branch = if(lessEquals) Opcode.BLES else Opcode.BLTS val resultReg = loadBooleanStatusAsByte(branch, resultRegister, result) return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1) @@ -1368,8 +1380,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val rightTr = translateExpression(binExpr.right) addToResult(result, rightTr, -1, rightTr.resultFpReg) val valueReg = codeGen.registers.next(IRDataType.BYTE) - addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=valueReg, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2 = RegisterNum(rightTr.resultFpReg)), null) - addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=valueReg, immediate = 0), null) + addInstr(result, IRInstructions.floatCompare(valueReg, leftTr.resultFpReg, rightTr.resultFpReg), null) + addInstr(result, IRInstructions.compareImmediate(IRDataType.BYTE, valueReg, 0), null) val resultReg = loadStatusAsBooleanResult(if(notEquals) Opcode.BSTNE else Opcode.BSTEQ, result) return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1) } else { @@ -1390,7 +1402,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { && lastInstr != null && lastInstr.opcode in OpcodesThatSetZeroFlagOnM68k if (!skipCmpi) { - addInstr(result, IRInstruction(Opcode.CMPI, leftTr.dt, reg1 = leftTr.resultReg, immediate = rightConst.toInt()), null) + addInstr(result, IRInstructions.compareImmediate(leftTr.dt, leftTr.resultReg, rightConst.toInt()), null) } val resultReg = loadStatusAsBooleanResult(if(notEquals) Opcode.BSTNE else Opcode.BSTEQ, result) ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1) @@ -1399,7 +1411,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { addToResult(result, leftTr, leftTr.resultReg, -1) val rightTr = translateExpression(binExpr.right) addToResult(result, rightTr, rightTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.CMP, leftTr.dt, reg1=leftTr.resultReg, reg2=rightTr.resultReg), null) + addInstr(result, IRInstructions.compare(leftTr.dt, leftTr.resultReg, rightTr.resultReg), null) val resultReg = loadStatusAsBooleanResult(if(notEquals) Opcode.BSTNE else Opcode.BSTEQ, result) ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1) } @@ -1412,12 +1424,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val labelDone = codeGen.createLabelName() val resultReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(branchForTrue, labelSymbol = labelTrue) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 0) - it += IRInstruction(Opcode.JUMP, labelSymbol = labelDone) + it += IRInstructions.branch(branchForTrue, codeLabel(labelTrue)) + it += IRInstructions.load(IRDataType.BYTE, resultReg, 0) + it += IRInstructions.jump(codeLabel(labelDone)) } result += IRCodeChunk(labelTrue, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 1) + it += IRInstructions.load(IRDataType.BYTE, resultReg, 1) } result += IRCodeChunk(labelDone, null) return resultReg @@ -1428,12 +1440,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val labelDone = codeGen.createLabelName() val resultReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(branchForTrue, dt, reg1 = reg1, reg2 = reg2, labelSymbol = labelTrue) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 0) - it += IRInstruction(Opcode.JUMP, labelSymbol = labelDone) + it += IRInstructions.branchRegister(branchForTrue, dt, reg1, reg2, codeLabel(labelTrue)) + it += IRInstructions.load(IRDataType.BYTE, resultReg, 0) + it += IRInstructions.jump(codeLabel(labelDone)) } result += IRCodeChunk(labelTrue, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 1) + it += IRInstructions.load(IRDataType.BYTE, resultReg, 1) } result += IRCodeChunk(labelDone, null) return resultReg @@ -1444,12 +1456,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val labelDone = codeGen.createLabelName() val resultReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(branchForTrue, dt, reg1 = reg1, immediate = imm, labelSymbol = labelTrue) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 0) - it += IRInstruction(Opcode.JUMP, labelSymbol = labelDone) + it += IRInstructions.branchImmediate(branchForTrue, dt, reg1, imm, codeLabel(labelTrue)) + it += IRInstructions.load(IRDataType.BYTE, resultReg, 0) + it += IRInstructions.jump(codeLabel(labelDone)) } result += IRCodeChunk(labelTrue, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 1) + it += IRInstructions.load(IRDataType.BYTE, resultReg, 1) } result += IRCodeChunk(labelDone, null) return resultReg @@ -1460,12 +1472,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val labelDone = codeGen.createLabelName() val resultReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(branchForTrue, IRDataType.BYTE, reg1 = reg1, immediate = 0, labelSymbol = labelTrue) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 0) - it += IRInstruction(Opcode.JUMP, labelSymbol = labelDone) + it += IRInstructions.branchImmediate(branchForTrue, IRDataType.BYTE, reg1, 0, codeLabel(labelTrue)) + it += IRInstructions.load(IRDataType.BYTE, resultReg, 0) + it += IRInstructions.jump(codeLabel(labelDone)) } result += IRCodeChunk(labelTrue, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 1) + it += IRInstructions.load(IRDataType.BYTE, resultReg, 1) } result += IRCodeChunk(labelDone, null) return resultReg @@ -1477,7 +1489,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val tr = translateExpression(binExpr.left) addToResult(result, tr, tr.resultReg, -1) val opc = if (signed) Opcode.ASR else Opcode.LSR - addInstr(result, IRInstruction(opc, vmDt, reg1 = tr.resultReg), null) + addInstr(result, IRInstructions.unary(opc, vmDt, tr.resultReg), null) ExpressionCodeResult(result, vmDt, tr.resultReg, -1) } else { val constCount = binExpr.right.asConstInteger() @@ -1485,10 +1497,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val leftTr = translateExpression(binExpr.left) addToResult(result, leftTr, leftTr.resultReg, -1) if(vmDt == IRDataType.LONG && !signed && constCount == 16) { - addInstr(result, IRInstruction(Opcode.MSIGW, IRDataType.LONG, reg1 = leftTr.resultReg, reg2 = leftTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.MSIGW, IRDataType.LONG, leftTr.resultReg, leftTr.resultReg), null) } else { val opc = if (signed) Opcode.ASRI else Opcode.LSRI - addInstr(result, IRInstruction(opc, vmDt, reg1 = leftTr.resultReg, immediate = constCount), null) + addInstr(result, IRInstructions.binaryImmediate(opc, vmDt, leftTr.resultReg, constCount), null) } ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1) } else { @@ -1498,7 +1510,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { require(rightTr.dt== IRDataType.BYTE) { "can only shift by 0-255" } addToResult(result, rightTr, rightTr.resultReg, -1) val opc = if (signed) Opcode.ASRN else Opcode.LSRN - addInstr(result, IRInstruction(opc, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null) + addInstr(result, IRInstructions.binary(opc, vmDt, leftTr.resultReg, rightTr.resultReg), null) ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1) } } @@ -1509,14 +1521,14 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { return if(codeGen.isOne(binExpr.right)){ val tr = translateExpression(binExpr.left) addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.LSL, vmDt, reg1=tr.resultReg), null) + addInstr(result, IRInstructions.unary(Opcode.LSL, vmDt, tr.resultReg), null) ExpressionCodeResult(result, vmDt, tr.resultReg, -1) } else { val constCount = binExpr.right.asConstInteger() if(constCount != null) { val leftTr = translateExpression(binExpr.left) addToResult(result, leftTr, leftTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.LSLI, vmDt, reg1 = leftTr.resultReg, immediate = constCount), null) + addInstr(result, IRInstructions.binaryImmediate(Opcode.LSLI, vmDt, leftTr.resultReg, constCount), null) ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1) } else { val leftTr = translateExpression(binExpr.left) @@ -1524,7 +1536,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val rightTr = translateExpression(binExpr.right) require(rightTr.dt== IRDataType.BYTE) { "can only shift by 0-255" } addToResult(result, rightTr, rightTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.LSLN, vmDt, reg1=leftTr.resultReg, rightTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.LSLN, vmDt, leftTr.resultReg, rightTr.resultReg), null) ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1) } } @@ -1536,17 +1548,17 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { addToResult(result, tr, tr.resultReg, -1) return when (binExpr.right) { is PtNumber -> { - addInstr(result, IRInstruction(Opcode.XOR, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null) + addInstr(result, IRInstructions.binaryImmediate(Opcode.XOR, vmDt, tr.resultReg, (binExpr.right as PtNumber).number.toInt()), null) ExpressionCodeResult(result, vmDt, tr.resultReg, -1) } is PtBool -> { - addInstr(result, IRInstruction(Opcode.XOR, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtBool).asInt()), null) + addInstr(result, IRInstructions.binaryImmediate(Opcode.XOR, vmDt, tr.resultReg, (binExpr.right as PtBool).asInt()), null) ExpressionCodeResult(result, vmDt, tr.resultReg, -1) } else -> { val rightTr = translateExpression(binExpr.right) addToResult(result, rightTr, rightTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.XORR, vmDt, reg1 = tr.resultReg, reg2 = rightTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.XORR, vmDt, tr.resultReg, rightTr.resultReg), null) ExpressionCodeResult(result, vmDt, tr.resultReg, -1) } } @@ -1559,7 +1571,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val leftTr = translateExpression(binExpr.left) addToResult(result, leftTr, leftTr.resultReg, -1) val shortcutLabel = codeGen.createLabelName() - addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel), null) + addInstr(result, IRInstructions.branch(Opcode.BSTEQ, codeLabel(shortcutLabel)), null) val rightTr = translateExpression(binExpr.right) addToResult(result, rightTr, leftTr.resultReg, -1) result += IRCodeChunk(shortcutLabel, null) @@ -1569,17 +1581,17 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { addToResult(result, tr, tr.resultReg, -1) return when (binExpr.right) { is PtNumber -> { - addInstr(result, IRInstruction(Opcode.AND, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null) + addInstr(result, IRInstructions.binaryImmediate(Opcode.AND, vmDt, tr.resultReg, (binExpr.right as PtNumber).number.toInt()), null) ExpressionCodeResult(result, vmDt, tr.resultReg, -1) } is PtBool -> { - addInstr(result, IRInstruction(Opcode.AND, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtBool).asInt()), null) + addInstr(result, IRInstructions.binaryImmediate(Opcode.AND, vmDt, tr.resultReg, (binExpr.right as PtBool).asInt()), null) ExpressionCodeResult(result, vmDt, tr.resultReg, -1) } else -> { val rightTr = translateExpression(binExpr.right) addToResult(result, rightTr, rightTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.ANDR, vmDt, reg1 = tr.resultReg, reg2 = rightTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.ANDR, vmDt, tr.resultReg, rightTr.resultReg), null) ExpressionCodeResult(result, vmDt, tr.resultReg, -1) } } @@ -1593,7 +1605,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val leftTr = translateExpression(binExpr.left) addToResult(result, leftTr, leftTr.resultReg, -1) val shortcutLabel = codeGen.createLabelName() - addInstr(result, IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel), null) + addInstr(result, IRInstructions.branch(Opcode.BSTNE, codeLabel(shortcutLabel)), null) val rightTr = translateExpression(binExpr.right) addToResult(result, rightTr, leftTr.resultReg, -1) result += IRCodeChunk(shortcutLabel, null) @@ -1603,17 +1615,17 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { addToResult(result, tr, tr.resultReg, -1) return when (binExpr.right) { is PtNumber -> { - addInstr(result, IRInstruction(Opcode.OR, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null) + addInstr(result, IRInstructions.binaryImmediate(Opcode.OR, vmDt, tr.resultReg, (binExpr.right as PtNumber).number.toInt()), null) ExpressionCodeResult(result, vmDt, tr.resultReg, -1) } is PtBool -> { - addInstr(result, IRInstruction(Opcode.OR, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtBool).asInt()), null) + addInstr(result, IRInstructions.binaryImmediate(Opcode.OR, vmDt, tr.resultReg, (binExpr.right as PtBool).asInt()), null) ExpressionCodeResult(result, vmDt, tr.resultReg, -1) } else -> { val rightTr = translateExpression(binExpr.right) addToResult(result, rightTr, rightTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.ORR, vmDt, reg1 = tr.resultReg, reg2 = rightTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.ORR, vmDt, tr.resultReg, rightTr.resultReg), null) ExpressionCodeResult(result, vmDt, tr.resultReg, -1) } } @@ -1628,7 +1640,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val tr = translateExpression(binExpr.left) addToResult(result, tr, tr.resultReg, -1) val opcode = if(isSigned) Opcode.MODS else Opcode.MOD - addInstr(result, IRInstruction(opcode, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null) + addInstr(result, IRInstructions.binaryImmediate(opcode, vmDt, tr.resultReg, (binExpr.right as PtNumber).number.toInt()), null) ExpressionCodeResult(result, vmDt, tr.resultReg, -1) } else { val leftTr = translateExpression(binExpr.left) @@ -1636,7 +1648,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val rightTr = translateExpression(binExpr.right) addToResult(result, rightTr, rightTr.resultReg, -1) val opcode = if(isSigned) Opcode.MODSR else Opcode.MODR - addInstr(result, IRInstruction(opcode, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null) + addInstr(result, IRInstructions.binary(opcode, vmDt, leftTr.resultReg, rightTr.resultReg), null) ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1) } } @@ -1658,9 +1670,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val rightTr = translateExpression(binExpr.right) addToResult(result, rightTr, -1, rightTr.resultFpReg) addInstr(result, if(dt.isSigned) - IRInstruction(Opcode.DIVSR, vmDt, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2=RegisterNum(rightTr.resultFpReg)) + IRInstructions.binary(Opcode.DIVSR, IRDataType.FLOAT, leftTr.resultFpReg, rightTr.resultFpReg) else - IRInstruction(Opcode.DIVR, vmDt, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2=RegisterNum(rightTr.resultFpReg)) + IRInstructions.binary(Opcode.DIVR, IRDataType.FLOAT, leftTr.resultFpReg, rightTr.resultFpReg) , null) return ExpressionCodeResult(result, vmDt, -1, leftTr.resultFpReg) } @@ -1676,9 +1688,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val leftTr = translateExpression(binExpr.left) addToResult(result, leftTr, leftTr.resultReg, -1) addInstr(result, if (dt.isSigned) - IRInstruction(Opcode.DIVS, vmDt, reg1 = leftTr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()) + IRInstructions.binaryImmediate(Opcode.DIVS, vmDt, leftTr.resultReg, (binExpr.right as PtNumber).number.toInt()) else - IRInstruction(Opcode.DIV, vmDt, reg1 = leftTr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()) + IRInstructions.binaryImmediate(Opcode.DIV, vmDt, leftTr.resultReg, (binExpr.right as PtNumber).number.toInt()) , null) ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1) } else { @@ -1687,9 +1699,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val rightTr = translateExpression(binExpr.right) addToResult(result, rightTr, rightTr.resultReg, -1) addInstr(result, if (dt.isSigned) - IRInstruction(Opcode.DIVSR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg) + IRInstructions.binary(Opcode.DIVSR, vmDt, leftTr.resultReg, rightTr.resultReg) else - IRInstruction(Opcode.DIVR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg) + IRInstructions.binary(Opcode.DIVR, vmDt, leftTr.resultReg, rightTr.resultReg) , null) ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1) } @@ -1720,7 +1732,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { addToResult(result, leftTr, -1, leftTr.resultFpReg) val rightTr = translateExpression(binExpr.right) addToResult(result, rightTr, -1, rightTr.resultFpReg) - addInstr(result, IRInstruction(Opcode.MULSR, vmDt, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2 = RegisterNum(rightTr.resultFpReg)), null) + addInstr(result, IRInstructions.binary(Opcode.MULSR, IRDataType.FLOAT, leftTr.resultFpReg, rightTr.resultFpReg), null) ExpressionCodeResult(result, vmDt, -1, leftTr.resultFpReg) } } else { @@ -1742,7 +1754,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val rightTr = translateExpression(binExpr.right) addToResult(result, rightTr, rightTr.resultReg, -1) val opcode = if(dt.isSigned) Opcode.MULSR else Opcode.MULR - addInstr(result, IRInstruction(opcode, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null) + addInstr(result, IRInstructions.binary(opcode, vmDt, leftTr.resultReg, rightTr.resultReg), null) ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1) } } @@ -1754,21 +1766,21 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { if((binExpr.right as? PtNumber)?.number==1.0) { val tr = translateExpression(binExpr.left) addToResult(result, tr, -1, tr.resultFpReg) - addInstr(result, IRInstruction(Opcode.DEC, vmDt, fpReg1 = RegisterNum(tr.resultFpReg)), null) + addInstr(result, IRInstructions.unary(Opcode.DEC, vmDt, tr.resultFpReg), null) return ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg) } else { return if(binExpr.right is PtNumber) { val tr = translateExpression(binExpr.left) addToResult(result, tr, -1, tr.resultFpReg) - addInstr(result, IRInstruction(Opcode.SUB, vmDt, fpReg1 = RegisterNum(tr.resultFpReg), immediateFp = (binExpr.right as PtNumber).number), null) + addInstr(result, IRInstructions.binaryImmediateFloat(Opcode.SUB, tr.resultFpReg, (binExpr.right as PtNumber).number), null) ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg) } else { val leftTr = translateExpression(binExpr.left) addToResult(result, leftTr, -1, leftTr.resultFpReg) val rightTr = translateExpression(binExpr.right) addToResult(result, rightTr, -1, rightTr.resultFpReg) - addInstr(result, IRInstruction(Opcode.SUBR, vmDt, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2 = RegisterNum(rightTr.resultFpReg)), null) + addInstr(result, IRInstructions.binary(Opcode.SUBR, IRDataType.FLOAT, leftTr.resultFpReg, rightTr.resultFpReg), null) ExpressionCodeResult(result, vmDt, -1, leftTr.resultFpReg) } } @@ -1776,21 +1788,21 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { if((binExpr.right as? PtNumber)?.number==1.0) { val tr = translateExpression(binExpr.left) addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.DEC, vmDt, reg1=tr.resultReg), null) + addInstr(result, IRInstructions.unary(Opcode.DEC, vmDt, tr.resultReg), null) return ExpressionCodeResult(result, vmDt, tr.resultReg, -1) } else { return if(binExpr.right is PtNumber) { val tr = translateExpression(binExpr.left) addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.SUB, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null) + addInstr(result, IRInstructions.binaryImmediate(Opcode.SUB, vmDt, tr.resultReg, (binExpr.right as PtNumber).number.toInt()), null) ExpressionCodeResult(result, vmDt, tr.resultReg, -1) } else { val leftTr = translateExpression(binExpr.left) addToResult(result, leftTr, leftTr.resultReg, -1) val rightTr = translateExpression(binExpr.right) addToResult(result, rightTr, rightTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.SUBR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.SUBR, vmDt, leftTr.resultReg, rightTr.resultReg), null) ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1) } } @@ -1803,27 +1815,27 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { if((binExpr.left as? PtNumber)?.number==1.0) { val tr = translateExpression(binExpr.right) addToResult(result, tr, -1, tr.resultFpReg) - addInstr(result, IRInstruction(Opcode.INC, vmDt, fpReg1=RegisterNum(tr.resultFpReg)), null) + addInstr(result, IRInstructions.unary(Opcode.INC, vmDt, tr.resultFpReg), null) return ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg) } else if((binExpr.right as? PtNumber)?.number==1.0) { val tr = translateExpression(binExpr.left) addToResult(result, tr, -1, tr.resultFpReg) - addInstr(result, IRInstruction(Opcode.INC, vmDt, fpReg1=RegisterNum(tr.resultFpReg)), null) + addInstr(result, IRInstructions.unary(Opcode.INC, vmDt, tr.resultFpReg), null) return ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg) } else { return if(binExpr.right is PtNumber) { val tr = translateExpression(binExpr.left) addToResult(result, tr, -1, tr.resultFpReg) - addInstr(result, IRInstruction(Opcode.ADD, vmDt, fpReg1 = RegisterNum(tr.resultFpReg), immediateFp = (binExpr.right as PtNumber).number), null) + addInstr(result, IRInstructions.binaryImmediateFloat(Opcode.ADD, tr.resultFpReg, (binExpr.right as PtNumber).number), null) ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg) } else { val leftTr = translateExpression(binExpr.left) addToResult(result, leftTr, -1, leftTr.resultFpReg) val rightTr = translateExpression(binExpr.right) addToResult(result, rightTr, -1, rightTr.resultFpReg) - addInstr(result, IRInstruction(Opcode.ADDR, vmDt, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2 = RegisterNum(rightTr.resultFpReg)), null) + addInstr(result, IRInstructions.binary(Opcode.ADDR, IRDataType.FLOAT, leftTr.resultFpReg, rightTr.resultFpReg), null) ExpressionCodeResult(result, vmDt, -1, leftTr.resultFpReg) } } @@ -1831,27 +1843,27 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { if((binExpr.left as? PtNumber)?.number==1.0) { val tr = translateExpression(binExpr.right) addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.INC, vmDt, reg1=tr.resultReg), null) + addInstr(result, IRInstructions.unary(Opcode.INC, vmDt, tr.resultReg), null) return ExpressionCodeResult(result, vmDt, tr.resultReg, -1) } else if((binExpr.right as? PtNumber)?.number==1.0) { val tr = translateExpression(binExpr.left) addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.INC, vmDt, reg1=tr.resultReg), null) + addInstr(result, IRInstructions.unary(Opcode.INC, vmDt, tr.resultReg), null) return ExpressionCodeResult(result, vmDt, tr.resultReg, -1) } else { return if(binExpr.right is PtNumber) { val tr = translateExpression(binExpr.left) addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.ADD, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null) + addInstr(result, IRInstructions.binaryImmediate(Opcode.ADD, vmDt, tr.resultReg, (binExpr.right as PtNumber).number.toInt()), null) ExpressionCodeResult(result, vmDt, tr.resultReg, -1) } else { val leftTr = translateExpression(binExpr.left) addToResult(result, leftTr, leftTr.resultReg, -1) val rightTr = translateExpression(binExpr.right) addToResult(result, rightTr, rightTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.ADDR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.ADDR, vmDt, leftTr.resultReg, rightTr.resultReg), null) ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1) } } @@ -1890,11 +1902,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { result += chunks if (fieldVmDt == IRDataType.FLOAT) { val fpReg = codeGen.registers.next(IRDataType.FLOAT) - addInstr(result, IRInstruction(Opcode.LOADX, fieldVmDt, fpReg1 = RegisterNum(fpReg), reg1 = indexReg, labelSymbol = left.variable!!.name, symbolOffset = fieldOffset, scale = struct.size.toInt()), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADX, IRDataType.FLOAT, fpReg, IRMemory.indexed(left.variable!!.name, indexReg, codeGen.options.compTarget.indexRegType, scale = struct.size.toInt(), displacement = fieldOffset)), null) return ExpressionCodeResult(result, fieldVmDt, -1, fpReg) } else { val r = codeGen.registers.next(fieldVmDt) - addInstr(result, IRInstruction(Opcode.LOADX, fieldVmDt, reg1 = r, reg2 = indexReg, labelSymbol = left.variable!!.name, symbolOffset = fieldOffset, scale = struct.size.toInt()), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADX, fieldVmDt, r, IRMemory.indexed(left.variable!!.name, indexReg, codeGen.options.compTarget.indexRegType, scale = struct.size.toInt(), displacement = fieldOffset)), null) return ExpressionCodeResult(result, fieldVmDt, r, -1) } } else { @@ -1906,7 +1918,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { result += chunks if(struct.size.toInt()!=1) result += codeGen.multiplyByConst(DataType.UWORD, indexReg, struct.size.toInt()) - addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.POINTER, reg1 = pointerReg, reg2 = indexReg), null) + addInstr(result, IRInstructions.binary(Opcode.ADDR, IRDataType.POINTER, pointerReg, indexReg), null) } } else if(left.pointerderef!=null) { TODO("get pointer from deref $left ${left.position}") @@ -1937,9 +1949,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { resultReg = codeGen.registers.next(fieldVmDt) require(fieldOffset<=65535) if(fieldVmDt==IRDataType.FLOAT) - addInstr(result, IRInstruction(Opcode.LOADI, fieldVmDt, fpReg1 = RegisterNum(resultFpReg), reg1 = pointerReg, immediate = fieldOffset), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, IRDataType.FLOAT, resultFpReg, IRMemory.indirect(pointerReg, fieldOffset)), null) else - addInstr(result, IRInstruction(Opcode.LOADI, fieldVmDt, reg1 = resultReg, reg2 = pointerReg, immediate = fieldOffset), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, fieldVmDt, resultReg, IRMemory.indirect(pointerReg, fieldOffset)), null) return ExpressionCodeResult(result, fieldVmDt, resultReg, resultFpReg) } @@ -1978,7 +1990,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { if(targetPointerDeref.derefLast) { require(fieldinfo.first.isPointer) // LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs - addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.POINTER, reg1 = pointerReg, reg2 = pointerReg, immediate = 0), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, IRDataType.POINTER, pointerReg, IRMemory.indirect(pointerReg, 0)), null) } return result to 0u } @@ -1986,7 +1998,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { private fun updatePointerFromField(fieldinfo: Pair, pointerReg: Int): IRCodeChunk { return IRCodeChunk(null, null).also { // LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs - it += IRInstruction(Opcode.LOADI, IRDataType.POINTER, reg1 = pointerReg, reg2 = pointerReg, immediate = fieldinfo.second.toInt()) + it += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.POINTER, pointerReg, IRMemory.indirect(pointerReg, fieldinfo.second.toInt())) } } @@ -2048,10 +2060,10 @@ internal fun addToResult( if(requiredResultFpReg!=-1) require(requiredResultReg==-1) if(requiredResultReg>=0 && requiredResultReg!=codeResult.resultReg) { - codeResult.chunks.last().instructions += IRInstruction(Opcode.LOADR, codeResult.dt, reg1=requiredResultReg, reg2=codeResult.resultReg) + codeResult.chunks.last().instructions += IRInstructions.move(codeResult.dt, requiredResultReg, codeResult.resultReg) } if(requiredResultFpReg>=0 && requiredResultFpReg!=codeResult.resultFpReg) { - codeResult.chunks.last().instructions += IRInstruction(Opcode.LOADR, IRDataType.FLOAT, fpReg1 = RegisterNum(requiredResultFpReg), fpReg2 = RegisterNum(codeResult.resultFpReg)) + codeResult.chunks.last().instructions += IRInstructions.move(IRDataType.FLOAT, requiredResultFpReg, codeResult.resultFpReg) } result += codeResult.chunks } diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt index 1eb9911d3..f78568839 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt @@ -162,7 +162,7 @@ class IRCodeGen( is IRVariableInitializer.Numeric -> { val dt = irType(variable.dt) val chunk = IRCodeChunk(null, null) - chunk += IRInstruction(Opcode.STOREIM, dt, immediate = initValue.value.toInt(), labelSymbol = variable.name) + chunk += IRInstructions.storeImmediate(dt, initValue.value.toInt(), IRMemory.direct(variable.name)) irProg.addGlobalInits(chunk) // replace with uninitialized version so it goes to NOINIT section replacements += IRStStaticVariable( @@ -282,15 +282,15 @@ class IRCodeGen( val sourceReg = registers.next(addressDt) val destReg = registers.next(addressDt) val countReg = registers.next(IRDataType.WORD) - chunk += IRInstruction(Opcode.LOAD, addressDt, reg1 = sourceReg, labelSymbol = shadowName) - chunk += IRInstruction(Opcode.LOAD, addressDt, reg1 = destReg, labelSymbol = variable.name) - chunk += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = countReg, immediate = initBytes.size) + chunk += IRInstructions.loadAddress(addressDt, sourceReg, shadowName) + chunk += IRInstructions.loadAddress(addressDt, destReg, variable.name) + chunk += IRInstructions.load(IRDataType.WORD, countReg, initBytes.size) val args = listOf( - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(addressDt, RegisterNum(sourceReg), null, null)), - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(addressDt, RegisterNum(destReg), null, null)), - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(countReg), null, null)) + Calls.argument(sourceReg, addressDt), + Calls.argument(destReg, addressDt), + Calls.argument(countReg, IRDataType.WORD) ) - chunk += IRInstruction(Opcode.SYSCALL, immediate = IMSyscall.MEMCOPY.number, fcallArgs = FunctionCallArgs(args, emptyList())) + chunk += IRInstructions.syscall(IMSyscall.MEMCOPY.number, args, emptyList()) } if(chunk.isNotEmpty()) @@ -398,9 +398,14 @@ class IRCodeGen( irProg.foreachCodeChunk { chunk -> chunk.instructions.withIndex().forEach { (idx, instr) -> - val symbolExpr = instr.labelSymbol + val symbolExpr = instr.memory?.symbolName ?: (instr.immediate as? ImmediateOperand.SymbolAddress)?.symbol if(symbolExpr!=null) { - val index = instr.labelSymbolOffset ?: 0 + val index = when(val mem = instr.memory) { + is MemoryReference.Direct -> mem.displacement + is MemoryReference.Indexed -> mem.displacement + is MemoryReference.Indirect -> 0 + null -> (instr.immediate as ImmediateOperand.SymbolAddress).offset + } val target = symbolTable.flat[symbolExpr] if (target is StMemVar) { replacements.add(Triple(chunk, idx, target.address+index.toUInt())) @@ -411,24 +416,21 @@ class IRCodeGen( replacements.forEach { val old = it.first.instructions[it.second] - val formats = instructionFormats.getValue(old.opcode) - val format = formats.getOrElse(old.type) { throw IllegalArgumentException("type ${old.type} invalid for ${old.opcode}") } - val addressUsed = format.address != OperandDirection.UNUSED - val immediateValue = if(addressUsed) old.immediate else if(format.immediate) it.third.toInt() else null - val addressValue: UInt? = if(addressUsed) it.third else null - - it.first.instructions[it.second] = IRInstruction( - old.opcode, - old.type, - old.reg1, - old.reg2, - old.reg3, - old.fpReg1, - old.fpReg2, - immediate = immediateValue, - immediateFp = old.immediateFp, - address = addressValue?.toAddress() - ) + val address = it.third + val newInstr = if(old.memory!=null) { + // keep index register, scale and any other operands intact; only the symbolic base changes + old.mapMemoryReferences { mem -> + when(mem) { + is MemoryReference.Direct -> MemoryReference.Direct(AddressBase.Absolute(address.toAddress())) + is MemoryReference.Indexed -> MemoryReference.Indexed(AddressBase.Absolute(address.toAddress()), mem.index, mem.scale) + is MemoryReference.Indirect -> mem + } + } + } else { + val symbolType = (old.immediate as ImmediateOperand.SymbolAddress).type + old.copy(immediate = ImmediateOperand.Integer(address.toInt(), symbolType)) + } + it.first.instructions[it.second] = newInstr } } @@ -452,12 +454,12 @@ class IRCodeGen( is PtLabel -> listOf(IRCodeChunk(node.name, null)) is PtBreakpoint -> { val chunk = IRCodeChunk(null, null) - chunk += IRInstruction(Opcode.BREAKPOINT) + chunk += IRInstructions.simple(Opcode.BREAKPOINT) listOf(chunk) } is PtAlign -> { val chunk = IRCodeChunk(null, null) - chunk += IRInstruction(Opcode.ALIGN, immediate = node.align.toInt()) + chunk += IRInstructions.align(node.align.toInt()) listOf(chunk) } is PtConditionalBranch -> translate(node) @@ -525,7 +527,7 @@ class IRCodeGen( val tr = expressionEval.translateExpression(goto.target) result += tr.chunks result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.JUMPI, reg1=tr.resultReg) + it += IRInstructions.jumpIndirect(tr.resultReg) } result += IRCodeChunk(skipJumpLabel, null) } @@ -541,7 +543,7 @@ class IRCodeGen( result += translateNode(branch.trueScope) if(branch.falseScope.children.isNotEmpty()) { val endLabel = createLabelName() - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null) + addInstr(result, IRInstructions.jump(codeLabel(endLabel)), null) val chunks = translateNode(branch.falseScope) result += labelFirstChunk(chunks, elseLabel) result += IRCodeChunk(endLabel, null) @@ -552,57 +554,45 @@ class IRCodeGen( } internal fun IRBranchInstr(condition: BranchCondition, label: String?=null, address: UInt?=null): IRInstruction { - if(label!=null) - return when(condition) { - BranchCondition.CS -> IRInstruction(Opcode.BSTCS, labelSymbol = label) - BranchCondition.CC -> IRInstruction(Opcode.BSTCC, labelSymbol = label) - BranchCondition.EQ, BranchCondition.Z -> IRInstruction(Opcode.BSTEQ, labelSymbol = label) - BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTNE, labelSymbol = label) - BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTNEG, labelSymbol = label) - BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTPOS, labelSymbol = label) - BranchCondition.VC -> IRInstruction(Opcode.BSTVC, labelSymbol = label) - BranchCondition.VS -> IRInstruction(Opcode.BSTVS, labelSymbol = label) - } - else if(address!=null) { - return when(condition) { - BranchCondition.CS -> IRInstruction(Opcode.BSTCS, address = address.toAddress()) - BranchCondition.CC -> IRInstruction(Opcode.BSTCC, address = address.toAddress()) - BranchCondition.EQ, BranchCondition.Z -> IRInstruction(Opcode.BSTEQ, address = address.toAddress()) - BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTNE, address = address.toAddress()) - BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTNEG, address = address.toAddress()) - BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTPOS, address = address.toAddress()) - BranchCondition.VC -> IRInstruction(Opcode.BSTVC, address = address.toAddress()) - BranchCondition.VS -> IRInstruction(Opcode.BSTVS, address = address.toAddress()) - } + val target = branchTarget(label, address) + val opcode = when(condition) { + BranchCondition.CS -> Opcode.BSTCS + BranchCondition.CC -> Opcode.BSTCC + BranchCondition.EQ, BranchCondition.Z -> Opcode.BSTEQ + BranchCondition.NE, BranchCondition.NZ -> Opcode.BSTNE + BranchCondition.MI, BranchCondition.NEG -> Opcode.BSTNEG + BranchCondition.PL, BranchCondition.POS -> Opcode.BSTPOS + BranchCondition.VC -> Opcode.BSTVC + BranchCondition.VS -> Opcode.BSTVS } - else throw AssemblyError("need label or address for branch") + return IRInstructions.branch(opcode, target) } private fun IRInvertedBranchInstr(condition: BranchCondition, label: String?=null, address: UInt?=null): IRInstruction { - if(label!=null) - return when(condition) { - BranchCondition.CS -> IRInstruction(Opcode.BSTCC, labelSymbol = label) - BranchCondition.CC -> IRInstruction(Opcode.BSTCS, labelSymbol = label) - BranchCondition.EQ, BranchCondition.Z -> IRInstruction(Opcode.BSTNE, labelSymbol = label) - BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTEQ, labelSymbol = label) - BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTPOS, labelSymbol = label) - BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTNEG, labelSymbol = label) - BranchCondition.VC -> IRInstruction(Opcode.BSTVS, labelSymbol = label) - BranchCondition.VS -> IRInstruction(Opcode.BSTVC, labelSymbol = label) - } - else if(address!=null) { - return when(condition) { - BranchCondition.CS -> IRInstruction(Opcode.BSTCC, address = address.toAddress()) - BranchCondition.CC -> IRInstruction(Opcode.BSTCS, address = address.toAddress()) - BranchCondition.EQ, BranchCondition.Z -> IRInstruction(Opcode.BSTNE, address = address.toAddress()) - BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTEQ, address = address.toAddress()) - BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTPOS, address = address.toAddress()) - BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTNEG, address = address.toAddress()) - BranchCondition.VC -> IRInstruction(Opcode.BSTVS, address = address.toAddress()) - BranchCondition.VS -> IRInstruction(Opcode.BSTVC, address = address.toAddress()) - } + val target = branchTarget(label, address) + val opcode = when(condition) { + BranchCondition.CS -> Opcode.BSTCC + BranchCondition.CC -> Opcode.BSTCS + BranchCondition.EQ, BranchCondition.Z -> Opcode.BSTNE + BranchCondition.NE, BranchCondition.NZ -> Opcode.BSTEQ + BranchCondition.MI, BranchCondition.NEG -> Opcode.BSTPOS + BranchCondition.PL, BranchCondition.POS -> Opcode.BSTNEG + BranchCondition.VC -> Opcode.BSTVS + BranchCondition.VS -> Opcode.BSTVC } - else throw AssemblyError("need label or address for branch") + return IRInstructions.branch(opcode, target) + } + + private fun branchTarget(label: String?, address: UInt?): CodeReference = when { + label!=null -> codeLabel(label) + address!=null -> codeAddress(address) + else -> throw AssemblyError("need label or address for branch") + } + + private fun branchTarget(label: String?, address: MemoryAddress?): CodeReference = when { + label!=null -> codeLabel(label) + address!=null -> codeAddress(address) + else -> throw AssemblyError("need label or address for branch") } private fun labelFirstChunk(chunks: IRCodeChunks, label: String): IRCodeChunks { @@ -660,8 +650,8 @@ class IRCodeGen( // no statements for this choice value, jump to the end immediately choice.values.children.map { v -> v as PtNumber }.sortedBy { v -> v.number }.forEach { value -> result += IRCodeChunk(null, null).also { chunk -> - chunk += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt()) - chunk += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel) + chunk += IRInstructions.compareImmediate(valueDt, valueTr.resultReg, value.number.toInt()) + chunk += IRInstructions.branch(Opcode.BSTEQ, codeLabel(endLabel)) } } } else { @@ -676,8 +666,8 @@ class IRCodeGen( } choice.values.children.map { v -> v as PtNumber }.sortedBy { v -> v.number }.forEach { value -> result += IRCodeChunk(null, null).also { chunk -> - chunk += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt()) - chunk += IRInstruction(Opcode.BSTEQ, labelSymbol = branchLabel) + chunk += IRInstructions.compareImmediate(valueDt, valueTr.resultReg, value.number.toInt()) + chunk += IRInstructions.branch(Opcode.BSTEQ, codeLabel(branchLabel)) } } } @@ -685,12 +675,12 @@ class IRCodeGen( } if(choices.isNotEmpty()) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null) + addInstr(result, IRInstructions.jump(codeLabel(endLabel)), null) choices.forEach { (label, choice) -> result += labelFirstChunk(translateNode(choice.statements), label) if(!choice.isOnlyGotoOrReturn()) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null) + addInstr(result, IRInstructions.jump(codeLabel(endLabel)), null) } result += IRCodeChunk(endLabel, null) @@ -727,70 +717,70 @@ class IRCodeGen( if(!needsWidening) return Pair(srcReg, srcDt) if(loopvarDt == IRDataType.LONG && srcDt == IRDataType.BYTE) { val longReg = registers.next(IRDataType.LONG) - chunk += IRInstruction(Opcode.EXTL, srcDt, reg1=longReg, reg2=srcReg) + chunk += IRInstructions.binary(Opcode.EXTL, srcDt, longReg, srcReg) return Pair(longReg, IRDataType.LONG) } val wordReg = registers.next(IRDataType.WORD) - chunk += IRInstruction(Opcode.EXT, srcDt, reg1=wordReg, reg2=srcReg) + chunk += IRInstructions.binary(Opcode.EXT, srcDt, wordReg, srcReg) return Pair(wordReg, loopvarDt) } when { iterable.type.isString -> { - addInstr(result, IRInstruction(Opcode.LOAD, indexRegType, reg1 = indexReg, immediate = 0), null) + addInstr(result, IRInstructions.load(indexRegType, indexReg, 0), null) val loopChunk = IRCodeChunk(loopLabel, null) - loopChunk += IRInstruction(Opcode.LOADX, elementDt, reg1 = tmpReg, reg2 = indexReg, labelSymbol = iterable.name) + loopChunk += IRInstructions.loadMemory(Opcode.LOADX, elementDt, tmpReg, IRMemory.indexed(iterable.name, indexReg, options.compTarget.indexRegType)) if(!options.compTarget.cpu.statusBitsOnMultiByteOps) - loopChunk += IRInstruction(Opcode.CMPI, elementDt, reg1 = tmpReg, immediate = 0) - loopChunk += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel) + loopChunk += IRInstructions.compareImmediate(elementDt, tmpReg, 0) + loopChunk += IRInstructions.branch(Opcode.BSTEQ, codeLabel(endLabel)) val (storeReg, storeDt) = emitWidening(loopChunk, tmpReg, elementDt) - loopChunk += IRInstruction(Opcode.STOREM, storeDt, reg1 = storeReg, labelSymbol = loopvarSymbol) + loopChunk += IRInstructions.storeMemory(Opcode.STOREM, storeDt, storeReg, IRMemory.direct(loopvarSymbol)) result += loopChunk result += translateNode(forLoop.statements) val jumpChunk = IRCodeChunk(null, null) - jumpChunk += IRInstruction(Opcode.INC, indexRegType, reg1 = indexReg) - jumpChunk += IRInstruction(Opcode.JUMP, labelSymbol = loopLabel) + jumpChunk += IRInstructions.unary(Opcode.INC, indexRegType, indexReg) + jumpChunk += IRInstructions.jump(codeLabel(loopLabel)) result += jumpChunk result += IRCodeChunk(endLabel, null) } iterable.type.isSplitWordArray(options.compTarget) -> { if(elementDt!=IRDataType.WORD && elementDt!=IRDataType.POINTER) throw AssemblyError("weird dt $elementDt") - addInstr(result, IRInstruction(Opcode.LOAD, indexRegType, reg1=indexReg, immediate = 0), null) + addInstr(result, IRInstructions.load(indexRegType, indexReg, 0), null) val loopChunk = IRCodeChunk(loopLabel, null) val tmpRegLsb = registers.next(IRDataType.BYTE) val tmpRegMsb = registers.next(IRDataType.BYTE) val concatReg = registers.next(IRDataType.WORD) - loopChunk += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2=indexReg, labelSymbol=iterable.name+"_msb") - loopChunk += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegLsb, reg2=indexReg, labelSymbol=iterable.name+"_lsb") - loopChunk += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=concatReg, reg2=tmpRegMsb, reg3=tmpRegLsb) + loopChunk += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, tmpRegMsb, IRMemory.indexed(iterable.name+"_msb", indexReg, options.compTarget.indexRegType)) + loopChunk += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, tmpRegLsb, IRMemory.indexed(iterable.name+"_lsb", indexReg, options.compTarget.indexRegType)) + loopChunk += IRInstructions.concat(IRDataType.BYTE, concatReg, tmpRegMsb, tmpRegLsb) val (storeReg, storeDt) = emitWidening(loopChunk, concatReg, IRDataType.WORD) - loopChunk += IRInstruction(Opcode.STOREM, storeDt, reg1=storeReg, labelSymbol = loopvarSymbol) + loopChunk += IRInstructions.storeMemory(Opcode.STOREM, storeDt, storeReg, IRMemory.direct(loopvarSymbol)) result += loopChunk result += translateNode(forLoop.statements) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.INC, indexRegType, reg1=indexReg) + it += IRInstructions.unary(Opcode.INC, indexRegType, indexReg) if(iterableLength!=256 || indexRegType==IRDataType.WORD) - it += IRInstruction(Opcode.CMPI, indexRegType, reg1=indexReg, immediate = iterableLength) - it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel) + it += IRInstructions.compareImmediate(indexRegType, indexReg, iterableLength!!) + it += IRInstructions.branch(Opcode.BSTNE, codeLabel(loopLabel)) } } else -> { val arrElementDt = iterable.type.elementType() val elementSize = program.memsizer.memorySize(arrElementDt, null) val arrElementIR = irType(arrElementDt) - addInstr(result, IRInstruction(Opcode.LOAD, indexRegType, reg1=indexReg, immediate = 0), null) + addInstr(result, IRInstructions.load(indexRegType, indexReg, 0), null) val loopChunk = IRCodeChunk(loopLabel, null) - loopChunk += IRInstruction(Opcode.LOADX, arrElementIR, reg1=tmpReg, reg2=indexReg, labelSymbol=iterable.name, scale = elementSize) + loopChunk += IRInstructions.loadMemory(Opcode.LOADX, arrElementIR, tmpReg, IRMemory.indexed(iterable.name, indexReg, options.compTarget.indexRegType, scale=elementSize)) val (storeReg, storeDt) = emitWidening(loopChunk, tmpReg, arrElementIR) - loopChunk += IRInstruction(Opcode.STOREM, storeDt, reg1=storeReg, labelSymbol = loopvarSymbol) + loopChunk += IRInstructions.storeMemory(Opcode.STOREM, storeDt, storeReg, IRMemory.direct(loopvarSymbol)) result += loopChunk result += translateNode(forLoop.statements) result += addConstToReg(indexReg, 1, indexRegType) result += IRCodeChunk(null, null).also { if(iterableLength!=256 || indexRegType==IRDataType.WORD) - it += IRInstruction(Opcode.CMPI, indexRegType, reg1=indexReg, immediate = iterableLength!!) - it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel) + it += IRInstructions.compareImmediate(indexRegType, indexReg, iterableLength!!) + it += IRInstructions.branch(Opcode.BSTNE, codeLabel(loopLabel)) } } } @@ -820,31 +810,31 @@ class IRCodeGen( // downto 0 optimization (byte) val fromTr = expressionEval.translateExpression(iterable.from) addToResult(result, fromTr, fromTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null) + addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, loopvarDtIr, fromTr.resultReg, IRMemory.direct(loopvarSymbol)), null) result += labelFirstChunk(translateNode(forLoop.statements), loopLabel) result += addConstMem(loopvarDtIr, null, loopvarSymbol, -1) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol) - it += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = fromTr.resultReg, immediate = 255) - it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel) + it += IRInstructions.loadMemory(Opcode.LOADM, loopvarDtIr, fromTr.resultReg, IRMemory.direct(loopvarSymbol)) + it += IRInstructions.compareImmediate(loopvarDtIr, fromTr.resultReg, 255) + it += IRInstructions.branch(Opcode.BSTNE, codeLabel(loopLabel)) } } else if(step==-1 && iterable.to.asConstInteger()==1) { // downto 1 optimization (byte and word) val fromTr = expressionEval.translateExpression(iterable.from) addToResult(result, fromTr, fromTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null) + addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, loopvarDtIr, fromTr.resultReg, IRMemory.direct(loopvarSymbol)), null) result += labelFirstChunk(translateNode(forLoop.statements), loopLabel) result += addConstMem(loopvarDtIr, null, loopvarSymbol, -1) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol) + it += IRInstructions.loadMemory(Opcode.LOADM, loopvarDtIr, fromTr.resultReg, IRMemory.direct(loopvarSymbol)) // Emit explicit CMPI #0 before the BSTNE branch on 8-bit targets where // LOADM doesn't reliably set Z for multi-byte results. Skip on targets // that honor the contract (e.g. M68000). See CpuType.statusBitsOnMultiByteOps. if(!options.compTarget.cpu.statusBitsOnMultiByteOps) { - it += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = fromTr.resultReg, immediate = 0) + it += IRInstructions.compareImmediate(loopvarDtIr, fromTr.resultReg, 0) } - it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel) + it += IRInstructions.branch(Opcode.BSTNE, codeLabel(loopLabel)) } } else { @@ -860,45 +850,45 @@ class IRCodeGen( val precheckInstruction = if(loopvarDt.isSigned) { if(step>0) - IRInstruction(Opcode.BGTSR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor) + IRInstructions.branchRegister(Opcode.BGTSR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, codeLabel(labelAfterFor)) else - IRInstruction(Opcode.BGTSR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor) + IRInstructions.branchRegister(Opcode.BGTSR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, codeLabel(labelAfterFor)) } else { if(step>0) - IRInstruction(Opcode.BGTR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor) + IRInstructions.branchRegister(Opcode.BGTR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, codeLabel(labelAfterFor)) else - IRInstruction(Opcode.BGTR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor) + IRInstructions.branchRegister(Opcode.BGTR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, codeLabel(labelAfterFor)) } addInstr(result, precheckInstruction, null) - addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null) + addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, loopvarDtIr, fromTr.resultReg, IRMemory.direct(loopvarSymbol)), null) result += labelFirstChunk(translateNode(forLoop.statements), loopLabel) if(step==1 || step==-1) { // if endvalue == loopvar, stop loop, else iterate result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol) - it += IRInstruction(Opcode.CMP, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg) - it += IRInstruction(Opcode.BSTEQ, labelSymbol = labelAfterFor) + it += IRInstructions.loadMemory(Opcode.LOADM, loopvarDtIr, fromTr.resultReg, IRMemory.direct(loopvarSymbol)) + it += IRInstructions.compare(loopvarDtIr, toTr.resultReg, fromTr.resultReg) + it += IRInstructions.branch(Opcode.BSTEQ, codeLabel(labelAfterFor)) } result += addConstMem(loopvarDtIr, null, loopvarSymbol, step) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = loopLabel), null) + addInstr(result, IRInstructions.jump(codeLabel(loopLabel)), null) } else { // ind/dec index, then: // ascending: if endvalue >= loopvar, iterate // descending: if loopvar >= endvalue, iterate val previousReg = registers.next(loopvarDtIr) - addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = previousReg, labelSymbol = loopvarSymbol), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, loopvarDtIr, previousReg, IRMemory.direct(loopvarSymbol)), null) result += addConstMem(loopvarDtIr, null, loopvarSymbol, step) - addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol = loopvarSymbol), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, loopvarDtIr, fromTr.resultReg, IRMemory.direct(loopvarSymbol)), null) val compareOpcode = if(loopvarDt.isSigned) Opcode.BGTSR else Opcode.BGTR if(step > 0) { - addInstr(result, IRInstruction(compareOpcode, loopvarDtIr, reg1 = previousReg, reg2 = fromTr.resultReg, labelSymbol = labelAfterFor), null) - addInstr(result, IRInstruction(compareOpcode, loopvarDtIr, reg1 = fromTr.resultReg, reg2 = toTr.resultReg, labelSymbol = labelAfterFor), null) + addInstr(result, IRInstructions.branchRegister(compareOpcode, loopvarDtIr, previousReg, fromTr.resultReg, codeLabel(labelAfterFor)), null) + addInstr(result, IRInstructions.branchRegister(compareOpcode, loopvarDtIr, fromTr.resultReg, toTr.resultReg, codeLabel(labelAfterFor)), null) } else { - addInstr(result, IRInstruction(compareOpcode, loopvarDtIr, reg1 = fromTr.resultReg, reg2 = previousReg, labelSymbol = labelAfterFor), null) - addInstr(result, IRInstruction(compareOpcode, loopvarDtIr, reg1 = toTr.resultReg, reg2 = fromTr.resultReg, labelSymbol = labelAfterFor), null) + addInstr(result, IRInstructions.branchRegister(compareOpcode, loopvarDtIr, fromTr.resultReg, previousReg, codeLabel(labelAfterFor)), null) + addInstr(result, IRInstructions.branchRegister(compareOpcode, loopvarDtIr, toTr.resultReg, fromTr.resultReg, codeLabel(labelAfterFor)), null) } - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = loopLabel), null) + addInstr(result, IRInstructions.jump(codeLabel(loopLabel)), null) } result += IRCodeChunk(labelAfterFor, null) } @@ -936,19 +926,19 @@ class IRCodeGen( stepTr.dt == loopvarDtIr -> stepTr.resultReg stepTr.dt == IRDataType.WORD && loopvarDtIr == IRDataType.LONG && !iterable.step.type.isSigned -> { val widenedReg = registers.next(IRDataType.LONG) - addInstr(result, IRInstruction(Opcode.EXT, IRDataType.WORD, reg1=widenedReg, reg2=stepTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.EXT, IRDataType.WORD, widenedReg, stepTr.resultReg), null) widenedReg } else -> throw AssemblyError("unexpected normalized loop step ${stepTr.dt} for $loopvarDtIr") } // step == 0 => empty loop - addInstr(result, IRInstruction(Opcode.CMPI, loopvarDtIr, reg1=stepReg, immediate=0), null) - addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol=labelAfterFor), null) + addInstr(result, IRInstructions.compareImmediate(loopvarDtIr, stepReg, 0), null) + addInstr(result, IRInstructions.branch(Opcode.BSTEQ, codeLabel(labelAfterFor)), null) // Determine direction from step sign if(signedStep) - addInstr(result, IRInstruction(Opcode.BSTNEG, labelSymbol=labelDescending!!), null) + addInstr(result, IRInstructions.branch(Opcode.BSTNEG, codeLabel(labelDescending!!)), null) val precheckOpcode = if(loopvarDt.isSigned) Opcode.BGTSR else Opcode.BGTR @@ -957,46 +947,46 @@ class IRCodeGen( val nextReg = registers.next(loopvarDtIr) if(signedStep) - addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=directionReg!!, immediate=0), null) - addInstr(result, IRInstruction(precheckOpcode, loopvarDtIr, reg1=fromTr.resultReg, reg2=toTr.resultReg, labelSymbol=labelAfterFor), null) - addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol=loopLabel), null) + addInstr(result, IRInstructions.load(IRDataType.BYTE, directionReg!!, 0), null) + addInstr(result, IRInstructions.branchRegister(precheckOpcode, loopvarDtIr, fromTr.resultReg, toTr.resultReg, codeLabel(labelAfterFor)), null) + addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, loopvarDtIr, fromTr.resultReg, IRMemory.direct(loopvarSymbol)), null) + addInstr(result, IRInstructions.jump(codeLabel(loopLabel)), null) if(signedStep) { result += IRCodeChunk(labelDescending!!, null) - addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=directionReg!!, immediate=1), null) - addInstr(result, IRInstruction(precheckOpcode, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg, labelSymbol=labelAfterFor), null) - addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null) + addInstr(result, IRInstructions.load(IRDataType.BYTE, directionReg!!, 1), null) + addInstr(result, IRInstructions.branchRegister(precheckOpcode, loopvarDtIr, toTr.resultReg, fromTr.resultReg, codeLabel(labelAfterFor)), null) + addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, loopvarDtIr, fromTr.resultReg, IRMemory.direct(loopvarSymbol)), null) } result += labelFirstChunk(translateNode(forLoop.statements), loopLabel) - addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1=currentReg, labelSymbol=loopvarSymbol), null) - addInstr(result, IRInstruction(Opcode.LOADR, loopvarDtIr, reg1=nextReg, reg2=currentReg), null) - addInstr(result, IRInstruction(Opcode.ADDR, loopvarDtIr, reg1=nextReg, reg2=stepReg), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, loopvarDtIr, currentReg, IRMemory.direct(loopvarSymbol)), null) + addInstr(result, IRInstructions.move(loopvarDtIr, nextReg, currentReg), null) + addInstr(result, IRInstructions.binary(Opcode.ADDR, loopvarDtIr, nextReg, stepReg), null) if(signedStep) { - addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=directionReg!!, immediate=0), null) - addInstr(result, IRInstruction(Opcode.BSTNE, labelSymbol=labelDescendingTail!!), null) + addInstr(result, IRInstructions.compareImmediate(IRDataType.BYTE, directionReg!!, 0), null) + addInstr(result, IRInstructions.branch(Opcode.BSTNE, codeLabel(labelDescendingTail!!)), null) } // Ascending: wrapping makes next smaller than current; otherwise // next must not exceed the upper bound. - addInstr(result, IRInstruction(precheckOpcode, loopvarDtIr, reg1=currentReg, reg2=nextReg, labelSymbol=labelAfterFor), null) - addInstr(result, IRInstruction(precheckOpcode, loopvarDtIr, reg1=nextReg, reg2=toTr.resultReg, labelSymbol=labelAfterFor), null) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol=labelStoreNext), null) + addInstr(result, IRInstructions.branchRegister(precheckOpcode, loopvarDtIr, currentReg, nextReg, codeLabel(labelAfterFor)), null) + addInstr(result, IRInstructions.branchRegister(precheckOpcode, loopvarDtIr, nextReg, toTr.resultReg, codeLabel(labelAfterFor)), null) + addInstr(result, IRInstructions.jump(codeLabel(labelStoreNext)), null) if(signedStep) { result += IRCodeChunk(labelDescendingTail!!, null) // Descending: wrapping makes next larger than current; otherwise // next must not fall below the lower bound. - addInstr(result, IRInstruction(precheckOpcode, loopvarDtIr, reg1=nextReg, reg2=currentReg, labelSymbol=labelAfterFor), null) - addInstr(result, IRInstruction(precheckOpcode, loopvarDtIr, reg1=toTr.resultReg, reg2=nextReg, labelSymbol=labelAfterFor), null) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol=labelStoreNext), null) + addInstr(result, IRInstructions.branchRegister(precheckOpcode, loopvarDtIr, nextReg, currentReg, codeLabel(labelAfterFor)), null) + addInstr(result, IRInstructions.branchRegister(precheckOpcode, loopvarDtIr, toTr.resultReg, nextReg, codeLabel(labelAfterFor)), null) + addInstr(result, IRInstructions.jump(codeLabel(labelStoreNext)), null) } result += IRCodeChunk(labelStoreNext, null) - addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=nextReg, labelSymbol=loopvarSymbol), null) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol=loopLabel), null) + addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, loopvarDtIr, nextReg, IRMemory.direct(loopvarSymbol)), null) + addInstr(result, IRInstructions.jump(codeLabel(loopLabel)), null) result += IRCodeChunk(labelAfterFor, null) return result @@ -1044,18 +1034,18 @@ class IRCodeGen( val result = mutableListOf() val indexReg = registers.next(loopvarDtIr) val chunk = IRCodeChunk(null, null) - chunk += IRInstruction(Opcode.STOREIM, loopvarDtIr, immediate = iterable.first, labelSymbol=loopvarSymbol) + chunk += IRInstructions.storeImmediate(loopvarDtIr, iterable.first, IRMemory.direct(loopvarSymbol)) result += chunk result += labelFirstChunk(translateNode(forLoop.statements), loopLabel) val chunk2 = addConstMem(loopvarDtIr, null, loopvarSymbol, iterable.step) if(loopvarDtIr==IRDataType.BYTE && iterable.step==-1 && iterable.last==0) { // downto 0 optimization (byte) if(loopvarDt.isSignedByte || iterable.first<=127) { - chunk2 += IRInstruction(Opcode.BSTPOS, labelSymbol = loopLabel) + chunk2 += IRInstructions.branch(Opcode.BSTPOS, codeLabel(loopLabel)) } else { - chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol) - chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped) - chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel) + chunk2 += IRInstructions.loadMemory(Opcode.LOADM, loopvarDtIr, indexReg, IRMemory.direct(loopvarSymbol)) + chunk2 += IRInstructions.compareImmediate(loopvarDtIr, indexReg, rangeEndExclusiveWrapped) + chunk2 += IRInstructions.branch(Opcode.BSTNE, codeLabel(loopLabel)) } } else if(iterable.step==-1 && iterable.last==1) { @@ -1064,16 +1054,16 @@ class IRCodeGen( // reliably set Z; we must explicitly test the loop var. Skip the explicit // CMPI on targets that honor the contract (e.g. M68000). // See CpuType.statusBitsOnMultiByteOps. - chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol) + chunk2 += IRInstructions.loadMemory(Opcode.LOADM, loopvarDtIr, indexReg, IRMemory.direct(loopvarSymbol)) if(!options.compTarget.cpu.statusBitsOnMultiByteOps) { - chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = 0) + chunk2 += IRInstructions.compareImmediate(loopvarDtIr, indexReg, 0) } - chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel) + chunk2 += IRInstructions.branch(Opcode.BSTNE, codeLabel(loopLabel)) } else { // downto some other value - chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol) - chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped) - chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel) + chunk2 += IRInstructions.loadMemory(Opcode.LOADM, loopvarDtIr, indexReg, IRMemory.direct(loopvarSymbol)) + chunk2 += IRInstructions.compareImmediate(loopvarDtIr, indexReg, rangeEndExclusiveWrapped) + chunk2 += IRInstructions.branch(Opcode.BSTNE, codeLabel(loopLabel)) } result += chunk2 return result @@ -1084,77 +1074,72 @@ class IRCodeGen( when(value) { 0 -> { /* do nothing */ } 1 -> { - code += IRInstruction(Opcode.INC, dt, reg1=reg) + code += IRInstructions.unary(Opcode.INC, dt, reg) } 2 -> { - code += IRInstruction(Opcode.INC, dt, reg1=reg) - code += IRInstruction(Opcode.INC, dt, reg1=reg) + code += IRInstructions.unary(Opcode.INC, dt, reg) + code += IRInstructions.unary(Opcode.INC, dt, reg) } -1 -> { - code += IRInstruction(Opcode.DEC, dt, reg1=reg) + code += IRInstructions.unary(Opcode.DEC, dt, reg) } -2 -> { - code += IRInstruction(Opcode.DEC, dt, reg1=reg) - code += IRInstruction(Opcode.DEC, dt, reg1=reg) + code += IRInstructions.unary(Opcode.DEC, dt, reg) + code += IRInstructions.unary(Opcode.DEC, dt, reg) } else -> { code += if(value>0) { - IRInstruction(Opcode.ADD, dt, reg1 = reg, immediate = value) + IRInstructions.binaryImmediate(Opcode.ADD, dt, reg, value) } else { - IRInstruction(Opcode.SUB, dt, reg1 = reg, immediate = -value) + IRInstructions.binaryImmediate(Opcode.SUB, dt, reg, -value) } } } return code } + /** the memory reference for an in-place operation on either a known absolute address or a symbol */ + private fun mem(knownAddress: UInt?, symbol: String?): MemoryReference = + if(knownAddress!=null) IRMemory.direct(knownAddress.toAddress()) else IRMemory.direct(symbol!!) + private fun addConstMem(dt: IRDataType, knownAddress: UInt?, symbol: String?, value: Int): IRCodeChunk { val code = IRCodeChunk(null, null) val is6502 = options.compTarget.cpu.is6502 + fun mem() = mem(knownAddress, symbol) when(value) { 0 -> { /* do nothing */ } 1 -> { - code += if(knownAddress!=null) - IRInstruction(Opcode.INCM, dt, address = knownAddress.toAddress()) - else - IRInstruction(Opcode.INCM, dt, labelSymbol = symbol) + code += IRInstructions.memoryOp(Opcode.INCM, dt, mem()) } 2 -> { if(is6502) { if(knownAddress!=null) { - code += IRInstruction(Opcode.INCM, dt, address = knownAddress.toAddress()) - code += IRInstruction(Opcode.INCM, dt, address = knownAddress.toAddress()) + code += IRInstructions.memoryOp(Opcode.INCM, dt, IRMemory.direct(knownAddress.toAddress())) + code += IRInstructions.memoryOp(Opcode.INCM, dt, IRMemory.direct(knownAddress.toAddress())) } else { - code += IRInstruction(Opcode.INCM, dt, labelSymbol = symbol) - code += IRInstruction(Opcode.INCM, dt, labelSymbol = symbol) + val symbolName = symbol!! + code += IRInstructions.memoryOp(Opcode.INCM, dt, IRMemory.direct(symbolName)) + code += IRInstructions.memoryOp(Opcode.INCM, dt, IRMemory.direct(symbolName)) } } else { - if(knownAddress!=null) - code += IRInstruction(Opcode.ADDIM, dt, immediate = 2, address = knownAddress.toAddress()) - else - code += IRInstruction(Opcode.ADDIM, dt, immediate = 2, labelSymbol = symbol) + code += IRInstructions.memoryOpImmediate(Opcode.ADDIM, dt, mem(), 2) } } -1 -> { - code += if(knownAddress!=null) - IRInstruction(Opcode.DECM, dt, address = knownAddress.toAddress()) - else - IRInstruction(Opcode.DECM, dt, labelSymbol = symbol) + code += IRInstructions.memoryOp(Opcode.DECM, dt, mem()) } -2 -> { if(is6502) { if(knownAddress!=null) { - code += IRInstruction(Opcode.DECM, dt, address = knownAddress.toAddress()) - code += IRInstruction(Opcode.DECM, dt, address = knownAddress.toAddress()) + code += IRInstructions.memoryOp(Opcode.DECM, dt, IRMemory.direct(knownAddress.toAddress())) + code += IRInstructions.memoryOp(Opcode.DECM, dt, IRMemory.direct(knownAddress.toAddress())) } else { - code += IRInstruction(Opcode.DECM, dt, labelSymbol = symbol) - code += IRInstruction(Opcode.DECM, dt, labelSymbol = symbol) + val symbolName = symbol!! + code += IRInstructions.memoryOp(Opcode.DECM, dt, IRMemory.direct(symbolName)) + code += IRInstructions.memoryOp(Opcode.DECM, dt, IRMemory.direct(symbolName)) } } else { - if(knownAddress!=null) - code += IRInstruction(Opcode.SUBIM, dt, immediate = 2, address = knownAddress.toAddress()) - else - code += IRInstruction(Opcode.SUBIM, dt, immediate = 2, labelSymbol = symbol) + code += IRInstructions.memoryOpImmediate(Opcode.SUBIM, dt, mem(), 2) } } else -> { @@ -1162,30 +1147,18 @@ class IRCodeGen( // float loop variables are not currently supported; keep the load+addm/subm form val valueReg = registers.next(dt) if(value>0) { - code += IRInstruction(Opcode.LOAD, dt, reg1=valueReg, immediate = value) - code += if(knownAddress!=null) - IRInstruction(Opcode.ADDM, dt, reg1=valueReg, address = knownAddress.toAddress()) - else - IRInstruction(Opcode.ADDM, dt, reg1=valueReg, labelSymbol = symbol) + code += IRInstructions.load(dt, valueReg, value) + code += IRInstructions.memoryOp(Opcode.ADDM, dt, mem(), valueReg) } else { - code += IRInstruction(Opcode.LOAD, dt, reg1=valueReg, immediate = -value) - code += if(knownAddress!=null) - IRInstruction(Opcode.SUBM, dt, reg1=valueReg, address = knownAddress.toAddress()) - else - IRInstruction(Opcode.SUBM, dt, reg1=valueReg, labelSymbol = symbol) + code += IRInstructions.load(dt, valueReg, -value) + code += IRInstructions.memoryOp(Opcode.SUBM, dt, mem(), valueReg) } } else { if(value>0) { - code += if(knownAddress!=null) - IRInstruction(Opcode.ADDIM, dt, immediate = value, address = knownAddress.toAddress()) - else - IRInstruction(Opcode.ADDIM, dt, immediate = value, labelSymbol = symbol) + code += IRInstructions.memoryOpImmediate(Opcode.ADDIM, dt, mem(), value) } else { - code += if(knownAddress!=null) - IRInstruction(Opcode.SUBIM, dt, immediate = -value, address = knownAddress.toAddress()) - else - IRInstruction(Opcode.SUBIM, dt, immediate = -value, labelSymbol = symbol) + code += IRInstructions.memoryOpImmediate(Opcode.SUBIM, dt, mem(), -value) } } } @@ -1198,9 +1171,9 @@ class IRCodeGen( if(factor==1.0) return code code += if(factor==0.0) { - IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = RegisterNum(fpReg), immediateFp = 0.0) + IRInstructions.loadFloat(fpReg, 0.0) } else { - IRInstruction(Opcode.MULS, IRDataType.FLOAT, fpReg1 = RegisterNum(fpReg), immediateFp = factor) + IRInstructions.binaryImmediateFloat(Opcode.MULS, fpReg, factor) } return code } @@ -1210,17 +1183,11 @@ class IRCodeGen( if(factor==1.0) return code if(factor==0.0) { - code += if(knownAddress!=null) - IRInstruction(Opcode.STOREZM, IRDataType.FLOAT, address = knownAddress.toAddress()) - else - IRInstruction(Opcode.STOREZM, IRDataType.FLOAT, labelSymbol = symbol) + code += IRInstructions.storeZero(Opcode.STOREZM, IRDataType.FLOAT, mem(knownAddress, symbol)) } else { val factorReg = registers.next(IRDataType.FLOAT) - code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1=RegisterNum(factorReg), immediateFp = factor) - code += if(knownAddress!=null) - IRInstruction(Opcode.MULSM, IRDataType.FLOAT, fpReg1 = RegisterNum(factorReg), address = knownAddress.toAddress()) - else - IRInstruction(Opcode.MULSM, IRDataType.FLOAT, fpReg1 = RegisterNum(factorReg), labelSymbol = symbol) + code += IRInstructions.loadFloat(factorReg, factor) + code += IRInstructions.memoryOp(Opcode.MULSM, IRDataType.FLOAT, mem(knownAddress, symbol), factorReg) } return code } @@ -1233,17 +1200,17 @@ class IRCodeGen( val pow2 = powersOfTwoInt.indexOf(factor) if(pow2==1) { // just shift 1 bit - code += IRInstruction(Opcode.LSL, irdt, reg1=reg) + code += IRInstructions.unary(Opcode.LSL, irdt, reg) } else if(pow2>=1) { // just shift multiple bits - code += IRInstruction(Opcode.LSLI, irdt, reg1=reg, immediate = pow2) + code += IRInstructions.binaryImmediate(Opcode.LSLI, irdt, reg, pow2) } else { code += if (factor == 0) { - IRInstruction(Opcode.LOAD, irdt, reg1=reg, immediate = 0) + IRInstructions.load(irdt, reg, 0) } else { val opcode = if(dt.isSigned) Opcode.MULS else Opcode.MUL - IRInstruction(opcode, irdt, reg1=reg, immediate = factor) + IRInstructions.binaryImmediate(opcode, irdt, reg, factor) } } return code @@ -1256,34 +1223,22 @@ class IRCodeGen( val pow2 = powersOfTwoInt.indexOf(factor) if(pow2==1) { // just shift 1 bit - code += if(knownAddress!=null) - IRInstruction(Opcode.LSLM, dt, address = knownAddress.toAddress()) - else - IRInstruction(Opcode.LSLM, dt, labelSymbol = symbol) + code += IRInstructions.memoryOp(Opcode.LSLM, dt, mem(knownAddress, symbol)) } else if(pow2>=1) { // just shift multiple bits val pow2reg = registers.next(IRDataType.BYTE) - code += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=pow2reg, immediate = pow2) - code += if(knownAddress!=null) - IRInstruction(Opcode.LSLNM, dt, reg1=pow2reg, address = knownAddress.toAddress()) - else - IRInstruction(Opcode.LSLNM, dt, reg1=pow2reg, labelSymbol = symbol) + code += IRInstructions.load(IRDataType.BYTE, pow2reg, pow2) + code += IRInstructions.memoryOp(Opcode.LSLNM, dt, mem(knownAddress, symbol), pow2reg) } else { if (factor == 0) { - code += if(knownAddress!=null) - IRInstruction(Opcode.STOREZM, dt, address = knownAddress.toAddress()) - else - IRInstruction(Opcode.STOREZM, dt, labelSymbol = symbol) + code += IRInstructions.storeZero(Opcode.STOREZM, dt, mem(knownAddress, symbol)) } else { val factorReg = registers.next(dt) - code += IRInstruction(Opcode.LOAD, dt, reg1=factorReg, immediate = factor) + code += IRInstructions.load(dt, factorReg, factor) val opcode = if(signed) Opcode.MULSM else Opcode.MULM - code += if(knownAddress!=null) - IRInstruction(opcode, dt, reg1=factorReg, address = knownAddress.toAddress()) - else - IRInstruction(opcode, dt, reg1=factorReg, labelSymbol = symbol) + code += IRInstructions.memoryOp(opcode, dt, mem(knownAddress, symbol), factorReg) } } return code @@ -1294,9 +1249,9 @@ class IRCodeGen( if(factor==1.0) return code code += if(factor==0.0) { - IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = RegisterNum(fpReg), immediateFp = Double.MAX_VALUE) + IRInstructions.loadFloat(fpReg, Double.MAX_VALUE) } else { - IRInstruction(Opcode.DIVS, IRDataType.FLOAT, fpReg1 = RegisterNum(fpReg), immediateFp = factor) + IRInstructions.binaryImmediateFloat(Opcode.DIVS, fpReg, factor) } return code } @@ -1307,18 +1262,12 @@ class IRCodeGen( return code if(factor==0.0) { val maxvalueReg = registers.next(IRDataType.FLOAT) - code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = RegisterNum(maxvalueReg), immediateFp = Double.MAX_VALUE) - code += if(knownAddress!=null) - IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = RegisterNum(maxvalueReg), address = knownAddress.toAddress()) - else - IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = RegisterNum(maxvalueReg), labelSymbol = symbol) + code += IRInstructions.loadFloat(maxvalueReg, Double.MAX_VALUE) + code += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.FLOAT, maxvalueReg, mem(knownAddress, symbol)) } else { val factorReg = registers.next(IRDataType.FLOAT) - code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1=RegisterNum(factorReg), immediateFp = factor) - code += if(knownAddress!=null) - IRInstruction(Opcode.DIVSM, IRDataType.FLOAT, fpReg1 = RegisterNum(factorReg), address = knownAddress.toAddress()) - else - IRInstruction(Opcode.DIVSM, IRDataType.FLOAT, fpReg1 = RegisterNum(factorReg), labelSymbol = symbol) + code += IRInstructions.loadFloat(factorReg, factor) + code += IRInstructions.memoryOp(Opcode.DIVSM, IRDataType.FLOAT, mem(knownAddress, symbol), factorReg) } return code } @@ -1331,12 +1280,12 @@ class IRCodeGen( if(pow2>=0 && !signed) { // unsigned division by a power of two: logical shift right (correct) if(pow2==1) { - code += IRInstruction(Opcode.LSR, dt, reg1=reg) + code += IRInstructions.unary(Opcode.LSR, dt, reg) } else if(dt == IRDataType.LONG && pow2 == 16) { // x / 65536 for unsigned long == x >> 16 == MSIGW(x) - code += IRInstruction(Opcode.MSIGW, dt, reg1=reg, reg2=reg) + code += IRInstructions.binary(Opcode.MSIGW, dt, reg, reg) } else { - code += IRInstruction(Opcode.LSRI, dt, reg1 = reg, immediate = pow2) + code += IRInstructions.binaryImmediate(Opcode.LSRI, dt, reg, pow2) } return code } @@ -1351,12 +1300,12 @@ class IRCodeGen( // regular div (also used for signed division by a power of two on non-6502 targets: >> floors, // whereas / truncates toward zero for negative dividends, so a plain shift is wrong) code += if (factor == 0) { - IRInstruction(Opcode.LOAD, dt, reg1=reg, immediate = 0xffff) + IRInstructions.load(dt, reg, 0xffff) } else { if(signed) - IRInstruction(Opcode.DIVS, dt, reg1=reg, immediate = factor) + IRInstructions.binaryImmediate(Opcode.DIVS, dt, reg, factor) else - IRInstruction(Opcode.DIV, dt, reg1=reg, immediate = factor) + IRInstructions.binaryImmediate(Opcode.DIV, dt, reg, factor) } return code } @@ -1370,17 +1319,17 @@ class IRCodeGen( // unsigned division by a power of two: logical shift right (correct) if(pow2==1) { code += if(knownAddress!=null) - IRInstruction(Opcode.LSRM, dt, address = knownAddress.toAddress()) + IRInstructions.memoryOp(Opcode.LSRM, dt, IRMemory.direct(knownAddress.toAddress())) else - IRInstruction(Opcode.LSRM, dt, labelSymbol = symbol) + IRInstructions.memoryOp(Opcode.LSRM, dt, IRMemory.direct(symbol!!)) } else { val pow2reg = registers.next(IRDataType.BYTE) - code += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=pow2reg, immediate = pow2) + code += IRInstructions.load(IRDataType.BYTE, pow2reg, pow2) code += if(knownAddress!=null) - IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, address = knownAddress.toAddress()) + IRInstructions.memoryOp(Opcode.LSRNM, dt, IRMemory.direct(knownAddress.toAddress()), pow2reg) else - IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, labelSymbol = symbol) + IRInstructions.memoryOp(Opcode.LSRNM, dt, IRMemory.direct(symbol!!), pow2reg) } return code } @@ -1392,40 +1341,40 @@ class IRCodeGen( // // signed division by a power of two: bias-corrected shift (much cheaper than a DIVS routine on the 6502) // val reg = registers.next(dt) // code += if(knownAddress!=null) -// IRInstruction(Opcode.LOADM, dt, reg1 = reg, address = knownAddress.toAddress()) +// IRInstructions.loadMemory(Opcode.LOADM, dt, reg, IRMemory.direct(knownAddress.toAddress())) // else -// IRInstruction(Opcode.LOADM, dt, reg1 = reg, labelSymbol = symbol) +// IRInstructions.loadMemory(Opcode.LOADM, dt, reg, IRMemory.direct(symbol)) // emitSignedDivByPow2Shift(code, dt, reg, pow2) // code += if(knownAddress!=null) -// IRInstruction(Opcode.STOREM, dt, reg1 = reg, address = knownAddress.toAddress()) +// IRInstructions.storeMemory(Opcode.STOREM, dt, reg, IRMemory.direct(knownAddress.toAddress())) // else -// IRInstruction(Opcode.STOREM, dt, reg1 = reg, labelSymbol = symbol) +// IRInstructions.storeMemory(Opcode.STOREM, dt, reg, IRMemory.direct(symbol)) // return code // } // regular div if (factor == 0) { val reg = registers.next(dt) - code += IRInstruction(Opcode.LOAD, dt, reg1=reg, immediate = 0xffff) + code += IRInstructions.load(dt, reg, 0xffff) code += if(knownAddress!=null) - IRInstruction(Opcode.STOREM, dt, reg1=reg, address = knownAddress.toAddress()) + IRInstructions.storeMemory(Opcode.STOREM, dt, reg, IRMemory.direct(knownAddress.toAddress())) else - IRInstruction(Opcode.STOREM, dt, reg1=reg, labelSymbol = symbol) + IRInstructions.storeMemory(Opcode.STOREM, dt, reg, IRMemory.direct(symbol!!)) } else { val factorReg = registers.next(dt) - code += IRInstruction(Opcode.LOAD, dt, reg1=factorReg, immediate = factor) + code += IRInstructions.load(dt, factorReg, factor) code += if(signed) { if(knownAddress!=null) - IRInstruction(Opcode.DIVSM, dt, reg1 = factorReg, address = knownAddress.toAddress()) + IRInstructions.memoryOp(Opcode.DIVSM, dt, IRMemory.direct(knownAddress.toAddress()), factorReg) else - IRInstruction(Opcode.DIVSM, dt, reg1 = factorReg, labelSymbol = symbol) + IRInstructions.memoryOp(Opcode.DIVSM, dt, IRMemory.direct(symbol!!), factorReg) } else { if(knownAddress!=null) - IRInstruction(Opcode.DIVM, dt, reg1 = factorReg, address = knownAddress.toAddress()) + IRInstructions.memoryOp(Opcode.DIVM, dt, IRMemory.direct(knownAddress.toAddress()), factorReg) else - IRInstruction(Opcode.DIVM, dt, reg1 = factorReg, labelSymbol = symbol) + IRInstructions.memoryOp(Opcode.DIVM, dt, IRMemory.direct(symbol!!), factorReg) } } return code @@ -1448,13 +1397,13 @@ class IRCodeGen( val wm1Reg = registers.next(IRDataType.BYTE) val nReg = registers.next(IRDataType.BYTE) val mask = (1 shl pow2) - 1 - code += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = wm1Reg, immediate = wordSize - 1) - code += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = nReg, immediate = pow2) - code += IRInstruction(Opcode.LOADR, dt, reg1 = signReg, reg2 = reg) // signReg = x - code += IRInstruction(Opcode.ASRN, dt, reg1 = signReg, reg2 = wm1Reg) // signReg = x >> (W-1) - code += IRInstruction(Opcode.AND, dt, reg1 = signReg, immediate = mask) // signReg = correction - code += IRInstruction(Opcode.ADDR, dt, reg1 = reg, reg2 = signReg) // reg = x + correction - code += IRInstruction(Opcode.ASRN, dt, reg1 = reg, reg2 = nReg) // reg = result + code += IRInstructions.load(IRDataType.BYTE, wm1Reg, wordSize - 1) + code += IRInstructions.load(IRDataType.BYTE, nReg, pow2) + code += IRInstructions.move(dt, signReg, reg) // signReg = x + code += IRInstructions.binary(Opcode.ASRN, dt, signReg, wm1Reg) // signReg = x >> (W-1) + code += IRInstructions.binaryImmediate(Opcode.AND, dt, signReg, mask) // signReg = correction + code += IRInstructions.binary(Opcode.ADDR, dt, reg, signReg) // reg = x + correction + code += IRInstructions.binary(Opcode.ASRN, dt, reg, nReg) // reg = result }*/ private fun translate(ifElse: PtIfElse): IRCodeChunks { @@ -1473,7 +1422,7 @@ class IRCodeGen( translateCondition(ifElse.condition, null, afterIfLabel, result) val tr = expressionEval.translateExpression(goto.target) result += tr.chunks - addInstr(result, IRInstruction(Opcode.JUMPI, reg1 = tr.resultReg), null) + addInstr(result, IRInstructions.jumpIndirect(tr.resultReg), null) result += IRCodeChunk(afterIfLabel, null) } else { val address = goto.target.asConstInteger()?.toUInt()?.toAddress() @@ -1541,16 +1490,16 @@ class IRCodeGen( && lastInstr != null && lastInstr.opcode in OpcodesThatSetZeroFlagOnM68k if (!skipCmpi) { - addInstr(result, IRInstruction(Opcode.CMPI, tr.dt, reg1 = tr.resultReg, immediate = 0), null) + addInstr(result, IRInstructions.compareImmediate(tr.dt, tr.resultReg, 0), null) } if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel != null || onFalseAddress != null)) { - addInstr(result, IRInstruction(Opcode.BSTNE, labelSymbol = onTrueLabel, address = onTrueAddress), null) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = onFalseLabel, address = onFalseAddress), null) + addInstr(result, IRInstructions.branch(Opcode.BSTNE, branchTarget(onTrueLabel, onTrueAddress)), null) + addInstr(result, IRInstructions.jump(branchTarget(onFalseLabel, onFalseAddress)), null) } else if (onTrueLabel != null || onTrueAddress != null) { - addInstr(result, IRInstruction(Opcode.BSTNE, labelSymbol = onTrueLabel, address = onTrueAddress), null) + addInstr(result, IRInstructions.branch(Opcode.BSTNE, branchTarget(onTrueLabel, onTrueAddress)), null) } else if (onFalseLabel != null || onFalseAddress != null) { - addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = onFalseLabel, address = onFalseAddress), null) + addInstr(result, IRInstructions.branch(Opcode.BSTEQ, branchTarget(onFalseLabel, onFalseAddress)), null) } } @@ -1561,14 +1510,14 @@ class IRCodeGen( val bitPos = Integer.numberOfTrailingZeros(bitmask) val leftTr = expressionEval.translateExpression(expr) addToResult(result, leftTr, leftTr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.BITTST, leftTr.dt, reg1 = leftTr.resultReg, immediate = bitPos), null) + addInstr(result, bitTest(leftTr.dt, leftTr.resultReg, bitPos), null) if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel == null && onFalseAddress == null)) { - addInstr(result, IRInstruction(getBitBranchOpcode(testBitSet), labelSymbol = onTrueLabel, address = onTrueAddress), null) + addInstr(result, IRInstructions.branch(getBitBranchOpcode(testBitSet), branchTarget(onTrueLabel, onTrueAddress)), null) } else if ((onFalseLabel != null || onFalseAddress != null) && (onTrueLabel == null && onTrueAddress == null)) { - addInstr(result, IRInstruction(getBitBranchOpcode(!testBitSet), labelSymbol = onFalseLabel, address = onFalseAddress), null) + addInstr(result, IRInstructions.branch(getBitBranchOpcode(!testBitSet), branchTarget(onFalseLabel, onFalseAddress)), null) } else if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel != null || onFalseAddress != null)) { - addInstr(result, IRInstruction(getBitBranchOpcode(testBitSet), labelSymbol = onTrueLabel, address = onTrueAddress), null) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = onFalseLabel, address = onFalseAddress), null) + addInstr(result, IRInstructions.branch(getBitBranchOpcode(testBitSet), branchTarget(onTrueLabel, onTrueAddress)), null) + addInstr(result, IRInstructions.jump(branchTarget(onFalseLabel, onFalseAddress)), null) } return } @@ -1604,7 +1553,7 @@ class IRCodeGen( var (opcode, useCmpi) = getIntegerComparisonBranch(condition.operator, false, signed) if (canSkipCmpi && (opcode == Opcode.BSTEQ || opcode == Opcode.BSTNE)) useCmpi = false emitIntegerComparisonBranch(result, opcode, useCmpi, branchDt, leftTr.resultReg, number, onTrueLabel, onTrueAddress) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = onFalseLabel, address = onFalseAddress), null) + addInstr(result, IRInstructions.jump(branchTarget(onFalseLabel, onFalseAddress)), null) } } else { val rightTr = expressionEval.translateExpression(condition.right) @@ -1618,7 +1567,7 @@ class IRCodeGen( } else if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel != null || onFalseAddress != null)) { val res = getIntegerComparisonRegBranch(condition.operator, false, signed, leftTr.resultReg, rightTr.resultReg) emitIntegerComparisonRegBranch(result, res.opcode, res.useCmp, branchDt, res.leftReg, res.rightReg, onTrueLabel, onTrueAddress) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = onFalseLabel, address = onFalseAddress), null) + addInstr(result, IRInstructions.jump(branchTarget(onFalseLabel, onFalseAddress)), null) } } } @@ -1629,7 +1578,7 @@ class IRCodeGen( val rightTr = expressionEval.translateExpression(condition.right) addToResult(result, rightTr, -1, rightTr.resultFpReg) val compResultReg = registers.next(IRDataType.BYTE) - addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1 = compResultReg, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2 = RegisterNum(rightTr.resultFpReg)), null) + addInstr(result, IRInstructions.floatCompare(compResultReg, leftTr.resultFpReg, rightTr.resultFpReg), null) if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel == null && onFalseAddress == null)) { val (opcode, useCmpi) = getFloatComparisonBranch(condition.operator, false) @@ -1640,7 +1589,7 @@ class IRCodeGen( } else if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel != null || onFalseAddress != null)) { val (opcode, useCmpi) = getFloatComparisonBranch(condition.operator, false) emitIntegerComparisonBranch(result, opcode, useCmpi, IRDataType.BYTE, compResultReg, 0, onTrueLabel, onTrueAddress) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = onFalseLabel, address = onFalseAddress), null) + addInstr(result, IRInstructions.jump(branchTarget(onFalseLabel, onFalseAddress)), null) } } @@ -1702,24 +1651,24 @@ class IRCodeGen( private fun emitIntegerComparisonBranch(result: MutableList, opcode: Opcode, useCmpi: Boolean, dt: IRDataType, reg: Int, immediate: Int, label: String?, address: MemoryAddress?) { if (useCmpi) { result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.CMPI, dt, reg1 = reg, immediate = immediate) - it += IRInstruction(opcode, labelSymbol = label, address = address) + it += IRInstructions.compareImmediate(dt, reg, immediate) + it += IRInstructions.branch(opcode, branchTarget(label, address)) } } else if (opcode in setOf(Opcode.BSTEQ, Opcode.BSTNE, Opcode.BSTPOS, Opcode.BSTNEG, Opcode.BSTCS, Opcode.BSTCC, Opcode.BSTVS, Opcode.BSTVC)) { - addInstr(result, IRInstruction(opcode, labelSymbol = label, address = address), null) + addInstr(result, IRInstructions.branch(opcode, branchTarget(label, address)), null) } else { - addInstr(result, IRInstruction(opcode, dt, reg1 = reg, immediate = immediate, labelSymbol = label, address = address), null) + addInstr(result, IRInstructions.branchImmediate(opcode, dt, reg, immediate, branchTarget(label, address)), null) } } private fun emitIntegerComparisonRegBranch(result: MutableList, opcode: Opcode, useCmp: Boolean, dt: IRDataType, leftReg: Int, rightReg: Int, label: String?, address: MemoryAddress?) { if (useCmp) { result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.CMP, dt, reg1 = leftReg, reg2 = rightReg) - it += IRInstruction(opcode, labelSymbol = label, address = address) + it += IRInstructions.compare(dt, leftReg, rightReg) + it += IRInstructions.branch(opcode, branchTarget(label, address)) } } else { - addInstr(result, IRInstruction(opcode, dt, reg1 = leftReg, reg2 = rightReg, labelSymbol = label, address = address), null) + addInstr(result, IRInstructions.branchRegister(opcode, dt, leftReg, rightReg, branchTarget(label, address)), null) } } @@ -1731,7 +1680,7 @@ class IRCodeGen( val elseLabel = createLabelName() translateCondition(ifElse.condition, null, elseLabel, result) result += translateNode(ifElse.ifScope) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null) + addInstr(result, IRInstructions.jump(codeLabel(afterIfLabel)), null) result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel) } else { translateCondition(ifElse.condition, null, afterIfLabel, result) @@ -1766,17 +1715,17 @@ class IRCodeGen( addToResult(result, countTr, countTr.resultReg, -1) if (repeat.count.asConstValue() == null) { if (needsExplicitCmpi) { - addInstr(result, IRInstruction(Opcode.CMPI, irDt, reg1 = countTr.resultReg, immediate = 0), null) + addInstr(result, IRInstructions.compareImmediate(irDt, countTr.resultReg, 0), null) } - addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = skipRepeatLabel), null) + addInstr(result, IRInstructions.branch(Opcode.BSTEQ, codeLabel(skipRepeatLabel)), null) } result += labelFirstChunk(translateNode(repeat.statements), repeatLabel) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.DEC, irDt, reg1 = countTr.resultReg) + it += IRInstructions.unary(Opcode.DEC, irDt, countTr.resultReg) if (needsExplicitCmpi) { - it += IRInstruction(Opcode.CMPI, irDt, reg1 = countTr.resultReg, immediate = 0) + it += IRInstructions.compareImmediate(irDt, countTr.resultReg, 0) } - it += IRInstruction(Opcode.BSTNE, labelSymbol = repeatLabel) + it += IRInstructions.branch(Opcode.BSTNE, codeLabel(repeatLabel)) } result += IRCodeChunk(skipRepeatLabel, null) return result @@ -1786,14 +1735,14 @@ class IRCodeGen( val result = mutableListOf() val chunk = IRCodeChunk(null, null) if(jump.target.asConstInteger()!=null) { - chunk += IRInstruction(Opcode.JUMP, address = jump.target.asConstInteger()?.toUInt()?.toAddress()) + chunk += IRInstructions.jump(codeAddress(jump.target.asConstInteger()!!.toUInt().toAddress())) result += chunk return result } else { val identifier = jump.target as? PtIdentifier if (identifier != null && !isIndirectJump(jump)) { // jump to label - chunk += IRInstruction(Opcode.JUMP, labelSymbol = identifier.name) + chunk += IRInstructions.jump(codeLabel(identifier.name)) result += chunk return result } @@ -1801,7 +1750,7 @@ class IRCodeGen( val tr = expressionEval.translateExpression(jump.target) result += tr.chunks result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.JUMPI, reg1=tr.resultReg) + it += IRInstructions.jumpIndirect(tr.resultReg) } return result } @@ -1858,13 +1807,13 @@ class IRCodeGen( // For float returns, use FAC1/FAC2 val tempFpReg = registers.next(IRDataType.FLOAT) when(fromReg.registerOrPair) { - RegisterOrPair.FAC1 -> addInstr(result, IRInstruction(Opcode.LOADHFACZERO, IRDataType.FLOAT, fpReg1 = RegisterNum(tempFpReg)), null) - RegisterOrPair.FAC2 -> addInstr(result, IRInstruction(Opcode.LOADHFACONE, IRDataType.FLOAT, fpReg1 = RegisterNum(tempFpReg)), null) + RegisterOrPair.FAC1 -> addInstr(result, IRInstructions.unary(Opcode.LOADHFACZERO, IRDataType.FLOAT, tempFpReg), null) + RegisterOrPair.FAC2 -> addInstr(result, IRInstructions.unary(Opcode.LOADHFACONE, IRDataType.FLOAT, tempFpReg), null) else -> throw AssemblyError("unexpected FP return register ${fromReg}") } when(toReg.registerOrPair) { - RegisterOrPair.FAC1 -> addInstr(result, IRInstruction(Opcode.STOREHFACZERO, IRDataType.FLOAT, fpReg1 = RegisterNum(tempFpReg)), null) - RegisterOrPair.FAC2 -> addInstr(result, IRInstruction(Opcode.STOREHFACONE, IRDataType.FLOAT, fpReg1 = RegisterNum(tempFpReg)), null) + RegisterOrPair.FAC1 -> addInstr(result, storeHwFac(Opcode.STOREHFACZERO, tempFpReg), null) + RegisterOrPair.FAC2 -> addInstr(result, storeHwFac(Opcode.STOREHFACONE, tempFpReg), null) else -> throw AssemblyError("unexpected FP return register ${toReg}") } } else { @@ -1874,7 +1823,7 @@ class IRCodeGen( result += setCpuRegister(toReg, irType(toType), tempReg, -1) } } - addInstr(result, IRInstruction(Opcode.RETURN), null) + addInstr(result, IRInstructions.returnVoid(), null) return result } @@ -1903,31 +1852,31 @@ class IRCodeGen( result += setCpuRegister(register.first, irType(register.second), tr.resultReg, -1) } } - addInstr(result, IRInstruction(Opcode.RETURN), null) + addInstr(result, IRInstructions.returnVoid(), null) return result } val value = ret.children.singleOrNull() if(value==null) { - addInstr(result, IRInstruction(Opcode.RETURN), null) + addInstr(result, IRInstructions.returnVoid(), null) } else { value as PtExpression if(value.type.isFloat) { if(value is PtNumber) { - addInstr(result, IRInstruction(Opcode.RETURNI, IRDataType.FLOAT, immediateFp = value.number), null) + addInstr(result, IRInstructions.returnImmediateFloat(value.number), null) } else { val tr = expressionEval.translateExpression(value) addToResult(result, tr, -1, tr.resultFpReg) - addInstr(result, IRInstruction(Opcode.RETURNR, IRDataType.FLOAT, fpReg1 = RegisterNum(tr.resultFpReg)), null) + addInstr(result, IRInstructions.returnRegister(IRDataType.FLOAT, tr.resultFpReg), null) } } else { if(value.asConstInteger()!=null) { - addInstr(result, IRInstruction(Opcode.RETURNI, irType(value.type), immediate = value.asConstInteger()), null) + addInstr(result, IRInstructions.returnImmediate(irType(value.type), value.asConstInteger()!!), null) } else { val tr = expressionEval.translateExpression(value) addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.RETURNR, irType(value.type), reg1 = tr.resultReg), null) + addInstr(result, IRInstructions.returnRegister(irType(value.type), tr.resultReg), null) } } } @@ -1951,7 +1900,7 @@ class IRCodeGen( is PtVariable, is PtConstant, is PtMemMapped, is PtMemorySlabReservation -> { /* vars should be looked up via symbol table */ } is PtAlign -> { val chunk = IRCodeChunk(null, null) - chunk += IRInstruction(Opcode.ALIGN, immediate = child.align.toInt()) + chunk += IRInstructions.align(child.align.toInt()) irBlock += chunk } is PtSub -> { @@ -2006,7 +1955,7 @@ class IRCodeGen( irBlock += IRCodeChunk(null, null).also { for(addr in child.children) { addr as PtIdentifier - it += IRInstruction(Opcode.JUMP, labelSymbol = addr.name) + it += IRInstructions.jump(codeLabel(addr.name)) } } } @@ -2105,12 +2054,10 @@ class IRCodeGen( internal fun makeSyscall(syscall: IMSyscall, params: List>, returns: Pair?, label: String?=null): IRCodeChunk { return IRCodeChunk(label, null).also { - val args = params.map { (dt, reg)-> - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(dt, RegisterNum(reg), null, null)) - } + val args = params.map { (dt, reg)-> Calls.argument(reg, dt) } // for now, syscalls have 0 or 1 return value - val returnSpec = if(returns==null) emptyList() else listOf(FunctionCallArgs.RegSpec(returns.first, RegisterNum(returns.second), null, null)) - it += IRInstruction(Opcode.SYSCALL, immediate = syscall.number, fcallArgs = FunctionCallArgs(args, returnSpec)) + val results = if(returns==null) emptyList() else listOf(Calls.result(returns.second, returns.first)) + it += IRInstructions.syscall(syscall.number, args, results) } } @@ -2122,27 +2069,27 @@ class IRCodeGen( else null to null val m68kSlot = slot?.takeIf { it.value >= 10 } if (m68kSlot != null) { - chunk += IRInstruction(Opcode.STOREHR, paramDt, reg1=resultReg, immediate=m68kSlot.value) + chunk += IRInstructions.hardwareStore(paramDt, resultReg, m68kSlot) } else when(registerOrFlag.registerOrPair) { - RegisterOrPair.A -> chunk += IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1=resultReg, immediate=0) - RegisterOrPair.X -> chunk += IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1=resultReg, immediate=1) - RegisterOrPair.Y -> chunk += IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1=resultReg, immediate=2) - RegisterOrPair.AX -> chunk += IRInstruction(Opcode.STOREHR, IRDataType.WORD, reg1=resultReg, immediate=3) - RegisterOrPair.AY -> chunk += IRInstruction(Opcode.STOREHR, IRDataType.WORD, reg1=resultReg, immediate=4) - RegisterOrPair.XY -> chunk += IRInstruction(Opcode.STOREHR, IRDataType.WORD, reg1=resultReg, immediate=5) - RegisterOrPair.FAC1 -> chunk += IRInstruction(Opcode.STOREHFACZERO, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpReg)) - RegisterOrPair.FAC2 -> chunk += IRInstruction(Opcode.STOREHFACONE, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpReg)) + RegisterOrPair.A -> chunk += IRInstructions.hardwareStore(IRDataType.BYTE, resultReg, CallingConventionSlot(0)) + RegisterOrPair.X -> chunk += IRInstructions.hardwareStore(IRDataType.BYTE, resultReg, CallingConventionSlot(1)) + RegisterOrPair.Y -> chunk += IRInstructions.hardwareStore(IRDataType.BYTE, resultReg, CallingConventionSlot(2)) + RegisterOrPair.AX -> chunk += IRInstructions.hardwareStore(IRDataType.WORD, resultReg, CallingConventionSlot(3)) + RegisterOrPair.AY -> chunk += IRInstructions.hardwareStore(IRDataType.WORD, resultReg, CallingConventionSlot(4)) + RegisterOrPair.XY -> chunk += IRInstructions.hardwareStore(IRDataType.WORD, resultReg, CallingConventionSlot(5)) + RegisterOrPair.FAC1 -> chunk += storeHwFac(Opcode.STOREHFACZERO, resultFpReg) + RegisterOrPair.FAC2 -> chunk += storeHwFac(Opcode.STOREHFACONE, resultFpReg) in Cx16VirtualRegisters -> { - chunk += IRInstruction(Opcode.STOREM, paramDt, reg1=resultReg, labelSymbol = "cx16.${registerOrFlag.registerOrPair.toString().lowercase()}") + chunk += IRInstructions.storeMemory(Opcode.STOREM, paramDt, resultReg, IRMemory.direct("cx16.${registerOrFlag.registerOrPair.toString().lowercase()}")) } in CombinedLongRegisters -> { require(paramDt==IRDataType.LONG) val startreg = registerOrFlag.registerOrPair!!.startregname() - chunk += IRInstruction(Opcode.STOREM, paramDt, reg1=resultReg, labelSymbol = "cx16.${startreg}") + chunk += IRInstructions.storeMemory(Opcode.STOREM, paramDt, resultReg, IRMemory.direct("cx16.${startreg}")) } null -> when(registerOrFlag.statusflag) { // TODO: do the statusflag argument as last - Statusflag.Pc -> chunk += IRInstruction(Opcode.LSR, paramDt, reg1=resultReg) + Statusflag.Pc -> chunk += IRInstructions.unary(Opcode.LSR, paramDt, resultReg) else -> throw AssemblyError("unsupported statusflag as param") } else -> throw AssemblyError("unsupported register arg $registerOrFlag") @@ -2159,28 +2106,28 @@ class IRCodeGen( else null to null val m68kSlot2 = slot2?.takeIf { it.value >= 10 } if (m68kSlot2 != null) { - chunk += IRInstruction(Opcode.LOADHR, irType, reg1=tempReg, immediate=m68kSlot2.value) + chunk += IRInstructions.hardwareLoad(irType, tempReg, m68kSlot2) } else when(registerOrFlag.registerOrPair) { - RegisterOrPair.A -> chunk += IRInstruction(Opcode.LOADHR, IRDataType.BYTE, reg1=tempReg, immediate=0) - RegisterOrPair.X -> chunk += IRInstruction(Opcode.LOADHR, IRDataType.BYTE, reg1=tempReg, immediate=1) - RegisterOrPair.Y -> chunk += IRInstruction(Opcode.LOADHR, IRDataType.BYTE, reg1=tempReg, immediate=2) - RegisterOrPair.AX -> chunk += IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=tempReg, immediate=3) - RegisterOrPair.AY -> chunk += IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=tempReg, immediate=4) - RegisterOrPair.XY -> chunk += IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=tempReg, immediate=5) - RegisterOrPair.FAC1 -> chunk += IRInstruction(Opcode.LOADHFACZERO, IRDataType.FLOAT, fpReg1 = RegisterNum(tempReg)) - RegisterOrPair.FAC2 -> chunk += IRInstruction(Opcode.LOADHFACONE, IRDataType.FLOAT, fpReg1 = RegisterNum(tempReg)) + RegisterOrPair.A -> chunk += IRInstructions.hardwareLoad(IRDataType.BYTE, tempReg, CallingConventionSlot(0)) + RegisterOrPair.X -> chunk += IRInstructions.hardwareLoad(IRDataType.BYTE, tempReg, CallingConventionSlot(1)) + RegisterOrPair.Y -> chunk += IRInstructions.hardwareLoad(IRDataType.BYTE, tempReg, CallingConventionSlot(2)) + RegisterOrPair.AX -> chunk += IRInstructions.hardwareLoad(IRDataType.WORD, tempReg, CallingConventionSlot(3)) + RegisterOrPair.AY -> chunk += IRInstructions.hardwareLoad(IRDataType.WORD, tempReg, CallingConventionSlot(4)) + RegisterOrPair.XY -> chunk += IRInstructions.hardwareLoad(IRDataType.WORD, tempReg, CallingConventionSlot(5)) + RegisterOrPair.FAC1 -> chunk += IRInstructions.unary(Opcode.LOADHFACZERO, IRDataType.FLOAT, tempReg) + RegisterOrPair.FAC2 -> chunk += IRInstructions.unary(Opcode.LOADHFACONE, IRDataType.FLOAT, tempReg) in Cx16VirtualRegisters -> { - chunk += IRInstruction(Opcode.LOADM, irType, reg1=tempReg, labelSymbol = "cx16.${registerOrFlag.registerOrPair.toString().lowercase()}") + chunk += IRInstructions.loadMemory(Opcode.LOADM, irType, tempReg, IRMemory.direct("cx16.${registerOrFlag.registerOrPair.toString().lowercase()}")) } in CombinedLongRegisters -> { require(fromType.isLong) val startreg = registerOrFlag.registerOrPair!!.startregname() - chunk += IRInstruction(Opcode.LOADM, IRDataType.LONG, reg1=tempReg, labelSymbol = "cx16.${startreg}") + chunk += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.LONG, tempReg, IRMemory.direct("cx16.${startreg}")) } null -> when(registerOrFlag.statusflag) { Statusflag.Pc -> { - chunk += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=tempReg, immediate = 0) - chunk += IRInstruction(Opcode.ROXL, IRDataType.BYTE, reg1=tempReg) + chunk += IRInstructions.load(IRDataType.BYTE, tempReg, 0) + chunk += IRInstructions.unary(Opcode.ROXL, IRDataType.BYTE, tempReg) } else -> throw AssemblyError("unsupported statusflag ${registerOrFlag.statusflag}") } @@ -2202,11 +2149,11 @@ class IRCodeGen( if(offset<=0u) { val irdt = irType(type) val instr = if(type.isFloat) { - if (valueIsZero) IRInstruction(Opcode.STOREZI, IRDataType.FLOAT, reg1 = addressReg, immediate = 0) - else IRInstruction(Opcode.STOREI, IRDataType.FLOAT, fpReg1 = RegisterNum(existingValueRegister), reg1 = addressReg, immediate = 0) + if (valueIsZero) IRInstructions.storeZero(Opcode.STOREZI, IRDataType.FLOAT, IRMemory.indirect(addressReg, 0)) + else IRInstructions.storeMemory(Opcode.STOREI, IRDataType.FLOAT, existingValueRegister, IRMemory.indirect(addressReg, 0)) } else { - if (valueIsZero) IRInstruction(Opcode.STOREZI, irdt, reg1 = addressReg, immediate = 0) - else IRInstruction(Opcode.STOREI, irdt, reg1 = existingValueRegister, reg2 = addressReg, immediate = 0) + if (valueIsZero) IRInstructions.storeZero(Opcode.STOREZI, irdt, IRMemory.indirect(addressReg, 0)) + else IRInstructions.storeMemory(Opcode.STOREI, irdt, existingValueRegister, IRMemory.indirect(addressReg, 0)) } addInstr(result, instr, null) return @@ -2216,12 +2163,12 @@ class IRCodeGen( val valueRegister = existingValueRegister val irdt = irType(type) if(valueIsZero && valueRegister<0) { - addInstr(result, IRInstruction(Opcode.STOREZI, irdt, reg1 = addressReg, immediate = offset.toInt()), null) + addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, irdt, IRMemory.indirect(addressReg, offset.toInt())), null) } else { val instr = if (type.isFloat) - IRInstruction(Opcode.STOREI, IRDataType.FLOAT, fpReg1 = RegisterNum(valueRegister), reg1 = addressReg, immediate = offset.toInt()) + IRInstructions.storeMemory(Opcode.STOREI, IRDataType.FLOAT, valueRegister, IRMemory.indirect(addressReg, offset.toInt())) else - IRInstruction(Opcode.STOREI, irdt, reg1 = valueRegister, reg2 = addressReg, immediate = offset.toInt()) + IRInstructions.storeMemory(Opcode.STOREI, irdt, valueRegister, IRMemory.indirect(addressReg, offset.toInt())) addInstr(result, instr, null) } } @@ -2244,8 +2191,8 @@ class IRCodeGen( if(indexDt != IRDataType.WORD) { val newReg = registers.next(IRDataType.WORD) when(indexDt) { - IRDataType.BYTE -> addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=newReg, reg2=tr.resultReg), null) - IRDataType.LONG -> addInstr(result, IRInstruction(Opcode.LSIGW, IRDataType.LONG, reg1=newReg, reg2=tr.resultReg), null) + IRDataType.BYTE -> addInstr(result, IRInstructions.binary(Opcode.EXT, IRDataType.BYTE, newReg, tr.resultReg), null) + IRDataType.LONG -> addInstr(result, IRInstructions.binary(Opcode.LSIGW, IRDataType.LONG, newReg, tr.resultReg), null) IRDataType.POINTER -> TODO("handle pointer-typed array index for word-indexed access at ${index.position}") else -> throw IllegalArgumentException("unexpected index dt $indexDt for wordIndex") } @@ -2267,13 +2214,13 @@ class IRCodeGen( val newReg = registers.next(indexRegType) when { indexRegType == IRDataType.WORD && indexDt == IRDataType.BYTE -> - addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=newReg, reg2=byteIndexTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.EXT, IRDataType.BYTE, newReg, byteIndexTr.resultReg), null) indexRegType == IRDataType.WORD && indexDt == IRDataType.LONG -> - addInstr(result, IRInstruction(Opcode.LSIGW, IRDataType.LONG, reg1=newReg, reg2=byteIndexTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.LSIGW, IRDataType.LONG, newReg, byteIndexTr.resultReg), null) indexRegType == IRDataType.BYTE && indexDt == IRDataType.WORD -> - addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=newReg, reg2=byteIndexTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, newReg, byteIndexTr.resultReg), null) indexRegType == IRDataType.BYTE && indexDt == IRDataType.LONG -> - addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1=newReg, reg2=byteIndexTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, newReg, byteIndexTr.resultReg), null) else -> throw IllegalArgumentException("unexpected index conversion $indexDt -> $indexRegType") } indexReg = newReg @@ -2293,21 +2240,21 @@ class IRCodeGen( val newReg = registers.next(indexRegType) when { indexRegType==IRDataType.WORD && indexTr.dt==IRDataType.BYTE -> - addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=newReg, reg2=indexTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.EXT, IRDataType.BYTE, newReg, indexTr.resultReg), null) indexRegType==IRDataType.WORD && indexTr.dt==IRDataType.LONG -> - addInstr(result, IRInstruction(Opcode.LSIGW, IRDataType.LONG, reg1=newReg, reg2=indexTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.LSIGW, IRDataType.LONG, newReg, indexTr.resultReg), null) indexRegType==IRDataType.WORD && indexTr.dt==IRDataType.POINTER -> - addInstr(result, IRInstruction(Opcode.LSIGW, IRDataType.LONG, reg1=newReg, reg2=indexTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.LSIGW, IRDataType.LONG, newReg, indexTr.resultReg), null) indexRegType==IRDataType.BYTE && indexTr.dt==IRDataType.WORD -> - addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=newReg, reg2=indexTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, newReg, indexTr.resultReg), null) indexRegType==IRDataType.BYTE && indexTr.dt==IRDataType.LONG -> - addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1=newReg, reg2=indexTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, newReg, indexTr.resultReg), null) indexRegType==IRDataType.BYTE && indexTr.dt==IRDataType.POINTER -> { val ptrType = options.compTarget.pointerIRType if(ptrType==IRDataType.WORD) - addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=newReg, reg2=indexTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, newReg, indexTr.resultReg), null) else - addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1=newReg, reg2=indexTr.resultReg), null) + addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, newReg, indexTr.resultReg), null) } else -> throw IllegalArgumentException("unexpected index conversion ${indexTr.dt} -> $indexRegType") } diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRFactoryHelpers.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRFactoryHelpers.kt new file mode 100644 index 000000000..338ad1406 --- /dev/null +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRFactoryHelpers.kt @@ -0,0 +1,36 @@ +package prog8.codegen.intermediate + +import prog8.intermediate.* + +/* +Construction helpers for the few IR opcodes that have no dedicated IRInstructions factory, +plus a small operand-inspection helper used by the optimizers. + +The structured IRInstructions factory covers almost every opcode, but three opcodes have an +operand shape that no named factory produces (a lone source register, or a source register plus +an immediate without a destination). They are built here from schema-derived operands, so this +module never constructs IRInstruction directly. +*/ + +/** STOREHFACZERO / STOREHFACONE: a single source float register (no dedicated factory exists). */ +internal fun storeHwFac(opcode: Opcode, fpRegister: Int): IRInstruction = + IRInstructions.push(IRDataType.FLOAT, fpRegister).copy(opcode = opcode) + +/** BITTST: a source register plus a byte immediate bit number, with no destination register. */ +internal fun bitTest(type: IRDataType, register: Int, bit: Int): IRInstruction = + IRInstructions.simple(Opcode.NOP).copy( + opcode = Opcode.BITTST, + type = type, + srcA = IRInstructions.operandFor(Opcode.BITTST, type, InstructionSlot.SRC_A, register), + immediate = IRInstructions.immediateFor(Opcode.BITTST, type, bit) + ) + +/** + * The name of the single symbol this instruction refers to, if any. + * That is either the symbol a memory reference is based on, the symbol whose address is taken + * in an immediate operand, or the label a branch/jump/call transfers control to. + */ +internal fun IRInstruction.referencedSymbol(): String? = + memory?.symbolName + ?: (immediate as? ImmediateOperand.SymbolAddress)?.symbol + ?: labelTarget diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt index 011bec42a..871e9c34c 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt @@ -119,7 +119,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram, private val retainSSA: indexedInstructions.reversed().forEach { (idx, ins) -> if (ins.opcode == Opcode.CONCAT && idx>0) { // if the previous instruction loads a zero in the msb, this can be turned into EXT.B instead - val msbRegister = ins.reg2 + val msbRegister = ins.srcA?.register var loadIndex: Int? = null var safe = true for (scanIndex in idx - 1 downTo 0) { @@ -128,26 +128,20 @@ class IRPeepholeOptimizer(private val irprog: IRProgram, private val retainSSA: safe = false break } - val format = instructionFormats.getValue(candidate.opcode)[candidate.type] - ?: instructionFormats.getValue(candidate.opcode)[null] - val writesMsbRegister = msbRegister != null && ( - (format?.reg1 in setOf(OperandDirection.WRITE, OperandDirection.READWRITE) && candidate.reg1 == msbRegister) || - (format?.reg2 in setOf(OperandDirection.WRITE, OperandDirection.READWRITE) && candidate.reg2 == msbRegister) || - (format?.reg3 in setOf(OperandDirection.WRITE, OperandDirection.READWRITE) && candidate.reg3 == msbRegister) - ) + val writesMsbRegister = msbRegister != null && msbRegister in candidate.definitions if (writesMsbRegister) { - if (candidate.opcode == Opcode.LOAD && candidate.immediate == 0 && candidate.reg1 == msbRegister) { + if (candidate.opcode == Opcode.LOAD && candidate.immediate?.integerValue == 0 && candidate.dest?.register == msbRegister) { loadIndex = scanIndex } break } - if (candidate.opcode == Opcode.LOAD && candidate.immediate == 0 && candidate.reg1 == msbRegister) { + if (candidate.opcode == Opcode.LOAD && candidate.immediate?.integerValue == 0 && candidate.dest?.register == msbRegister) { loadIndex = scanIndex break } } if (safe && loadIndex != null) { - chunk.instructions[idx] = IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1 = ins.reg1, reg2 = ins.reg3) + chunk.instructions[idx] = IRInstructions.binary(Opcode.EXT, IRDataType.BYTE, ins.requireDest().registerNumber, ins.requireSrcB().registerNumber) chunk.instructions.removeAt(loadIndex) changed = true } @@ -209,16 +203,17 @@ class IRPeepholeOptimizer(private val irprog: IRProgram, private val retainSSA: sub.chunks.forEach { chunk -> chunk.instructions.withIndex().forEach { (idx, instr) -> - instr.labelSymbol?.let { - if(instr.opcode in OpcodesThatBranch) { + if(instr.opcode in OpcodesThatBranch) { + val label = instr.labelTarget + if(label != null) { replaceLabels.forEach { (from, to) -> - if (it == from) { - chunk.instructions[idx] = instr.copy(labelSymbol = to) + if (label == from) { + chunk.instructions[idx] = instr.withTarget(codeLabel(to)) } else { val actualPrefix = "$from." - if (it.startsWith(actualPrefix)) - chunk.instructions[idx] = instr.copy(labelSymbol = "$to.") + if (label.startsWith(actualPrefix)) + chunk.instructions[idx] = instr.withTarget(codeLabel("$to.")) } } } @@ -304,11 +299,13 @@ class IRPeepholeOptimizer(private val irprog: IRProgram, private val retainSSA: if(idx < chunk.instructions.size-1) { val insAfter = chunk.instructions[idx+1] if(insAfter.opcode == Opcode.POP) { - if(ins.reg1==insAfter.reg1) { + val srcReg = ins.requireSrcA() + val destReg = insAfter.requireDest() + if(srcReg.register==destReg.register) { chunk.instructions.removeAt(idx) chunk.instructions.removeAt(idx) } else { - chunk.instructions[idx] = IRInstruction(Opcode.LOADR, ins.type, reg1=insAfter.reg1, reg2=ins.reg1) + chunk.instructions[idx] = IRInstructions.move(destReg, srcReg) chunk.instructions.removeAt(idx+1) } changed = true @@ -366,11 +363,11 @@ class IRPeepholeOptimizer(private val irprog: IRProgram, private val retainSSA: private fun removeWeirdBranches(chunk: IRCodeChunk, nextChunk: IRCodeChunkBase?, indexedInstructions: List>): Boolean { var changed = false indexedInstructions.reversed().forEach { (idx, ins) -> - val labelSymbol = ins.labelSymbol + val labelSymbol = ins.labelTarget // remove jump/branch to label immediately below (= next chunk if it has that label) if(ins.opcode== Opcode.JUMP && labelSymbol!=null) { - if(idx==chunk.instructions.size-1 && ins.branchTarget===nextChunk) { + if(idx==chunk.instructions.size-1 && irprog.resolveCodeTarget(ins.codeTarget!!)===nextChunk) { chunk.instructions.removeAt(idx) changed = true } @@ -397,14 +394,6 @@ class IRPeepholeOptimizer(private val irprog: IRProgram, private val retainSSA: // replace call + return --> jump // This can no longer be done here on the IR level, with the current CALL opcode that encodes the full subroutine call setup. // If machine code is ever generated from this IR, *that* should possibly optimize the JSR + RTS into a JMP. -// if(idx>0 && ins.opcode==Opcode.RETURN) { -// val previous = chunk.instructions[idx-1] -// if(previous.opcode==Opcode.CALL) { -// chunk.instructions[idx-1] = IRInstruction(Opcode.JUMP, address = previous.address, labelSymbol = previous.labelSymbol, branchTarget = previous.branchTarget) -// chunk.instructions.removeAt(idx) -// changed = true -// } -// } } return changed } @@ -421,9 +410,9 @@ class IRPeepholeOptimizer(private val irprog: IRProgram, private val retainSSA: if (!targetHonorsContract) return false var changed = false indexedInstructions.reversed().forEach { (idx, ins) -> - if(idx>0 && idx<(indexedInstructions.size-1) && ins.opcode==Opcode.CMPI && ins.immediate==0) { + if(idx>0 && idx<(indexedInstructions.size-1) && ins.opcode==Opcode.CMPI && ins.immediate?.integerValue==0) { val previous = indexedInstructions[idx-1].value - if(previous.reg1==ins.reg1) { + if(previous.dest?.register==ins.srcA?.register) { if (previous.opcode in OpcodesThatSetZeroFlagOnM68k) { chunk.instructions.removeAt(idx) changed = true @@ -446,8 +435,8 @@ jump p8_label_gen_2 val previous = indexedInstructions[idx-1].value val previous2 = indexedInstructions[idx-2].value if(previous.opcode==Opcode.LOADR && previous2.opcode in OpcodesThatLoad) { - if(previous.reg2==previous2.reg1) { - chunk.instructions[idx-2] = previous2.copy(reg1=previous.reg1) + if(previous.srcA?.register==previous2.dest?.register) { + chunk.instructions[idx-2] = previous2.copy(dest = previous2.dest!!.withRegister(previous.dest!!.register)) chunk.instructions.removeAt(idx-1) changed=true } @@ -462,8 +451,8 @@ jump p8_label_gen_2 var changed = false fun arithmeticDelta(instruction: IRInstruction): Int? = when(instruction.opcode) { - Opcode.ADD -> instruction.immediate - Opcode.SUB -> instruction.immediate?.let { -it } + Opcode.ADD -> instruction.immediate?.integerValue + Opcode.SUB -> instruction.immediate?.integerValue?.let { -it } Opcode.INC -> 1 Opcode.DEC -> -1 else -> null @@ -472,18 +461,18 @@ jump p8_label_gen_2 indexedInstructions.reversed().forEach { (idx, ins) -> if(idx < chunk.instructions.size-1) { val nextInstr = chunk.instructions[idx+1] - val sameTarget = ins.reg1 != null && ins.reg1 == nextInstr.reg1 && ins.type == nextInstr.type + val sameTarget = ins.dest != null && ins.dest?.register == nextInstr.dest?.register && ins.type == nextInstr.type if(sameTarget && ins.opcode in setOf(Opcode.MUL, Opcode.MULS) && ins.opcode == nextInstr.opcode && ins.type in setOf(IRDataType.BYTE, IRDataType.WORD, IRDataType.LONG) && - ins.immediate != null && nextInstr.immediate != null) { - val product = ins.immediate!!.toLong() * nextInstr.immediate!!.toLong() + ins.immediate?.integerValue != null && nextInstr.immediate?.integerValue != null) { + val product = ins.immediate!!.integerValue!!.toLong() * nextInstr.immediate!!.integerValue!!.toLong() val foldedImmediate = when(ins.type) { IRDataType.BYTE -> product.toInt() and 0xff IRDataType.WORD -> product.toInt() and 0xffff IRDataType.LONG -> product.toInt() else -> error("unexpected integer multiplication type") } - chunk.instructions[idx] = ins.copy(immediate = foldedImmediate) + chunk.instructions[idx] = ins.copy(immediate = ImmediateOperand.Integer(foldedImmediate, ins.type!!)) chunk.instructions.removeAt(idx + 1) changed = true return@forEach @@ -495,16 +484,16 @@ jump p8_label_gen_2 val newDelta = delta + nextDelta when(newDelta) { 0 -> { - chunk.instructions[idx] = IRInstruction(Opcode.NOP) - chunk.instructions[idx+1] = IRInstruction(Opcode.NOP) + chunk.instructions[idx] = IRInstructions.simple(Opcode.NOP) + chunk.instructions[idx+1] = IRInstructions.simple(Opcode.NOP) } - 1 -> chunk.instructions[idx] = IRInstruction(Opcode.INC, ins.type, reg1 = ins.reg1) - -1 -> chunk.instructions[idx] = IRInstruction(Opcode.DEC, ins.type, reg1 = ins.reg1) - else -> chunk.instructions[idx] = IRInstruction( + 1 -> chunk.instructions[idx] = IRInstructions.unary(Opcode.INC, ins.type!!, ins.dest!!.registerNumber) + -1 -> chunk.instructions[idx] = IRInstructions.unary(Opcode.DEC, ins.type!!, ins.dest!!.registerNumber) + else -> chunk.instructions[idx] = IRInstructions.binaryImmediate( if(newDelta > 0) Opcode.ADD else Opcode.SUB, - ins.type, - reg1 = ins.reg1, - immediate = kotlin.math.abs(newDelta) + ins.type!!, + ins.dest!!.registerNumber, + kotlin.math.abs(newDelta) ) } chunk.instructions.removeAt(idx+1) @@ -513,20 +502,20 @@ jump p8_label_gen_2 } if(ins.opcode == Opcode.AND && nextInstr.opcode == Opcode.AND) { - chunk.instructions[idx] = ins.copy(immediate = ins.immediate!! and nextInstr.immediate!!) + chunk.instructions[idx] = ins.copy(immediate = ImmediateOperand.Integer(ins.immediate!!.integerValue!! and nextInstr.immediate!!.integerValue!!, ins.type!!)) chunk.instructions.removeAt(idx+1) changed = true return@forEach } if(ins.opcode == Opcode.OR && nextInstr.opcode == Opcode.OR) { - chunk.instructions[idx] = ins.copy(immediate = ins.immediate!! or nextInstr.immediate!!) + chunk.instructions[idx] = ins.copy(immediate = ImmediateOperand.Integer(ins.immediate!!.integerValue!! or nextInstr.immediate!!.integerValue!!, ins.type!!)) chunk.instructions.removeAt(idx+1) changed = true return@forEach } - if(ins.opcode == Opcode.XOR && nextInstr.opcode == Opcode.XOR && ins.immediate == nextInstr.immediate) { - chunk.instructions[idx] = IRInstruction(Opcode.NOP) - chunk.instructions[idx+1] = IRInstruction(Opcode.NOP) + if(ins.opcode == Opcode.XOR && nextInstr.opcode == Opcode.XOR && ins.immediate?.integerValue == nextInstr.immediate?.integerValue) { + chunk.instructions[idx] = IRInstructions.simple(Opcode.NOP) + chunk.instructions[idx+1] = IRInstructions.simple(Opcode.NOP) changed = true return@forEach } @@ -535,57 +524,61 @@ jump p8_label_gen_2 when (ins.opcode) { Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MULS, Opcode.MOD -> { - if (ins.immediate == 0 && ins.opcode in setOf(Opcode.MUL, Opcode.MULS) && + val imm = ins.immediate?.integerValue + if (imm == 0 && ins.opcode in setOf(Opcode.MUL, Opcode.MULS) && ins.type in setOf(IRDataType.BYTE, IRDataType.WORD, IRDataType.LONG)) { - chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, immediate = 0) + chunk.instructions[idx] = IRInstructions.load(ins.type!!, ins.dest!!.registerNumber, 0) changed = true - } else if (ins.immediate == 1) { + } else if (imm == 1) { chunk.instructions.removeAt(idx) changed = true } } Opcode.ADD, Opcode.SUB -> { - if (ins.immediate == 1) { - chunk.instructions[idx] = IRInstruction( + val imm = ins.immediate?.integerValue + if (imm == 1) { + chunk.instructions[idx] = IRInstructions.unary( if (ins.opcode == Opcode.ADD) Opcode.INC else Opcode.DEC, - ins.type, - ins.reg1 + ins.type!!, + ins.dest!!.registerNumber ) changed = true - } else if (ins.immediate == 0) { + } else if (imm == 0) { chunk.instructions.removeAt(idx) changed = true } if(!changed && idx < chunk.instructions.size-1) { val nextInstr = chunk.instructions[idx+1] - if(nextInstr.reg1==ins.reg1) { + if(nextInstr.dest?.register==ins.dest?.register) { when (nextInstr.opcode) { Opcode.INC -> { // INC after ADD or SUB - val newValue = if (ins.opcode == Opcode.ADD) ins.immediate!! + 1 else ins.immediate!! - 1 - chunk.instructions[idx] = IRInstruction(ins.opcode, ins.type, reg1 = ins.reg1, immediate = newValue) + val newValue = if (ins.opcode == Opcode.ADD) imm!! + 1 else imm!! - 1 + chunk.instructions[idx] = IRInstructions.binaryImmediate(ins.opcode, ins.type!!, ins.dest!!.registerNumber, newValue) chunk.instructions.removeAt(idx + 1) changed = true } Opcode.DEC -> { // DEC after ADD or SUB - val newValue = if (ins.opcode == Opcode.ADD) ins.immediate!! - 1 else ins.immediate!! + 1 - chunk.instructions[idx] = IRInstruction(ins.opcode, ins.type, reg1 = ins.reg1, immediate = newValue) + val newValue = if (ins.opcode == Opcode.ADD) imm!! - 1 else imm!! + 1 + chunk.instructions[idx] = IRInstructions.binaryImmediate(ins.opcode, ins.type!!, ins.dest!!.registerNumber, newValue) chunk.instructions.removeAt(idx + 1) changed = true } Opcode.ADD -> { // ADD after ADD or SUB - val newValue = if (ins.opcode == Opcode.ADD) ins.immediate!! + nextInstr.immediate!! else ins.immediate!! - nextInstr.immediate!! - chunk.instructions[idx] = IRInstruction(ins.opcode, ins.type, reg1 = ins.reg1, immediate = newValue) + val nextImm = nextInstr.immediate!!.integerValue!! + val newValue = if (ins.opcode == Opcode.ADD) imm!! + nextImm else imm!! - nextImm + chunk.instructions[idx] = IRInstructions.binaryImmediate(ins.opcode, ins.type!!, ins.dest!!.registerNumber, newValue) chunk.instructions.removeAt(idx + 1) changed = true } Opcode.SUB -> { // SUB after ADD or SUB - val newValue = if (ins.opcode == Opcode.ADD) ins.immediate!! - nextInstr.immediate!! else ins.immediate!! + nextInstr.immediate!! - chunk.instructions[idx] = IRInstruction(ins.opcode, ins.type, reg1 = ins.reg1, immediate = newValue) + val nextImm = nextInstr.immediate!!.integerValue!! + val newValue = if (ins.opcode == Opcode.ADD) imm!! - nextImm else imm!! + nextImm + chunk.instructions[idx] = IRInstructions.binaryImmediate(ins.opcode, ins.type!!, ins.dest!!.registerNumber, newValue) chunk.instructions.removeAt(idx + 1) changed = true } @@ -595,9 +588,10 @@ jump p8_label_gen_2 } } Opcode.AND -> { - when (ins.immediate) { + val imm = ins.immediate?.integerValue + when (imm) { 0 -> { - chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, immediate = 0) + chunk.instructions[idx] = IRInstructions.load(ins.type!!, ins.dest!!.registerNumber, 0) changed = true } 255 if ins.type == IRDataType.BYTE -> { @@ -615,22 +609,22 @@ jump p8_label_gen_2 } // convert AND with all-ones-except-one-bit into BITCLR if(!changed) { - val imm = ins.immediate ?: return@forEach + val immv = imm ?: return@forEach val clearedBits = when(ins.type) { - IRDataType.BYTE -> imm xor 0xFF - IRDataType.WORD -> imm xor 0xFFFF - IRDataType.LONG -> imm xor -1 + IRDataType.BYTE -> immv xor 0xFF + IRDataType.WORD -> immv xor 0xFFFF + IRDataType.LONG -> immv xor -1 else -> 0 } if(clearedBits > 0 && clearedBits and (clearedBits - 1) == 0) { val bitPos = Integer.numberOfTrailingZeros(clearedBits) - chunk.instructions[idx] = IRInstruction(Opcode.BITCLR, ins.type, reg1 = ins.reg1, immediate = bitPos) + chunk.instructions[idx] = IRInstructions.binaryImmediate(Opcode.BITCLR, ins.type!!, ins.dest!!.registerNumber, bitPos) changed = true } } } Opcode.OR -> { - val imm = ins.immediate + val imm = ins.immediate?.integerValue if (imm == null) return@forEach if (imm == 0) { chunk.instructions.removeAt(idx) @@ -638,18 +632,18 @@ jump p8_label_gen_2 } else if ((imm == 255 && ins.type == IRDataType.BYTE) || (imm == 65535 && ins.type == IRDataType.WORD) || (imm == -1 && ins.type == IRDataType.LONG)) { - chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, immediate = imm) + chunk.instructions[idx] = IRInstructions.load(ins.type!!, ins.dest!!.registerNumber, imm) changed = true } // convert OR with power-of-2 into BITSET if(!changed && imm > 0 && imm and (imm - 1) == 0) { val bitPos = Integer.numberOfTrailingZeros(imm) - chunk.instructions[idx] = IRInstruction(Opcode.BITSET, ins.type, reg1 = ins.reg1, immediate = bitPos) + chunk.instructions[idx] = IRInstructions.binaryImmediate(Opcode.BITSET, ins.type!!, ins.dest!!.registerNumber, bitPos) changed = true } } Opcode.XOR -> { - val imm = ins.immediate + val imm = ins.immediate?.integerValue if (imm == null) return@forEach if (imm == 0) { chunk.instructions.removeAt(idx) @@ -658,7 +652,7 @@ jump p8_label_gen_2 // convert XOR with power-of-2 into BITTOG if(!changed && imm > 0 && imm and (imm - 1) == 0) { val bitPos = Integer.numberOfTrailingZeros(imm) - chunk.instructions[idx] = IRInstruction(Opcode.BITTOG, ins.type, reg1 = ins.reg1, immediate = bitPos) + chunk.instructions[idx] = IRInstructions.binaryImmediate(Opcode.BITTOG, ins.type!!, ins.dest!!.registerNumber, bitPos) changed = true } } @@ -667,35 +661,37 @@ jump p8_label_gen_2 fun optimizeImmediateLoad(replacementOpcode: Opcode, isCommutative: Boolean) { - fun getImmediateLoad(reg: Int): Pair? { + fun getImmediateLoad(reg: VirtualRegister): Pair? { // look if the given register gets an immediate value 1 or 2 istructions back // returns (index of load instruction, immediate value) or null. if(idx>=1) { val previous = indexedInstructions[idx-1].value - if(previous.opcode==Opcode.LOAD && previous.reg1==reg && previous.immediate!=null) - return idx-1 to previous.immediate!! + if(previous.opcode==Opcode.LOAD && previous.dest?.register==reg && previous.immediate?.integerValue!=null) + return idx-1 to previous.immediate!!.integerValue!! } if(idx>=2) { val previous = indexedInstructions[idx-2].value - if(previous.opcode==Opcode.LOAD && previous.reg1==reg && previous.immediate!=null) - return idx - 2 to previous.immediate!! + if(previous.opcode==Opcode.LOAD && previous.dest?.register==reg && previous.immediate?.integerValue!=null) + return idx - 2 to previous.immediate!!.integerValue!! } return null } - if(ins.reg1!=null) { + val destOperand = ins.dest + val srcOperand = ins.srcA + if(destOperand!=null) { if (isCommutative) { - val immediate1 = getImmediateLoad(ins.reg1!!) + val immediate1 = getImmediateLoad(destOperand.register) if (immediate1 != null) { - chunk.instructions[idx] = IRInstruction(replacementOpcode, ins.type, reg1 = ins.reg2, immediate = immediate1.second) + chunk.instructions[idx] = IRInstructions.binaryImmediate(replacementOpcode, ins.type!!, srcOperand!!.registerNumber, immediate1.second) chunk.instructions.removeAt(immediate1.first) changed = true return } } - val immediate2 = getImmediateLoad(ins.reg2!!) + val immediate2 = getImmediateLoad(srcOperand!!.register) if (immediate2 != null) { - chunk.instructions[idx] = IRInstruction(replacementOpcode, ins.type, reg1 = ins.reg1, immediate = immediate2.second) + chunk.instructions[idx] = IRInstructions.binaryImmediate(replacementOpcode, ins.type!!, destOperand.registerNumber, immediate2.second) chunk.instructions.removeAt(immediate2.first) changed = true } @@ -728,15 +724,9 @@ jump p8_label_gen_2 if(nextInstr.opcode != Opcode.LOADR || ins.type != nextInstr.type) return@forEach - if(ins.type in setOf(IRDataType.BYTE, IRDataType.WORD, IRDataType.LONG, IRDataType.POINTER) && - ins.reg1 != null && ins.reg2 != null && nextInstr.reg2 == ins.reg1) { - chunk.instructions[idx + 1] = nextInstr.copy(reg2 = ins.reg2) - chunk.instructions[idx] = IRInstruction(Opcode.NOP) - changed = true - } else if(ins.type == IRDataType.FLOAT && - ins.fpReg1 != null && ins.fpReg2 != null && nextInstr.fpReg2 == ins.fpReg1) { - chunk.instructions[idx + 1] = nextInstr.copy(fpReg2 = ins.fpReg2) - chunk.instructions[idx] = IRInstruction(Opcode.NOP) + if(nextInstr.srcA?.register == ins.dest?.register) { + chunk.instructions[idx + 1] = nextInstr.copy(srcA = nextInstr.srcA!!.withRegister(ins.srcA!!.register)) + chunk.instructions[idx] = IRInstructions.simple(Opcode.NOP) changed = true } } @@ -748,20 +738,20 @@ jump p8_label_gen_2 indexedInstructions.reversed().forEach { (idx, ins) -> when(ins.opcode) { Opcode.LOADR -> { - if((ins.reg1 != null && ins.reg1 == ins.reg2) || (ins.fpReg1 != null && ins.fpReg1 == ins.fpReg2)) { - chunk.instructions[idx] = IRInstruction(Opcode.NOP) + if(ins.dest?.register == ins.srcA?.register) { + chunk.instructions[idx] = IRInstructions.simple(Opcode.NOP) changed = true } } Opcode.ANDR, Opcode.ORR -> { - if(ins.reg1 == ins.reg2) { - chunk.instructions[idx] = IRInstruction(Opcode.NOP) + if(ins.dest?.register == ins.srcA?.register) { + chunk.instructions[idx] = IRInstructions.simple(Opcode.NOP) changed = true } } Opcode.XORR -> { - if(ins.reg1 == ins.reg2) { - chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, immediate = 0) + if(ins.dest?.register == ins.srcA?.register) { + chunk.instructions[idx] = IRInstructions.load(ins.type!!, ins.dest!!.registerNumber, 0) changed = true } } @@ -774,10 +764,10 @@ jump p8_label_gen_2 private fun simplifyShiftByZero(chunk: IRCodeChunk, indexedInstructions: List>): Boolean { var changed = false indexedInstructions.reversed().forEach { (idx, ins) -> - if(idx>0 && ins.opcode in setOf(Opcode.LSLN, Opcode.LSRN, Opcode.ASRN) && ins.reg2 != null) { + if(idx>0 && ins.opcode in setOf(Opcode.LSLN, Opcode.LSRN, Opcode.ASRN) && ins.srcA != null) { val prev = indexedInstructions[idx-1].value - if(prev.opcode == Opcode.LOAD && prev.reg1 == ins.reg2 && prev.immediate == 0) { - chunk.instructions[idx] = IRInstruction(Opcode.NOP) + if(prev.opcode == Opcode.LOAD && prev.dest?.register == ins.srcA?.register && prev.immediate?.integerValue == 0) { + chunk.instructions[idx] = IRInstructions.simple(Opcode.NOP) chunk.instructions.removeAt(idx-1) changed = true } @@ -795,37 +785,36 @@ jump p8_label_gen_2 when(ins.opcode) { Opcode.INV -> { - if(insAfter.opcode == Opcode.INV && insAfter.reg1 == ins.reg1) { - chunk.instructions[idx] = IRInstruction(Opcode.NOP) - chunk.instructions[idx+1] = IRInstruction(Opcode.NOP) + if(insAfter.opcode == Opcode.INV && insAfter.dest?.register == ins.dest?.register) { + chunk.instructions[idx] = IRInstructions.simple(Opcode.NOP) + chunk.instructions[idx+1] = IRInstructions.simple(Opcode.NOP) changed = true } } Opcode.NEG -> { - val sameReg = (ins.reg1 != null && ins.reg1 == insAfter.reg1) || (ins.fpReg1 != null && ins.fpReg1 == insAfter.fpReg1) - if(insAfter.opcode == Opcode.NEG && sameReg) { - chunk.instructions[idx] = IRInstruction(Opcode.NOP) - chunk.instructions[idx+1] = IRInstruction(Opcode.NOP) + if(insAfter.opcode == Opcode.NEG && insAfter.dest?.register == ins.dest?.register) { + chunk.instructions[idx] = IRInstructions.simple(Opcode.NOP) + chunk.instructions[idx+1] = IRInstructions.simple(Opcode.NOP) changed = true } } Opcode.EXT, Opcode.EXTS, Opcode.EXTL, Opcode.EXTLS -> { - if(insAfter.opcode == ins.opcode && insAfter.reg1 == ins.reg1 && insAfter.type == ins.type) { - chunk.instructions[idx+1] = IRInstruction(Opcode.NOP) + if(insAfter.opcode == ins.opcode && insAfter.dest?.register == ins.dest?.register && insAfter.type == ins.type) { + chunk.instructions[idx+1] = IRInstructions.simple(Opcode.NOP) changed = true } } Opcode.INC -> { - if(insAfter.opcode == Opcode.DEC && insAfter.reg1 == ins.reg1) { - chunk.instructions[idx] = IRInstruction(Opcode.NOP) - chunk.instructions[idx+1] = IRInstruction(Opcode.NOP) + if(insAfter.opcode == Opcode.DEC && insAfter.dest?.register == ins.dest?.register) { + chunk.instructions[idx] = IRInstructions.simple(Opcode.NOP) + chunk.instructions[idx+1] = IRInstructions.simple(Opcode.NOP) changed = true } } Opcode.DEC -> { - if(insAfter.opcode == Opcode.INC && insAfter.reg1 == ins.reg1) { - chunk.instructions[idx] = IRInstruction(Opcode.NOP) - chunk.instructions[idx+1] = IRInstruction(Opcode.NOP) + if(insAfter.opcode == Opcode.INC && insAfter.dest?.register == ins.dest?.register) { + chunk.instructions[idx] = IRInstructions.simple(Opcode.NOP) + chunk.instructions[idx+1] = IRInstructions.simple(Opcode.NOP) changed = true } } @@ -841,41 +830,30 @@ jump p8_label_gen_2 // loadm.l rX, P ; storei.b rY,rX,#0 ; ... ; incm.l P => storep_inc.b rY,P // Only if rX is defined once and used once (the loadi/storei), and incm is after. if(chunk.instructions.size < 3) return false - // Count register reads - val readCounts = mutableMapOf() + // Count register uses + val readCounts = mutableMapOf() for(ins in chunk.instructions) { - if(ins.reg2 != null) readCounts[ins.reg2!!] = (readCounts[ins.reg2!!] ?: 0) + 1 - if(ins.reg3 != null) readCounts[ins.reg3!!] = (readCounts[ins.reg3!!] ?: 0) + 1 - // LOADI/STOREI uses reg2 as base, that's already counted - // For our pattern, base reg is reg2 of LOADI/STOREI + ins.uses.forEach { reg -> readCounts[reg] = (readCounts[reg] ?: 0) + 1 } } - // Also need to count reg1 reads for other ops? For LOADM, reg1 is written, not read - // For LOADI, reg1 is written, reg2 is read - // For STOREI, reg1 is read, reg2 is read - // For INCM, no regs - // So readCounts for base reg already correct via reg2 var changed = false // Find loadm indices val loadmByAddr = mutableMapOf>() // address string -> list of loadm indices - val loadmRegByIdx = mutableMapOf() // loadm idx -> rX - val loadmAddrByIdx = mutableMapOf() + val loadmRegByIdx = mutableMapOf() // loadm idx -> rX for((idx, ins) in indexedInstructions) { - if(ins.opcode == Opcode.LOADM && ins.labelSymbol != null && ins.reg1 != null) { - // only integer loadm (float uses fpReg1) - if(ins.type == IRDataType.FLOAT) continue - val addr = ins.labelSymbol!! + if(ins.opcode == Opcode.LOADM && ins.type != IRDataType.FLOAT) { + // only integer loadm (float uses a float register, not fusible into a pointer) + val addr = ins.memory?.symbolName ?: continue loadmByAddr.getOrPut(addr) { mutableListOf() }.add(idx) - loadmRegByIdx[idx] = ins.reg1!! - loadmAddrByIdx[idx] = addr + loadmRegByIdx[idx] = ins.requireDest().register } } // For each incm, try to find matching loadm+loadi/storei val toRemove = mutableSetOf() val toReplace = mutableMapOf() for((incIdx, incIns) in indexedInstructions) { - if(incIns.opcode != Opcode.INCM || incIns.labelSymbol == null) continue - val addr = incIns.labelSymbol!! + if(incIns.opcode != Opcode.INCM) continue + val addr = incIns.memory?.symbolName ?: continue val loadmIndices = loadmByAddr[addr] ?: continue // Find the latest loadm before incm that has a matching loadi/storei using its reg for(loadmIdx in loadmIndices.reversed()) { @@ -891,16 +869,17 @@ jump p8_label_gen_2 if(midIdx <= loadmIdx || midIdx >= incIdx) continue if(midIdx in toRemove || midIdx in toReplace) continue if(midIns.type == IRDataType.FLOAT) continue - if(midIns.reg1 == null) continue - if(midIns.opcode == Opcode.LOADI && midIns.reg2 == baseReg) { + val indirect = midIns.memory as? MemoryReference.Indirect ?: continue + if(indirect.pointer.register != baseReg) continue + if(midIns.opcode == Opcode.LOADI) { // LOADI must have offset 0 to be fusible to post-inc (otherwise need add) - if(midIns.immediate != 0) continue + if(indirect.displacement != 0) continue foundIdx = midIdx foundIns = midIns break } - if(midIns.opcode == Opcode.STOREI && midIns.reg2 == baseReg) { - if(midIns.immediate != 0) continue + if(midIns.opcode == Opcode.STOREI) { + if(indirect.displacement != 0) continue foundIdx = midIdx foundIns = midIns break @@ -910,10 +889,11 @@ jump p8_label_gen_2 // Also ensure no other use of baseReg between loadm and incm besides this one // Already ensured readCounts==1, but also ensure no other loadm defines same reg // Fuse - val newOpcode = if(foundIns.opcode == Opcode.LOADI) Opcode.LOADP_INC else Opcode.STOREP_INC val newType = foundIns.type!! - val newReg = foundIns.reg1 ?: continue - val newIns = IRInstruction(newOpcode, newType, reg1 = newReg, labelSymbol = addr) + val newIns = if(foundIns.opcode == Opcode.LOADI) + IRInstructions.loadMemory(Opcode.LOADP_INC, newType, foundIns.requireDest().registerNumber, IRMemory.direct(addr)) + else + IRInstructions.storeMemory(Opcode.STOREP_INC, newType, foundIns.requireSrcA().registerNumber, IRMemory.direct(addr)) toReplace[foundIdx] = newIns toRemove.add(loadmIdx) toRemove.add(incIdx) @@ -926,7 +906,7 @@ jump p8_label_gen_2 val allIndices = (toRemove + toReplace.keys).sortedDescending() for(idx in allIndices) { if(idx in toRemove) { - chunk.instructions[idx] = IRInstruction(Opcode.NOP) + chunk.instructions[idx] = IRInstructions.simple(Opcode.NOP) } } for((idx, newIns) in toReplace) { @@ -955,24 +935,19 @@ jump p8_label_gen_2 indexedInstructions.forEach { (idx, ins) -> if(ins.opcode==Opcode.STOREM && idx>0) { val prev = indexedInstructions[idx-1].value - if(prev.opcode==Opcode.LOAD && prev.labelSymbol==null) { - val isInt = prev.immediate != null && prev.immediateFp == null - val isFloat = prev.immediateFp != null - val sameReg = when(ins.type) { - IRDataType.FLOAT -> prev.fpReg1 != null && ins.fpReg1 == prev.fpReg1 - else -> prev.reg1 != null && ins.reg1 == prev.reg1 - } - if(sameReg && (isInt || isFloat)) { - val newIns = ins.copy( - opcode = Opcode.STOREIM, - reg1 = null, - fpReg1 = null, - immediate = if(isInt) prev.immediate else null, - immediateFp = if(isFloat) prev.immediateFp else null - ) - chunk.instructions[idx] = newIns - chunk.instructions[idx-1] = IRInstruction(Opcode.NOP) - changed = true + if(prev.opcode==Opcode.LOAD && prev.immediate !is ImmediateOperand.SymbolAddress) { + val sameReg = ins.srcA?.register == prev.dest?.register + if(sameReg) { + val newIns = when(val immediate = prev.immediate) { + is ImmediateOperand.Integer -> IRInstructions.storeImmediate(ins.type!!, immediate.value, ins.requireMemory()) + is ImmediateOperand.FloatValue -> IRInstructions.storeImmediateFloat(immediate.value, ins.requireMemory()) + else -> null + } + if(newIns != null) { + chunk.instructions[idx] = newIns + chunk.instructions[idx-1] = IRInstructions.simple(Opcode.NOP) + changed = true + } } } } @@ -987,11 +962,7 @@ jump p8_label_gen_2 val prev = indexedInstructions[idx-1].value if(prev.opcode==Opcode.LOADM) { // loadm.X rX,something | storem.X rX,something ?? -> get rid of the store. - if(ins.labelSymbol!=null && ins.labelSymbol==prev.labelSymbol && ins.labelSymbolOffset==prev.labelSymbolOffset) { - changed=true - chunk.instructions.removeAt(idx) - } - else if(ins.address!=null && ins.address==prev.address) { + if(ins.memory == prev.memory) { changed=true chunk.instructions.removeAt(idx) } @@ -1020,60 +991,15 @@ jump p8_label_gen_2 // side-effecting instruction occurs between. Keeps the optimization single-chunk. if(chunk.instructions.size < 2) return false - fun indexRegOf(ins: IRInstruction): Int? = when(ins.opcode) { - Opcode.LOADX -> if(ins.type==IRDataType.FLOAT) ins.reg1 else ins.reg2 - Opcode.STOREX -> if(ins.type==IRDataType.FLOAT) ins.reg1 else ins.reg2 - Opcode.STOREZX -> ins.reg1 + fun indexRegOf(ins: IRInstruction): VirtualRegister? = (ins.memory as? MemoryReference.Indexed)?.index?.register + fun valueRegOf(ins: IRInstruction): VirtualRegister? = when(ins.opcode) { + Opcode.LOADX -> ins.dest?.register + Opcode.STOREX -> ins.srcA?.register else -> null } - fun valueIntRegOf(ins: IRInstruction): Int? = when(ins.opcode) { - Opcode.LOADX, Opcode.STOREX -> if(ins.type==IRDataType.FLOAT) null else ins.reg1 - else -> null - } - fun valueFpRegOf(ins: IRInstruction): RegisterNum? = when(ins.opcode) { - Opcode.LOADX, Opcode.STOREX -> if(ins.type==IRDataType.FLOAT) ins.fpReg1 else null - else -> null - } - fun labelKey(ins: IRInstruction): String { - return "${ins.type}:${ins.labelSymbol}:${ins.labelSymbolOffset}:${ins.address}" - } - fun readsReg(ins: IRInstruction, reg: Int): Boolean { - val fmt = instructionFormats[ins.opcode]?.get(ins.type) ?: instructionFormats[ins.opcode]?.get(null) - if(fmt!=null) { - if((fmt.reg1 == OperandDirection.READ || fmt.reg1 == OperandDirection.READWRITE) && ins.reg1 == reg) return true - if((fmt.reg2 == OperandDirection.READ || fmt.reg2 == OperandDirection.READWRITE) && ins.reg2 == reg) return true - if((fmt.reg3 == OperandDirection.READ || fmt.reg3 == OperandDirection.READWRITE) && ins.reg3 == reg) return true - } - ins.fcallArgs?.arguments?.forEach { if(it.reg.registerNum.value==reg && it.reg.dt!=IRDataType.FLOAT) return true } - return false - } - fun writesReg(ins: IRInstruction, reg: Int): Boolean { - val fmt = instructionFormats[ins.opcode]?.get(ins.type) ?: instructionFormats[ins.opcode]?.get(null) - if(fmt!=null) { - if((fmt.reg1 == OperandDirection.WRITE || fmt.reg1 == OperandDirection.READWRITE) && ins.reg1 == reg) return true - if((fmt.reg2 == OperandDirection.WRITE || fmt.reg2 == OperandDirection.READWRITE) && ins.reg2 == reg) return true - if((fmt.reg3 == OperandDirection.WRITE || fmt.reg3 == OperandDirection.READWRITE) && ins.reg3 == reg) return true - } - ins.fcallArgs?.returns?.forEach { if(it.registerNum.value==reg && it.dt!=IRDataType.FLOAT) return true } - return false - } - fun readsFpReg(ins: IRInstruction, fp: RegisterNum): Boolean { - val fmt = instructionFormats[ins.opcode]?.get(ins.type) ?: instructionFormats[ins.opcode]?.get(null) - if(fmt!=null) { - if((fmt.fpReg1 == OperandDirection.READ || fmt.fpReg1 == OperandDirection.READWRITE) && ins.fpReg1 == fp) return true - if((fmt.fpReg2 == OperandDirection.READ || fmt.fpReg2 == OperandDirection.READWRITE) && ins.fpReg2 == fp) return true - } - ins.fcallArgs?.arguments?.forEach { if(it.reg.registerNum==fp && it.reg.dt==IRDataType.FLOAT) return true } - return false - } - fun writesFpReg(ins: IRInstruction, fp: RegisterNum): Boolean { - val fmt = instructionFormats[ins.opcode]?.get(ins.type) ?: instructionFormats[ins.opcode]?.get(null) - if(fmt!=null) { - if((fmt.fpReg1 == OperandDirection.WRITE || fmt.fpReg1 == OperandDirection.READWRITE) && ins.fpReg1 == fp) return true - if((fmt.fpReg2 == OperandDirection.WRITE || fmt.fpReg2 == OperandDirection.READWRITE) && ins.fpReg2 == fp) return true - } - ins.fcallArgs?.returns?.forEach { if(it.registerNum==fp && it.dt==IRDataType.FLOAT) return true } - return false + fun locationKey(ins: IRInstruction): Any? { + val mem = ins.memory as? MemoryReference.Indexed ?: return null + return Triple(ins.type, mem.base, mem.displacement) } val toRemove = mutableSetOf() @@ -1085,9 +1011,8 @@ jump p8_label_gen_2 if(currIdx in toRemove) continue if(currIns.opcode != Opcode.STOREX) continue val currIndexReg = indexRegOf(currIns) ?: continue - val currValueReg = valueIntRegOf(currIns) - val currFpReg = valueFpRegOf(currIns) - val currKey = labelKey(currIns) + val currValueReg = valueRegOf(currIns) ?: continue + val currKey = locationKey(currIns) // search backwards for matching LOADX for(prevPos in currPos - 1 downTo 0) { val prevEntry = indexedInstructions[prevPos] @@ -1096,27 +1021,18 @@ jump p8_label_gen_2 if(prevIdx in toRemove) continue if(prevIns.opcode != Opcode.LOADX) continue if(prevIns.type != currIns.type) continue - if(labelKey(prevIns) != currKey) continue + if(locationKey(prevIns) != currKey) continue if(indexRegOf(prevIns) != currIndexReg) continue - if(currValueReg != null) { - if(valueIntRegOf(prevIns) != currValueReg) continue - } else if(currFpReg != null) { - if(valueFpRegOf(prevIns) != currFpReg) continue - } else continue + if(valueRegOf(prevIns) != currValueReg) continue var blocked = false for(midPos in prevPos + 1 until currPos) { val midIns = indexedInstructions[midPos].value if(midIns.opcode in OpcodesWithSideEffects) { blocked = true; break } - if(currValueReg != null) { - if(writesReg(midIns, currValueReg) || readsReg(midIns, currValueReg)) { blocked = true; break } - if(writesReg(midIns, currIndexReg)) { blocked = true; break } - } else if(currFpReg != null) { - if(writesFpReg(midIns, currFpReg) || readsFpReg(midIns, currFpReg)) { blocked = true; break } - if(writesReg(midIns, currIndexReg)) { blocked = true; break } - } + if(currValueReg in midIns.definitions || currValueReg in midIns.uses) { blocked = true; break } + if(currIndexReg in midIns.definitions) { blocked = true; break } // intervening indexed access to same location breaks direct coalescing (let closer pair handle it) - if(labelKey(midIns) == currKey && midIns.opcode in setOf(Opcode.LOADX, Opcode.STOREX, Opcode.STOREZX)) { + if(locationKey(midIns) == currKey && midIns.opcode in setOf(Opcode.LOADX, Opcode.STOREX, Opcode.STOREZX)) { blocked = true; break } } @@ -1134,7 +1050,7 @@ jump p8_label_gen_2 if(currIdx in toRemove) continue if(currIns.opcode !in setOf(Opcode.STOREX, Opcode.STOREZX)) continue val currIndexReg = indexRegOf(currIns) ?: continue - val currKey = labelKey(currIns) + val currKey = locationKey(currIns) for(prevPos in currPos - 1 downTo 0) { val prevEntry = indexedInstructions[prevPos] val prevIdx = prevEntry.index @@ -1145,15 +1061,15 @@ jump p8_label_gen_2 // require exact same type for indexed store coalescing continue } - if(labelKey(prevIns) != currKey) continue + if(locationKey(prevIns) != currKey) continue if(indexRegOf(prevIns) != currIndexReg) continue var blocked = false for(midPos in prevPos + 1 until currPos) { val midIns = indexedInstructions[midPos].value if(midIns.opcode in OpcodesWithSideEffects) { blocked = true; break } - if(writesReg(midIns, currIndexReg)) { blocked = true; break } + if(currIndexReg in midIns.definitions) { blocked = true; break } // any LOADX to same location is a read of memory - prevents dead store elimination - if(midIns.opcode == Opcode.LOADX && labelKey(midIns) == currKey && indexRegOf(midIns) == currIndexReg) { blocked = true; break } + if(midIns.opcode == Opcode.LOADX && locationKey(midIns) == currKey && indexRegOf(midIns) == currIndexReg) { blocked = true; break } } if(!blocked) { toRemove.add(prevIdx) @@ -1177,10 +1093,16 @@ jump p8_label_gen_2 if(idx>0) { val insBefore = chunk.instructions[idx-1] if(insBefore.opcode == Opcode.LOAD && insBefore.immediate!=null) { - val constvalue = insBefore.immediate!! - chunk.instructions[idx] = IRInstruction(Opcode.RETURNI, ins.type, immediate = constvalue) - chunk.instructions.removeAt(idx-1) - changed = true + val newIns = when(val imm = insBefore.immediate!!) { + is ImmediateOperand.Integer -> IRInstructions.returnImmediate(ins.type!!, imm.value) + is ImmediateOperand.FloatValue -> IRInstructions.returnImmediateFloat(imm.value) + is ImmediateOperand.SymbolAddress -> null + } + if(newIns != null) { + chunk.instructions[idx] = newIns + chunk.instructions.removeAt(idx-1) + changed = true + } } } } @@ -1201,7 +1123,7 @@ jump p8_label_gen_2 // lsigb.? rX,rA + ext.b rY,rX -> loadr.? rY,rA ; and.? rY,#$ff // Only applied when the intermediate register is not read anywhere else in this chunk. if(chunk.instructions.size<2) return false - val readCounts = chunk.usedRegisters(irprog.options.compTarget.indexRegType).readRegs.withDefault { 0 } + val readCounts = chunk.usedRegisters().readRegs.withDefault { 0 } class Mod(val idx: Int, val replacement: IRInstruction?) // null replacement = delete val mods = mutableListOf() @@ -1210,10 +1132,10 @@ jump p8_label_gen_2 for(k in 0 until indexedInstructions.size-1) { val (i, inner) = indexedInstructions[k] val (_, outer) = indexedInstructions[k+1] - val mid = inner.reg1 + val mid = inner.dest?.register fun singleResult(opcode: Opcode) = - IRInstruction(opcode, IRDataType.BYTE, reg1 = outer.reg1, reg2 = inner.reg2) + IRInstructions.binary(opcode, IRDataType.BYTE, outer.requireDest().registerNumber, inner.requireSrcA().registerNumber) if(i in claimed || i+1 in claimed) continue @@ -1222,8 +1144,8 @@ jump p8_label_gen_2 // composed sign/zero extension chains on bytes outer.opcode in setOf(Opcode.EXT, Opcode.EXTS) && outer.type==IRDataType.WORD && inner.opcode in setOf(Opcode.EXT, Opcode.EXTS) && inner.type==IRDataType.BYTE - && mid!=null && mid==outer.reg2 - && readCounts[RegisterNum(mid)]==1 -> { + && mid!=null && mid==outer.srcA?.register + && readCounts.getValue(mid)==1 -> { val newOp = if(inner.opcode==Opcode.EXTS && outer.opcode==Opcode.EXTS) Opcode.EXTLS else Opcode.EXTL mods.add(Mod(i+1, null)) mods.add(Mod(i, singleResult(newOp))) @@ -1231,18 +1153,18 @@ jump p8_label_gen_2 // truncate-long-to-word followed by zero-extend word-to-long == mask with $ffff inner.opcode==Opcode.LSIGW && inner.type==IRDataType.LONG && outer.opcode==Opcode.EXT && outer.type==IRDataType.WORD - && mid!=null && mid==outer.reg2 && inner.reg2!=null && outer.reg1!=null - && readCounts[RegisterNum(mid)]==1 -> { - mods.add(Mod(i+1, IRInstruction(Opcode.AND, IRDataType.LONG, reg1 = outer.reg1, immediate = 0xffff))) - mods.add(Mod(i, IRInstruction(Opcode.LOADR, IRDataType.LONG, reg1 = outer.reg1, reg2 = inner.reg2))) + && mid!=null && mid==outer.srcA?.register && inner.srcA!=null && outer.dest!=null + && readCounts.getValue(mid)==1 -> { + mods.add(Mod(i+1, IRInstructions.binaryImmediate(Opcode.AND, IRDataType.LONG, outer.requireDest().registerNumber, 0xffff))) + mods.add(Mod(i, IRInstructions.move(IRDataType.LONG, outer.requireDest().registerNumber, inner.requireSrcA().registerNumber))) } // truncate-long/word-to-byte followed by zero-extend byte-to-word == mask with $ff inner.opcode==Opcode.LSIGB && inner.type in setOf(IRDataType.WORD, IRDataType.LONG) && outer.opcode==Opcode.EXT && outer.type==IRDataType.BYTE - && mid!=null && mid==outer.reg2 && inner.reg2!=null && outer.reg1!=null - && readCounts[RegisterNum(mid)]==1 -> { - mods.add(Mod(i+1, IRInstruction(Opcode.AND, inner.type, reg1 = outer.reg1, immediate = 0xff))) - mods.add(Mod(i, IRInstruction(Opcode.LOADR, inner.type, reg1 = outer.reg1, reg2 = inner.reg2))) + && mid!=null && mid==outer.srcA?.register && inner.srcA!=null && outer.dest!=null + && readCounts.getValue(mid)==1 -> { + mods.add(Mod(i+1, IRInstructions.binaryImmediate(Opcode.AND, inner.type!!, outer.requireDest().registerNumber, 0xff))) + mods.add(Mod(i, IRInstructions.move(inner.type!!, outer.requireDest().registerNumber, inner.requireSrcA().registerNumber))) } else -> continue } @@ -1274,38 +1196,27 @@ jump p8_label_gen_2 // between them, the second group is replaced by a single loadr.p copy from the first group's dest. if(chunk.instructions.size < 8) return false + data class MemKey(val type: IRDataType?, val base: AddressBase, val displacement: Int) + data class AddrGroup( val startIdx: Int, val ptrReg: Int, val idxReg: Int, val extReg: Int, val destReg: Int, - val bufKey: String, - val idxKey: String + val bufKey: MemKey, + val idxKey: MemKey ) - fun memKey(ins: IRInstruction): String { - // labelSymbol/labelSymbolOffset/address/type uniquely identify the variable - return "${ins.type}:${ins.labelSymbol}:${ins.labelSymbolOffset}:${ins.address}" + fun memKey(ins: IRInstruction): MemKey? { + // type/base/displacement uniquely identify the variable + val mem = ins.memory as? MemoryReference.Direct ?: return null + return MemKey(ins.type, mem.base, mem.displacement) } - - fun writesReg(ins: IRInstruction, reg: Int): Boolean { - val formats = instructionFormats[ins.opcode] ?: return false - val fmt = formats[ins.type] ?: formats[null] ?: return false - if((fmt.reg1 == OperandDirection.WRITE || fmt.reg1 == OperandDirection.READWRITE) && ins.reg1 == reg) return true - if((fmt.reg2 == OperandDirection.WRITE || fmt.reg2 == OperandDirection.READWRITE) && ins.reg2 == reg) return true - if((fmt.reg3 == OperandDirection.WRITE || fmt.reg3 == OperandDirection.READWRITE) && ins.reg3 == reg) return true - // fp regs are separate, not relevant for pointer regs - return false - } - - fun writesMem(ins: IRInstruction, memKey: String): Boolean { - if(memKey == "null:null:null:null") return false - if(memKey(ins) != memKey) return false - val formats = instructionFormats[ins.opcode] ?: return false - val fmt = formats[ins.type] ?: formats[null] ?: return false - return fmt.address == OperandDirection.WRITE || fmt.address == OperandDirection.READWRITE + fun writesMem(ins: IRInstruction, key: MemKey): Boolean { + if(memKey(ins) != key) return false + return ins.memoryEffect == MemoryEffect.WRITE || ins.memoryEffect == MemoryEffect.READ_WRITE } val groups = mutableListOf() @@ -1315,20 +1226,24 @@ jump p8_label_gen_2 val b = chunk.instructions[i+1] val c = chunk.instructions[i+2] val d = chunk.instructions[i+3] - val isA = a.opcode == Opcode.LOADM && a.type == IRDataType.POINTER && a.reg1 != null - val isB = b.opcode == Opcode.LOADM && b.type in setOf(IRDataType.BYTE, IRDataType.WORD) && b.reg1 != null - val isC = c.opcode in setOf(Opcode.EXT, Opcode.EXTS, Opcode.EXTL, Opcode.EXTLS) && c.reg1 != null && c.reg2 == b.reg1 - val isD = d.opcode == Opcode.ADDR && d.type == IRDataType.POINTER && d.reg1 != null && d.reg2 != null + val isA = a.opcode == Opcode.LOADM && a.type == IRDataType.POINTER && a.dest != null + val isB = b.opcode == Opcode.LOADM && b.type in setOf(IRDataType.BYTE, IRDataType.WORD) && b.dest != null + val isC = c.opcode in setOf(Opcode.EXT, Opcode.EXTS, Opcode.EXTL, Opcode.EXTLS) && c.dest != null && c.srcA?.register == b.dest?.register + val isD = d.opcode == Opcode.ADDR && d.type == IRDataType.POINTER && d.dest != null && d.srcA != null if(isA && isB && isC && isD) { - val ptrReg = a.reg1!! - val extReg = c.reg1!! - val d1 = d.reg1!! - val d2 = d.reg2!! + val ptrReg = a.requireDest().register + val extReg = c.requireDest().register + val d1 = d.requireDest().register + val d2 = d.requireSrcA().register val destOk = (d1 == ptrReg && d2 == extReg) || (d1 == extReg && d2 == ptrReg) if(destOk) { - groups.add(AddrGroup(i, ptrReg, b.reg1!!, extReg, d1, memKey(a), memKey(b))) - i += 4 - continue + val bufKey = memKey(a) + val idxKey = memKey(b) + if(bufKey != null && idxKey != null) { + groups.add(AddrGroup(i, ptrReg.num, b.requireDest().registerNumber, extReg.num, d1.num, bufKey, idxKey)) + i += 4 + continue + } } } i++ @@ -1353,7 +1268,7 @@ jump p8_label_gen_2 for(t in gk.startIdx + 4 until gj.startIdx) { if(t in claimed) continue // already slated for removal, ignore? but we haven't applied mods yet val ins = chunk.instructions[t] - if(writesReg(ins, gk.destReg)) { barrier = true; break } + if(VirtualRegister.int(gk.destReg) in ins.definitions) { barrier = true; break } if(writesMem(ins, gk.bufKey) || writesMem(ins, gk.idxKey)) { barrier = true; break } } if(!barrier) { found = gk; break } @@ -1364,7 +1279,7 @@ jump p8_label_gen_2 mods.add(Mod(gj.startIdx + 3, null)) mods.add(Mod(gj.startIdx + 2, null)) mods.add(Mod(gj.startIdx + 1, null)) - mods.add(Mod(gj.startIdx, IRInstruction(Opcode.LOADR, IRDataType.POINTER, reg1 = gj.destReg, reg2 = found.destReg))) + mods.add(Mod(gj.startIdx, IRInstructions.move(IRDataType.POINTER, gj.destReg, found.destReg))) claimed.add(gj.startIdx); claimed.add(gj.startIdx+1); claimed.add(gj.startIdx+2); claimed.add(gj.startIdx+3) } } @@ -1382,9 +1297,9 @@ jump p8_label_gen_2 // LOAD r1, #5 <- dead store (r1 overwritten before use) // LOAD r1, #10 // USE r1 - + // Track for each register: (index of last write, whether value was read since) - val pendingWrites = mutableMapOf>() // reg -> (writeIdx, wasRead) + val pendingWrites = mutableMapOf>() // reg -> (writeIdx, wasRead) val deadStores = mutableSetOf() indexedInstructions.forEach { (idx, ins) -> @@ -1394,49 +1309,30 @@ jump p8_label_gen_2 pendingWrites.clear() return@forEach } - val formats = instructionFormats.getValue(ins.opcode) - val format = formats[ins.type] ?: formats[null] + // Reads and writes are taken from ALL operand slots (including the index and pointer + // registers inside a memory reference), so a value that is only read through a + // secondary slot is never mistaken for a dead store. + val accesses = ins.registerAccesses - // First, check if this instruction READS any registers - if(format?.reg1 == OperandDirection.READ || format?.reg1 == OperandDirection.READWRITE) { - val reg = ins.reg1 ?: ins.fpReg1?.value - if(reg != null) { - val existing = pendingWrites[reg] - if(existing != null) { - pendingWrites[reg] = existing.first to true - } - } - } - if(format?.reg2 == OperandDirection.READ || format?.reg2 == OperandDirection.READWRITE) { - val reg = ins.reg2 ?: ins.fpReg2?.value - if(reg != null) { - val existing = pendingWrites[reg] - if(existing != null) { - pendingWrites[reg] = existing.first to true - } - } - } - if(format?.reg3 == OperandDirection.READ || format?.reg3 == OperandDirection.READWRITE) { - val reg = ins.reg3 - if(reg != null) { - val existing = pendingWrites[reg] - if(existing != null) { - pendingWrites[reg] = existing.first to true - } + // First, mark the registers that this instruction READS + for(access in accesses) { + if(access.direction != OperandDirection.DEF) { + val existing = pendingWrites[access.register] + if(existing != null) + pendingWrites[access.register] = existing.first to true } } - // Then, check if this instruction WRITES to any registers - if(format?.reg1 == OperandDirection.WRITE || format?.reg1 == OperandDirection.READWRITE) { - val reg = ins.reg1 ?: ins.fpReg1?.value - if(reg != null) { + // Then, record the registers that this instruction WRITES + for(access in accesses) { + if(access.direction != OperandDirection.USE) { // Check if previous write to this reg was dead (never read before this overwrite) - val existing = pendingWrites[reg] + val existing = pendingWrites[access.register] if(existing != null && !existing.second) { deadStores.add(existing.first) } // Record this new write as pending (not yet read) - pendingWrites[reg] = idx to false + pendingWrites[access.register] = idx to false } } } @@ -1467,9 +1363,11 @@ jump p8_label_gen_2 val stChunk = sub.chunks[idx] if (stChunk !is IRCodeChunk || stChunk.instructions.size != 1) { idx++; continue } val stInstr = stChunk.instructions[0] - if (stInstr.opcode != Opcode.STOREM || stInstr.labelSymbol == null || stInstr.reg1 == null) { idx++; continue } - val loopvar = stInstr.labelSymbol!! - val loopReg = stInstr.reg1!! + if (stInstr.opcode != Opcode.STOREM || stInstr.srcA == null) { idx++; continue } + val loopvarSymbol = stInstr.memory?.symbolName + if (loopvarSymbol == null) { idx++; continue } + val loopvar = loopvarSymbol + val loopReg = stInstr.requireSrcA().register val loopType = stInstr.type ?: IRDataType.WORD // next chunk should be the loop body start with a label if (idx + 1 >= sub.chunks.size) { idx++; continue } @@ -1482,13 +1380,13 @@ jump p8_label_gen_2 for (k in idx + 1 until sub.chunks.size) { val c = sub.chunks[k] if (c !is IRCodeChunk) continue - val hasInc = c.instructions.any { it.opcode == Opcode.INCM && it.labelSymbol == loopvar } + val hasInc = c.instructions.any { it.opcode == Opcode.INCM && it.memory?.symbolName == loopvar } if (!hasInc) continue - val hasJumpInSame = c.instructions.any { it.opcode == Opcode.JUMP && it.labelSymbol == loopLabel } + val hasJumpInSame = c.instructions.any { it.opcode == Opcode.JUMP && it.labelTarget == loopLabel } if (hasJumpInSame) { incIdx = k; break } if (k + 1 < sub.chunks.size) { val nxt = sub.chunks[k + 1] - if (nxt is IRCodeChunk && nxt.instructions.size == 1 && nxt.instructions[0].opcode == Opcode.JUMP && nxt.instructions[0].labelSymbol == loopLabel) { + if (nxt is IRCodeChunk && nxt.instructions.size == 1 && nxt.instructions[0].opcode == Opcode.JUMP && nxt.instructions[0].labelTarget == loopLabel) { incIdx = k; break } } @@ -1502,7 +1400,7 @@ jump p8_label_gen_2 if (ch !is IRCodeChunk) continue for (ins in ch.instructions) { // any store/inc/dec that writes to loopvar memory - if (ins.labelSymbol == loopvar && ins.opcode in setOf(Opcode.STOREM, Opcode.STOREIM, Opcode.STOREZM, Opcode.STOREX, Opcode.STOREZX, Opcode.INCM, Opcode.DECM, Opcode.ADDIM, Opcode.SUBIM)) { + if (ins.memory?.symbolName == loopvar && ins.opcode in setOf(Opcode.STOREM, Opcode.STOREIM, Opcode.STOREZM, Opcode.STOREX, Opcode.STOREZX, Opcode.INCM, Opcode.DECM, Opcode.ADDIM, Opcode.SUBIM)) { // exclude the initial STORM itself (not in body) and the inc chunk (not in body) bodyWrites = true; break } @@ -1517,10 +1415,10 @@ jump p8_label_gen_2 for (b in idx + 1 until incIdx) { val ch = sub.chunks[b] as? IRCodeChunk ?: continue for (ins in ch.instructions) { - if (ins.opcode == Opcode.BSTEQ && ins.labelSymbol != null) { + if (ins.opcode == Opcode.BSTEQ && ins.labelTarget != null) { // This is likely the loop's exit branch (LOADM+CMP+BSTEQ pattern) // Verify it follows a CMP that uses loopvar (heuristic: preceding LOADM loopvar) - labelAfter = ins.labelSymbol + labelAfter = ins.labelTarget break } } @@ -1537,7 +1435,7 @@ jump p8_label_gen_2 for (k in exitIdx until sub.chunks.size) { val ch = sub.chunks[k] as? IRCodeChunk ?: continue for (ins in ch.instructions) { - if (ins.opcode == Opcode.LOADM && ins.labelSymbol == loopvar) { liveOut = true; break } + if (ins.opcode == Opcode.LOADM && ins.memory?.symbolName == loopvar) { liveOut = true; break } } if (liveOut) break } @@ -1554,14 +1452,14 @@ jump p8_label_gen_2 val ch = sub.chunks[b] as? IRCodeChunk ?: continue for (i in ch.instructions.indices) { val ins = ch.instructions[i] - if (ins.opcode == Opcode.LOADM && ins.labelSymbol == loopvar) { - val dest = ins.reg1!! + if (ins.opcode == Opcode.LOADM && ins.memory?.symbolName == loopvar) { + val dest = ins.requireDest().register if (dest == loopReg) { // Already holds the correct value; remove the reload - ch.instructions[i] = IRInstruction(Opcode.NOP) + ch.instructions[i] = IRInstructions.simple(Opcode.NOP) } else { val tp = ins.type ?: loopType - ch.instructions[i] = IRInstruction(Opcode.LOADR, tp, reg1 = dest, reg2 = loopReg) + ch.instructions[i] = IRInstructions.move(tp, dest.num, loopReg.num) } } } @@ -1570,8 +1468,8 @@ jump p8_label_gen_2 val incChunk = sub.chunks[incIdx] as IRCodeChunk for (i in incChunk.instructions.indices) { val ins = incChunk.instructions[i] - if (ins.opcode == Opcode.INCM && ins.labelSymbol == loopvar) { - incChunk.instructions[i] = IRInstruction(Opcode.INC, loopType, reg1 = loopReg) + if (ins.opcode == Opcode.INCM && ins.memory?.symbolName == loopvar) { + incChunk.instructions[i] = IRInstructions.unary(Opcode.INC, loopType, loopReg.num) break } } diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt index 81b7da073..b70aa212a 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt @@ -37,7 +37,7 @@ class IRUnusedCodeRemover( blockVars.forEach { stVar -> irprog.allSubs().flatMap { it.chunks }.forEach { chunk -> chunk.instructions.forEach { ins -> - if(ins.labelSymbol == stVar.name) { + if(ins.referencedSymbol() == stVar.name) { return // symbol occurs in an instruction } } @@ -74,42 +74,26 @@ class IRUnusedCodeRemover( private fun removeBlockInits(code: IRProgram, blockLabel: String) { val instructions = code.globalInits.instructions instructions.toTypedArray().forEach {ins -> - if(ins.labelSymbol?.startsWith(blockLabel)==true) { + if(ins.referencedSymbol()?.startsWith(blockLabel)==true) { instructions.remove(ins) } } // remove stray loads - val indexRegType = irprog.options.compTarget.indexRegType - val readRegs = mutableSetOf() - val readFpRegs = mutableSetOf() + val readRegs = mutableSetOf() instructions.forEach { ins -> - val readRegsCounts = mutableMapOf() - val readFpRegsCounts = mutableMapOf() - val writeRegsCounts = mutableMapOf() - val writeFpRegsCounts = mutableMapOf() - val regsTypes = mutableMapOf() - ins.addUsedRegistersCounts(readRegsCounts, writeRegsCounts, readFpRegsCounts, writeFpRegsCounts, regsTypes, null, indexRegType) - readRegs.addAll(readRegsCounts.keys.map { it.value }) - readFpRegs.addAll(readFpRegsCounts.keys.map { it.value }) + val readRegsCounts = mutableMapOf() + val writeRegsCounts = mutableMapOf() + val regsTypes = mutableMapOf() + ins.addUsedRegistersCounts(readRegsCounts, writeRegsCounts, regsTypes, null) + readRegs.addAll(readRegsCounts.keys) } instructions.toTypedArray().forEach { ins -> if(ins.opcode in arrayOf(Opcode.LOAD, Opcode.LOADR, Opcode.LOADM)) { - val reg1 = ins.reg1 - val fpReg1 = ins.fpReg1 - if(reg1!=null) { - if(reg1 !in readRegs) { - if(ins.labelSymbol!=null) - code.st.removeIfExists(ins.labelSymbol!!) - instructions.remove(ins) - } - } - else if(fpReg1!=null) { - if(fpReg1.value !in readFpRegs) { - if(ins.labelSymbol!=null) - code.st.removeIfExists(ins.labelSymbol!!) - instructions.remove(ins) - } + val dest = ins.dest + if(dest != null && dest.register !in readRegs) { + ins.referencedSymbol()?.let { code.st.removeIfExists(it) } + instructions.remove(ins) } } } @@ -192,8 +176,8 @@ class IRUnusedCodeRemover( // check if asmsub is linked or called from another regular subroutine irprog.foreachCodeChunk { chunk -> chunk.instructions.forEach { - it.labelSymbol?.let { label -> allSubs[label]?.let { cc -> linkedAsmSubs += cc } } - // note: branchTarget can't yet point to another IRAsmSubroutine, so do nothing when it's set + it.referencedSymbol()?.let { label -> allSubs[label]?.let { cc -> linkedAsmSubs += cc } } + // note: codeTarget can't yet point to another IRAsmSubroutine, so do nothing when it's set } } @@ -247,16 +231,20 @@ class IRUnusedCodeRemover( new += it.body } it.instructions.forEach { instr -> - if (instr.branchTarget == null) - instr.labelSymbol?.let { label -> + val codeTarget = instr.codeTarget + if (codeTarget == null) { + instr.referencedSymbol()?.let { label -> val chunk = allLabeledChunks[label] ?: allLabeledChunks[label.substringBeforeLast('.')] if(chunk!=null) new+=chunk - else - allLabeledChunks[label]?.let { c -> new += c } } - else - new += instr.branchTarget!! + } + else { + val chunk = irprog.resolveCodeTarget(codeTarget) + ?: codeTarget.labelName?.let { label -> allLabeledChunks[label] } + if(chunk!=null) + new += chunk + } } } reachable += new @@ -292,10 +280,14 @@ class IRUnusedCodeRemover( irprog.foreachCodeChunk { chunk -> chunk.next?.let { next -> linkedChunks += next } chunk.instructions.forEach { - if(it.branchTarget==null) { - it.labelSymbol?.let { label -> allLabeledChunks[label]?.let { cc -> linkedChunks += cc } } + val codeTarget = it.codeTarget + if(codeTarget==null) { + it.referencedSymbol()?.let { label -> allLabeledChunks[label]?.let { cc -> linkedChunks += cc } } } else { - linkedChunks += it.branchTarget!! + val target = irprog.resolveCodeTarget(codeTarget) + ?: codeTarget.labelName?.let { label -> allLabeledChunks[label] } + if(target!=null) + linkedChunks += target } } if (chunk.label in entrypointNames) @@ -305,8 +297,8 @@ class IRUnusedCodeRemover( // make sure that chunks that are only used as a prefix of a label, are also marked as linked linkedChunks.toList().forEach { chunk -> chunk.instructions.forEach { - if(it.labelSymbol!=null) { - val chunkName = it.labelSymbol!!.substringBeforeLast('.') + it.referencedSymbol()?.let { symbol -> + val chunkName = symbol.substringBeforeLast('.') allLabeledChunks[chunkName]?.let { c -> linkedChunks += c } } } diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/RegisterPacker.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/RegisterPacker.kt index 22b24c463..e470f059b 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/RegisterPacker.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/RegisterPacker.kt @@ -84,26 +84,22 @@ import prog8.intermediate.* object RegisterPacker { - data class Interval(val register: Int, val start: Int, val end: Int, val type: IRDataType) + data class Interval(val register: VirtualRegister, val start: Int, val end: Int, val type: IRDataType) fun pack(irProg: IRProgram) { - val indexRegType = irProg.options.compTarget.indexRegType - val allRegTypes = mutableMapOf() + val allRegTypes = mutableMapOf() irProg.forEachInstruction { instr -> - val (written, read) = getRegisterAccess(instr) - for (r in written + read) { - val dt = getRegisterType(instr, r, indexRegType) - allRegTypes.putIfAbsent(r, dt) - } + for (access in instr.registerAccesses) + allRegTypes.putIfAbsent(access.register, access.type) } val beforeCount = allRegTypes.size // Start packing slots after the highest original register number to avoid // collisions between packed slot numbers and original (non-packed) register numbers. - val maxReg = allRegTypes.keys.maxOrNull() ?: 0 + val maxReg = allRegTypes.keys.maxOfOrNull { it.num } ?: 0 val startSlot = maxReg + 1 - val globalSlotTypes = mutableMapOf() - irProg.foreachSub { sub -> packSubroutine(sub, allRegTypes, globalSlotTypes, startSlot, indexRegType) } + val globalSlotTypes = mutableMapOf() + irProg.foreachSub { sub -> packSubroutine(irProg, sub, allRegTypes, globalSlotTypes, startSlot) } val afterTypes = rebuildTypeMap(irProg) val afterCount = afterTypes.size @@ -114,31 +110,20 @@ object RegisterPacker { // Rebuild the register type map after packing, using the same type determination as the packer. // This avoids the strict per-instruction validation in usedRegisters() that POINTER↔LONG↔WORD // cross-type packing can trigger. - fun rebuildTypeMap(irProg: IRProgram): Map { - val indexRegType = irProg.options.compTarget.indexRegType - val newTypes = mutableMapOf() + fun rebuildTypeMap(irProg: IRProgram): Map { + val newTypes = mutableMapOf() irProg.forEachInstruction { instr -> - for ((reg, regNum) in listOf(instr.reg1 to 1, instr.reg2 to 2, instr.reg3 to 3)) { - if (reg != null) - newTypes.putIfAbsent(RegisterNum(reg), determineIntRegType(instr, regNum, indexRegType)) - } - instr.fpReg1?.let { fpr -> newTypes.putIfAbsent(RegisterNum(fpr.value), IRDataType.FLOAT) } - instr.fpReg2?.let { fpr -> newTypes.putIfAbsent(RegisterNum(fpr.value), IRDataType.FLOAT) } - instr.fcallArgs?.let { fc -> - for (a in fc.arguments) - newTypes.putIfAbsent(RegisterNum(a.reg.registerNum.value), a.reg.dt) - for (r in fc.returns) - newTypes.putIfAbsent(RegisterNum(r.registerNum.value), IRDataType.BYTE) - } + for (access in instr.registerAccesses) + newTypes.putIfAbsent(access.register, access.type) } return newTypes } - private fun packSubroutine(sub: IRSubroutine, allRegTypes: Map, globalSlotTypes: MutableMap, startSlot: Int, indexRegType: IRDataType) { + private fun packSubroutine(irProg: IRProgram, sub: IRSubroutine, allRegTypes: Map, globalSlotTypes: MutableMap, startSlot: Int) { if (sub.chunks.isEmpty()) return - val successors = buildCFG(sub) + val successors = buildCFG(sub, irProg) val (_, liveOut) = computeLiveness(sub, successors) val codeChunks = sub.chunks.filterIsInstance() @@ -154,8 +139,8 @@ object RegisterPacker { globalIdx += chunk.instructions.size } - val registerIntervals = mutableMapOf>() - val registerTypes = mutableMapOf() + val registerIntervals = mutableMapOf>() + val registerTypes = mutableMapOf() for (chunk in codeChunks) { val range = chunkRanges[chunk]!! @@ -164,33 +149,37 @@ object RegisterPacker { continue val liveSet = liveOut[chunk]?.toMutableSet() ?: mutableSetOf() - val lastUse = mutableMapOf() + val lastUse = mutableMapOf() // Scan backward through instructions for (i in chunk.instructions.indices.reversed()) { val instr = chunk.instructions[i] val globalI = chunkStart + i - val (written, read) = getRegisterAccess(instr) + val accesses = instr.registerAccesses - // Process READ first so types are set before WRITE processing - for (r in read) { - val dt = getRegisterType(instr, r, indexRegType) - registerTypes.putIfAbsent(r, dt) - if (r !in liveSet) { - lastUse[r] = globalI - liveSet.add(r) + // Process READ (USE / USE_DEF) first so types are set before WRITE processing + for (access in accesses) { + if (access.direction != OperandDirection.DEF) { + val r = access.register + registerTypes.putIfAbsent(r, access.type) + if (r !in liveSet) { + lastUse[r] = globalI + liveSet.add(r) + } } } - // Process WRITTEN registers: end their live range (start of interval going forward) - for (r in written) { - if (r in liveSet) { - val end = lastUse.getOrElse(r) { globalI } - val actualType = getRegisterType(instr, r, indexRegType) - registerIntervals.getOrPut(r) { mutableListOf() } - .add(Interval(r, globalI, end, actualType)) - liveSet.remove(r) - lastUse.remove(r) + // Process WRITTEN (DEF / USE_DEF) registers: end their live range (start of interval going forward) + for (access in accesses) { + if (access.direction != OperandDirection.USE) { + val r = access.register + if (r in liveSet) { + val end = lastUse.getOrElse(r) { globalI } + registerIntervals.getOrPut(r) { mutableListOf() } + .add(Interval(r, globalI, end, access.type)) + liveSet.remove(r) + lastUse.remove(r) + } } } } @@ -212,7 +201,7 @@ object RegisterPacker { // gap to the next use (otherwise another register packed to the same slot // would clobber it between intervals). val mergedIntervals = mutableListOf() - val skipRegs = mutableSetOf() + val skipRegs = mutableSetOf() for ((reg, intervals) in registerIntervals) { val sorted = intervals.sortedBy { it.start } var current = sorted.first() @@ -230,43 +219,33 @@ object RegisterPacker { mergedIntervals.removeAll { it.register in skipRegs } } - val conflictGraph = buildConflictGraph(sub, indexRegType) + val conflictGraph = buildConflictGraph(sub) val packing = greedyColor(mergedIntervals, conflictGraph, allRegTypes, globalSlotTypes, startSlot) if (packing.isNotEmpty()) rewrite(sub, packing) } - private fun buildConflictGraph(sub: IRSubroutine, indexRegType: IRDataType): Map> { - val conflicts = mutableMapOf>() + private fun buildConflictGraph(sub: IRSubroutine): Map> { + val conflicts = mutableMapOf>() for (chunk in sub.chunks.filterIsInstance()) { - val typeRegs = mutableMapOf>() + val typeRegs = mutableMapOf>() for (instr in chunk.instructions) { - for ((reg, regNum) in listOf(instr.reg1 to 1, instr.reg2 to 2, instr.reg3 to 3)) { - if (reg != null) { - val dt = determineIntRegType(instr, regNum, indexRegType) - typeRegs.getOrPut(dt) { mutableSetOf() }.add(reg) - } - } - instr.fpReg1?.let { fpr -> typeRegs.getOrPut(IRDataType.FLOAT) { mutableSetOf() }.add(fpr.value) } - instr.fpReg2?.let { fpr -> typeRegs.getOrPut(IRDataType.FLOAT) { mutableSetOf() }.add(fpr.value) } + val accesses = instr.registerAccesses + for (access in accesses) + typeRegs.getOrPut(access.type) { mutableSetOf() }.add(access.register) - instr.fcallArgs?.let { fc -> - for (arg in fc.arguments) { - val r = arg.reg.registerNum.value - typeRegs.getOrPut(arg.reg.dt) { mutableSetOf() }.add(r) + // Registers accessed together within a single instruction must not share a slot + // (generalizes the old reg1/reg2-only conflict rule to every register access of + // the instruction, now that they are all enumerable via registerAccesses). + val regsInInstr = accesses.map { it.register }.distinct() + for (i in regsInInstr.indices) { + for (j in i + 1 until regsInInstr.size) { + val r1 = regsInInstr[i] + val r2 = regsInInstr[j] + conflicts.getOrPut(r1) { mutableSetOf() }.add(r2) + conflicts.getOrPut(r2) { mutableSetOf() }.add(r1) } - for (ret in fc.returns) { - val r = ret.registerNum.value - typeRegs.getOrPut(ret.dt) { mutableSetOf() }.add(r) - } - } - - val r1 = instr.reg1 - val r2 = instr.reg2 - if (r1 != null && r2 != null) { - conflicts.getOrPut(r1) { mutableSetOf() }.add(r2) - conflicts.getOrPut(r2) { mutableSetOf() }.add(r1) } } // Add conflicts between registers with incompatible types in the same chunk @@ -291,7 +270,7 @@ object RegisterPacker { return conflicts } - private fun buildCFG(sub: IRSubroutine): Map> { + private fun buildCFG(sub: IRSubroutine, irProg: IRProgram): Map> { val successors = mutableMapOf>() val conditionals = setOf( @@ -315,13 +294,13 @@ object RegisterPacker { chunk.next?.let { succ.add(it) } } if (endsWithConditional) { - lastInstr.branchTarget?.let { target -> + lastInstr.codeTarget?.let { irProg.resolveCodeTarget(it) }?.let { target -> if (!succ.contains(target)) succ.add(target) } } else if (lastInstr?.opcode == Opcode.JUMP) { succ.clear() - lastInstr.branchTarget?.let { succ.add(it) } + lastInstr.codeTarget?.let { irProg.resolveCodeTarget(it) }?.let { succ.add(it) } } } else { chunk.next?.let { succ.add(it) } @@ -336,22 +315,23 @@ object RegisterPacker { private fun computeLiveness( sub: IRSubroutine, successors: Map> - ): Pair>, Map>> { + ): Pair>, Map>> { - val liveIn = mutableMapOf>() - val liveOut = mutableMapOf>() - val gen = mutableMapOf>() - val kill = mutableMapOf>() + val liveIn = mutableMapOf>() + val liveOut = mutableMapOf>() + val gen = mutableMapOf>() + val kill = mutableMapOf>() for (chunk in sub.chunks) { if (chunk !is IRCodeChunk) continue - val genSet = mutableSetOf() - val killSet = mutableSetOf() + val genSet = mutableSetOf() + val killSet = mutableSetOf() for (instr in chunk.instructions) { - val (written, read) = getRegisterAccess(instr) + val written = instr.definitions + val read = instr.uses for (r in read) { if (r !in killSet) genSet.add(r) @@ -376,7 +356,7 @@ object RegisterPacker { if (chunk !is IRCodeChunk) continue - val newLiveOut = mutableSetOf() + val newLiveOut = mutableSetOf() for (succ in successors[chunk].orEmpty()) { if (succ is IRCodeChunk) newLiveOut.addAll(liveIn[succ].orEmpty()) @@ -387,7 +367,7 @@ object RegisterPacker { changed = true } - val newLiveIn = mutableSetOf() + val newLiveIn = mutableSetOf() newLiveIn.addAll(gen[chunk].orEmpty()) newLiveIn.addAll(liveOut[chunk].orEmpty() - kill[chunk].orEmpty()) @@ -401,153 +381,35 @@ object RegisterPacker { return liveIn to liveOut } - private fun getRegisterAccess(instr: IRInstruction): Pair, Set> { - val written = mutableSetOf() - val read = mutableSetOf() - - for ((reg, dir) in listOf( - instr.reg1 to instr.reg1direction, - instr.reg2 to instr.reg2direction, - instr.reg3 to instr.reg3direction - )) { - when (dir) { - OperandDirection.READ -> reg?.let { read.add(it) } - OperandDirection.WRITE -> reg?.let { written.add(it) } - OperandDirection.READWRITE -> { reg?.let { read.add(it); written.add(it) } } - OperandDirection.UNUSED -> {} - } - } - - for ((fpReg, dir) in listOf( - instr.fpReg1 to instr.fpReg1direction, - instr.fpReg2 to instr.fpReg2direction - )) { - when (dir) { - OperandDirection.READ -> fpReg?.let { read.add(it.value) } - OperandDirection.WRITE -> fpReg?.let { written.add(it.value) } - OperandDirection.READWRITE -> { fpReg?.let { read.add(it.value); written.add(it.value) } } - OperandDirection.UNUSED -> {} - } - } - - val fcallArgs = instr.fcallArgs - if (fcallArgs != null) { - for (arg in fcallArgs.arguments) - read.add(arg.reg.registerNum.value) - for (ret in fcallArgs.returns) - written.add(ret.registerNum.value) - } - - return written to read - } - - private fun getRegisterType(instr: IRInstruction, register: Int, indexRegType: IRDataType): IRDataType { - if (instr.fpReg1?.value == register || instr.fpReg2?.value == register) - return IRDataType.FLOAT - - return when { - instr.reg1 == register -> determineIntRegType(instr, 1, indexRegType) - instr.reg2 == register -> determineIntRegType(instr, 2, indexRegType) - instr.reg3 == register -> determineIntRegType(instr, 3, indexRegType) - else -> { - // fcallArgs registers use their own type from the RegSpec, not instr.type - val fcallArgs = instr.fcallArgs - if (fcallArgs != null) { - for (arg in fcallArgs.arguments) - if (arg.reg.registerNum.value == register) - return arg.reg.dt - for (ret in fcallArgs.returns) - if (ret.registerNum.value == register) - return ret.dt - } - instr.type ?: IRDataType.BYTE - } - } - } - - private fun determineIntRegType(instr: IRInstruction, regNum: Int, indexRegType: IRDataType): IRDataType { - val opcode = instr.opcode - val type = instr.type - - if (type == IRDataType.FLOAT) { - return when (opcode) { - Opcode.FFROMUB, Opcode.FFROMSB, Opcode.FTOUB, Opcode.FTOSB, - Opcode.FCOMP, Opcode.SGN -> IRDataType.BYTE - Opcode.LOADX, Opcode.STOREX, Opcode.STOREZX -> indexRegType - Opcode.FFROMSL, Opcode.FTOSL -> IRDataType.LONG - Opcode.LOADI, Opcode.STOREI -> IRDataType.POINTER - else -> IRDataType.WORD - } - } - - if (type == IRDataType.WORD && regNum == 1) { - return when (opcode) { - Opcode.SGN, Opcode.SQRT -> IRDataType.BYTE - Opcode.STOREZX -> indexRegType - Opcode.EXT, Opcode.EXTS, Opcode.CONCAT -> IRDataType.LONG - else -> type - } - } - - if (type == IRDataType.LONG && regNum == 1) { - return when (opcode) { - Opcode.SGN -> IRDataType.BYTE - Opcode.SQRT -> IRDataType.WORD - Opcode.STOREZX -> indexRegType - else -> type - } - } - - if (regNum == 2 || regNum == 3) { - return when (opcode) { - Opcode.LOADX, Opcode.STOREX -> indexRegType - Opcode.LOADI, Opcode.STOREI -> IRDataType.POINTER - Opcode.ASRN, Opcode.LSRN, Opcode.LSLN -> IRDataType.BYTE - else -> type ?: IRDataType.BYTE - } - } - - if (regNum == 1) { - if (opcode in setOf(Opcode.JUMPI, Opcode.CALLI, Opcode.STOREZI)) - return IRDataType.POINTER - if (opcode in setOf(Opcode.LSIGW, Opcode.MSIGW)) - return IRDataType.WORD - if (opcode in setOf(Opcode.ASRNM, Opcode.LSRNM, Opcode.LSLNM, Opcode.SQRT, Opcode.LSIGB, Opcode.MSIGB, Opcode.BSIGB, Opcode.MIDB)) - return IRDataType.BYTE - if (type == IRDataType.BYTE && opcode in setOf(Opcode.EXT, Opcode.EXTS, Opcode.CONCAT)) - return IRDataType.WORD - if (type == IRDataType.BYTE && opcode == Opcode.STOREZX) - return indexRegType - } - return type ?: IRDataType.BYTE - } - private fun greedyColor( intervals: List, - conflictGraph: Map>, - allRegTypes: Map, - slotTypes: MutableMap, + conflictGraph: Map>, + allRegTypes: Map, + slotTypes: MutableMap, startSlot: Int - ): Map { + ): Map { if (intervals.isEmpty()) return emptyMap() - val packing = mutableMapOf() + val packing = mutableMapOf() // Reserve slots for non-packed registers - val packedRegNums = intervals.map { it.register }.toSet() - for ((regNum, type) in allRegTypes) { - if (regNum !in packedRegNums) - slotTypes.putIfAbsent(regNum, type) + val packedRegs = intervals.map { it.register }.toSet() + for ((reg, type) in allRegTypes) { + if (reg !in packedRegs) + slotTypes.putIfAbsent(reg, type) } val sorted = intervals.sortedWith(compareBy { it.type.ordinal }.thenBy { it.start }) - val activeSlots = mutableMapOf>() + val activeSlots = mutableMapOf>() for (interval in sorted) { activeSlots.entries.removeAll { it.value.second < interval.start } - var slot = startSlot + var slotNum = startSlot + var slot: VirtualRegister while (true) { + // a packed slot stays in the same register file (int/float) as the register it replaces + slot = if (interval.register is VirtualRegister.FloatReg) VirtualRegister.float(slotNum) else VirtualRegister.int(slotNum) if (slot !in activeSlots) { val existingType = slotTypes[slot] if (existingType == null || existingType == interval.type || typesCompatible(existingType, interval.type)) { @@ -555,7 +417,7 @@ object RegisterPacker { break } } - slot++ + slotNum++ } packing[interval.register] = slot @@ -572,12 +434,12 @@ object RegisterPacker { } private fun conflictsWithRegister( - slot: Int, - regNum: Int, - conflictGraph: Map>, - packing: Map + slot: VirtualRegister, + register: VirtualRegister, + conflictGraph: Map>, + packing: Map ): Boolean { - val conflictingRegs = conflictGraph[regNum] ?: return false + val conflictingRegs = conflictGraph[register] ?: return false for (cr in conflictingRegs) { val crSlot = packing[cr] if (crSlot != null && crSlot == slot) @@ -588,70 +450,17 @@ object RegisterPacker { return false } - private fun rewrite(sub: IRSubroutine, packing: Map) { + private fun rewrite(sub: IRSubroutine, packing: Map) { for (chunk in sub.chunks) { if (chunk !is IRCodeChunk) continue for (i in chunk.instructions.indices) { val instr = chunk.instructions[i] - - val newReg1 = if (instr.reg1 != null) (packing[instr.reg1] ?: instr.reg1) else null - val newReg2 = if (instr.reg2 != null) (packing[instr.reg2] ?: instr.reg2) else null - val newReg3 = if (instr.reg3 != null) (packing[instr.reg3] ?: instr.reg3) else null - val oldFpReg1 = instr.fpReg1 - val newFpReg1 = if (oldFpReg1 != null && oldFpReg1.value in packing) - RegisterNum(packing[oldFpReg1.value]!!) else oldFpReg1 - val oldFpReg2 = instr.fpReg2 - val newFpReg2 = if (oldFpReg2 != null && oldFpReg2.value in packing) - RegisterNum(packing[oldFpReg2.value]!!) else oldFpReg2 - val oldFcallArgs = instr.fcallArgs - val newFcallArgs = if (oldFcallArgs != null) - remapFcallArgs(oldFcallArgs, packing) else oldFcallArgs - - val anyChange = newReg1 != instr.reg1 || newReg2 != instr.reg2 || newReg3 != instr.reg3 - || newFpReg1 != instr.fpReg1 || newFpReg2 != instr.fpReg2 - || newFcallArgs != instr.fcallArgs - - if (anyChange) { - chunk.instructions[i] = instr.copy( - reg1 = newReg1, - reg2 = newReg2, - reg3 = newReg3, - fpReg1 = newFpReg1, - fpReg2 = newFpReg2, - fcallArgs = newFcallArgs - ) - } + val newInstr = instr.mapRegisters { vr -> packing[vr] ?: vr } + if (newInstr != instr) + chunk.instructions[i] = newInstr } } } - - private fun remapFcallArgs(args: FunctionCallArgs, packing: Map): FunctionCallArgs { - var changed = false - - val newArgs = args.arguments.map { arg -> - val newRegNum = packing[arg.reg.registerNum.value] - if (newRegNum != null && newRegNum != arg.reg.registerNum.value) { - changed = true - FunctionCallArgs.ArgumentSpec( - arg.name, arg.address, - FunctionCallArgs.RegSpec(arg.reg.dt, RegisterNum(newRegNum), arg.reg.callingConventionSlot, arg.reg.statusflag) - ) - } else arg - } - - val newReturns = args.returns.map { ret -> - val newRegNum = packing[ret.registerNum.value] - if (newRegNum != null && newRegNum != ret.registerNum.value) { - changed = true - FunctionCallArgs.RegSpec(ret.dt, RegisterNum(newRegNum), ret.callingConventionSlot, ret.statusflag) - } else ret - } - - return if (changed) - FunctionCallArgs(newArgs, newReturns) - else - args - } } diff --git a/codeGenIntermediate/src/prog8/codegen/vm/VmCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/vm/VmCodeGen.kt index 746f81ab3..446e20c4f 100644 --- a/codeGenIntermediate/src/prog8/codegen/vm/VmCodeGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/vm/VmCodeGen.kt @@ -7,8 +7,10 @@ import prog8.code.ast.PtProgram import prog8.code.core.CompilationOptions import prog8.code.core.IErrorReporter import prog8.codegen.intermediate.IRCodeGen +import prog8.intermediate.IRDataType import prog8.intermediate.IRFileWriter import prog8.intermediate.IRProgram +import prog8.intermediate.VirtualRegister import prog8.intermediate.dumpVariables class VmCodeGen(val retainSSA: Boolean, @@ -23,7 +25,11 @@ class VmCodeGen(val retainSSA: Boolean, val irCodeGen = IRCodeGen(program, symbolTable, options, errors, retainSSA, preassignedCallSiteIds) val irProgram = irCodeGen.generate() - irProgram.verifyRegisterTypes(irCodeGen.registerTypes()) + val virtualRegisterTypes = irCodeGen.registerTypes().entries.associate { (num, type) -> + val register: VirtualRegister = if (type == IRDataType.FLOAT) VirtualRegister.float(num.value) else VirtualRegister.int(num.value) + register to type + } + irProgram.verifyRegisterTypes(virtualRegisterTypes) if (options.dumpVariables) dumpVariables(irProgram) diff --git a/codeGenIntermediate/test/TestIRPeepholeOpt.kt b/codeGenIntermediate/test/TestIRPeepholeOpt.kt index c8d697a01..a097af4d6 100644 --- a/codeGenIntermediate/test/TestIRPeepholeOpt.kt +++ b/codeGenIntermediate/test/TestIRPeepholeOpt.kt @@ -10,6 +10,8 @@ import prog8.intermediate.* import prog8tests.helpers.ErrorReporterForTests class TestIRPeepholeOpt: FunSpec({ + val indexRegType = VMTarget().indexRegType + fun makeIRProgram(chunks: List): IRProgram { require(chunks.first().label=="p8b_main.p8s_start") val block = IRBlock("p8b_main", false, IRBlock.Options(), Position.DUMMY) @@ -40,9 +42,9 @@ class TestIRPeepholeOpt: FunSpec({ test("remove nops") { val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=42), - IRInstruction(Opcode.NOP), - IRInstruction(Opcode.NOP) + IRInstructions.load(IRDataType.BYTE, 1, 42), + IRInstructions.simple(Opcode.NOP), + IRInstructions.simple(Opcode.NOP) )) irProg.chunks().single().instructions.size shouldBe 3 val opt = IRPeepholeOptimizer(irProg, false) @@ -52,13 +54,13 @@ class TestIRPeepholeOpt: FunSpec({ test("remove jmp to label below but keep labels") { val c1 = IRCodeChunk("p8b_main.p8s_start", null) - c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label") + c1 += IRInstructions.jump(codeLabel("label")) val c2 = IRCodeChunk("label", null) - c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2") - c2 += IRInstruction(Opcode.NOP) // removed + c2 += IRInstructions.jump(codeLabel("label2")) + c2 += IRInstructions.simple(Opcode.NOP) // removed val c3 = IRCodeChunk("label2", null) - c3 += IRInstruction(Opcode.JUMP, labelSymbol = "label3") - c3 += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=1) + c3 += IRInstructions.jump(codeLabel("label3")) + c3 += IRInstructions.unary(Opcode.INC, IRDataType.BYTE, 1) val c4 = IRCodeChunk("label3", null) val irProg = makeIRProgram(listOf(c1, c2, c3, c4)) @@ -84,18 +86,18 @@ class TestIRPeepholeOpt: FunSpec({ test("remove double sec/clc/sei/cli") { val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.SEC), - IRInstruction(Opcode.SEC), - IRInstruction(Opcode.SEC), - IRInstruction(Opcode.CLC), - IRInstruction(Opcode.CLC), - IRInstruction(Opcode.CLC), - IRInstruction(Opcode.SEI), - IRInstruction(Opcode.SEI), - IRInstruction(Opcode.SEI), - IRInstruction(Opcode.CLI), - IRInstruction(Opcode.CLI), - IRInstruction(Opcode.CLI), + IRInstructions.simple(Opcode.SEC), + IRInstructions.simple(Opcode.SEC), + IRInstructions.simple(Opcode.SEC), + IRInstructions.simple(Opcode.CLC), + IRInstructions.simple(Opcode.CLC), + IRInstructions.simple(Opcode.CLC), + IRInstructions.simple(Opcode.SEI), + IRInstructions.simple(Opcode.SEI), + IRInstructions.simple(Opcode.SEI), + IRInstructions.simple(Opcode.CLI), + IRInstructions.simple(Opcode.CLI), + IRInstructions.simple(Opcode.CLI), )) irProg.chunks().single().instructions.size shouldBe 12 val opt = IRPeepholeOptimizer(irProg, false) @@ -108,18 +110,18 @@ class TestIRPeepholeOpt: FunSpec({ test("remove double sec/clc/sei/cli reversed") { val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.CLC), - IRInstruction(Opcode.CLC), - IRInstruction(Opcode.CLC), - IRInstruction(Opcode.SEC), - IRInstruction(Opcode.SEC), - IRInstruction(Opcode.SEC), - IRInstruction(Opcode.CLI), - IRInstruction(Opcode.CLI), - IRInstruction(Opcode.CLI), - IRInstruction(Opcode.SEI), - IRInstruction(Opcode.SEI), - IRInstruction(Opcode.SEI), + IRInstructions.simple(Opcode.CLC), + IRInstructions.simple(Opcode.CLC), + IRInstructions.simple(Opcode.CLC), + IRInstructions.simple(Opcode.SEC), + IRInstructions.simple(Opcode.SEC), + IRInstructions.simple(Opcode.SEC), + IRInstructions.simple(Opcode.CLI), + IRInstructions.simple(Opcode.CLI), + IRInstructions.simple(Opcode.CLI), + IRInstructions.simple(Opcode.SEI), + IRInstructions.simple(Opcode.SEI), + IRInstructions.simple(Opcode.SEI), )) irProg.chunks().single().instructions.size shouldBe 12 val opt = IRPeepholeOptimizer(irProg, false) @@ -132,10 +134,10 @@ class TestIRPeepholeOpt: FunSpec({ test("push followed by pop") { val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=42), - IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=42), - IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=99), - IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=222) + IRInstructions.push(IRDataType.BYTE, 42), + IRInstructions.pop(IRDataType.BYTE, 42), + IRInstructions.push(IRDataType.BYTE, 99), + IRInstructions.pop(IRDataType.BYTE, 222) )) irProg.chunks().single().instructions.size shouldBe 4 val opt = IRPeepholeOptimizer(irProg, false) @@ -143,23 +145,23 @@ class TestIRPeepholeOpt: FunSpec({ val instr = irProg.chunks().single().instructions instr.size shouldBe 1 instr[0].opcode shouldBe Opcode.LOADR - instr[0].reg1 shouldBe 222 - instr[0].reg2 shouldBe 99 + instr[0].requireDest().register shouldBe VirtualRegister.int(222) + instr[0].requireSrcA().register shouldBe VirtualRegister.int(99) } test("remove useless div/mul, add/sub") { // Use different registers for each test case to avoid dead store elimination removing them val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=1, immediate = 1), - IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=2, immediate = 1), - IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=3, immediate = 1), - IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=4, immediate = 1), - IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=5, immediate = 2), - IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=6, immediate = 2), - IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=7, immediate = 2), - IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=8, immediate = 2), - IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=9, immediate = 0), - IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=10, immediate = 0) + IRInstructions.binaryImmediate(Opcode.DIV, IRDataType.BYTE, 1, 1), + IRInstructions.binaryImmediate(Opcode.DIVS, IRDataType.BYTE, 2, 1), + IRInstructions.binaryImmediate(Opcode.MUL, IRDataType.BYTE, 3, 1), + IRInstructions.binaryImmediate(Opcode.MOD, IRDataType.BYTE, 4, 1), + IRInstructions.binaryImmediate(Opcode.DIV, IRDataType.BYTE, 5, 2), + IRInstructions.binaryImmediate(Opcode.DIVS, IRDataType.BYTE, 6, 2), + IRInstructions.binaryImmediate(Opcode.MUL, IRDataType.BYTE, 7, 2), + IRInstructions.binaryImmediate(Opcode.MOD, IRDataType.BYTE, 8, 2), + IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.BYTE, 9, 0), + IRInstructions.binaryImmediate(Opcode.SUB, IRDataType.BYTE, 10, 0) )) irProg.chunks().single().instructions.size shouldBe 10 val opt = IRPeepholeOptimizer(irProg, false) @@ -170,11 +172,11 @@ class TestIRPeepholeOpt: FunSpec({ test("replace integer multiply by zero with load") { val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=1, immediate = 0), - IRInstruction(Opcode.MULS, IRDataType.WORD, reg1=2, immediate = 0), - IRInstruction(Opcode.MULS, IRDataType.LONG, reg1=3, immediate = 0), - IRInstruction(Opcode.MUL, IRDataType.FLOAT, fpReg1=RegisterNum(4), immediateFp = 0.0), - IRInstruction(Opcode.RETURN) + IRInstructions.binaryImmediate(Opcode.MUL, IRDataType.BYTE, 1, 0), + IRInstructions.binaryImmediate(Opcode.MULS, IRDataType.WORD, 2, 0), + IRInstructions.binaryImmediate(Opcode.MULS, IRDataType.LONG, 3, 0), + IRInstructions.binaryImmediateFloat(Opcode.MUL, 4, 0.0), + IRInstructions.returnVoid() )) val opt = IRPeepholeOptimizer(irProg, false) opt.optimize(true, ErrorReporterForTests()) @@ -185,52 +187,49 @@ class TestIRPeepholeOpt: FunSpec({ test("fold adjacent integer immediate multiplications") { val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=1, immediate = 7), - IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=1, immediate = 9), - IRInstruction(Opcode.MULS, IRDataType.WORD, reg1=2, immediate = -3), - IRInstruction(Opcode.MULS, IRDataType.WORD, reg1=2, immediate = 4), - IRInstruction(Opcode.MULS, IRDataType.LONG, reg1=3, immediate = 100000), - IRInstruction(Opcode.MULS, IRDataType.LONG, reg1=3, immediate = 30000), - IRInstruction(Opcode.RETURN) + IRInstructions.binaryImmediate(Opcode.MUL, IRDataType.BYTE, 1, 7), + IRInstructions.binaryImmediate(Opcode.MUL, IRDataType.BYTE, 1, 9), + IRInstructions.binaryImmediate(Opcode.MULS, IRDataType.WORD, 2, -3), + IRInstructions.binaryImmediate(Opcode.MULS, IRDataType.WORD, 2, 4), + IRInstructions.binaryImmediate(Opcode.MULS, IRDataType.LONG, 3, 100000), + IRInstructions.binaryImmediate(Opcode.MULS, IRDataType.LONG, 3, 30000), + IRInstructions.returnVoid() )) val opt = IRPeepholeOptimizer(irProg, false) opt.optimize(true, ErrorReporterForTests()) val instr = irProg.chunks().single().instructions instr.size shouldBe 4 - instr[0].immediate shouldBe 63 - instr[1].immediate shouldBe 65524 - instr[2].immediate shouldBe 3000000000L.toInt() + instr[0].requireImmediateInt() shouldBe 63 + instr[1].requireImmediateInt() shouldBe 65524 + instr[2].requireImmediateInt() shouldBe 3000000000L.toInt() } test("collapse adjacent integer and float loadr chains") { val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=2, reg2=1), - IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=3, reg2=2), - IRInstruction(Opcode.LOADR, IRDataType.FLOAT, fpReg1=RegisterNum(5), fpReg2=RegisterNum(4)), - IRInstruction(Opcode.LOADR, IRDataType.FLOAT, fpReg1=RegisterNum(6), fpReg2=RegisterNum(5)), - IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=3, address=MemoryAddress(100u)), - IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1=RegisterNum(6), address=MemoryAddress(200u)) + IRInstructions.move(IRDataType.BYTE, 2, 1), + IRInstructions.move(IRDataType.BYTE, 3, 2), + IRInstructions.move(IRDataType.FLOAT, 5, 4), + IRInstructions.move(IRDataType.FLOAT, 6, 5), + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 3, IRMemory.direct(MemoryAddress(100u))), + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.FLOAT, 6, IRMemory.direct(MemoryAddress(200u))) )) val opt = IRPeepholeOptimizer(irProg, false) opt.optimize(true, ErrorReporterForTests()) val instr = irProg.chunks().single().instructions - instr.map { Triple(it.opcode, it.reg1, it.fpReg1) } shouldBe listOf( - Triple(Opcode.LOADR, 3, null), - Triple(Opcode.LOADR, null, RegisterNum(6)), - Triple(Opcode.STOREM, 3, null), - Triple(Opcode.STOREM, null, RegisterNum(6)) - ) - instr[0].reg1 shouldBe 3 - instr[0].reg2 shouldBe 1 - instr[1].fpReg1 shouldBe RegisterNum(6) - instr[1].fpReg2 shouldBe RegisterNum(4) + instr.map { it.opcode } shouldBe listOf(Opcode.LOADR, Opcode.LOADR, Opcode.STOREM, Opcode.STOREM) + instr[0].requireDest().register shouldBe VirtualRegister.int(3) + instr[0].requireSrcA().register shouldBe VirtualRegister.int(1) + instr[1].requireDest().register shouldBe VirtualRegister.float(6) + instr[1].requireSrcA().register shouldBe VirtualRegister.float(4) + instr[2].requireSrcA().register shouldBe VirtualRegister.int(3) + instr[3].requireSrcA().register shouldBe VirtualRegister.float(6) } test("replace add/sub 1 by inc/dec") { // Use different registers for each test case to avoid dead store elimination removing them val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=1, immediate = 1), - IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=2, immediate = 1) + IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.BYTE, 1, 1), + IRInstructions.binaryImmediate(Opcode.SUB, IRDataType.BYTE, 2, 1) )) irProg.chunks().single().instructions.size shouldBe 2 val opt = IRPeepholeOptimizer(irProg, false) @@ -244,23 +243,23 @@ class TestIRPeepholeOpt: FunSpec({ test("remove useless and/or/xor") { // Use different registers for each test case to avoid dead store elimination removing them val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=1, immediate = 0), - IRInstruction(Opcode.AND, IRDataType.WORD, reg1=2, immediate = 0), - IRInstruction(Opcode.AND, IRDataType.LONG, reg1=3, immediate = 0), - IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=4, immediate = 255), - IRInstruction(Opcode.AND, IRDataType.WORD, reg1=5, immediate = 65535), - IRInstruction(Opcode.AND, IRDataType.LONG, reg1=6, immediate = 2147483647), - IRInstruction(Opcode.AND, IRDataType.LONG, reg1=7, immediate = -1), - IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=8, immediate = 0), - IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=9, immediate = 255), - IRInstruction(Opcode.OR, IRDataType.WORD, reg1=10, immediate = 65535), - IRInstruction(Opcode.OR, IRDataType.LONG, reg1=11, immediate = 2147483647), - IRInstruction(Opcode.OR, IRDataType.LONG, reg1=12, immediate = -1), - IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=13, immediate = 0), - IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=14, immediate = 200), - IRInstruction(Opcode.AND, IRDataType.WORD, reg1=15, immediate = 60000), - IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=16, immediate = 1), - IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=17, immediate = 1) + IRInstructions.binaryImmediate(Opcode.AND, IRDataType.BYTE, 1, 0), + IRInstructions.binaryImmediate(Opcode.AND, IRDataType.WORD, 2, 0), + IRInstructions.binaryImmediate(Opcode.AND, IRDataType.LONG, 3, 0), + IRInstructions.binaryImmediate(Opcode.AND, IRDataType.BYTE, 4, 255), + IRInstructions.binaryImmediate(Opcode.AND, IRDataType.WORD, 5, 65535), + IRInstructions.binaryImmediate(Opcode.AND, IRDataType.LONG, 6, 2147483647), + IRInstructions.binaryImmediate(Opcode.AND, IRDataType.LONG, 7, -1), + IRInstructions.binaryImmediate(Opcode.OR, IRDataType.BYTE, 8, 0), + IRInstructions.binaryImmediate(Opcode.OR, IRDataType.BYTE, 9, 255), + IRInstructions.binaryImmediate(Opcode.OR, IRDataType.WORD, 10, 65535), + IRInstructions.binaryImmediate(Opcode.OR, IRDataType.LONG, 11, 2147483647), + IRInstructions.binaryImmediate(Opcode.OR, IRDataType.LONG, 12, -1), + IRInstructions.binaryImmediate(Opcode.XOR, IRDataType.BYTE, 13, 0), + IRInstructions.binaryImmediate(Opcode.AND, IRDataType.BYTE, 14, 200), + IRInstructions.binaryImmediate(Opcode.AND, IRDataType.WORD, 15, 60000), + IRInstructions.binaryImmediate(Opcode.OR, IRDataType.BYTE, 16, 1), + IRInstructions.binaryImmediate(Opcode.XOR, IRDataType.BYTE, 17, 1) )) irProg.chunks().single().instructions.size shouldBe 17 val opt = IRPeepholeOptimizer(irProg, false) @@ -281,96 +280,86 @@ class TestIRPeepholeOpt: FunSpec({ test("replace and/or/xor by constant number") { // Use different registers for each test case to avoid dead store elimination removing them val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=1, immediate = 0), - IRInstruction(Opcode.AND, IRDataType.WORD, reg1=2, immediate = 0), - IRInstruction(Opcode.AND, IRDataType.LONG, reg1=3, immediate = 0), - IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=4, immediate = 255), - IRInstruction(Opcode.OR, IRDataType.WORD, reg1=5, immediate = 65535), - IRInstruction(Opcode.OR, IRDataType.LONG, reg1=6, immediate = -1) + IRInstructions.binaryImmediate(Opcode.AND, IRDataType.BYTE, 1, 0), + IRInstructions.binaryImmediate(Opcode.AND, IRDataType.WORD, 2, 0), + IRInstructions.binaryImmediate(Opcode.AND, IRDataType.LONG, 3, 0), + IRInstructions.binaryImmediate(Opcode.OR, IRDataType.BYTE, 4, 255), + IRInstructions.binaryImmediate(Opcode.OR, IRDataType.WORD, 5, 65535), + IRInstructions.binaryImmediate(Opcode.OR, IRDataType.LONG, 6, -1) )) irProg.chunks().single().instructions.size shouldBe 6 val opt = IRPeepholeOptimizer(irProg, false) opt.optimize(true, ErrorReporterForTests()) val instr = irProg.chunks().single().instructions instr.size shouldBe 6 - instr[0].opcode shouldBe Opcode.LOAD - instr[1].opcode shouldBe Opcode.LOAD - instr[2].opcode shouldBe Opcode.LOAD - instr[3].opcode shouldBe Opcode.LOAD - instr[4].opcode shouldBe Opcode.LOAD - instr[5].opcode shouldBe Opcode.LOAD - instr[0].immediate shouldBe 0 - instr[1].immediate shouldBe 0 - instr[2].immediate shouldBe 0 - instr[3].immediate shouldBe 255 - instr[4].immediate shouldBe 65535 - instr[5].immediate shouldBe -1 + instr.map { it.opcode } shouldBe List(6) { Opcode.LOAD } + instr.map { it.requireImmediateInt() } shouldBe listOf(0, 0, 0, 255, 65535, -1) } test("combine adjacent immediate and/or operations") { val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=1, immediate = 0xf0), - IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=1, immediate = 0xcc), - IRInstruction(Opcode.OR, IRDataType.WORD, reg1=2, immediate = 0x0010), - IRInstruction(Opcode.OR, IRDataType.WORD, reg1=2, immediate = 0x0003), + IRInstructions.binaryImmediate(Opcode.AND, IRDataType.BYTE, 1, 0xf0), + IRInstructions.binaryImmediate(Opcode.AND, IRDataType.BYTE, 1, 0xcc), + IRInstructions.binaryImmediate(Opcode.OR, IRDataType.WORD, 2, 0x0010), + IRInstructions.binaryImmediate(Opcode.OR, IRDataType.WORD, 2, 0x0003), )) val opt = IRPeepholeOptimizer(irProg, false) opt.optimize(true, ErrorReporterForTests()) val instr = irProg.chunks().single().instructions instr.size shouldBe 2 instr[0].opcode shouldBe Opcode.AND - instr[0].immediate shouldBe 0xc0 + instr[0].requireImmediateInt() shouldBe 0xc0 instr[1].opcode shouldBe Opcode.OR - instr[1].immediate shouldBe 0x0013 + instr[1].requireImmediateInt() shouldBe 0x0013 } test("cancel identical adjacent immediate xor operations") { val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=1, immediate = 3), - IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=1, immediate = 3), - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate = 42) + IRInstructions.binaryImmediate(Opcode.XOR, IRDataType.BYTE, 1, 3), + IRInstructions.binaryImmediate(Opcode.XOR, IRDataType.BYTE, 1, 3), + IRInstructions.load(IRDataType.BYTE, 2, 42) )) val opt = IRPeepholeOptimizer(irProg, false) opt.optimize(true, ErrorReporterForTests()) val instr = irProg.chunks().single().instructions instr.size shouldBe 1 instr[0].opcode shouldBe Opcode.LOAD - instr[0].immediate shouldBe 42 + instr[0].requireImmediateInt() shouldBe 42 } test("fold adjacent immediate arithmetic with inc/dec") { val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=1, immediate = 4), - IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=1), - IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=2), - IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=2, immediate = 4), - IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=3, immediate = 4), - IRInstruction(Opcode.DEC, IRDataType.BYTE, reg1=3), - IRInstruction(Opcode.DEC, IRDataType.BYTE, reg1=4), - IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=4, immediate = 4) + IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.BYTE, 1, 4), + IRInstructions.unary(Opcode.INC, IRDataType.BYTE, 1), + IRInstructions.unary(Opcode.INC, IRDataType.BYTE, 2), + IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.BYTE, 2, 4), + IRInstructions.binaryImmediate(Opcode.SUB, IRDataType.BYTE, 3, 4), + IRInstructions.unary(Opcode.DEC, IRDataType.BYTE, 3), + IRInstructions.unary(Opcode.DEC, IRDataType.BYTE, 4), + IRInstructions.binaryImmediate(Opcode.SUB, IRDataType.BYTE, 4, 4) )) val opt = IRPeepholeOptimizer(irProg, false) opt.optimize(true, ErrorReporterForTests()) val instr = irProg.chunks().single().instructions instr.size shouldBe 4 instr[0].opcode shouldBe Opcode.ADD - instr[0].immediate shouldBe 5 + instr[0].requireImmediateInt() shouldBe 5 instr[1].opcode shouldBe Opcode.ADD - instr[1].immediate shouldBe 5 + instr[1].requireImmediateInt() shouldBe 5 instr[2].opcode shouldBe Opcode.SUB - instr[2].immediate shouldBe 5 + instr[2].requireImmediateInt() shouldBe 5 instr[3].opcode shouldBe Opcode.SUB - instr[3].immediate shouldBe 5 + instr[3].requireImmediateInt() shouldBe 5 } test("replace register arithmetic by immediate operations") { // Test the new optimization: LOAD + SUBR/DIVR/MODR → immediate operation // Pattern: LOAD r1, #const followed by SUBR r2, r1 → SUB r2, #const - + // SUBR optimization val subrProg = makeIRProgram(listOf( - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate = 5), - IRInstruction(Opcode.SUBR, IRDataType.BYTE, reg1=2, reg2=1) + IRInstructions.load(IRDataType.BYTE, 1, 5), + IRInstructions.binary(Opcode.SUBR, IRDataType.BYTE, 2, 1) )) subrProg.chunks().single().instructions.size shouldBe 2 val opt1 = IRPeepholeOptimizer(subrProg, false) @@ -378,12 +367,12 @@ class TestIRPeepholeOpt: FunSpec({ val subInstr = subrProg.chunks().single().instructions subInstr.size shouldBe 1 subInstr[0].opcode shouldBe Opcode.SUB - subInstr[0].immediate shouldBe 5 - + subInstr[0].requireImmediateInt() shouldBe 5 + // DIVR optimization val divrProg = makeIRProgram(listOf( - IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=10, immediate = 100), - IRInstruction(Opcode.DIVR, IRDataType.WORD, reg1=20, reg2=10) + IRInstructions.load(IRDataType.WORD, 10, 100), + IRInstructions.binary(Opcode.DIVR, IRDataType.WORD, 20, 10) )) divrProg.chunks().single().instructions.size shouldBe 2 val opt2 = IRPeepholeOptimizer(divrProg, false) @@ -391,12 +380,12 @@ class TestIRPeepholeOpt: FunSpec({ val divInstr = divrProg.chunks().single().instructions divInstr.size shouldBe 1 divInstr[0].opcode shouldBe Opcode.DIV - divInstr[0].immediate shouldBe 100 - + divInstr[0].requireImmediateInt() shouldBe 100 + // MODR optimization val modrProg = makeIRProgram(listOf( - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=30, immediate = 7), - IRInstruction(Opcode.MODR, IRDataType.BYTE, reg1=40, reg2=30) + IRInstructions.load(IRDataType.BYTE, 30, 7), + IRInstructions.binary(Opcode.MODR, IRDataType.BYTE, 40, 30) )) modrProg.chunks().single().instructions.size shouldBe 2 val opt3 = IRPeepholeOptimizer(modrProg, false) @@ -404,34 +393,42 @@ class TestIRPeepholeOpt: FunSpec({ val modInstr = modrProg.chunks().single().instructions modInstr.size shouldBe 1 modInstr[0].opcode shouldBe Opcode.MOD - modInstr[0].immediate shouldBe 7 + modInstr[0].requireImmediateInt() shouldBe 7 } test("remove self identity operations") { - // LOADR with same fp register is allowed (constructor only blocks integer reg1==reg2) + // The IR is not SSA: a written register may also be a read-only source register. + // Self-identity LOADR/ANDR/ORR become NOP, self-identity XORR becomes load 0. val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.LOADR, IRDataType.FLOAT, fpReg1=RegisterNum(10), fpReg2=RegisterNum(10)), - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=99, immediate=42) // keep chunk non-empty + IRInstructions.move(IRDataType.FLOAT, 10, 10), + IRInstructions.move(IRDataType.BYTE, 11, 11), + IRInstructions.binary(Opcode.ANDR, IRDataType.BYTE, 12, 12), + IRInstructions.binary(Opcode.ORR, IRDataType.BYTE, 13, 13), + IRInstructions.binary(Opcode.XORR, IRDataType.BYTE, 14, 14), + IRInstructions.load(IRDataType.BYTE, 99, 42) // keep chunk non-empty )) - irProg.chunks().single().instructions.size shouldBe 2 + irProg.chunks().single().instructions.size shouldBe 6 val opt = IRPeepholeOptimizer(irProg, false) opt.optimize(true, ErrorReporterForTests()) - val instr = irProg.chunks().single().instructions - instr.size shouldBe 1 + val instr = irProg.chunks().single().instructions.filter { it.opcode != Opcode.NOP } + instr.size shouldBe 2 instr[0].opcode shouldBe Opcode.LOAD - instr[0].reg1 shouldBe 99 - instr[0].immediate shouldBe 42 + instr[0].requireDest().register shouldBe VirtualRegister.int(14) + instr[0].requireImmediateInt() shouldBe 0 + instr[1].opcode shouldBe Opcode.LOAD + instr[1].requireDest().register shouldBe VirtualRegister.int(99) + instr[1].requireImmediateInt() shouldBe 42 } test("remove shift by zero") { val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=10, immediate=0), - IRInstruction(Opcode.LSLN, IRDataType.BYTE, reg1=1, reg2=10), - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=11, immediate=0), - IRInstruction(Opcode.LSRN, IRDataType.BYTE, reg1=2, reg2=11), - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=12, immediate=0), - IRInstruction(Opcode.ASRN, IRDataType.BYTE, reg1=3, reg2=12), - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=99, immediate=42) // keep chunk non-empty + IRInstructions.load(IRDataType.BYTE, 10, 0), + IRInstructions.binary(Opcode.LSLN, IRDataType.BYTE, 1, 10), + IRInstructions.load(IRDataType.BYTE, 11, 0), + IRInstructions.binary(Opcode.LSRN, IRDataType.BYTE, 2, 11), + IRInstructions.load(IRDataType.BYTE, 12, 0), + IRInstructions.binary(Opcode.ASRN, IRDataType.BYTE, 3, 12), + IRInstructions.load(IRDataType.BYTE, 99, 42) // keep chunk non-empty )) irProg.chunks().single().instructions.size shouldBe 7 val opt = IRPeepholeOptimizer(irProg, false) @@ -439,30 +436,34 @@ class TestIRPeepholeOpt: FunSpec({ val instr = irProg.chunks().single().instructions instr.size shouldBe 1 instr[0].opcode shouldBe Opcode.LOAD - instr[0].reg1 shouldBe 99 - instr[0].immediate shouldBe 42 + instr[0].requireDest().register shouldBe VirtualRegister.int(99) + instr[0].requireImmediateInt() shouldBe 42 } test("replace concat with ext after harmless instruction") { val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=10, immediate=0), - IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=20), - IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=1, reg2=10, reg3=11), - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=99, immediate=42) + IRInstructions.load(IRDataType.BYTE, 10, 0), + IRInstructions.unary(Opcode.INC, IRDataType.BYTE, 20), + IRInstructions.concat(IRDataType.BYTE, 1, 10, 11), + IRInstructions.load(IRDataType.BYTE, 99, 42) )) val opt = IRPeepholeOptimizer(irProg, false) opt.optimize(true, ErrorReporterForTests()) val instr = irProg.chunks().single().instructions - instr.any { it.opcode == Opcode.EXT && it.reg1 == 1 && it.reg2 == 11 } shouldBe true + instr.any { + it.opcode == Opcode.EXT && + it.requireDest().register == VirtualRegister.int(1) && + it.requireSrcA().register == VirtualRegister.int(11) + } shouldBe true instr.any { it.opcode == Opcode.CONCAT } shouldBe false } test("do not replace concat after intervening write") { val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=10, immediate=0), - IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=10), - IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=1, reg2=10, reg3=11), - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=99, immediate=42) + IRInstructions.load(IRDataType.BYTE, 10, 0), + IRInstructions.unary(Opcode.INC, IRDataType.BYTE, 10), + IRInstructions.concat(IRDataType.BYTE, 1, 10, 11), + IRInstructions.load(IRDataType.BYTE, 99, 42) )) val opt = IRPeepholeOptimizer(irProg, false) opt.optimize(true, ErrorReporterForTests()) @@ -471,10 +472,10 @@ class TestIRPeepholeOpt: FunSpec({ test("do not replace concat across control flow") { val c1 = IRCodeChunk("p8b_main.p8s_start", null) - c1 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=10, immediate=0) - c1 += IRInstruction(Opcode.JUMP, labelSymbol="after") - c1 += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=1, reg2=10, reg3=11) - c1 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=99, immediate=42) + c1 += IRInstructions.load(IRDataType.BYTE, 10, 0) + c1 += IRInstructions.jump(codeLabel("after")) + c1 += IRInstructions.concat(IRDataType.BYTE, 1, 10, 11) + c1 += IRInstructions.load(IRDataType.BYTE, 99, 42) val c2 = IRCodeChunk("after", null) val irProg = makeIRProgram(listOf(c1, c2)) val opt = IRPeepholeOptimizer(irProg, false) @@ -484,19 +485,19 @@ class TestIRPeepholeOpt: FunSpec({ test("cancel adjacent ops") { val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.INV, IRDataType.BYTE, reg1=1), - IRInstruction(Opcode.INV, IRDataType.BYTE, reg1=1), - IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=2), - IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=2), - IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=3, reg2=33), - IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=3, reg2=33), - IRInstruction(Opcode.EXTS, IRDataType.WORD, reg1=4, reg2=44), - IRInstruction(Opcode.EXTS, IRDataType.WORD, reg1=4, reg2=44), - IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=5), - IRInstruction(Opcode.DEC, IRDataType.BYTE, reg1=5), - IRInstruction(Opcode.DEC, IRDataType.BYTE, reg1=6), - IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=6), - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=99, immediate=42) // keep chunk non-empty + IRInstructions.unary(Opcode.INV, IRDataType.BYTE, 1), + IRInstructions.unary(Opcode.INV, IRDataType.BYTE, 1), + IRInstructions.unary(Opcode.NEG, IRDataType.BYTE, 2), + IRInstructions.unary(Opcode.NEG, IRDataType.BYTE, 2), + IRInstructions.binary(Opcode.EXT, IRDataType.BYTE, 3, 33), + IRInstructions.binary(Opcode.EXT, IRDataType.BYTE, 3, 33), + IRInstructions.binary(Opcode.EXTS, IRDataType.WORD, 4, 44), + IRInstructions.binary(Opcode.EXTS, IRDataType.WORD, 4, 44), + IRInstructions.unary(Opcode.INC, IRDataType.BYTE, 5), + IRInstructions.unary(Opcode.DEC, IRDataType.BYTE, 5), + IRInstructions.unary(Opcode.DEC, IRDataType.BYTE, 6), + IRInstructions.unary(Opcode.INC, IRDataType.BYTE, 6), + IRInstructions.load(IRDataType.BYTE, 99, 42) // keep chunk non-empty )) irProg.chunks().single().instructions.size shouldBe 13 val opt = IRPeepholeOptimizer(irProg, false) @@ -504,21 +505,21 @@ class TestIRPeepholeOpt: FunSpec({ val instr = irProg.chunks().single().instructions instr.size shouldBe 3 instr[0].opcode shouldBe Opcode.EXT - instr[0].reg1 shouldBe 3 - instr[0].reg2 shouldBe 33 + instr[0].requireDest().register shouldBe VirtualRegister.int(3) + instr[0].requireSrcA().register shouldBe VirtualRegister.int(33) instr[1].opcode shouldBe Opcode.EXTS - instr[1].reg1 shouldBe 4 - instr[1].reg2 shouldBe 44 + instr[1].requireDest().register shouldBe VirtualRegister.int(4) + instr[1].requireSrcA().register shouldBe VirtualRegister.int(44) instr[2].opcode shouldBe Opcode.LOAD - instr[2].immediate shouldBe 42 + instr[2].requireImmediateInt() shouldBe 42 } test("coalesce redundant LOADX/STOREX to same index is removed") { val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=1, reg2=10, labelSymbol="myArray"), - IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=99), - IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=1, reg2=10, labelSymbol="myArray"), - IRInstruction(Opcode.RETURN) + IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, 1, IRMemory.indexed("myArray", 10, indexRegType)), + IRInstructions.unary(Opcode.INC, IRDataType.BYTE, 99), + IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, 1, IRMemory.indexed("myArray", 10, indexRegType)), + IRInstructions.returnVoid() )) irProg.chunks().single().instructions.count { it.opcode==Opcode.STOREX } shouldBe 1 val opt = IRPeepholeOptimizer(irProg, false) @@ -531,9 +532,9 @@ class TestIRPeepholeOpt: FunSpec({ test("do not coalesce LOADX/STOREX when index differs") { val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=1, reg2=10, labelSymbol="myArray"), - IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=1, reg2=11, labelSymbol="myArray"), - IRInstruction(Opcode.RETURN) + IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, 1, IRMemory.indexed("myArray", 10, indexRegType)), + IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, 1, IRMemory.indexed("myArray", 11, indexRegType)), + IRInstructions.returnVoid() )) val opt = IRPeepholeOptimizer(irProg, false) opt.optimize(true, ErrorReporterForTests()) @@ -544,10 +545,10 @@ class TestIRPeepholeOpt: FunSpec({ test("do not coalesce LOADX/STOREX with intervening read of value") { val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=1, reg2=10, labelSymbol="myArray"), - IRInstruction(Opcode.ADDR, IRDataType.BYTE, reg1=1, reg2=2), - IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=1, reg2=10, labelSymbol="myArray"), - IRInstruction(Opcode.RETURN) + IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, 1, IRMemory.indexed("myArray", 10, indexRegType)), + IRInstructions.binary(Opcode.ADDR, IRDataType.BYTE, 1, 2), + IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, 1, IRMemory.indexed("myArray", 10, indexRegType)), + IRInstructions.returnVoid() )) val opt = IRPeepholeOptimizer(irProg, false) opt.optimize(true, ErrorReporterForTests()) @@ -557,10 +558,10 @@ class TestIRPeepholeOpt: FunSpec({ test("do not coalesce LOADX/STOREX across chunks single-chunk") { val c1 = IRCodeChunk("p8b_main.p8s_start", null) - c1 += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=1, reg2=10, labelSymbol="myArray") + c1 += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, 1, IRMemory.indexed("myArray", 10, indexRegType)) val c2 = IRCodeChunk("other", null) - c2 += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=1, reg2=10, labelSymbol="myArray") - c2 += IRInstruction(Opcode.RETURN) + c2 += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, 1, IRMemory.indexed("myArray", 10, indexRegType)) + c2 += IRInstructions.returnVoid() val irProg = makeIRProgram(listOf(c1, c2)) val opt = IRPeepholeOptimizer(irProg, false) opt.optimize(true, ErrorReporterForTests()) @@ -571,9 +572,9 @@ class TestIRPeepholeOpt: FunSpec({ test("do not fold storem+loadm same symbol different offsets") { val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=5, labelSymbol="myVar", symbolOffset=0), - IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=6, labelSymbol="myVar", symbolOffset=1), - IRInstruction(Opcode.RETURN) + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 5, IRMemory.direct("myVar", 0)), + IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, 6, IRMemory.direct("myVar", 1)), + IRInstructions.returnVoid() )) val opt = IRPeepholeOptimizer(irProg, false) opt.optimize(true, ErrorReporterForTests()) @@ -585,10 +586,10 @@ class TestIRPeepholeOpt: FunSpec({ test("do not fold storem+loadm across chunks single-chunk") { val c1 = IRCodeChunk("p8b_main.p8s_start", null) - c1 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=5, labelSymbol="myVar") + c1 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 5, IRMemory.direct("myVar")) val c2 = IRCodeChunk("other", null) - c2 += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=6, labelSymbol="myVar") - c2 += IRInstruction(Opcode.RETURN) + c2 += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, 6, IRMemory.direct("myVar")) + c2 += IRInstructions.returnVoid() val irProg = makeIRProgram(listOf(c1, c2)) val opt = IRPeepholeOptimizer(irProg, false) opt.optimize(true, ErrorReporterForTests()) @@ -600,9 +601,9 @@ class TestIRPeepholeOpt: FunSpec({ test("do not fold storem+loadm different types") { val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=5, labelSymbol="myVar"), - IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1=6, labelSymbol="myVar"), - IRInstruction(Opcode.RETURN) + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 5, IRMemory.direct("myVar")), + IRInstructions.loadMemory(Opcode.LOADM, IRDataType.WORD, 6, IRMemory.direct("myVar")), + IRInstructions.returnVoid() )) val opt = IRPeepholeOptimizer(irProg, false) opt.optimize(true, ErrorReporterForTests()) @@ -612,22 +613,57 @@ class TestIRPeepholeOpt: FunSpec({ instr.count { it.opcode==Opcode.LOADR } shouldBe 0 } + test("dead store analysis sees reads via secondary operand slots") { + // r10 is only read as the INDEX register inside the LOADX memory reference. The dead + // store detector must see that read, otherwise the first LOAD r10 is wrongly removed + // and the array would be indexed with an uninitialized register. + val irProg = makeIRProgram(listOf( + IRInstructions.load(indexRegType, 10, 3), + IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, 1, IRMemory.indexed("myArray", 10, indexRegType)), + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 1, IRMemory.direct("sym")), + IRInstructions.load(indexRegType, 10, 7), + IRInstructions.binaryImmediate(Opcode.ADD, indexRegType, 10, 1), + IRInstructions.storeMemory(Opcode.STOREM, indexRegType, 10, IRMemory.direct("sym2")), + IRInstructions.returnVoid() + )) + val opt = IRPeepholeOptimizer(irProg, false) + opt.optimize(true, ErrorReporterForTests()) + val instr = irProg.chunks().single().instructions + instr.count { it.opcode == Opcode.LOADX } shouldBe 1 + instr.any { it.opcode == Opcode.LOAD && it.requireImmediateInt() == 3 } shouldBe true + } + + test("dead store to the same register is removed") { + val irProg = makeIRProgram(listOf( + IRInstructions.load(IRDataType.BYTE, 1, 5), + IRInstructions.load(IRDataType.BYTE, 1, 10), + IRInstructions.unary(Opcode.INC, IRDataType.BYTE, 1), + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 1, IRMemory.direct("sym")), + IRInstructions.returnVoid() + )) + val opt = IRPeepholeOptimizer(irProg, false) + opt.optimize(true, ErrorReporterForTests()) + val instr = irProg.chunks().single().instructions + instr.any { it.opcode == Opcode.LOAD && it.requireImmediateInt() == 5 } shouldBe false + instr.any { it.opcode == Opcode.LOAD && it.requireImmediateInt() == 10 } shouldBe true + } + test("loadr forwarding does not delete live-out source") { // r1 is live-out to next chunk, so LOAD r1 should be kept even though r1 has single use in first chunk. // The forwarder is conservative: it keeps the source live when it may be needed in a successor chunk, // preventing the previous bug where the source LOAD was deleted. val c1 = IRCodeChunk("p8b_main.p8s_start", null) - c1 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=42) - c1 += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=2, reg2=1) - c1 += IRInstruction(Opcode.JUMP, labelSymbol="next") + c1 += IRInstructions.load(IRDataType.BYTE, 1, 42) + c1 += IRInstructions.move(IRDataType.BYTE, 2, 1) + c1 += IRInstructions.jump(codeLabel("next")) val c2 = IRCodeChunk("next", null) - c2 += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=3, reg2=1) - c2 += IRInstruction(Opcode.RETURN) + c2 += IRInstructions.move(IRDataType.BYTE, 3, 1) + c2 += IRInstructions.returnVoid() val irProg = makeIRProgram(listOf(c1, c2)) val opt = IRPeepholeOptimizer(irProg, false) opt.optimize(true, ErrorReporterForTests()) val all = irProg.chunks().flatMap { it.instructions } // LOAD r1 must be kept because r1 is live-out; whether LOADR is forwarded to LOAD r2 is secondary. - all.count { it.opcode == Opcode.LOAD && it.reg1==1 } shouldBe 1 + all.count { it.opcode == Opcode.LOAD && it.dest?.register == VirtualRegister.int(1) } shouldBe 1 } }) diff --git a/codeGenIntermediate/test/TestRegisterPacker.kt b/codeGenIntermediate/test/TestRegisterPacker.kt index 221449aaf..75a0b1fc0 100644 --- a/codeGenIntermediate/test/TestRegisterPacker.kt +++ b/codeGenIntermediate/test/TestRegisterPacker.kt @@ -43,18 +43,19 @@ class TestRegisterPacker: FunSpec({ fun IRProgram.instructions(): List = chunks().flatMap { it.instructions } + fun IRProgram.registerAccesses(): List = instructions().flatMap { it.registerAccesses } + test("simple coalescing") { // r1=[0,1], r2=[2,3] non-overlapping -> same packed slot val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=10), - IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=1, labelSymbol = "sym"), - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate=20), - IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=2, labelSymbol = "sym2") + IRInstructions.load(IRDataType.BYTE, 1, 10), + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 1, IRMemory.direct("sym")), + IRInstructions.load(IRDataType.BYTE, 2, 20), + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 2, IRMemory.direct("sym2")) )) RegisterPacker.pack(irProg) - val instrs = irProg.instructions() - val regs = instrs.mapNotNull { it.reg1 } - // After packing, all reg1 values should be the same (both r1 and r2 mapped to 1 slot) + val regs = irProg.registerAccesses().map { it.register } + // After packing, all register accesses should refer to the same register (both r1 and r2 mapped to 1 slot) regs.toSet().size shouldBe 1 val used = irProg.registersUsed() (used.readRegs.keys + used.writeRegs.keys).size shouldBe 1 @@ -62,17 +63,17 @@ class TestRegisterPacker: FunSpec({ test("no coalescing with overlapping intervals") { // r1=[0,3], r2=[1,4] overlap -> 2 distinct slots needed - // LOADR uses both at same time -> must have reg1 != reg2 + // LOADR uses both at same time -> destination must differ from source val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=10), - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate=20), - IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=1, labelSymbol="s"), - IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=1, reg2=2) + IRInstructions.load(IRDataType.BYTE, 1, 10), + IRInstructions.load(IRDataType.BYTE, 2, 20), + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 1, IRMemory.direct("s")), + IRInstructions.move(IRDataType.BYTE, 1, 2) )) RegisterPacker.pack(irProg) val instrs = irProg.instructions() val loadr = instrs.first { it.opcode == Opcode.LOADR } - loadr.reg1 shouldNotBe loadr.reg2 + loadr.requireDest().register shouldNotBe loadr.requireSrcA().register } test("cross-chunk liveness") { @@ -84,19 +85,18 @@ class TestRegisterPacker: FunSpec({ // Non-overlapping: r1=[0,2] and r2=[3,4] -> can share // Overlapping: r2=[3,4] and r3=[3,5] -> must differ val c1 = IRCodeChunk("p8b_main.p8s_start", null) - c1 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=10) - c1 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate=20) - c1 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=1, labelSymbol="sym") + c1 += IRInstructions.load(IRDataType.BYTE, 1, 10) + c1 += IRInstructions.load(IRDataType.BYTE, 2, 20) + c1 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 1, IRMemory.direct("sym")) val c2 = IRCodeChunk(null, null) - c2 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=3, immediate=30) - c2 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=2, labelSymbol="sym2") - c2 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=3, labelSymbol="sym3") + c2 += IRInstructions.load(IRDataType.BYTE, 3, 30) + c2 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 2, IRMemory.direct("sym2")) + c2 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 3, IRMemory.direct("sym3")) val irProg = makeIRProgram(listOf(c1, c2)) RegisterPacker.pack(irProg) - val instrs = irProg.instructions() - val distinctRegs = instrs.mapNotNull { it.reg1 }.toSet() + val distinctRegs = irProg.registerAccesses().map { it.register }.toSet() // r1 and r2 can share a slot (non-overlapping), r3 must differ from r2 (overlapping) // Results in exactly 2 distinct slots used @@ -108,10 +108,10 @@ class TestRegisterPacker: FunSpec({ // Non-overlapping but different incompatible types. // Packer must NOT assign both to slot 1 (would cause type conflict). val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=5, immediate=10), - IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=5, labelSymbol="s"), - IRInstruction(Opcode.LOAD, IRDataType.POINTER, reg1=1, immediate=0x1000), - IRInstruction(Opcode.STOREM, IRDataType.POINTER, reg1=1, labelSymbol="s2") + IRInstructions.load(IRDataType.BYTE, 5, 10), + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 5, IRMemory.direct("s")), + IRInstructions.load(IRDataType.POINTER, 1, 0x1000), + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.POINTER, 1, IRMemory.direct("s2")) )) RegisterPacker.pack(irProg) // After packing, usedRegisters() must not throw (internal type consistency) @@ -126,16 +126,16 @@ class TestRegisterPacker: FunSpec({ // Chunk A -> unconditional jump -> chunk C // Chunk B is unreachable from A (but still a predecessor of C via fall-through) val c1 = IRCodeChunk("p8b_main.p8s_start", null) - c1 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=10) - c1 += IRInstruction(Opcode.JUMP, labelSymbol="label_c") + c1 += IRInstructions.load(IRDataType.BYTE, 1, 10) + c1 += IRInstructions.jump(codeLabel("label_c")) val c2 = IRCodeChunk("label_b", null) - c2 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate=20) + c2 += IRInstructions.load(IRDataType.BYTE, 2, 20) val c3 = IRCodeChunk("label_c", null) - c3 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=1, labelSymbol="sym") - c3 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate=99) - c3 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=2, labelSymbol="sym2") + c3 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 1, IRMemory.direct("sym")) + c3 += IRInstructions.load(IRDataType.BYTE, 2, 99) + c3 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 2, IRMemory.direct("sym2")) val irProg = makeIRProgram(listOf(c1, c2, c3)) RegisterPacker.pack(irProg) @@ -151,18 +151,18 @@ class TestRegisterPacker: FunSpec({ // Chunk A -> conditional branch to C, fall-through to B // Liveness flows from A to both B and C val c1 = IRCodeChunk("p8b_main.p8s_start", null) - c1 += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=1, immediate=0) - c1 += IRInstruction(Opcode.BSTNE, labelSymbol="label_c") + c1 += IRInstructions.compareImmediate(IRDataType.BYTE, 1, 0) + c1 += IRInstructions.branch(Opcode.BSTNE, codeLabel("label_c")) val c2 = IRCodeChunk(null, null) // fall-through (B) - c2 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate=42) - c2 += IRInstruction(Opcode.JUMP, labelSymbol="label_end") + c2 += IRInstructions.load(IRDataType.BYTE, 2, 42) + c2 += IRInstructions.jump(codeLabel("label_end")) val c3 = IRCodeChunk("label_c", null) - c3 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate=99) + c3 += IRInstructions.load(IRDataType.BYTE, 2, 99) val c4 = IRCodeChunk("label_end", null) - c4 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=2, labelSymbol="sym") + c4 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 2, IRMemory.direct("sym")) val irProg = makeIRProgram(listOf(c1, c2, c3, c4)) RegisterPacker.pack(irProg) @@ -173,29 +173,28 @@ class TestRegisterPacker: FunSpec({ test("multi-chunk register reduction") { // 3 chunks with 8 registers, non-overlapping patterns val c1 = IRCodeChunk("p8b_main.p8s_start", null) - c1 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=10) - c1 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=1, labelSymbol="sym") - c1 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate=20) - c1 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=2, labelSymbol="sym2") - c1 += IRInstruction(Opcode.JUMP, labelSymbol="cont") + c1 += IRInstructions.load(IRDataType.BYTE, 1, 10) + c1 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 1, IRMemory.direct("sym")) + c1 += IRInstructions.load(IRDataType.BYTE, 2, 20) + c1 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 2, IRMemory.direct("sym2")) + c1 += IRInstructions.jump(codeLabel("cont")) val c2 = IRCodeChunk("cont", null) - c2 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=3, immediate=30) - c2 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=3, labelSymbol="sym3") - c2 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=4, immediate=40) - c2 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=4, labelSymbol="sym4") - c2 += IRInstruction(Opcode.JUMP, labelSymbol="cont2") + c2 += IRInstructions.load(IRDataType.BYTE, 3, 30) + c2 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 3, IRMemory.direct("sym3")) + c2 += IRInstructions.load(IRDataType.BYTE, 4, 40) + c2 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 4, IRMemory.direct("sym4")) + c2 += IRInstructions.jump(codeLabel("cont2")) val c3 = IRCodeChunk("cont2", null) - c3 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=5, immediate=50) - c3 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=5, labelSymbol="sym5") - c3 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=6, immediate=60) - c3 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=6, labelSymbol="sym6") + c3 += IRInstructions.load(IRDataType.BYTE, 5, 50) + c3 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 5, IRMemory.direct("sym5")) + c3 += IRInstructions.load(IRDataType.BYTE, 6, 60) + c3 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 6, IRMemory.direct("sym6")) val irProg = makeIRProgram(listOf(c1, c2, c3)) RegisterPacker.pack(irProg) - val instrs = irProg.instructions() - val distinctRegs = instrs.mapNotNull { it.reg1 }.toSet() + val distinctRegs = irProg.registerAccesses().map { it.register }.toSet() // Each chunk defines and uses 2 regs. Within each chunk the regs are non-overlapping // (def rX at i, use at i+1, then def rY at i+2). Across chunks they don't overlap either // (sequential, jumps between chunks). So all 6 regs can share 1 slot. @@ -204,43 +203,40 @@ class TestRegisterPacker: FunSpec({ test("floating point registers packed independently") { val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = RegisterNum(1), immediateFp = 1.0), - IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = RegisterNum(1), labelSymbol="sym"), - IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = RegisterNum(2), immediateFp = 2.0), - IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = RegisterNum(2), labelSymbol="sym2") + IRInstructions.loadFloat(1, 1.0), + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.FLOAT, 1, IRMemory.direct("sym")), + IRInstructions.loadFloat(2, 2.0), + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.FLOAT, 2, IRMemory.direct("sym2")) )) RegisterPacker.pack(irProg) - val instrs = irProg.instructions() // Both fp regs have non-overlapping intervals -> same packed slot - val fpRegs = instrs.mapNotNull { it.fpReg1?.value } + val fpRegs = irProg.registerAccesses().filter { it.isFloat }.map { it.register } fpRegs.distinct().size shouldBe 1 } test("function call args are remapped") { - val args = listOf( - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.BYTE, RegisterNum(1), null, null)), - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.BYTE, RegisterNum(2), null, null)) + val callSite = CallSite( + CallTarget.SystemCall(0), + arguments = listOf( + Calls.argument(1, IRDataType.BYTE), + Calls.argument(2, IRDataType.BYTE) + ), + results = listOf(Calls.result(3, IRDataType.BYTE)) ) - val returns = listOf( - FunctionCallArgs.RegSpec(IRDataType.BYTE, RegisterNum(3), null, null) - ) - val fcallArgs = FunctionCallArgs(args, returns) val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=10), - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate=20), + IRInstructions.load(IRDataType.BYTE, 1, 10), + IRInstructions.load(IRDataType.BYTE, 2, 20), // Non-overlapping: r1=[0,0], r2=[1,1], r3=live only as return from SYSCALL - IRInstruction(Opcode.SYSCALL, immediate = 0, fcallArgs = fcallArgs) + IRInstructions.call(Opcode.SYSCALL, callSite) )) RegisterPacker.pack(irProg) val instrs = irProg.instructions() val syscall = instrs.first { it.opcode == Opcode.SYSCALL } - syscall.fcallArgs shouldNotBe null - val remappedArgs = syscall.fcallArgs!!.arguments - val remappedReturns = syscall.fcallArgs!!.returns + val remapped = syscall.requireCallSite() // Should have same packed register numbers - remappedArgs.map { it.reg.registerNum.value }.toSet().size shouldBe 2 - remappedReturns.first().registerNum.value shouldBe 3 + remapped.arguments.map { it.source.register }.toSet().size shouldBe 2 + remapped.results.single().destination!!.registerNumber shouldBe 3 } test("empty subroutine no crash") { @@ -252,52 +248,56 @@ class TestRegisterPacker: FunSpec({ test("no registers used") { val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.NOP), - IRInstruction(Opcode.NOP) + IRInstructions.simple(Opcode.NOP), + IRInstructions.simple(Opcode.NOP) )) RegisterPacker.pack(irProg) irProg.instructions().size shouldBe 2 } - test("fcallArgs with mixed types must not share same slot") { - // Regression: fcallArgs registers with incompatible types (WORD vs LONG) + test("call arguments with mixed types must not share same slot") { + // Regression: call site registers with incompatible types (WORD vs LONG) // in the same chunk must be assigned different packed slots. - // The conflict graph must track fcallArgs register types. - val args = listOf( - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(1), null, null)), - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.LONG, RegisterNum(2), null, null)) + // The conflict graph must track call site register types. + val callSite = CallSite( + CallTarget.SystemCall(0), + arguments = listOf( + Calls.argument(1, IRDataType.WORD), + Calls.argument(2, IRDataType.LONG) + ) ) - val fcallArgs = FunctionCallArgs(args, emptyList()) val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=1, immediate=100), - IRInstruction(Opcode.SYSCALL, immediate = 0, fcallArgs = fcallArgs) + IRInstructions.load(IRDataType.WORD, 1, 100), + IRInstructions.call(Opcode.SYSCALL, callSite) )) RegisterPacker.pack(irProg) // usedRegisters() must not throw (type consistency within chunk) val used = irProg.registersUsed() - val regs = (used.readRegs.keys + used.writeRegs.keys).map { it.value }.toSet() + val regs = (used.readRegs.keys + used.writeRegs.keys).map { it.num }.toSet() // Must have at least 2 distinct packed slots (WORD and LONG cannot share) regs.size shouldBe 2 } - test("fcallArgs returns with mixed types must not share same slot") { + test("call results with mixed types must not share same slot") { // Same as above but for return registers - val returns = listOf( - FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(1), null, null), - FunctionCallArgs.RegSpec(IRDataType.LONG, RegisterNum(2), null, null) + val callSite = CallSite( + CallTarget.SystemCall(0), + results = listOf( + Calls.result(1, IRDataType.WORD), + Calls.result(2, IRDataType.LONG) + ) ) - val fcallArgs = FunctionCallArgs(emptyList(), returns) val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.SYSCALL, immediate = 0, fcallArgs = fcallArgs), - IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=1, labelSymbol="s"), - IRInstruction(Opcode.STOREM, IRDataType.LONG, reg1=2, labelSymbol="s2") + IRInstructions.call(Opcode.SYSCALL, callSite), + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.WORD, 1, IRMemory.direct("s")), + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.LONG, 2, IRMemory.direct("s2")) )) RegisterPacker.pack(irProg) // usedRegisters() must not throw val used = irProg.registersUsed() - val regs = (used.readRegs.keys + used.writeRegs.keys).map { it.value }.toSet() + val regs = (used.readRegs.keys + used.writeRegs.keys).map { it.num }.toSet() regs.size shouldBe 2 } @@ -307,10 +307,10 @@ class TestRegisterPacker: FunSpec({ // the same packed slot, because usedRegisters() enforces a single type // per register number within a chunk. val irProg = makeIRProgram(listOf( - IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=1, immediate=100), - IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=1, labelSymbol="s"), - IRInstruction(Opcode.LOAD, IRDataType.LONG, reg1=2, immediate=0x10000), - IRInstruction(Opcode.STOREM, IRDataType.LONG, reg1=2, labelSymbol="s2") + IRInstructions.load(IRDataType.WORD, 1, 100), + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.WORD, 1, IRMemory.direct("s")), + IRInstructions.load(IRDataType.LONG, 2, 0x10000), + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.LONG, 2, IRMemory.direct("s2")) )) RegisterPacker.pack(irProg) // usedRegisters() must not throw @@ -352,15 +352,15 @@ class TestRegisterPacker: FunSpec({ // Sub 1: POINTER interval (gets slot ≥ startSlot), and then the same slot again with WORD val c1 = IRCodeChunk("p8b_main.sub1", null) - c1 += IRInstruction(Opcode.LOAD, IRDataType.POINTER, reg1=100, immediate=0x1000) - c1 += IRInstruction(Opcode.STOREM, IRDataType.POINTER, reg1=100, labelSymbol="sp") - c1 += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=101, immediate=200) - c1 += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=101, labelSymbol="sw") + c1 += IRInstructions.load(IRDataType.POINTER, 100, 0x1000) + c1 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.POINTER, 100, IRMemory.direct("sp")) + c1 += IRInstructions.load(IRDataType.WORD, 101, 200) + c1 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.WORD, 101, IRMemory.direct("sw")) // Sub 2: LONG interval that should be able to share the same slot as sub 1's POINTER val c2 = IRCodeChunk("p8b_main.sub2", null) - c2 += IRInstruction(Opcode.LOAD, IRDataType.LONG, reg1=200, immediate=0x100000) - c2 += IRInstruction(Opcode.STOREM, IRDataType.LONG, reg1=200, labelSymbol="sl") + c2 += IRInstructions.load(IRDataType.LONG, 200, 0x100000) + c2 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.LONG, 200, IRMemory.direct("sl")) val irProg = makeMultiSubProgram(listOf(c1), listOf(c2)) RegisterPacker.pack(irProg) @@ -377,20 +377,20 @@ class TestRegisterPacker: FunSpec({ // maxReg+1 to avoid this. val irProg = makeIRProgram(listOf( // Register 5 is used directly (not packed? actually it HAS an interval and gets packed) - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=5, immediate=99), - IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=5, labelSymbol="s"), + IRInstructions.load(IRDataType.BYTE, 5, 99), + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 5, IRMemory.direct("s")), // Register 1 and 2 can be packed - IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=1, immediate=100), - IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=1, labelSymbol="s2"), - IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=2, immediate=200), - IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=2, labelSymbol="s3") + IRInstructions.load(IRDataType.WORD, 1, 100), + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.WORD, 1, IRMemory.direct("s2")), + IRInstructions.load(IRDataType.WORD, 2, 200), + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.WORD, 2, IRMemory.direct("s3")) )) RegisterPacker.pack(irProg) // after packing: // - all registers should have been packed to slots > 5 (since maxReg=5, startSlot=6) // - no packed slot should equal an original register number val used = irProg.registersUsed() - val allRegNums = (used.readRegs.keys + used.writeRegs.keys).map { it.value }.toSet() + val allRegNums = (used.readRegs.keys + used.writeRegs.keys).map { it.num }.toSet() // packed slots start at 6, so no register should be 5 anymore (5 in allRegNums) shouldBe false // all registers should be >= 6 @@ -414,15 +414,15 @@ class TestRegisterPacker: FunSpec({ // Sub 1: uses r37 as POINTER val sub1 = IRSubroutine("p8b_main.sub1", emptyList(), emptyList(), Position.DUMMY) val c1 = IRCodeChunk("p8b_main.sub1", null) - c1 += IRInstruction(Opcode.LOAD, IRDataType.POINTER, reg1=37, immediate=0x1000) - c1 += IRInstruction(Opcode.STOREM, IRDataType.POINTER, reg1=37, labelSymbol="sp") + c1 += IRInstructions.load(IRDataType.POINTER, 37, 0x1000) + c1 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.POINTER, 37, IRMemory.direct("sp")) sub1 += c1 // Sub 2: uses same r37 as BYTE (incompatible) val sub2 = IRSubroutine("p8b_main.sub2", emptyList(), emptyList(), Position.DUMMY) val c2 = IRCodeChunk("p8b_main.sub2", null) - c2 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=37, immediate=42) - c2 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=37, labelSymbol="sb") + c2 += IRInstructions.load(IRDataType.BYTE, 37, 42) + c2 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 37, IRMemory.direct("sb")) sub2 += c2 block += sub1 @@ -460,13 +460,13 @@ class TestRegisterPacker: FunSpec({ val sub = IRSubroutine("p8b_main.sub1", emptyList(), emptyList(), Position.DUMMY) val c1 = IRCodeChunk("p8b_main.sub1", null) - c1 += IRInstruction(Opcode.LOAD, IRDataType.POINTER, reg1=37, immediate=0x1000) - c1 += IRInstruction(Opcode.STOREM, IRDataType.POINTER, reg1=37, labelSymbol="sp") + c1 += IRInstructions.load(IRDataType.POINTER, 37, 0x1000) + c1 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.POINTER, 37, IRMemory.direct("sp")) sub += c1 val c2 = IRCodeChunk("p8b_main.sub1.c2", c1) - c2 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=37, immediate=42) - c2 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=37, labelSymbol="sb") + c2 += IRInstructions.load(IRDataType.BYTE, 37, 42) + c2 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 37, IRMemory.direct("sb")) sub += c2 block += sub @@ -480,7 +480,7 @@ class TestRegisterPacker: FunSpec({ // registersUsed() in permissive mode: putIfAbsent, should not throw val used = prog.registersUsed() // r37 should still appear (was skipped by packer) - used.regsTypes.containsKey(RegisterNum(37)) shouldBe true + used.regsTypes.containsKey(VirtualRegister.int(37)) shouldBe true } test("disjoint intervals of same register must be merged into one contiguous range") { @@ -501,20 +501,20 @@ class TestRegisterPacker: FunSpec({ // Chunk 1: write r1 (value should persist to chunk 3) val c1 = IRCodeChunk("p8b_main.sub1", null) - c1 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=42) - c1 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=1, labelSymbol="s1") + c1 += IRInstructions.load(IRDataType.BYTE, 1, 42) + c1 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 1, IRMemory.direct("s1")) sub += c1 // Chunk 2: write and read r2 (this interval falls in the gap between r1's uses) val c2 = IRCodeChunk("p8b_main.sub1.c2", c1) - c2 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate=99) - c2 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=2, labelSymbol="s2") + c2 += IRInstructions.load(IRDataType.BYTE, 2, 99) + c2 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 2, IRMemory.direct("s2")) sub += c2 // Chunk 3: read r1 (uses the value from chunk 1) val c3 = IRCodeChunk("p8b_main.sub1.c3", c2) - c3 += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=3, reg2=1) - c3 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=3, labelSymbol="s3") + c3 += IRInstructions.move(IRDataType.BYTE, 3, 1) + c3 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 3, IRMemory.direct("s3")) sub += c3 block += sub @@ -528,12 +528,10 @@ class TestRegisterPacker: FunSpec({ // merged interval spans [0..?] covering the gap, preventing r2 // from sharing the same slot. val instructions = prog.instructions() - // Find the register numbers used for the STOREM to s1 (was r1) and s2 (was r2) - val s1Reg = instructions.first { it.labelSymbol=="s1" }.reg1 - val s2Reg = instructions.first { it.labelSymbol=="s2" }.reg1 + // Find the register operands used for the STOREM to s1 (was r1) and s2 (was r2) + val s1Reg = instructions.first { it.memory?.symbolName=="s1" }.requireSrcA().register + val s2Reg = instructions.first { it.memory?.symbolName=="s2" }.requireSrcA().register // They must be different (can't share a slot) - s1Reg shouldNotBe null - s2Reg shouldNotBe null s1Reg shouldNotBe s2Reg } }) diff --git a/codeGenIntermediate/test/TestVmCodeGen.kt b/codeGenIntermediate/test/TestVmCodeGen.kt index 554acfb6f..bb860588d 100644 --- a/codeGenIntermediate/test/TestVmCodeGen.kt +++ b/codeGenIntermediate/test/TestVmCodeGen.kt @@ -11,6 +11,8 @@ import prog8.codegen.vm.VmAssemblyProgram import prog8.codegen.vm.VmCodeGen import prog8.intermediate.IRSubroutine import prog8.intermediate.Opcode +import prog8.intermediate.codeAddress +import prog8.intermediate.requireCallSite import prog8.intermediate.toAddress import prog8tests.helpers.ErrorReporterForTests @@ -208,12 +210,12 @@ class TestVmCodeGen: FunSpec({ andrCount shouldBe 1 xorrCount shouldBe 1 - // Verify that the immediate-only variants are NOT used with reg2 - val wrongAdd = instructions.any { it.opcode == Opcode.ADD && it.reg2 != null } - val wrongSub = instructions.any { it.opcode == Opcode.SUB && it.reg2 != null } - val wrongOr = instructions.any { it.opcode == Opcode.OR && it.reg2 != null } - val wrongAnd = instructions.any { it.opcode == Opcode.AND && it.reg2 != null } - val wrongXor = instructions.any { it.opcode == Opcode.XOR && it.reg2 != null } + // Verify that the immediate-only variants are NOT used with a register source + val wrongAdd = instructions.any { it.opcode == Opcode.ADD && it.srcA != null } + val wrongSub = instructions.any { it.opcode == Opcode.SUB && it.srcA != null } + val wrongOr = instructions.any { it.opcode == Opcode.OR && it.srcA != null } + val wrongAnd = instructions.any { it.opcode == Opcode.AND && it.srcA != null } + val wrongXor = instructions.any { it.opcode == Opcode.XOR && it.srcA != null } (wrongAdd || wrongSub || wrongOr || wrongAnd || wrongXor) shouldBe false } @@ -645,7 +647,7 @@ class TestVmCodeGen: FunSpec({ irChunks[0].instructions.size shouldBe 1 val callInstr = irChunks[0].instructions[0] callInstr.opcode shouldBe Opcode.CALL - callInstr.address shouldBe 0x5000u.toAddress() + callInstr.requireCallSite().codeReference shouldBe codeAddress(0x5000u.toAddress()) } test("ir codegen for target 'virtual' produces no prefixed names") { diff --git a/codeGenM68k/src/prog8/codegen/m68k/AsmGen.kt b/codeGenM68k/src/prog8/codegen/m68k/AsmGen.kt index 64be6e382..384f3524d 100644 --- a/codeGenM68k/src/prog8/codegen/m68k/AsmGen.kt +++ b/codeGenM68k/src/prog8/codegen/m68k/AsmGen.kt @@ -8,14 +8,26 @@ internal const val FP_ACC = "fp0" // primary FPU scratch / accumulator internal const val FP_SRC = "fp1" // secondary FPU scratch / source operand /** - * Identity of a virtual register for call-argument forwarding, distinguishing - * the integer register file (rN) from the floating-point register file (frN). - * The two namespaces are independent, so the same numeric value can refer to - * both an integer and a floating-point register. + * The virtual register that an instruction writes into its primary destination slot, + * but only when that slot is a pure definition (not a read-modify-write). This is the + * "defining write" used by the call-argument forwarding analysis. */ -internal sealed interface RegId { - data class IntReg(val num: Int) : RegId - data class FloatReg(val num: RegisterNum) : RegId +internal fun IRInstruction.exclusiveDestination(): VirtualRegister? = + dest?.takeIf { it.direction == OperandDirection.DEF }?.register + +/** + * The immediate value a LOAD instruction puts into [register], or null when this + * instruction isn't a plain immediate load of that register. Symbol addresses are + * not forwardable, only plain integer and float constants are. + */ +internal fun IRInstruction.forwardableImmediateFor(register: VirtualRegister, type: IRDataType): ImmediateOperand? { + if (opcode != Opcode.LOAD || this.type != type || exclusiveDestination() != register) + return null + return when (immediate) { + is ImmediateOperand.Integer -> if (register.isFloat) null else immediate + is ImmediateOperand.FloatValue -> if (register.isFloat) immediate else null + else -> null + } } /** @@ -44,8 +56,8 @@ internal sealed interface RegId { * depend on liveness and is still applied in those cases. */ internal data class ImmediateCallOptimization( - val loads: Map, - val deadRegisters: Set + val loads: Map, + val deadRegisters: Set ) /** @@ -280,8 +292,12 @@ internal class AsmGen(val program: IRProgram, internal val target: ICompilationT IRDataType.POINTER -> target.POINTER_MEM_SIZE.toInt() } + // intRegsTypes is a derived (recomputed) view on RegistersUsed, so cache it: it is + // consulted for every register operand that needs its natural width. + private val intRegTypes: Map by lazy { regsUsed.intRegsTypes } + private val regFileLayout: RegFileLayout by lazy { - val allRegs = regsUsed.regsTypes + val allRegs = intRegTypes val offsets = mutableMapOf() var currentOffset = 0 for ((regNum, regType) in allRegs.entries.sortedBy { it.key.value }) { @@ -297,7 +313,7 @@ internal class AsmGen(val program: IRProgram, internal val target: ICompilationT private val floatRegFileLayout: RegFileLayout by lazy { val used = regsUsed val allFpRegs = mutableMapOf() - for (reg in used.readFpRegs.keys + used.writeFpRegs.keys) { + for (reg in used.floatRegsRead.keys + used.floatRegsWritten.keys) { allFpRegs[reg] = IRDataType.FLOAT } val offsets = mutableMapOf() @@ -339,7 +355,7 @@ internal class AsmGen(val program: IRProgram, internal val target: ICompilationT } /** Returns the IR data type of the given integer virtual register. */ - fun regType(reg: Int): IRDataType = regsUsed.regsTypes[RegisterNum(reg)] ?: IRDataType.BYTE + fun regType(reg: Int): IRDataType = intRegTypes[RegisterNum(reg)] ?: IRDataType.BYTE /** Load a virtual register into d0, zero-extending if its natural width is smaller than [requiredType]. */ fun loadRegOrZeroExtendToD0(reg: Int, requiredType: IRDataType) { @@ -413,14 +429,106 @@ internal class AsmGen(val program: IRProgram, internal val target: ICompilationT fun constLabel(name: String): String = "p8c_${fixNameSymbols(name)}" - fun resolveAddress(addr: MemoryAddress?, label: String?, offset: Int? = null): String { - return when { - label != null -> { - val resolved = resolveSymbolRef(label) - if (offset != null && offset != 0) "$resolved+$offset" else resolved + /** + * Load an array index into d0 for use with `(a0,d0.w)` addressing. + * Word indices can be loaded directly; byte indices must be zero-extended + * because `move.w` from a byte regfile slot would read the adjacent byte too. + */ + private fun loadIndexToD0(idx: Int) { + if (regType(idx) == IRDataType.BYTE) { + invalidateD0Cache() // moveq clobbers d0 + emitLine("moveq #0, d0") + emitLoadD0(idx, IRDataType.BYTE) // cache as byte; consumer widens + } else { + emitLoadD0(idx, IRDataType.WORD) // cache as word; consumer widens + } + } + + private fun addIndirectOffset(offset: Int) { + when (offset) { + in 1..8 -> emitLine("addq.l #$offset, a0") + in -8..-1 -> emitLine("subq.l #${-offset}, a0") + else -> emitLine("adda.l #$offset, a0") + } + } + + private fun resolveMemoryBase(base: AddressBase, displacement: Int): String { + val resolved = when (base) { + is AddressBase.Symbol -> resolveSymbolRef(base.name) + is AddressBase.Absolute -> base.address.value.toHex() + } + return if (displacement != 0) "$resolved+$displacement" else resolved + } + + /** + * Centralized lowering of a MemoryReference (Direct/Indexed/Indirect) into the effective + * m68k operand/addressing-mode text, emitting whatever setup instructions the mode requires + * (lea for a symbol/index base, index register loading/scaling, or a pointer load into a0). + * This replaces the old ad-hoc decoding of address/labelSymbol/symbolOffset/reg2/scale fields + * that used to be duplicated across every load/store opcode and the memory-operand arithmetic ops. + * [forFloat] selects the FPU indexed/indirect addressing conventions (32-bit d0.l index, + * unconditional addIndirectOffset) used by the float load/store opcodes. + */ + fun resolveMemory(memory: MemoryReference, forFloat: Boolean = false): String { + return when (memory) { + is MemoryReference.Direct -> resolveMemoryBase(memory.base, memory.displacement) + is MemoryReference.Indexed -> { + val idx = memory.index.intNumber + val scale = memory.scale + val baseAddr = resolveMemoryBase(memory.base, memory.displacement) + if (forFloat) { + // full-width d0.l indexing requires zero-extending the word index to 32 bits + invalidateD0Cache() // moveq clobbers d0 + emitLine("moveq #0, d0") + emitLoadD0(idx, IRDataType.WORD) // cache as word; d0.l widens it + if (scale != 1) { + invalidateD0Cache() // scaling transforms d0 + when (scale) { + 2 -> emitLine("add.l d0,d0") + 4 -> emitLine("lsl.l #2, d0") + 8 -> emitLine("lsl.l #3, d0") + else -> emitLine("muls.w #$scale, d0") + } + } + emitLine("lea $baseAddr, a0") + "(0, a0, d0.l)" + } else { + loadIndexToD0(idx) + if (scale != 1) { + invalidateD0Cache() // scaling transforms d0 + if (program.options.compTarget.cpu >= CpuType.M68020) { + if (scale != 2 && scale != 4 && scale != 8) emitLine("muls.w #$scale, d0") + } else { + when (scale) { + 2 -> emitLine("add.w d0,d0") + 4 -> emitLine("lsl.w #2, d0") + else -> emitLine("muls.w #$scale, d0") + } + } + } + emitLine("lea $baseAddr, a0") + when { + program.options.compTarget.cpu >= CpuType.M68020 && scale == 2 -> "(a0,d0.w*2)" + program.options.compTarget.cpu >= CpuType.M68020 && scale == 4 -> "(a0,d0.w*4)" + program.options.compTarget.cpu >= CpuType.M68020 && scale == 8 -> "(a0,d0.w*8)" + else -> "(a0,d0.w)" + } + } + } + is MemoryReference.Indirect -> { + loadPointerToA0(memory.pointer.intNumber) + val off = memory.displacement + if (forFloat) { + if (off != 0) addIndirectOffset(off) + "(a0)" + } else { + when { + off < -32768 || off > 32767 -> { addIndirectOffset(off); "(a0)" } + off == 0 -> "(a0)" + else -> "($off,a0)" + } + } } - addr != null -> addr.value.toHex() - else -> "0" } } @@ -682,7 +790,7 @@ internal class AsmGen(val program: IRProgram, internal val target: ICompilationT private fun inlineAsmMayReadRegisters(chunk: IRInlineAsmChunk): Boolean { if (chunk.isIR) { val used = chunk.usedRegisters() - return used.readRegs.isNotEmpty() || used.readFpRegs.isNotEmpty() + return used.readRegs.isNotEmpty() } return REGFILE_LABEL in chunk.assembly || FLOAT_REGFILE_LABEL in chunk.assembly } @@ -741,7 +849,7 @@ internal class AsmGen(val program: IRProgram, internal val target: ICompilationT val insn = chunk.instructions[index] if (insn.opcode != Opcode.CALL) continue - val optimization = immediateLoadsForCall(livenessInstructions, instructionOffset + index, instructionOffset, insn.fcallArgs) + val optimization = immediateLoadsForCall(livenessInstructions, instructionOffset + index, instructionOffset, insn.callSite) ?: continue val effectiveOptimization = if (deadStoreSuppressionAllowed) optimization @@ -749,8 +857,8 @@ internal class AsmGen(val program: IRProgram, internal val target: ICompilationT callOptimizations[index] = effectiveOptimization // Mark the defining LOAD immediates for dead registers as dead (to suppress their regfile store). // These may be non-contiguous with the CALL, so search for each dead reg's defining LOAD. - for (regId in effectiveOptimization.deadRegisters) { - val forwardedLoad = effectiveOptimization.loads[regId] ?: continue + for (register in effectiveOptimization.deadRegisters) { + val forwardedLoad = effectiveOptimization.loads[register] ?: continue // Find the chunk-local index of this exact LOAD instruction for (loadIndex in index - 1 downTo 0) { val load = chunk.instructions[loadIndex] @@ -759,20 +867,12 @@ internal class AsmGen(val program: IRProgram, internal val target: ICompilationT break } // Also match by reg and immediate value in case of different object identity - val isMatch = when (regId) { - is RegId.IntReg -> load.opcode == Opcode.LOAD && load.reg1 == regId.num && load.immediate == forwardedLoad.immediate - is RegId.FloatReg -> load.opcode == Opcode.LOAD && load.fpReg1 == regId.num && load.immediateFp == forwardedLoad.immediateFp - } - if (isMatch) { + if (load.opcode == Opcode.LOAD && load.dest?.register == register && load.immediate == forwardedLoad.immediate) { deadLoadIndices.add(loadIndex) break } // If we hit another write to the same register, the forwarded LOAD is not in this chunk - val writesSameReg = when (regId) { - is RegId.IntReg -> load.reg1 == regId.num && load.reg1direction == OperandDirection.WRITE - is RegId.FloatReg -> load.fpReg1 == regId.num && load.fpReg1direction == OperandDirection.WRITE - } - if (writesSameReg) break + if (load.exclusiveDestination() == register) break } } } @@ -810,26 +910,12 @@ internal class AsmGen(val program: IRProgram, internal val target: ICompilationT return false } - private fun isRegisterReadElsewhere(instructions: List, callIndex: Int, register: RegId): Boolean { + private fun isRegisterReadElsewhere(instructions: List, callIndex: Int, register: VirtualRegister): Boolean { for (index in instructions.indices) { if (index == callIndex) continue - val insn = instructions[index] - when (register) { - is RegId.IntReg -> { - if ((insn.reg1 == register.num && insn.reg1direction in setOf(OperandDirection.READ, OperandDirection.READWRITE)) || - (insn.reg2 == register.num && insn.reg2direction in setOf(OperandDirection.READ, OperandDirection.READWRITE)) || - (insn.reg3 == register.num && insn.reg3direction in setOf(OperandDirection.READ, OperandDirection.READWRITE)) || - insn.fcallArgs?.arguments?.any { it.reg.dt != IRDataType.FLOAT && it.reg.registerNum.value == register.num } == true) - return true - } - is RegId.FloatReg -> { - if ((insn.fpReg1 == register.num && insn.fpReg1direction in setOf(OperandDirection.READ, OperandDirection.READWRITE)) || - (insn.fpReg2 == register.num && insn.fpReg2direction in setOf(OperandDirection.READ, OperandDirection.READWRITE)) || - insn.fcallArgs?.arguments?.any { it.reg.dt == IRDataType.FLOAT && it.reg.registerNum == register.num } == true) - return true - } - } + if (register in instructions[index].uses) + return true } return false } @@ -838,33 +924,24 @@ internal class AsmGen(val program: IRProgram, internal val target: ICompilationT instructions: List, callIndex: Int, chunkStartIndex: Int, - args: FunctionCallArgs? + callSite: CallSite? ): ImmediateCallOptimization? { - if (args == null || args.arguments.isEmpty() || args.arguments.any { it.reg.callingConventionSlot == null }) + if (callSite == null || callSite.arguments.isEmpty() || callSite.arguments.any { it.location !is CallLocation.HardwareRegister }) return null - val loads = mutableMapOf() + val loads = mutableMapOf() // For each call argument, find the most recent LOAD immediate that defines it, // allowing gaps and non-LOAD instructions between the LOAD and the CALL, // as long as there's no intervening write to that register. - for (arg in args.arguments) { - val regId = if (arg.reg.dt == IRDataType.FLOAT) RegId.FloatReg(arg.reg.registerNum) else RegId.IntReg(arg.reg.registerNum.value) + for (arg in callSite.arguments) { + val register = arg.source.register // search backwards for the defining LOAD immediate for (idx in callIndex - 1 downTo chunkStartIndex) { val insn = instructions[idx] // If this instruction writes to the same register, it's the defining write - val writesThisReg = when (regId) { - is RegId.IntReg -> insn.reg1 == regId.num && insn.reg1direction == OperandDirection.WRITE - is RegId.FloatReg -> insn.fpReg1 == regId.num && insn.fpReg1direction == OperandDirection.WRITE - } - if (writesThisReg) { - if (insn.opcode == Opcode.LOAD) { - val isImmediateInt = insn.reg1 != null && insn.immediate != null && regId is RegId.IntReg && insn.reg1 == regId.num - val isImmediateFloat = insn.fpReg1 != null && insn.immediateFp != null && regId is RegId.FloatReg && insn.fpReg1 == regId.num - if ((isImmediateInt || isImmediateFloat) && insn.type == arg.reg.dt) { - loads[regId] = insn - } - } + if (insn.exclusiveDestination() == register) { + if (insn.forwardableImmediateFor(register, arg.source.type) != null) + loads[register] = insn break // found the defining write, whether immediate or not } } diff --git a/codeGenM68k/src/prog8/codegen/m68k/InstrArithmetic.kt b/codeGenM68k/src/prog8/codegen/m68k/InstrArithmetic.kt index 2a28d292c..dfee14f8b 100644 --- a/codeGenM68k/src/prog8/codegen/m68k/InstrArithmetic.kt +++ b/codeGenM68k/src/prog8/codegen/m68k/InstrArithmetic.kt @@ -5,12 +5,6 @@ import prog8.intermediate.* internal fun AsmGen.translateArithmetic(insn: IRInstruction) { val type = insn.type ?: IRDataType.BYTE - val r1 = insn.reg1 - val r2 = insn.reg2 - val imm = insn.immediate - val addr = insn.address - val label = insn.labelSymbol - val offset = insn.labelSymbolOffset if (type == IRDataType.FLOAT) { translateFloatArithmetic(insn) @@ -19,52 +13,52 @@ internal fun AsmGen.translateArithmetic(insn: IRInstruction) { when (insn.opcode) { Opcode.INC -> { - val reg = r1 ?: error("INC needs reg1") + val reg = insn.requireIntDest().intNumber emitLine("addq${dtSuffix(type)} #1, ${regAddr(reg)}") invalidateD0CacheForSlot(reg) } Opcode.INCM -> { - val target = resolveAddress(addr, label, offset) + val target = resolveMemory(insn.requireMemory()) emitLine("addq${dtSuffix(type)} #1, $target") invalidateD0CacheForAddress(target) } Opcode.DEC -> { - val reg = r1 ?: error("DEC needs reg1") + val reg = insn.requireIntDest().intNumber emitLine("subq${dtSuffix(type)} #1, ${regAddr(reg)}") invalidateD0CacheForSlot(reg) } Opcode.DECM -> { - val target = resolveAddress(addr, label, offset) + val target = resolveMemory(insn.requireMemory()) emitLine("subq${dtSuffix(type)} #1, $target") invalidateD0CacheForAddress(target) } Opcode.NEG -> { - val reg = r1 ?: error("NEG needs reg1") + val reg = insn.requireIntDest().intNumber emitLine("neg${dtSuffix(type)} ${regAddr(reg)}") invalidateD0CacheForSlot(reg) } Opcode.NEGM -> { - val target = resolveAddress(addr, label, offset) + val target = resolveMemory(insn.requireMemory()) emitLine("neg${dtSuffix(type)} $target") invalidateD0CacheForAddress(target) } Opcode.ADDR -> { - val dstReg = r1 ?: error("ADDR needs reg1") - val srcReg = r2 ?: error("ADDR needs reg2") + val dstReg = insn.requireIntDest().intNumber + val srcReg = insn.requireIntSourceA().intNumber loadRegOrZeroExtendToD0(srcReg, type) emitLine("add${dtSuffix(type)} d0, ${regAddr(dstReg)}") invalidateD0CacheForSlot(dstReg) } Opcode.ADD -> { - val reg = r1 ?: error("ADD needs reg1") - val value = imm ?: error("ADD needs immediate") + val reg = insn.requireIntDest().intNumber + val value = insn.requireImmediateInt() if(value in 1..8) { emitLine("addq${dtSuffix(type)} #$value, ${regAddr(reg)}") } else { @@ -74,8 +68,8 @@ internal fun AsmGen.translateArithmetic(insn: IRInstruction) { } Opcode.ADDM -> { - val reg = r1 ?: error("ADDM needs reg1") - val target = resolveAddress(addr, label, offset) + val reg = insn.requireIntSourceA().intNumber + val target = resolveMemory(insn.requireMemory()) val sv = dtSuffix(type) emitLoadD0(reg, type) emitLine("add$sv d0, $target") @@ -83,8 +77,8 @@ internal fun AsmGen.translateArithmetic(insn: IRInstruction) { } Opcode.ADDIM -> { - val value = imm ?: error("ADDIM needs immediate") - val target = resolveAddress(addr, label, offset) + val value = insn.requireImmediateInt() + val target = resolveMemory(insn.requireMemory()) val sv = dtSuffix(type) if(value in 1..8) { emitLine("addq$sv #$value,$target") @@ -108,16 +102,16 @@ internal fun AsmGen.translateArithmetic(insn: IRInstruction) { } Opcode.SUBR -> { - val dstReg = r1 ?: error("SUBR needs reg1") - val srcReg = r2 ?: error("SUBR needs reg2") + val dstReg = insn.requireIntDest().intNumber + val srcReg = insn.requireIntSourceA().intNumber loadRegOrZeroExtendToD0(srcReg, type) emitLine("sub${dtSuffix(type)} d0, ${regAddr(dstReg)}") invalidateD0CacheForSlot(dstReg) } Opcode.SUB -> { - val reg = r1 ?: error("SUB needs reg1") - val value = imm ?: error("SUB needs immediate") + val reg = insn.requireIntDest().intNumber + val value = insn.requireImmediateInt() if(value in 1..8) { emitLine("subq${dtSuffix(type)} #$value, ${regAddr(reg)}") } else { @@ -127,8 +121,8 @@ internal fun AsmGen.translateArithmetic(insn: IRInstruction) { } Opcode.SUBM -> { - val reg = r1 ?: error("SUBM needs reg1") - val target = resolveAddress(addr, label, offset) + val reg = insn.requireIntSourceA().intNumber + val target = resolveMemory(insn.requireMemory()) val sv = dtSuffix(type) emitLoadD0(reg, type) emitLine("sub$sv d0, $target") @@ -136,8 +130,8 @@ internal fun AsmGen.translateArithmetic(insn: IRInstruction) { } Opcode.SUBIM -> { - val value = imm ?: error("SUBIM needs immediate") - val target = resolveAddress(addr, label, offset) + val value = insn.requireImmediateInt() + val target = resolveMemory(insn.requireMemory()) val sv = dtSuffix(type) if(value in 1..8) { emitLine("subq$sv #$value,$target") @@ -163,135 +157,49 @@ internal fun AsmGen.translateArithmetic(insn: IRInstruction) { // --- Multiply (unsigned) --- // M68k has no mulu.b or muls.b; use .w with zero-extension for byte. - Opcode.MULR -> { - val dstReg = r1 ?: error("MULR needs reg1") - val srcReg = r2 ?: error("MULR needs reg2") - emitMulOp(dstReg, srcReg, type, unsigned=true, imm=null, target=null) - } - - Opcode.MUL -> { - val reg = r1 ?: error("MUL needs reg1") - val value = imm ?: error("MUL needs immediate") - emitMulOp(reg, null, type, unsigned=true, imm=value, target=null) - } - - Opcode.MULM -> { - val reg = r1 ?: error("MULM needs reg1") - val target = resolveAddress(addr, label, offset) - emitMulOp(reg, null, type, unsigned=true, imm=null, target=target) - } + Opcode.MULR -> emitMulOp(insn.requireIntDest().intNumber, insn.requireIntSourceA().intNumber, type, unsigned=true, imm=null, target=null) + Opcode.MUL -> emitMulOp(insn.requireIntDest().intNumber, null, type, unsigned=true, imm=insn.requireImmediateInt(), target=null) + Opcode.MULM -> emitMulOp(insn.requireIntSourceA().intNumber, null, type, unsigned=true, imm=null, target=resolveMemory(insn.requireMemory())) // --- Multiply (signed) --- - Opcode.MULSR -> { - val dstReg = r1 ?: error("MULSR needs reg1") - val srcReg = r2 ?: error("MULSR needs reg2") - emitMulOp(dstReg, srcReg, type, unsigned=false, imm=null, target=null) - } - - Opcode.MULS -> { - val reg = r1 ?: error("MULS needs reg1") - val value = imm ?: error("MULS needs immediate") - emitMulOp(reg, null, type, unsigned=false, imm=value, target=null) - } - - Opcode.MULSM -> { - val reg = r1 ?: error("MULSM needs reg1") - val target = resolveAddress(addr, label, offset) - emitMulOp(reg, null, type, unsigned=false, imm=null, target=target) - } + Opcode.MULSR -> emitMulOp(insn.requireIntDest().intNumber, insn.requireIntSourceA().intNumber, type, unsigned=false, imm=null, target=null) + Opcode.MULS -> emitMulOp(insn.requireIntDest().intNumber, null, type, unsigned=false, imm=insn.requireImmediateInt(), target=null) + Opcode.MULSM -> emitMulOp(insn.requireIntSourceA().intNumber, null, type, unsigned=false, imm=null, target=resolveMemory(insn.requireMemory())) // --- Divide (unsigned) --- - Opcode.DIVR -> { - val dstReg = r1 ?: error("DIVR needs reg1") - val srcReg = r2 ?: error("DIVR needs reg2") - emitDivOp(dstReg, srcReg, type, unsigned=true, imm=null, target=null) - } - - Opcode.DIV -> { - val reg = r1 ?: error("DIV needs reg1") - val value = imm ?: error("DIV needs immediate") - emitDivOp(reg, null, type, unsigned=true, imm=value, target=null) - } - - Opcode.DIVM -> { - val reg = r1 ?: error("DIVM needs reg1") - val target = resolveAddress(addr, label, offset) - emitDivOp(reg, null, type, unsigned=true, imm=null, target=target) - } + Opcode.DIVR -> emitDivOp(insn.requireIntDest().intNumber, insn.requireIntSourceA().intNumber, type, unsigned=true, imm=null, target=null) + Opcode.DIV -> emitDivOp(insn.requireIntDest().intNumber, null, type, unsigned=true, imm=insn.requireImmediateInt(), target=null) + Opcode.DIVM -> emitDivOp(insn.requireIntSourceA().intNumber, null, type, unsigned=true, imm=null, target=resolveMemory(insn.requireMemory())) // --- Divide (signed) --- - Opcode.DIVSR -> { - val dstReg = r1 ?: error("DIVSR needs reg1") - val srcReg = r2 ?: error("DIVSR needs reg2") - emitDivOp(dstReg, srcReg, type, unsigned=false, imm=null, target=null) - } - - Opcode.DIVS -> { - val reg = r1 ?: error("DIVS needs reg1") - val value = imm ?: error("DIVS needs immediate") - emitDivOp(reg, null, type, unsigned=false, imm=value, target=null) - } - - Opcode.DIVSM -> { - val reg = r1 ?: error("DIVSM needs reg1") - val target = resolveAddress(addr, label, offset) - emitDivOp(reg, null, type, unsigned=false, imm=null, target=target) - } + Opcode.DIVSR -> emitDivOp(insn.requireIntDest().intNumber, insn.requireIntSourceA().intNumber, type, unsigned=false, imm=null, target=null) + Opcode.DIVS -> emitDivOp(insn.requireIntDest().intNumber, null, type, unsigned=false, imm=insn.requireImmediateInt(), target=null) + Opcode.DIVSM -> emitDivOp(insn.requireIntSourceA().intNumber, null, type, unsigned=false, imm=null, target=resolveMemory(insn.requireMemory())) // --- Modulus --- // Use divu.w/divs.w remainder (upper 16 bits) for byte/word; divul/divsl for long (68020+) - Opcode.MODR -> { - val dstReg = r1 ?: error("MODR needs reg1") - val srcReg = r2 ?: error("MODR needs reg2") - emitModOp(dstReg, srcReg, type, unsigned=true, imm=null) - } - - Opcode.MOD -> { - val dstReg = r1 ?: error("MOD needs reg1") - val divisor = imm ?: error("MOD needs immediate") - emitModOp(dstReg, null, type, unsigned=true, imm=divisor) - } - - Opcode.MODSR -> { - val dstReg = r1 ?: error("MODSR needs reg1") - val srcReg = r2 ?: error("MODSR needs reg2") - emitModOp(dstReg, srcReg, type, unsigned=false, imm=null) - } - - Opcode.MODS -> { - val dstReg = r1 ?: error("MODS needs reg1") - val divisor = imm ?: error("MODS needs immediate") - emitModOp(dstReg, null, type, unsigned=false, imm=divisor) - } + Opcode.MODR -> emitModOp(insn.requireIntDest().intNumber, insn.requireIntSourceA().intNumber, type, unsigned=true, imm=null) + Opcode.MOD -> emitModOp(insn.requireIntDest().intNumber, null, type, unsigned=true, imm=insn.requireImmediateInt()) + Opcode.MODSR -> emitModOp(insn.requireIntDest().intNumber, insn.requireIntSourceA().intNumber, type, unsigned=false, imm=null) + Opcode.MODS -> emitModOp(insn.requireIntDest().intNumber, null, type, unsigned=false, imm=insn.requireImmediateInt()) // --- DIVMOD (unsigned) --- // 68020+ divul.l gives quotient + remainder in one instruction - Opcode.DIVMODR -> { - emitDivModOp(r1 ?: error("DIVMODR needs reg1"), r2 ?: error("DIVMODR needs reg2"), type, unsigned=true, imm=null) - } - - Opcode.DIVMOD -> { - emitDivModOp(r1 ?: error("DIVMOD needs reg1"), r2 ?: error("DIVMOD needs reg2"), type, unsigned=true, imm=imm ?: error("DIVMOD needs immediate")) - } - - Opcode.SDIVMODR -> { - emitDivModOp(r1 ?: error("SDIVMODR needs reg1"), r2 ?: error("SDIVMODR needs reg2"), type, unsigned=false, imm=null) - } - - Opcode.SDIVMOD -> { - emitDivModOp(r1 ?: error("SDIVMOD needs reg1"), r2 ?: error("SDIVMOD needs reg2"), type, unsigned=false, imm=imm ?: error("SDIVMOD needs immediate")) - } + Opcode.DIVMODR -> emitDivModOp(insn.requireIntDest().intNumber, insn.requireDestB().intNumber, type, unsigned=true, imm=null) + Opcode.DIVMOD -> emitDivModOp(insn.requireIntDest().intNumber, insn.requireDestB().intNumber, type, unsigned=true, imm=insn.requireImmediateInt()) + Opcode.SDIVMODR -> emitDivModOp(insn.requireIntDest().intNumber, insn.requireDestB().intNumber, type, unsigned=false, imm=null) + Opcode.SDIVMOD -> emitDivModOp(insn.requireIntDest().intNumber, insn.requireDestB().intNumber, type, unsigned=false, imm=insn.requireImmediateInt()) // --- Integer sqrt/square --- Opcode.SQRT -> { - val dstReg = r1 ?: error("SQRT needs reg1 (output)") - val srcReg = r2 ?: error("SQRT needs reg2 (input)") + val dstReg = insn.requireIntDest().intNumber + val srcReg = insn.requireIntSourceA().intNumber when (type) { IRDataType.BYTE -> { emitLine("move.b ${regAddr(srcReg)}, math._sqrt_ub.value", "sqrt byte") @@ -314,8 +222,8 @@ internal fun AsmGen.translateArithmetic(insn: IRInstruction) { } } Opcode.SQUARE -> { - val dstReg = r1 ?: error("SQUARE needs reg1") - val srcReg = r2 ?: error("SQUARE needs reg2") + val dstReg = insn.requireIntDest().intNumber + val srcReg = insn.requireIntSourceA().intNumber when (type) { IRDataType.BYTE -> { emitLoadD0(srcReg, IRDataType.BYTE) @@ -341,17 +249,16 @@ internal fun AsmGen.translateArithmetic(insn: IRInstruction) { } Opcode.CMP -> { - val leftReg = insn.reg1 ?: error("CMP needs reg1") - val rightReg = insn.reg2 ?: error("CMP needs reg2") + val leftReg = insn.requireIntSourceA().intNumber + val rightReg = insn.requireSrcB().intNumber val s = dtSuffix(type) emitLoadD0(leftReg, type) emitLine("cmp$s ${regAddr(rightReg)}, d0") } Opcode.CMPI -> { - val reg = insn.reg1 ?: error("CMPI needs reg1") - val value = insn.immediate ?: error("CMPI needs immediate") - val masked = immVal(value, type) + val reg = insn.requireIntSourceA().intNumber + val masked = immVal(insn.requireImmediateInt(), type) // cmpi.x #0, operand can be replaced by the faster tst.x operand if(masked==0) emitLine("tst${dtSuffix(type)} ${regAddr(reg)}") @@ -810,117 +717,67 @@ private fun AsmGen.emitFloatOpWithConstant(fpReg: RegisterNum, op: String, value } private fun AsmGen.translateFloatArithmetic(insn: IRInstruction) { - val immFp = insn.immediateFp - val fpReg1 = insn.fpReg1 - val fpReg2 = insn.fpReg2 - val addr = insn.address - val label = insn.labelSymbol - val offset = insn.labelSymbolOffset - when (insn.opcode) { - Opcode.INCM -> emitFloatMemUnary("fadd.s", addr, label, offset, immediateStr = "#1.0") - Opcode.DECM -> emitFloatMemUnary("fsub.s", addr, label, offset, immediateStr = "#1.0") - Opcode.NEGM -> emitFloatMemUnary("fneg", addr, label, offset) + Opcode.INCM -> emitFloatMemUnary("fadd.s", resolveMemory(insn.requireMemory(), forFloat = true), immediateStr = "#1.0") + Opcode.DECM -> emitFloatMemUnary("fsub.s", resolveMemory(insn.requireMemory(), forFloat = true), immediateStr = "#1.0") + Opcode.NEGM -> emitFloatMemUnary("fneg", resolveMemory(insn.requireMemory(), forFloat = true)) Opcode.INC -> { - emitLine("fmove.s ${floatRegFileAddr(fpReg1!!)}, $FP_ACC") + val dst = insn.requireFloatDest().floatNumber + emitLine("fmove.s ${floatRegFileAddr(dst)}, $FP_ACC") emitLine("fadd.s #1.0, $FP_ACC") - emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(fpReg1)}") + emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(dst)}") } Opcode.DEC -> { - emitLine("fmove.s ${floatRegFileAddr(fpReg1!!)}, $FP_ACC") + val dst = insn.requireFloatDest().floatNumber + emitLine("fmove.s ${floatRegFileAddr(dst)}, $FP_ACC") emitLine("fsub.s #1.0, $FP_ACC") - emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(fpReg1)}") + emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(dst)}") } Opcode.NEG -> { - emitLine("fmove.s ${floatRegFileAddr(fpReg1!!)}, $FP_ACC") + val dst = insn.requireFloatDest().floatNumber + emitLine("fmove.s ${floatRegFileAddr(dst)}, $FP_ACC") emitLine("fneg $FP_ACC, $FP_ACC") - emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(fpReg1)}") + emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(dst)}") } - Opcode.ADDR -> { - val src = fpReg2 ?: error("ADDR.f needs fpReg2") - emitFloatBinaryOp("fadd", fpReg1!!, src) - } - Opcode.ADD -> when { - immFp != null -> emitFloatOpWithConstant(fpReg1!!, "fadd", immFp) - else -> TODO("FLOAT ADD without immediate") - } + Opcode.ADDR -> emitFloatBinaryOp("fadd", insn.requireFloatDest().floatNumber, insn.requireFloatSourceA().floatNumber) + Opcode.ADD -> emitFloatOpWithConstant(insn.requireFloatDest().floatNumber, "fadd", insn.requireImmediateFloat()) - Opcode.SUBR -> { - val src = fpReg2 ?: error("SUBR.f needs fpReg2") - emitFloatBinaryOp("fsub", fpReg1!!, src) - } - Opcode.SUB -> when { - immFp != null -> emitFloatOpWithConstant(fpReg1!!, "fsub", immFp) - else -> TODO("FLOAT SUB without immediate") - } + Opcode.SUBR -> emitFloatBinaryOp("fsub", insn.requireFloatDest().floatNumber, insn.requireFloatSourceA().floatNumber) + Opcode.SUB -> emitFloatOpWithConstant(insn.requireFloatDest().floatNumber, "fsub", insn.requireImmediateFloat()) - Opcode.MULR -> { - val src = fpReg2 ?: error("MULR.f needs fpReg2") - emitFloatBinaryOp("fmul", fpReg1!!, src) - } - Opcode.MUL -> when { - immFp != null -> emitFloatOpWithConstant(fpReg1!!, "fmul", immFp) - else -> TODO("FLOAT MUL without immediate") - } + Opcode.MULR, Opcode.MULSR -> emitFloatBinaryOp("fmul", insn.requireFloatDest().floatNumber, insn.requireFloatSourceA().floatNumber) + Opcode.MUL, Opcode.MULS -> emitFloatOpWithConstant(insn.requireFloatDest().floatNumber, "fmul", insn.requireImmediateFloat()) - Opcode.DIVR -> { - val src = fpReg2 ?: error("DIVR.f needs fpReg2") - emitFloatBinaryOp("fdiv", fpReg1!!, src) - } - Opcode.DIV -> when { - immFp != null -> emitFloatOpWithConstant(fpReg1!!, "fdiv", immFp) - else -> TODO("FLOAT DIV without immediate") - } - - Opcode.MULSR -> { - val src = fpReg2 ?: error("MULSR.f needs fpReg2") - emitFloatBinaryOp("fmul", fpReg1!!, src) - } - Opcode.MULS -> when { - immFp != null -> emitFloatOpWithConstant(fpReg1!!, "fmul", immFp) - else -> TODO("FLOAT MULS without immediate") - } - - Opcode.DIVSR -> { - val src = fpReg2 ?: error("DIVSR.f needs fpReg2") - emitFloatBinaryOp("fdiv", fpReg1!!, src) - } - Opcode.DIVS -> when { - immFp != null -> emitFloatOpWithConstant(fpReg1!!, "fdiv", immFp) - else -> TODO("FLOAT DIVS without immediate") - } + Opcode.DIVR, Opcode.DIVSR -> emitFloatBinaryOp("fdiv", insn.requireFloatDest().floatNumber, insn.requireFloatSourceA().floatNumber) + Opcode.DIV, Opcode.DIVS -> emitFloatOpWithConstant(insn.requireFloatDest().floatNumber, "fdiv", insn.requireImmediateFloat()) Opcode.SQRT -> { - val src = fpReg2 ?: error("SQRT.f needs fpReg2") + val dst = insn.requireFloatDest().floatNumber + val src = insn.requireFloatSourceA().floatNumber emitLine("fmove.s ${floatRegFileAddr(src)}, $FP_ACC") emitLine("fsqrt $FP_ACC, $FP_ACC") - emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(fpReg1!!)}") + emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(dst)}") } - Opcode.SQUARE -> { - val src = fpReg2 ?: error("SQUARE.f needs fpReg2") - emitFloatBinaryOp("fmul", fpReg1!!, src) - } + Opcode.SQUARE -> emitFloatBinaryOp("fmul", insn.requireFloatDest().floatNumber, insn.requireFloatSourceA().floatNumber) - Opcode.ADDM -> emitFloatMemBinary("fadd", fpReg1!!, addr, label, offset) - Opcode.SUBM -> emitFloatMemBinary("fsub", fpReg1!!, addr, label, offset) - Opcode.MULM, Opcode.MULSM -> emitFloatMemBinary("fmul", fpReg1!!, addr, label, offset) - Opcode.DIVM, Opcode.DIVSM -> emitFloatMemBinary("fdiv", fpReg1!!, addr, label, offset) + Opcode.ADDM -> emitFloatMemBinary("fadd", insn.requireFloatSourceA().floatNumber, resolveMemory(insn.requireMemory(), forFloat = true)) + Opcode.SUBM -> emitFloatMemBinary("fsub", insn.requireFloatSourceA().floatNumber, resolveMemory(insn.requireMemory(), forFloat = true)) + Opcode.MULM, Opcode.MULSM -> emitFloatMemBinary("fmul", insn.requireFloatSourceA().floatNumber, resolveMemory(insn.requireMemory(), forFloat = true)) + Opcode.DIVM, Opcode.DIVSM -> emitFloatMemBinary("fdiv", insn.requireFloatSourceA().floatNumber, resolveMemory(insn.requireMemory(), forFloat = true)) - Opcode.ADDIM -> emitFloatMemBinaryImmediate("fadd", immFp, addr, label, offset) - Opcode.SUBIM -> emitFloatMemBinaryImmediate("fsub", immFp, addr, label, offset) + Opcode.ADDIM -> emitFloatMemBinaryImmediate("fadd", insn.requireImmediateFloat(), resolveMemory(insn.requireMemory(), forFloat = true)) + Opcode.SUBIM -> emitFloatMemBinaryImmediate("fsub", insn.requireImmediateFloat(), resolveMemory(insn.requireMemory(), forFloat = true)) else -> TODO("FLOAT arithmetic: ${insn.opcode}") } } -private fun AsmGen.emitFloatMemBinaryImmediate(op: String, value: Double?, addr: MemoryAddress?, label: String?, offset: Int?) { - val target = resolveAddress(addr, label, offset) - val v = value ?: error("float mem op with constant needs immediate value") +private fun AsmGen.emitFloatMemBinaryImmediate(op: String, value: Double, target: String) { emitLine("fmove.s $target,$FP_ACC") - when (v) { + when (value) { 0.0 -> { emitLine("fmove.s #0.0,$FP_SRC"); emitLine("$op $FP_SRC,$FP_ACC") } 1.0 -> { if (op == "fadd" || op == "fsub") { @@ -930,7 +787,7 @@ private fun AsmGen.emitFloatMemBinaryImmediate(op: String, value: Double?, addr: } } else -> { - val lbl = makeFloatConstLabel(v) + val lbl = makeFloatConstLabel(value) emitLine("lea $lbl,a0") emitLine("fmove.s (a0),$FP_SRC") emitLine("$op $FP_SRC,$FP_ACC") @@ -939,8 +796,7 @@ private fun AsmGen.emitFloatMemBinaryImmediate(op: String, value: Double?, addr: emitLine("fmove.s $FP_ACC,$target") } -private fun AsmGen.emitFloatMemUnary(op: String, addr: MemoryAddress?, label: String?, offset: Int?, immediateStr: String? = null) { - val target = resolveAddress(addr, label, offset) +private fun AsmGen.emitFloatMemUnary(op: String, target: String, immediateStr: String? = null) { emitLine("fmove.s $target, fp0") if (immediateStr != null) emitLine("$op $immediateStr, fp0") @@ -949,8 +805,7 @@ private fun AsmGen.emitFloatMemUnary(op: String, addr: MemoryAddress?, label: St emitLine("fmove.s fp0, $target") } -private fun AsmGen.emitFloatMemBinary(op: String, fpReg1: RegisterNum, addr: MemoryAddress?, label: String?, offset: Int?) { - val target = resolveAddress(addr, label, offset) +private fun AsmGen.emitFloatMemBinary(op: String, fpReg1: RegisterNum, target: String) { emitLine("fmove.s $target, $FP_ACC") emitLine("fmove.s ${floatRegFileAddr(fpReg1)}, $FP_SRC") emitLine("$op $FP_SRC, $FP_ACC") diff --git a/codeGenM68k/src/prog8/codegen/m68k/InstrBitwise.kt b/codeGenM68k/src/prog8/codegen/m68k/InstrBitwise.kt index 8ed21d123..3391415f5 100644 --- a/codeGenM68k/src/prog8/codegen/m68k/InstrBitwise.kt +++ b/codeGenM68k/src/prog8/codegen/m68k/InstrBitwise.kt @@ -17,113 +17,64 @@ import prog8.code.target.Qemu68kTarget import prog8.intermediate.IRDataType import prog8.intermediate.IRInstruction import prog8.intermediate.Opcode +import prog8.intermediate.intNumber +import prog8.intermediate.requireImmediateInt +import prog8.intermediate.requireIntDest +import prog8.intermediate.requireIntSourceA +import prog8.intermediate.requireMemory internal fun AsmGen.translateBitwise(insn: IRInstruction) { val type = insn.type ?: IRDataType.BYTE - val r1 = insn.reg1 - val r2 = insn.reg2 - val imm = insn.immediate - val addr = insn.address - val label = insn.labelSymbol - val offset = insn.labelSymbolOffset 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 sourceAddress = resolveAddress(addr, label, offset) - andMemory(r1 ?: error("ANDM needs reg1"), sourceAddress, type) - } + Opcode.ANDR -> andRegisters(insn.requireIntDest().intNumber, insn.requireIntSourceA().intNumber, type) + Opcode.AND -> andImmediate(insn.requireIntDest().intNumber, insn.requireImmediateInt(), type) + Opcode.ANDM -> andMemory(insn.requireIntSourceA().intNumber, resolveMemory(insn.requireMemory()), 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 sourceAddress = resolveAddress(addr, label, offset) - orMemory(r1 ?: error("ORM needs reg1"), sourceAddress, type) - } + Opcode.ORR -> orRegisters(insn.requireIntDest().intNumber, insn.requireIntSourceA().intNumber, type) + Opcode.OR -> orImmediate(insn.requireIntDest().intNumber, insn.requireImmediateInt(), type) + Opcode.ORM -> orMemory(insn.requireIntSourceA().intNumber, resolveMemory(insn.requireMemory()), 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 sourceAddress = resolveAddress(addr, label, offset) - xorMemory(r1 ?: error("XORM needs reg1"), sourceAddress, type) - } + Opcode.XORR -> xorRegisters(insn.requireIntDest().intNumber, insn.requireIntSourceA().intNumber, type) + Opcode.XOR -> xorImmediate(insn.requireIntDest().intNumber, insn.requireImmediateInt(), type) + Opcode.XORM -> xorMemory(insn.requireIntSourceA().intNumber, resolveMemory(insn.requireMemory()), type) - Opcode.INV -> invertRegister(r1 ?: error("INV needs reg1"), type) - Opcode.INVM -> invertMemory(resolveAddress(addr, label, offset), type) + Opcode.INV -> invertRegister(insn.requireIntDest().intNumber, type) + Opcode.INVM -> invertMemory(resolveMemory(insn.requireMemory()), type) - 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) - } + Opcode.ASRN -> arithmeticShiftRightVar(insn.requireIntDest().intNumber, insn.requireIntSourceA().intNumber, type) + Opcode.LSRN -> logicalShiftRightVar(insn.requireIntDest().intNumber, insn.requireIntSourceA().intNumber, type) + Opcode.LSLN -> logicalShiftLeftVar(insn.requireIntDest().intNumber, insn.requireIntSourceA().intNumber, type) - Opcode.ASRNM -> shiftMemoryVar(resolveAddress(addr, label, offset), r1 ?: error("ASRNM needs reg1"), type, isArithmetic = true) - Opcode.LSRNM -> shiftMemoryVar(resolveAddress(addr, label, offset), r1 ?: error("LSRNM needs reg1"), type, isArithmetic = false) - Opcode.LSLNM -> shiftMemoryLeftVar(resolveAddress(addr, label, offset), r1 ?: error("LSLNM needs reg1"), type) + Opcode.ASRNM -> shiftMemoryVar(resolveMemory(insn.requireMemory()), insn.requireIntSourceA().intNumber, type, isArithmetic = true) + Opcode.LSRNM -> shiftMemoryVar(resolveMemory(insn.requireMemory()), insn.requireIntSourceA().intNumber, type, isArithmetic = false) + Opcode.LSLNM -> shiftMemoryLeftVar(resolveMemory(insn.requireMemory()), insn.requireIntSourceA().intNumber, type) - // Immediate-count shifts (LSLI/LSRI/ASRI): the count is a literal in insn.immediate. - Opcode.ASRI -> shiftRegister(r1 ?: error("ASRI needs reg1"), imm ?: error("ASRI needs immediate count"), type, isArithmetic = true, isLeft = false) - Opcode.LSRI -> shiftRegister(r1 ?: error("LSRI needs reg1"), imm ?: error("LSRI needs immediate count"), type, isArithmetic = false, isLeft = false) - Opcode.LSLI -> shiftRegister(r1 ?: error("LSLI needs reg1"), imm ?: error("LSLI needs immediate count"), type, isArithmetic = false, isLeft = true) + // Immediate-count shifts (LSLI/LSRI/ASRI): the count is a literal in the immediate operand. + Opcode.ASRI -> shiftRegister(insn.requireIntDest().intNumber, insn.requireImmediateInt(), type, isArithmetic = true, isLeft = false) + Opcode.LSRI -> shiftRegister(insn.requireIntDest().intNumber, insn.requireImmediateInt(), type, isArithmetic = false, isLeft = false) + Opcode.LSLI -> shiftRegister(insn.requireIntDest().intNumber, insn.requireImmediateInt(), type, isArithmetic = false, isLeft = true) - Opcode.ASR -> shiftRegister(r1 ?: error("ASR needs reg1"), 1, type, isArithmetic = true, isLeft = false) - Opcode.ASRM -> memoryShiftRotate(resolveAddress(addr, label, offset), 1, type, isArithmetic = true, isLeft = false, isRotate = false, throughCarry = false) - Opcode.LSR -> shiftRegister(r1 ?: error("LSR needs reg1"), 1, type, isArithmetic = false, isLeft = false) - Opcode.LSRM -> memoryShiftRotate(resolveAddress(addr, label, offset), 1, type, isArithmetic = false, isLeft = false, isRotate = false, throughCarry = false) - Opcode.LSL -> shiftRegister(r1 ?: error("LSL needs reg1"), 1, type, isArithmetic = false, isLeft = true) - Opcode.LSLM -> memoryShiftRotate(resolveAddress(addr, label, offset), 1, type, isArithmetic = false, isLeft = true, isRotate = false, throughCarry = false) + Opcode.ASR -> shiftRegister(insn.requireIntDest().intNumber, 1, type, isArithmetic = true, isLeft = false) + Opcode.ASRM -> memoryShiftRotate(resolveMemory(insn.requireMemory()), 1, type, isArithmetic = true, isLeft = false, isRotate = false, throughCarry = false) + Opcode.LSR -> shiftRegister(insn.requireIntDest().intNumber, 1, type, isArithmetic = false, isLeft = false) + Opcode.LSRM -> memoryShiftRotate(resolveMemory(insn.requireMemory()), 1, type, isArithmetic = false, isLeft = false, isRotate = false, throughCarry = false) + Opcode.LSL -> shiftRegister(insn.requireIntDest().intNumber, 1, type, isArithmetic = false, isLeft = true) + Opcode.LSLM -> memoryShiftRotate(resolveMemory(insn.requireMemory()), 1, type, isArithmetic = false, isLeft = true, isRotate = false, throughCarry = false) - Opcode.ROR -> rotateRight(r1 ?: error("ROR needs reg1"), type) - Opcode.RORM -> memoryShiftRotate(resolveAddress(addr, label, offset), 1, type, isArithmetic = false, isLeft = false, isRotate = true, throughCarry = false) - Opcode.ROL -> rotateLeft(r1 ?: error("ROL needs reg1"), type) - Opcode.ROLM -> memoryShiftRotate(resolveAddress(addr, label, offset), 1, type, isArithmetic = false, isLeft = true, isRotate = true, throughCarry = false) - Opcode.ROXR -> rotateRightThroughCarry(r1 ?: error("ROXR needs reg1"), type) - Opcode.ROXRM -> memoryShiftRotate(resolveAddress(addr, label, offset), 1, type, isArithmetic = false, isLeft = false, isRotate = true, throughCarry = true) - Opcode.ROXL -> rotateLeftThroughCarry(r1 ?: error("ROXL needs reg1"), type) - Opcode.ROXLM -> memoryShiftRotate(resolveAddress(addr, label, offset), 1, type, isArithmetic = false, isLeft = true, isRotate = true, throughCarry = true) + Opcode.ROR -> rotateRight(insn.requireIntDest().intNumber, type) + Opcode.RORM -> memoryShiftRotate(resolveMemory(insn.requireMemory()), 1, type, isArithmetic = false, isLeft = false, isRotate = true, throughCarry = false) + Opcode.ROL -> rotateLeft(insn.requireIntDest().intNumber, type) + Opcode.ROLM -> memoryShiftRotate(resolveMemory(insn.requireMemory()), 1, type, isArithmetic = false, isLeft = true, isRotate = true, throughCarry = false) + Opcode.ROXR -> rotateRightThroughCarry(insn.requireIntDest().intNumber, type) + Opcode.ROXRM -> memoryShiftRotate(resolveMemory(insn.requireMemory()), 1, type, isArithmetic = false, isLeft = false, isRotate = true, throughCarry = true) + Opcode.ROXL -> rotateLeftThroughCarry(insn.requireIntDest().intNumber, type) + Opcode.ROXLM -> memoryShiftRotate(resolveMemory(insn.requireMemory()), 1, type, isArithmetic = false, isLeft = true, isRotate = true, throughCarry = true) - 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) - } + Opcode.BITTST -> bitTest(insn.requireIntSourceA().intNumber, insn.requireImmediateInt(), type) + Opcode.BITSET -> bitSet(insn.requireIntDest().intNumber, insn.requireImmediateInt(), type) + Opcode.BITCLR -> bitClear(insn.requireIntDest().intNumber, insn.requireImmediateInt(), type) + Opcode.BITTOG -> bitToggle(insn.requireIntDest().intNumber, insn.requireImmediateInt(), type) else -> error("Unknown bitwise opcode: ${insn.opcode}") } diff --git a/codeGenM68k/src/prog8/codegen/m68k/InstrBranch.kt b/codeGenM68k/src/prog8/codegen/m68k/InstrBranch.kt index 162a217ec..0d660f6dd 100644 --- a/codeGenM68k/src/prog8/codegen/m68k/InstrBranch.kt +++ b/codeGenM68k/src/prog8/codegen/m68k/InstrBranch.kt @@ -17,18 +17,21 @@ package prog8.codegen.m68k +import prog8.intermediate.CodeReference import prog8.intermediate.IRDataType import prog8.intermediate.IRInstruction import prog8.intermediate.Opcode +import prog8.intermediate.intNumber +import prog8.intermediate.requireImmediateInt +import prog8.intermediate.requireSrcA +import prog8.intermediate.requireSrcB +import prog8.intermediate.requireTarget internal fun AsmGen.translateBranch(insn: IRInstruction) { - val target = insn.branchTarget - val ls = insn.labelSymbol - - val label: String = when { - target?.label != null -> fixNameSymbols(target.label!!) - ls != null -> fixNameSymbols(ls) - else -> "unknown_target" + val label: String = when (val target = insn.requireTarget()) { + is CodeReference.Label -> fixNameSymbols(target.name) + is CodeReference.Absolute -> target.address.toHex() + is CodeReference.Indirect -> error("branch needs a static target") } when (insn.opcode) { @@ -69,8 +72,8 @@ internal fun AsmGen.translateBranch(insn: IRInstruction) { private fun AsmGen.cmpBranchUnsignedImm(insn: IRInstruction, label: String, branchOp: String) { val type = insn.type ?: IRDataType.BYTE - val reg = insn.reg1 ?: error("branch needs reg1") - val imm = insn.immediate ?: error("unsigned branch needs immediate") + val reg = insn.requireSrcA().intNumber + val imm = insn.requireImmediateInt() val s = dtSuffix(type) // cmpi.x #imm, and tst.x accept memory operands, so the // register-file load into d0 is redundant @@ -85,11 +88,11 @@ private fun AsmGen.cmpBranchUnsignedImm(insn: IRInstruction, label: String, bran private fun AsmGen.cmpBranchUnsignedReg(insn: IRInstruction, label: String, branchOp: String) { val type = insn.type ?: IRDataType.BYTE - val reg1 = insn.reg1 ?: error("branch needs reg1") - val reg2 = insn.reg2 ?: error("reg branch needs reg2") + val left = insn.requireSrcA().intNumber + val right = insn.requireSrcB().intNumber val s = dtSuffix(type) - emitLoadD0(reg1, type) - emitLine("cmp$s ${regAddr(reg2)}, d0") + emitLoadD0(left, type) + emitLine("cmp$s ${regAddr(right)}, d0") emitBranch(branchOp, label) } @@ -97,8 +100,8 @@ private fun AsmGen.cmpBranchUnsignedReg(insn: IRInstruction, label: String, bran private fun AsmGen.cmpBranchSignedImm(insn: IRInstruction, label: String, branchOp: String) { val type = insn.type ?: IRDataType.BYTE - val reg = insn.reg1 ?: error("branch needs reg1") - val imm = insn.immediate ?: error("signed branch needs immediate") + val reg = insn.requireSrcA().intNumber + val imm = insn.requireImmediateInt() val s = dtSuffix(type) // cmpi.x #imm, and tst.x accept memory operands, so the // register-file load into d0 is redundant @@ -113,11 +116,11 @@ private fun AsmGen.cmpBranchSignedImm(insn: IRInstruction, label: String, branch private fun AsmGen.cmpBranchSignedReg(insn: IRInstruction, label: String, branchOp: String) { val type = insn.type ?: IRDataType.BYTE - val reg1 = insn.reg1 ?: error("branch needs reg1") - val reg2 = insn.reg2 ?: error("reg branch needs reg2") + val left = insn.requireSrcA().intNumber + val right = insn.requireSrcB().intNumber val s = dtSuffix(type) - emitLoadD0(reg1, type) - emitLine("cmp$s ${regAddr(reg2)}, d0") + emitLoadD0(left, type) + emitLine("cmp$s ${regAddr(right)}, d0") emitBranch(branchOp, label) } diff --git a/codeGenM68k/src/prog8/codegen/m68k/InstrControl.kt b/codeGenM68k/src/prog8/codegen/m68k/InstrControl.kt index 279af69b0..f4117367f 100644 --- a/codeGenM68k/src/prog8/codegen/m68k/InstrControl.kt +++ b/codeGenM68k/src/prog8/codegen/m68k/InstrControl.kt @@ -22,27 +22,19 @@ import prog8.code.target.Amiga500Target import prog8.intermediate.* internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall: ImmediateCallOptimization? = null) { - 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 labelTarget = label?.let { fixNameSymbols(it) } invalidateD0Cache() - if (labelTarget != null) { - // PC-relative branch; vasm picks the optimal size and falls back to jmp if out of range - emitLine("bra $labelTarget") - } else { - val target = addr?.value?.toHex() ?: error("JUMP needs target") - emitLine("jmp $target") + when (val t = insn.requireTarget()) { + is CodeReference.Label -> emitLine("bra ${fixNameSymbols(t.name)}") // PC-relative branch; vasm picks the optimal size and falls back to jmp if out of range + is CodeReference.Absolute -> emitLine("jmp ${t.address.value.toHex()}") + is CodeReference.Indirect -> error("JUMP needs target") } } Opcode.JUMPI -> { - val reg = r1 ?: error("JUMPI needs reg1") + val ref = insn.requireTarget() as? CodeReference.Indirect ?: error("JUMPI needs an indirect target register") + val reg = ref.pointer.intNumber invalidateD0Cache() if(program.options.compTarget.cpu >= CpuType.M68020) { // 68020+ supports memory-indirect addressing: fetch the target address from memory directly. @@ -54,14 +46,17 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall } Opcode.CALL -> { - val fnLabel = label?.let { fixNameSymbols(it) } ?: addr?.value?.toHex() ?: error("CALL needs label or address") - val args = insn.fcallArgs + val callSite = insn.requireCallSite() + val fnLabel = insn.labelTarget?.let { fixNameSymbols(it) } + ?: (insn.codeTarget as? CodeReference.Absolute)?.address?.value?.toHex() + ?: error("CALL needs label or address") invalidateD0Cache() - translateCall(fnLabel, args, forwardedImmediateCall) + translateCall(fnLabel, callSite, forwardedImmediateCall) } Opcode.CALLI -> { - val reg = r1 ?: error("CALLI needs reg1") + val ref = insn.codeTarget as? CodeReference.Indirect ?: error("CALLI needs an indirect target register") + val reg = ref.pointer.intNumber invalidateD0Cache() if(program.options.compTarget.cpu >= CpuType.M68020) { // 68020+ supports memory-indirect addressing: fetch the target address from memory directly. @@ -74,13 +69,15 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall Opcode.CALLFAR -> { // On the amiga target, CALLFAR is repurposed to encode automatic library LVO calls: - // bank = library number (1=exec, 2=dos, 3=graphics, 4=intuition, ...) - // address = negative LVO offset (the offset into the library's jump table) - // extSubName = symbolic function name, emitted as the displacement in jsr Symbol(a6) + // library = library number (1=exec, 2=dos, 3=graphics, 4=intuition, ...) + // lvo = negative LVO offset (the offset into the library's jump table) + // name = symbolic function name, emitted as the displacement in jsr Symbol(a6) if(program.options.compTarget.name.contains("amiga")) { - val offset = insn.address!!.value.toInt() + val callSite = insn.requireCallSite() + val amigaTarget = callSite.target as? CallTarget.AmigaLibrary ?: error("CALLFAR needs an amiga library target") + val offset = amigaTarget.lvo require(offset<0) { "amiga libcall offsets should all be <0" } - val bank = insn.immediate!! + val bank = amigaTarget.library if(bank==1) { // exec.library is always at 4.w emitLine("move.l 4.w,a6") @@ -90,12 +87,11 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall emitLine("move.l $baseVar,a6") } - val fcallArgs = insn.fcallArgs!! invalidateD0Cache() - for(arg in fcallArgs.arguments) + for(arg in callSite.arguments) translateArgument(arg) - if(insn.extSubName!=null) { - emitLine("jsr ${insn.extSubName}(a6)") // do the actual call (symbolic ref) + if(amigaTarget.name!=null) { + emitLine("jsr ${amigaTarget.name}(a6)") // do the actual call (symbolic ref) } else { emitLine("jsr $offset(a6)") // do the actual call } @@ -105,9 +101,9 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall // In single-return context, process slot returns normally. // LIMITATION: multiple status flag returns in one multi-assign are not supported // (codegen limitation: first flag extraction clobbers flags before second can be read). - val isMultiReturn = fcallArgs.returns.size > 1 - for(ret in fcallArgs.returns) { - if (ret.statusflag != null) + val isMultiReturn = callSite.results.size > 1 + for(ret in callSite.results) { + if (ret.location is CallLocation.StatusFlag) continue if (isMultiReturn) continue @@ -122,11 +118,11 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall } Opcode.SYSCALL -> { - val num = imm ?: 0 - val args = insn.fcallArgs + val callSite = insn.requireCallSite() + val num = (callSite.target as CallTarget.SystemCall).number invalidateD0Cache() - if (args != null) - translateSyscall(num, args) + if (callSite.arguments.isNotEmpty()) + translateSyscall(num, callSite) else emitLine("; syscall #$num (no args)") } @@ -140,10 +136,10 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall invalidateD0Cache() val type = insn.type ?: IRDataType.BYTE if (type == IRDataType.FLOAT) { - val fpReg = insn.fpReg1 ?: error("RETURNR.f needs fpReg1") + val fpReg = insn.requireFloatSourceA().floatNumber emitLine("fmove.s ${floatRegFileAddr(fpReg)}, $FP_ACC") } else { - val reg = r1 ?: error("RETURNR needs reg1") + val reg = insn.requireIntSourceA().intNumber emitLoadD0(reg, type) } emitLine("rts") @@ -151,7 +147,7 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall Opcode.RETURNI -> { invalidateD0Cache() - val value = imm ?: 0 + val value = insn.immediate?.integerValue ?: 0 val type = insn.type ?: IRDataType.BYTE val s = dtSuffix(type) if (value in -128..127) @@ -165,7 +161,7 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall Opcode.PUSH -> { invalidateD0Cache() - val reg = r1 ?: error("PUSH needs reg1") + val reg = insn.requireIntSourceA().intNumber val type = insn.type ?: IRDataType.BYTE val s = dtSuffix(type) emitLine("move$s ${regAddr(reg)}, -(sp)") @@ -173,7 +169,7 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall Opcode.POP -> { invalidateD0Cache() - val reg = r1 ?: error("POP needs reg1") + val reg = insn.requireIntDest().intNumber val type = insn.type ?: IRDataType.BYTE val s = dtSuffix(type) emitLine("move$s (sp)+, ${regAddr(reg)}") @@ -210,54 +206,54 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall Opcode.SEI -> emitLine($$"ori #$04, ccr") Opcode.ALIGN -> { - val alignment = imm ?: 2 + val alignment = insn.immediate?.integerValue ?: 2 emitLine("ALIGN $alignment") } // === Byte/word extraction (no Scc needed) === Opcode.LSIGB -> { - val dstReg = r1 ?: error("LSIGB needs reg1") - val srcReg = r2 ?: error("LSIGB needs reg2") + val dstReg = insn.requireIntDest().intNumber + val srcReg = insn.requireIntSourceA().intNumber val type = insn.type ?: IRDataType.WORD emitLoadD0(srcReg, type) emitStoreD0(dstReg, IRDataType.BYTE) } Opcode.LSIGW -> { - val dstReg = r1 ?: error("LSIGW needs reg1") - val srcReg = r2 ?: error("LSIGW needs reg2") + val dstReg = insn.requireIntDest().intNumber + val srcReg = insn.requireIntSourceA().intNumber emitLoadD0(srcReg, IRDataType.LONG) emitStoreD0(dstReg, IRDataType.WORD) } Opcode.MSIGB -> { - val dstReg = r1 ?: error("MSIGB needs reg1") - val srcReg = r2 ?: error("MSIGB needs reg2") + val dstReg = insn.requireIntDest().intNumber + val srcReg = insn.requireIntSourceA().intNumber // Big-endian: MSB is at byte offset 0 for both word and long. emitLoadD0FromAddress(regAddrByte(srcReg, 0), IRDataType.BYTE) emitStoreD0(dstReg, IRDataType.BYTE) } Opcode.MSIGW -> { - val dstReg = r1 ?: error("MSIGW needs reg1") - val srcReg = r2 ?: error("MSIGW needs reg2") + val dstReg = insn.requireIntDest().intNumber + val srcReg = insn.requireIntSourceA().intNumber // Big-endian: word 0 (offset+0) is most significant. Extract directly. emitLoadD0FromAddress(regAddrByte(srcReg, 0), IRDataType.WORD) emitStoreD0(dstReg, IRDataType.WORD) } Opcode.BSIGB -> { - val dstReg = r1 ?: error("BSIGB needs reg1") - val srcReg = r2 ?: error("BSIGB needs reg2") + val dstReg = insn.requireIntDest().intNumber + val srcReg = insn.requireIntSourceA().intNumber // Big-endian: bits 16-23 are at byte offset 1 of a long. emitLoadD0FromAddress(regAddrByte(srcReg, 1), IRDataType.BYTE) emitStoreD0(dstReg, IRDataType.BYTE) } Opcode.MIDB -> { - val dstReg = r1 ?: error("MIDB needs reg1") - val srcReg = r2 ?: error("MIDB needs reg2") + val dstReg = insn.requireIntDest().intNumber + val srcReg = insn.requireIntSourceA().intNumber // Big-endian: bits 8-15 are at byte offset 2 of a long. emitLoadD0FromAddress(regAddrByte(srcReg, 2), IRDataType.BYTE) emitStoreD0(dstReg, IRDataType.BYTE) @@ -266,8 +262,8 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall // === Sign/zero extension === Opcode.EXT -> { - val dstReg = r1 ?: error("EXT needs reg1") - val srcReg = r2 ?: error("EXT needs reg2") + val dstReg = insn.requireIntDest().intNumber + val srcReg = insn.requireIntSourceA().intNumber val type = insn.type ?: IRDataType.WORD when (type) { IRDataType.BYTE -> { @@ -294,8 +290,8 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall } Opcode.EXTS -> { - val dstReg = r1 ?: error("EXTS needs reg1") - val srcReg = r2 ?: error("EXTS needs reg2") + val dstReg = insn.requireIntDest().intNumber + val srcReg = insn.requireIntSourceA().intNumber val type = insn.type ?: IRDataType.WORD when (type) { IRDataType.BYTE -> { @@ -320,8 +316,8 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall } Opcode.EXTL -> { - val dstReg = r1 ?: error("EXTL needs reg1") - val srcReg = r2 ?: error("EXTL needs reg2") + val dstReg = insn.requireIntDest().intNumber + val srcReg = insn.requireIntSourceA().intNumber // zero-extend byte to long: move.b only writes the low byte, so clear d0 first invalidateD0Cache() emitLine("moveq #0, d0") @@ -330,8 +326,8 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall } Opcode.EXTLS -> { - val dstReg = r1 ?: error("EXTLS needs reg1") - val srcReg = r2 ?: error("EXTLS needs reg2") + val dstReg = insn.requireIntDest().intNumber + val srcReg = insn.requireIntSourceA().intNumber // sign-extend byte to long emitLoadD0(srcReg, IRDataType.BYTE) emitSignExtendByteToLong("d0") @@ -341,9 +337,9 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall // === Concatenation === Opcode.CONCAT -> { - val dstReg = r1 ?: error("CONCAT needs reg1") - val srcReg2 = r2 ?: error("CONCAT needs reg2") - val srcReg3 = insn.reg3 ?: error("CONCAT needs reg3") + val dstReg = insn.requireIntDest().intNumber + val srcReg2 = insn.requireIntSourceA().intNumber + val srcReg3 = insn.requireSrcB().intNumber val type = insn.type ?: IRDataType.BYTE when (type) { IRDataType.BYTE -> { @@ -370,9 +366,9 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall // === Math === Opcode.SGN -> { - val dstReg = r1 ?: error("SGN needs reg1") + val dstReg = insn.requireIntDest().intNumber if (insn.type == IRDataType.FLOAT) { - val srcFp = insn.fpReg1 ?: error("SGN.f needs fpReg1") + val srcFp = insn.requireFloatSourceA().floatNumber invalidateD0Cache() emitLine("ftst.s ${floatRegFileAddr(srcFp)}") emitLine("fslt d0") @@ -381,7 +377,7 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall emitLine("or.b d1, d0") emitStoreD0(dstReg, IRDataType.BYTE) } else { - val srcReg = r2 ?: error("SGN needs reg2") + val srcReg = insn.requireIntSourceA().intNumber val type = insn.type ?: IRDataType.BYTE val s = dtSuffix(type) val zeroLabel = makeLabel("sgn_zero") @@ -404,8 +400,8 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall // === Floating point operations via 68881/68882 FPU === Opcode.FFROMUB -> { - val fpDst = insn.fpReg1 ?: error("FFROMUB needs fpReg1") - val srcReg = r1 ?: error("FFROMUB needs reg1") + val fpDst = insn.requireFloatDest().floatNumber + val srcReg = insn.requireIntSourceA().intNumber emitLoadD0(srcReg, IRDataType.BYTE) emitLine($$"and.l #$ff, d0") invalidateD0Cache() @@ -414,8 +410,8 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall } Opcode.FFROMSB -> { - val fpDst = insn.fpReg1 ?: error("FFROMSB needs fpReg1") - val srcReg = r1 ?: error("FFROMSB needs reg1") + val fpDst = insn.requireFloatDest().floatNumber + val srcReg = insn.requireIntSourceA().intNumber emitLoadD0(srcReg, IRDataType.BYTE) emitSignExtendByteToLong("d0") emitLine("fmove.l d0, $FP_ACC") @@ -423,8 +419,8 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall } Opcode.FFROMUW -> { - val fpDst = insn.fpReg1 ?: error("FFROMUW needs fpReg1") - val srcReg = r1 ?: error("FFROMUW needs reg1") + val fpDst = insn.requireFloatDest().floatNumber + val srcReg = insn.requireIntSourceA().intNumber emitLoadD0(srcReg, IRDataType.WORD) emitLine($$"and.l #$ffff, d0") invalidateD0Cache() @@ -433,8 +429,8 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall } Opcode.FFROMSW -> { - val fpDst = insn.fpReg1 ?: error("FFROMSW needs fpReg1") - val srcReg = r1 ?: error("FFROMSW needs reg1") + val fpDst = insn.requireFloatDest().floatNumber + val srcReg = insn.requireIntSourceA().intNumber emitLoadD0(srcReg, IRDataType.WORD) emitLine("ext.l d0") emitLine("fmove.l d0, $FP_ACC") @@ -442,16 +438,16 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall } Opcode.FFROMSL -> { - val fpDst = insn.fpReg1 ?: error("FFROMSL needs fpReg1") - val srcReg = r1 ?: error("FFROMSL needs reg1") + val fpDst = insn.requireFloatDest().floatNumber + val srcReg = insn.requireIntSourceA().intNumber emitLoadD0(srcReg, IRDataType.LONG) emitLine("fmove.l d0, $FP_ACC") emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(fpDst)}") } Opcode.FTOUB -> { - val dstReg = r1 ?: error("FTOUB needs reg1") - val fpSrc = insn.fpReg1 ?: error("FTOUB needs fpReg1") + val dstReg = insn.requireIntDest().intNumber + val fpSrc = insn.requireFloatSourceA().floatNumber emitLine("fmove.s ${floatRegFileAddr(fpSrc)}, $FP_ACC") invalidateD0Cache() emitLine("fmove.b $FP_ACC, d0") @@ -460,8 +456,8 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall } Opcode.FTOSB -> { - val dstReg = r1 ?: error("FTOSB needs reg1") - val fpSrc = insn.fpReg1 ?: error("FTOSB needs fpReg1") + val dstReg = insn.requireIntDest().intNumber + val fpSrc = insn.requireFloatSourceA().floatNumber emitLine("fmove.s ${floatRegFileAddr(fpSrc)}, $FP_ACC") invalidateD0Cache() emitLine("fmove.b $FP_ACC, d0") @@ -469,8 +465,8 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall } Opcode.FTOUW -> { - val dstReg = r1 ?: error("FTOUW needs reg1") - val fpSrc = insn.fpReg1 ?: error("FTOUW needs fpReg1") + val dstReg = insn.requireIntDest().intNumber + val fpSrc = insn.requireFloatSourceA().floatNumber emitLine("fmove.s ${floatRegFileAddr(fpSrc)}, $FP_ACC") invalidateD0Cache() emitLine("fmove.w $FP_ACC, d0") @@ -478,8 +474,8 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall } Opcode.FTOSW -> { - val dstReg = r1 ?: error("FTOSW needs reg1") - val fpSrc = insn.fpReg1 ?: error("FTOSW needs fpReg1") + val dstReg = insn.requireIntDest().intNumber + val fpSrc = insn.requireFloatSourceA().floatNumber emitLine("fmove.s ${floatRegFileAddr(fpSrc)}, $FP_ACC") invalidateD0Cache() emitLine("fmove.w $FP_ACC, d0") @@ -487,8 +483,8 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall } Opcode.FTOSL -> { - val dstReg = r1 ?: error("FTOSL needs reg1") - val fpSrc = insn.fpReg1 ?: error("FTOSL needs fpReg1") + val dstReg = insn.requireIntDest().intNumber + val fpSrc = insn.requireFloatSourceA().floatNumber emitLine("fmove.s ${floatRegFileAddr(fpSrc)}, $FP_ACC") invalidateD0Cache() emitLine("fmove.l $FP_ACC, d0") @@ -496,80 +492,80 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall } Opcode.FABS -> { - val dst = insn.fpReg1 ?: error("FABS needs fpReg1") - val src = insn.fpReg2 ?: error("FABS needs fpReg2") + val dst = insn.requireFloatDest().floatNumber + val src = insn.requireFloatSourceA().floatNumber emitLine("fmove.s ${floatRegFileAddr(src)}, $FP_ACC") emitLine("fabs $FP_ACC, $FP_ACC") emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(dst)}") } Opcode.FSIN -> { - val dst = insn.fpReg1 ?: error("FSIN needs fpReg1") - val src = insn.fpReg2 ?: error("FSIN needs fpReg2") + val dst = insn.requireFloatDest().floatNumber + val src = insn.requireFloatSourceA().floatNumber emitLine("fmove.s ${floatRegFileAddr(src)}, $FP_ACC") emitLine("fsin $FP_ACC, $FP_ACC") emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(dst)}") } Opcode.FCOS -> { - val dst = insn.fpReg1 ?: error("FCOS needs fpReg1") - val src = insn.fpReg2 ?: error("FCOS needs fpReg2") + val dst = insn.requireFloatDest().floatNumber + val src = insn.requireFloatSourceA().floatNumber emitLine("fmove.s ${floatRegFileAddr(src)}, $FP_ACC") emitLine("fcos $FP_ACC, $FP_ACC") emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(dst)}") } Opcode.FTAN -> { - val dst = insn.fpReg1 ?: error("FTAN needs fpReg1") - val src = insn.fpReg2 ?: error("FTAN needs fpReg2") + val dst = insn.requireFloatDest().floatNumber + val src = insn.requireFloatSourceA().floatNumber emitLine("fmove.s ${floatRegFileAddr(src)}, $FP_ACC") emitLine("ftan $FP_ACC, $FP_ACC") emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(dst)}") } Opcode.FATAN -> { - val dst = insn.fpReg1 ?: error("FATAN needs fpReg1") - val src = insn.fpReg2 ?: error("FATAN needs fpReg2") + val dst = insn.requireFloatDest().floatNumber + val src = insn.requireFloatSourceA().floatNumber emitLine("fmove.s ${floatRegFileAddr(src)}, $FP_ACC") emitLine("fatan $FP_ACC, $FP_ACC") emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(dst)}") } Opcode.FPOW -> { - val dst = insn.fpReg1 ?: error("FPOW needs fpReg1") - val src = insn.fpReg2 ?: error("FPOW needs fpReg2") + val dst = insn.requireFloatDest().floatNumber + val src = insn.requireFloatSourceA().floatNumber emitLine("fmove.s ${floatRegFileAddr(src)}, $FP_ACC") emitLine("fpow $FP_ACC, $FP_ACC") emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(dst)}") } Opcode.FLN -> { - val dst = insn.fpReg1 ?: error("FLN needs fpReg1") - val src = insn.fpReg2 ?: error("FLN needs fpReg2") + val dst = insn.requireFloatDest().floatNumber + val src = insn.requireFloatSourceA().floatNumber emitLine("fmove.s ${floatRegFileAddr(src)}, $FP_ACC") emitLine("flogn $FP_ACC, $FP_ACC") emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(dst)}") } Opcode.FLOG -> { - val dst = insn.fpReg1 ?: error("FLOG needs fpReg1") - val src = insn.fpReg2 ?: error("FLOG needs fpReg2") + val dst = insn.requireFloatDest().floatNumber + val src = insn.requireFloatSourceA().floatNumber emitLine("fmove.s ${floatRegFileAddr(src)}, $FP_ACC") emitLine("flog2 $FP_ACC, $FP_ACC") emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(dst)}") } Opcode.FROUND -> { - val dst = insn.fpReg1 ?: error("FROUND needs fpReg1") - val src = insn.fpReg2 ?: error("FROUND needs fpReg2") + val dst = insn.requireFloatDest().floatNumber + val src = insn.requireFloatSourceA().floatNumber emitLine("fmove.s ${floatRegFileAddr(src)}, $FP_ACC") emitLine("fround $FP_ACC, $FP_ACC") emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(dst)}") } Opcode.FFLOOR -> { - val dst = insn.fpReg1 ?: error("FFLOOR needs fpReg1") - val src = insn.fpReg2 ?: error("FFLOOR needs fpReg2") + val dst = insn.requireFloatDest().floatNumber + val src = insn.requireFloatSourceA().floatNumber emitLine("fmove.s ${floatRegFileAddr(src)}, $FP_ACC") emitLine("ffloor $FP_ACC, $FP_ACC") emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(dst)}") @@ -577,8 +573,8 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall Opcode.FCEIL -> { // 68881 has no fceil; implement as: if x == int(x) then x else x > 0 ? int(x)+1 : int(x) - val dst = insn.fpReg1 ?: error("FCEIL needs fpReg1") - val src = insn.fpReg2 ?: error("FCEIL needs fpReg2") + val dst = insn.requireFloatDest().floatNumber + val src = insn.requireFloatSourceA().floatNumber val isIntLabel = makeLabel("fceil_is_int") val doneLabel = makeLabel("fceil_done") val posLabel = makeLabel(".fceil_pos") @@ -600,9 +596,9 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall } Opcode.FCOMP -> { - val dstReg = r1 ?: error("FCOMP needs reg1 (int output)") - val fr1 = insn.fpReg1 ?: error("FCOMP needs fpReg1") - val fr2 = insn.fpReg2 ?: error("FCOMP needs fpReg2") + val dstReg = insn.requireIntDest().intNumber + val fr1 = insn.requireFloatSourceA().floatNumber + val fr2 = insn.requireSrcB().floatNumber val eqLabel = makeLabel("fcomp_eq") val doneLabel = makeLabel("fcomp_done") val gtLabel = makeLabel("fcomp_gt") @@ -631,28 +627,23 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall // === CALL translation with argument handling === -private fun AsmGen.translateCall(fnLabel: String, args: FunctionCallArgs?, forwardedImmediateCall: ImmediateCallOptimization? = null) { - if (fnLabel == "sys.memcopy" && emitInlineMemcopyCall(args!!, forwardedImmediateCall)) +private fun AsmGen.translateCall(fnLabel: String, callSite: CallSite, forwardedImmediateCall: ImmediateCallOptimization? = null) { + if (fnLabel == "sys.memcopy" && emitInlineMemcopyCall(callSite, forwardedImmediateCall)) return - if (args != null) { - // Emit non-constant arguments first (from regfile), then constant immediates - // just before the BSR/JSR to avoid clobbering the constant registers while - // evaluating non-const arguments (e.g. address calculations that may use D0/A0 as scratch). - val (forwarded, nonForwarded) = if (forwardedImmediateCall != null) { - args.arguments.partition { arg -> - val regId = if (arg.reg.dt == IRDataType.FLOAT) RegId.FloatReg(arg.reg.registerNum) else RegId.IntReg(arg.reg.registerNum.value) - regId in forwardedImmediateCall.loads - } - } else { - emptyList() to args.arguments - } - for (arg in nonForwarded) { - translateArgument(arg, fnLabel, forwardedImmediateCall) - } - for (arg in forwarded) { - translateArgument(arg, fnLabel, forwardedImmediateCall) - } + // Emit non-constant arguments first (from regfile), then constant immediates + // just before the BSR/JSR to avoid clobbering the constant registers while + // evaluating non-const arguments (e.g. address calculations that may use D0/A0 as scratch). + val (forwarded, nonForwarded) = if (forwardedImmediateCall != null) { + callSite.arguments.partition { it.source.register in forwardedImmediateCall.loads } + } else { + emptyList() to callSite.arguments + } + for (arg in nonForwarded) { + translateArgument(arg, fnLabel, forwardedImmediateCall) + } + for (arg in forwarded) { + translateArgument(arg, fnLabel, forwardedImmediateCall) } // Check if this call targets an inline asmsub — emit its body directly instead of jsr @@ -666,44 +657,39 @@ private fun AsmGen.translateCall(fnLabel: String, args: FunctionCallArgs?, forwa // Move return values back to virtual registers. // Skip status flag returns: always handled by IR's bsteq/bstneg/bstvs branch pattern. - // In multi-assign context (returns.size > 1), also skip slot-based returns: + // In multi-assign context (results.size > 1), also skip slot-based returns: // the IR generates LOADHR for them, and emitting here would clobber CPU flags // before the branch pattern can read them. // In single-return expression context, process all slot returns normally // (the IR doesn't generate LOADHR for single-return calls). // LIMITATION: multiple status flag returns in one multi-assign (e.g. -> bool @Pz, bool @Pc) // are not supported - codegen limitation: the first flag's extraction clobbers the state for subsequent flags. - if (args != null) { - val isMultiReturn = args.returns.size > 1 - for (ret in args.returns) { - if (ret.statusflag != null) - continue - if (isMultiReturn) - continue - translateReturnValue(ret) - } + val isMultiReturn = callSite.results.size > 1 + for (ret in callSite.results) { + if (ret.location is CallLocation.StatusFlag) + continue + if (isMultiReturn) + continue + translateReturnValue(ret) } } -private fun AsmGen.emitInlineMemcopyCall(args: FunctionCallArgs, forwardedImmediateCall: ImmediateCallOptimization?): Boolean { - if (args.arguments.size != 3) return false +private fun AsmGen.emitInlineMemcopyCall(callSite: CallSite, forwardedImmediateCall: ImmediateCallOptimization?): Boolean { + if (callSite.arguments.size != 3) return false val fwd = forwardedImmediateCall ?: return false - val srcArg = args.arguments[0] - val tgtArg = args.arguments[1] - val countArg = args.arguments[2] + val srcArg = callSite.arguments[0] + val tgtArg = callSite.arguments[1] + val countArg = callSite.arguments[2] // size must be a constant immediate forwarded to the call; non-constant -> always call - val countRegId = RegId.IntReg(countArg.reg.registerNum.value) - val countLoad = fwd.loads[countRegId] ?: return false - val count = countLoad.immediate ?: return false + val countLoad = fwd.loads[countArg.source.register] ?: return false + val count = countLoad.immediate?.integerValue ?: return false if (count <= 0) return true // Only use .w/.l if we can 100% prove both pointers are aligned. // At codegen we only know immediates; computed addresses (e.g. &arr[i]) are not immediate // so we conservatively fall back to byte copies. A future IR alignment analysis could prove more. - val srcRegId = RegId.IntReg(srcArg.reg.registerNum.value) - val tgtRegId = RegId.IntReg(tgtArg.reg.registerNum.value) - val srcImm = fwd.loads[srcRegId]?.immediate - val tgtImm = fwd.loads[tgtRegId]?.immediate + val srcImm = fwd.loads[srcArg.source.register]?.immediate?.integerValue + val tgtImm = fwd.loads[tgtArg.source.register]?.immediate?.integerValue val bothEven = srcImm != null && tgtImm != null && srcImm % 2 == 0 && tgtImm % 2 == 0 val bothLongAligned = srcImm != null && tgtImm != null && srcImm % 4 == 0 && tgtImm % 4 == 0 val useLong = bothLongAligned && count % 4 == 0 @@ -769,32 +755,27 @@ private fun AsmGen.emitInlineCopyLoop(iterations: Int, size: Int) { } private fun AsmGen.translateArgument( - arg: FunctionCallArgs.ArgumentSpec, + arg: CallArgument, fnLabel: String? = null, forwardedImmediateCall: ImmediateCallOptimization? = null ) { - val argReg = arg.reg + val argReg = arg.source + val forwarded = forwardedImmediateCall?.loads?.get(argReg.register) // If the argument has a calling convention slot, load it into that hardware register - val slot = argReg.callingConventionSlot + val slot = (arg.location as? CallLocation.HardwareRegister)?.slot if (slot != null) { val hwReg = m68kSlotRegister(slot) - if (argReg.dt == IRDataType.FLOAT) { - val forwarded = forwardedImmediateCall?.loads?.get(RegId.FloatReg(argReg.registerNum)) + if (argReg.isFloat) { if (forwarded != null) { - val value = forwarded.immediateFp - ?: error("missing forwarded float immediate for call argument fr${argReg.registerNum.value}") - emitFloadConstantTo(hwReg, value) + emitFloadConstantTo(hwReg, forwarded.requireImmediateFloat()) } else { - emitLine("fmove.s ${floatRegFileAddr(argReg.registerNum)}, $hwReg") + emitLine("fmove.s ${floatRegFileAddr(argReg.floatNumber)}, $hwReg") } } else { - val regIdInt = RegId.IntReg(argReg.registerNum.value) - val forwardedLoad = forwardedImmediateCall?.loads?.get(regIdInt) - if (forwardedLoad != null) { - val value = forwardedLoad.immediate - ?: error("missing forwarded immediate for call argument r${argReg.registerNum.value}") - val s = dtSuffix(argReg.dt) + if (forwarded != null) { + val value = forwarded.requireImmediateInt() + val s = dtSuffix(argReg.type) if (hwReg.startsWith("a")) { // address registers only accept movea/suba, not clr or moveq when (value) { @@ -807,7 +788,7 @@ private fun AsmGen.translateArgument( else { // use moveq (2 bytes, 4 cycles) when the value fits in its signed 8-bit range; // for byte args the value is unsigned 0-255, so map 128-255 to -128--1 (low byte is the same) - val moveqValue = if (argReg.dt == IRDataType.BYTE && value in 128..255) value - 256 else value + val moveqValue = if (argReg.type == IRDataType.BYTE && value in 128..255) value - 256 else value if (moveqValue in -128..127) emitLine("moveq #$moveqValue, $hwReg") else @@ -815,55 +796,49 @@ private fun AsmGen.translateArgument( } } } else { - val source = regAddr(argReg.registerNum.value) - if (hwReg.startsWith("a") && argReg.dt in setOf(IRDataType.LONG, IRDataType.POINTER)) { + val source = regAddr(argReg.intNumber) + if (hwReg.startsWith("a") && argReg.type in setOf(IRDataType.LONG, IRDataType.POINTER)) { // loading a pointer into an address register; movea.l is the proper form and skips the +0 offset emitLine("movea.l ${source.removeSuffix("+0")}, $hwReg") } else { - val s = dtSuffix(argReg.dt) + val s = dtSuffix(argReg.type) emitLine("move$s $source, $hwReg") } } } } else { // Store to the callee's parameter variable (if this is a named param) - if (arg.name.isNotEmpty() && fnLabel != null) { - val paramVarName = "$fnLabel.${arg.name}" - val target = fixNameSymbols(paramVarName) - when (argReg.dt) { - IRDataType.FLOAT -> { - val fRegVal = floatRegFileAddr(argReg.registerNum) - emitLine("fmove.s $fRegVal, $FP_ACC") - emitLine("fmove.s $FP_ACC, $target") - } - else -> { - val regVal = regAddr(argReg.registerNum.value) - val s = dtSuffix(argReg.dt) - emitLine("move$s $regVal, $target") - } + val paramName = (arg.location as? CallLocation.ParameterMemory)?.name.orEmpty() + if (paramName.isNotEmpty() && fnLabel != null) { + val target = fixNameSymbols("$fnLabel.$paramName") + if (argReg.isFloat) { + emitLine("fmove.s ${floatRegFileAddr(argReg.floatNumber)}, $FP_ACC") + emitLine("fmove.s $FP_ACC, $target") + } else { + val s = dtSuffix(argReg.type) + emitLine("move$s ${regAddr(argReg.intNumber)}, $target") } } } } -private fun AsmGen.translateReturnValue(ret: FunctionCallArgs.RegSpec) { - val retReg = ret.registerNum - +private fun AsmGen.translateReturnValue(ret: CallResult) { // Status flag returns are handled by the IR's branch-based pattern (bsteq/bstneg/bstvs). // Do NOT emit Scc here - it would clobber the flags before the branch can read them. - if (ret.statusflag != null) { + if (ret.location is CallLocation.StatusFlag) return - } + + val destination = ret.destination ?: return // Otherwise, return value is in a hardware register - val slot = ret.callingConventionSlot + val slot = (ret.location as? CallLocation.HardwareRegister)?.slot if (slot != null) { val hwReg = m68kSlotRegister(slot) - if (ret.dt == IRDataType.FLOAT) { - emitLine("fmove.s $hwReg, ${floatRegFileAddr(RegisterNum(retReg.value))}") + if (destination.isFloat) { + emitLine("fmove.s $hwReg, ${floatRegFileAddr(destination.floatNumber)}") } else { - val s = dtSuffix(ret.dt) - emitLine("move$s $hwReg, ${regAddr(retReg.value)}") + val s = dtSuffix(destination.type) + emitLine("move$s $hwReg, ${regAddr(destination.intNumber)}") } } else { // Default: return value in d0 (standard m68k calling convention) @@ -872,10 +847,10 @@ private fun AsmGen.translateReturnValue(ret: FunctionCallArgs.RegSpec) { // callers read return values from data regs unless the slot annotation says otherwise. // Slightly inefficient: m68k pointers ideally live in address registers, but returning // them in d0 is simpler and avoids ambiguity. Explicit @A0 can be used for hot paths. - if (ret.dt == IRDataType.FLOAT) { - emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(RegisterNum(retReg.value))}") + if (destination.isFloat) { + emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(destination.floatNumber)}") } else { - emitStoreD0(retReg.value, ret.dt) + emitStoreD0(destination.intNumber, destination.type) } } } diff --git a/codeGenM68k/src/prog8/codegen/m68k/InstrLoadStore.kt b/codeGenM68k/src/prog8/codegen/m68k/InstrLoadStore.kt index 51f08f6b6..d18a2a64a 100644 --- a/codeGenM68k/src/prog8/codegen/m68k/InstrLoadStore.kt +++ b/codeGenM68k/src/prog8/codegen/m68k/InstrLoadStore.kt @@ -1,46 +1,26 @@ package prog8.codegen.m68k -import prog8.code.core.CpuType -import prog8.intermediate.CallingConventionSlot import prog8.intermediate.IRDataType import prog8.intermediate.IRInstruction +import prog8.intermediate.ImmediateOperand import prog8.intermediate.Opcode - -/** - * Load an array index into d0 for use with `(a0,d0.w)` addressing. - * Word indices can be loaded directly; byte indices must be zero-extended - * because `move.w` from a byte regfile slot would read the adjacent byte too. - */ -private fun AsmGen.loadIndexToD0(idx: Int) { - if (regType(idx) == IRDataType.BYTE) { - invalidateD0Cache() // moveq clobbers d0 - emitLine("moveq #0, d0") - emitLoadD0(idx, IRDataType.BYTE) // cache as byte; consumer widens - } else { - emitLoadD0(idx, IRDataType.WORD) // cache as word; consumer widens - } -} - -private fun AsmGen.addIndirectOffset(offset: Int) { - when (offset) { - in 1..8 -> emitLine("addq.l #$offset, a0") - in -8..-1 -> emitLine("subq.l #${-offset}, a0") - else -> emitLine("adda.l #$offset, a0") - } -} +import prog8.intermediate.floatNumber +import prog8.intermediate.intNumber +import prog8.intermediate.requireFloatDest +import prog8.intermediate.requireFloatSourceA +import prog8.intermediate.requireHardwareSlot +import prog8.intermediate.requireImmediate +import prog8.intermediate.requireImmediateFloat +import prog8.intermediate.requireImmediateInt +import prog8.intermediate.requireIntDest +import prog8.intermediate.requireIntSourceA +import prog8.intermediate.requireMemory internal fun AsmGen.translateLoadStore(insn: IRInstruction, suppressRegfileStore: Boolean = false) { val type = insn.type ?: IRDataType.BYTE - val r1 = insn.reg1 - val r2 = insn.reg2 - val imm = insn.immediate - val addr = insn.address - val label = insn.labelSymbol - val offset = insn.labelSymbolOffset - val target = resolveAddress(addr, label, offset) if (type == IRDataType.FLOAT) { - translateFloatLoadStore(insn, target, suppressRegfileStore) + translateFloatLoadStore(insn, suppressRegfileStore) return } @@ -48,103 +28,65 @@ internal fun AsmGen.translateLoadStore(insn: IRInstruction, suppressRegfileStore when (insn.opcode) { Opcode.LOAD -> { - val dst = r1 ?: error("LOAD needs reg1") - val value = insn.immediate - val sym = insn.labelSymbol - when { - value != null -> { + val dst = insn.requireIntDest().intNumber + when (val value = insn.requireImmediate()) { + is ImmediateOperand.Integer -> { if (suppressRegfileStore) return - if(value == 0) + if(value.value == 0) emitLine("clr$s ${regAddr(dst)}") else - emitLine("move$s #$value, ${regAddr(dst)}") + emitLine("move$s #${value.value}, ${regAddr(dst)}") invalidateD0CacheForSlot(dst) } - sym != null -> { - val resolved = resolveSymbolRef(sym) - val symOff = if (offset != null) "$resolved+$offset" else resolved + is ImmediateOperand.SymbolAddress -> { + val resolved = resolveSymbolRef(value.symbol) + val symOff = if (value.offset != 0) "$resolved+${value.offset}" else resolved emitLine("move.l #$symOff, ${regAddr(dst)}") invalidateD0CacheForSlot(dst) } - else -> error("LOAD needs immediate or labelSymbol") + is ImmediateOperand.FloatValue -> error("LOAD: float immediate for an integer register") } } Opcode.LOADM -> { - val dst = r1 ?: error("LOADM needs reg1") - emitLine("move${dtSuffix(type)} $target, ${regAddr(dst)}") + val dst = insn.requireIntDest().intNumber + val target = resolveMemory(insn.requireMemory()) + emitLine("move$s $target, ${regAddr(dst)}") invalidateD0CacheForSlot(dst) } Opcode.LOADR -> { - val dst = r1 ?: error("LOADR needs reg1") - val src = r2 ?: error("LOADR needs reg2") + val dst = insn.requireIntDest().intNumber + val src = insn.requireIntSourceA().intNumber emitLine("move$s ${regAddr(src)}, ${regAddr(dst)}") invalidateD0CacheForSlot(dst) } - Opcode.LOADX -> { - val dst = r1 ?: error("LOADX needs reg1") - val idx = r2 ?: error("LOADX needs reg2") - val scale = insn.scale - loadIndexToD0(idx) - if(scale!=1) { - invalidateD0Cache() // scaling transforms d0 - if(program.options.compTarget.cpu >= CpuType.M68020) { - if(scale!=2 && scale!=4 && scale!=8) emitLine("muls.w #$scale, d0") - } else { - when(scale) { - 2 -> emitLine("add.w d0,d0") - 4 -> emitLine("lsl.w #2, d0") - else -> emitLine("muls.w #$scale, d0") - } - } - } - emitLine("lea $target, a0") - val indexMode = when { - program.options.compTarget.cpu >= CpuType.M68020 && scale==2 -> "(a0,d0.w*2)" - program.options.compTarget.cpu >= CpuType.M68020 && scale==4 -> "(a0,d0.w*4)" - program.options.compTarget.cpu >= CpuType.M68020 && scale==8 -> "(a0,d0.w*8)" - else -> "(a0,d0.w)" - } - emitLoadD0FromAddress(indexMode, type) + Opcode.LOADX, Opcode.LOADI -> { + val dst = insn.requireIntDest().intNumber + val source = resolveMemory(insn.requireMemory()) + emitLoadD0FromAddress(source, type) emitStoreD0(dst, type) } Opcode.LOADHR -> { - val dst = r1 ?: error("LOADHR needs reg1") - val slot = imm ?: error("LOADHR needs slot immediate") - val hwReg = m68kSlotRegister(CallingConventionSlot(slot)) + val dst = insn.requireIntDest().intNumber + val hwReg = m68kSlotRegister(insn.requireHardwareSlot().slot) emitLine("move$s $hwReg, ${regAddr(dst)}") invalidateD0CacheForSlot(dst) } - Opcode.LOADI -> { - val dst = r1 ?: error("LOADI needs reg1") - val base = r2 ?: error("LOADI needs reg2") - val off = imm ?: 0 - loadPointerToA0(base) - if(off<-32768 || off>32767) { - addIndirectOffset(off) - emitLoadD0FromAddress("(a0)", type) - } else { - if(off==0) - emitLoadD0FromAddress("(a0)", type) - else - emitLoadD0FromAddress("($off,a0)", type) - } - emitStoreD0(dst, type) - } - Opcode.STOREM -> { - val src = r1 ?: error("STOREM needs reg1") - emitLine("move${dtSuffix(type)} ${regAddr(src)}, $target") + val src = insn.requireIntSourceA().intNumber + val target = resolveMemory(insn.requireMemory()) + emitLine("move$s ${regAddr(src)}, $target") invalidateD0CacheForAddress(target) } Opcode.STOREIM -> { - val value = imm ?: error("STOREIM needs immediate value") + val value = insn.requireImmediateInt() + val target = resolveMemory(insn.requireMemory()) if(value == 0) emitLine("clr$s $target") else @@ -153,107 +95,35 @@ internal fun AsmGen.translateLoadStore(insn: IRInstruction, suppressRegfileStore } Opcode.STOREX -> { - val value = r1 ?: error("STOREX needs reg1") - val idx = r2 ?: error("STOREX needs reg2") - val scale = insn.scale - loadIndexToD0(idx) - if(scale!=1) { - invalidateD0Cache() // scaling transforms d0 - if(program.options.compTarget.cpu >= CpuType.M68020) { - if(scale!=2 && scale!=4 && scale!=8) emitLine("muls.w #$scale, d0") - } else { - when(scale) { - 2 -> emitLine("add.w d0,d0") - 4 -> emitLine("lsl.w #2, d0") - else -> emitLine("muls.w #$scale, d0") - } - } - } - emitLine("lea $target, a0") - val sx = dtSuffix(type) - emitLine("move$sx ${regAddr(value)}, d1") - val indexMode = when { - program.options.compTarget.cpu >= CpuType.M68020 && scale==2 -> "(a0,d0.w*2)" - program.options.compTarget.cpu >= CpuType.M68020 && scale==4 -> "(a0,d0.w*4)" - program.options.compTarget.cpu >= CpuType.M68020 && scale==8 -> "(a0,d0.w*8)" - else -> "(a0,d0.w)" - } - emitLine("move$sx d1, $indexMode") + val value = insn.requireIntSourceA().intNumber + val target = resolveMemory(insn.requireMemory()) + // the indexed effective address uses a0/d0 as scratch, so stage the value in d1 + emitLine("move$s ${regAddr(value)}, d1") + emitLine("move$s d1, $target") } - Opcode.STOREZM -> { - emitLine("clr${dtSuffix(type)} $target") + Opcode.STOREZM, Opcode.STOREZI, Opcode.STOREZX -> { + val target = resolveMemory(insn.requireMemory()) + emitLine("clr$s $target") invalidateD0CacheForAddress(target) } - Opcode.STOREZI -> { - val base = r1 ?: error("STOREZI needs reg1") - val off = imm ?: 0 - loadPointerToA0(base) - if(off<-32768 || off>32767) { - addIndirectOffset(off) - emitLine("clr$s (a0)") - } else { - if(off==0) - emitLine("clr$s (a0)") - else - emitLine("clr$s ($off,a0)") - } - } - - Opcode.STOREZX -> { - val idx = r1 ?: error("STOREZX needs reg1") - val scale = insn.scale - loadIndexToD0(idx) - if(scale!=1) { - invalidateD0Cache() // scaling transforms d0 - if(program.options.compTarget.cpu >= CpuType.M68020) { - if(scale!=2 && scale!=4 && scale!=8) emitLine("muls.w #$scale, d0") - } else { - when(scale) { - 2 -> emitLine("add.w d0,d0") - 4 -> emitLine("lsl.w #2, d0") - else -> emitLine("muls.w #$scale, d0") - } - } - } - emitLine("lea $target, a0") - val indexMode = when { - program.options.compTarget.cpu >= CpuType.M68020 && scale==2 -> "(a0,d0.w*2)" - program.options.compTarget.cpu >= CpuType.M68020 && scale==4 -> "(a0,d0.w*4)" - program.options.compTarget.cpu >= CpuType.M68020 && scale==8 -> "(a0,d0.w*8)" - else -> "(a0,d0.w)" - } - emitLine("clr$s $indexMode") - } - Opcode.STOREHR -> { - val src = r1 ?: error("STOREHR needs reg1") - val slot = imm ?: error("STOREHR needs slot immediate") - val hwReg = m68kSlotRegister(CallingConventionSlot(slot)) + val src = insn.requireIntSourceA().intNumber + val hwReg = m68kSlotRegister(insn.requireHardwareSlot().slot) emitLine("move$s ${regAddr(src)}, $hwReg") } Opcode.STOREI -> { - val value = r1 ?: error("STOREI needs reg1") - val base = r2 ?: error("STOREI needs reg2") - val off = imm ?: 0 - loadPointerToA0(base) - if(off<-32768 || off>32767) { - addIndirectOffset(off) - emitLine("move$s ${regAddr(value)}, (a0)") - } else { - if(off==0) - emitLine("move$s ${regAddr(value)}, (a0)") - else - emitLine("move$s ${regAddr(value)}, ($off,a0)") - } + val value = insn.requireIntSourceA().intNumber + val target = resolveMemory(insn.requireMemory()) + emitLine("move$s ${regAddr(value)}, $target") } Opcode.LOADP_INC -> { - val dst = r1 ?: error("LOADP_INC needs reg1") - // >r1,<>a : r1 = *a; a += sizeof(type) - // a is pointer variable (LONG) in memory + val dst = insn.requireIntDest().intNumber + // r1 = *a; a += sizeof(type) where a is a pointer variable (LONG) in memory + val target = resolveMemory(insn.requireMemory()) emitLine("move.l $target, a0") emitLoadD0FromAddress("(a0)+", type) emitLine("move.l a0, $target") @@ -262,8 +132,9 @@ internal fun AsmGen.translateLoadStore(insn: IRInstruction, suppressRegfileStore } Opcode.STOREP_INC -> { - val value = r1 ?: error("STOREP_INC needs reg1") - // a : *a = r1; a += sizeof(type) + val value = insn.requireIntSourceA().intNumber + // *a = r1; a += sizeof(type) + val target = resolveMemory(insn.requireMemory()) emitLine("move.l $target, a0") emitLine("move$s ${regAddr(value)}, (a0)+") emitLine("move.l a0, $target") @@ -276,200 +147,101 @@ internal fun AsmGen.translateLoadStore(insn: IRInstruction, suppressRegfileStore // === Float load/store via FPU (68881/68882) === -private fun AsmGen.translateFloatLoadStore(insn: IRInstruction, target: String, suppressRegfileStore: Boolean = false) { - val fpReg1 = insn.fpReg1 - val fpReg2 = insn.fpReg2 - val r1 = insn.reg1 - val imm = insn.immediate - val immFp = insn.immediateFp - val label = insn.labelSymbol - val offset = insn.labelSymbolOffset - +private fun AsmGen.translateFloatLoadStore(insn: IRInstruction, suppressRegfileStore: Boolean = false) { when (insn.opcode) { - Opcode.STOREZM -> { - emitLine($$"fmovecr #$0f, fp0") - emitLine("fmove.s fp0, $target") - invalidateD0CacheForAddress(target) - } - - Opcode.STOREZI -> { - val base = r1 ?: error("STOREZI.f needs reg1 (base)") - val off = imm ?: 0 - loadPointerToA0(base) - if (off != 0) addIndirectOffset(off) - emitLine($$"fmovecr #$0f, fp0") - emitLine("fmove.s fp0, (a0)") - } - - Opcode.STOREZX -> { - val idx = r1 ?: error("STOREZX.f needs reg1 (index)") - val scale = insn.scale - // full-width d0.l indexing requires zero-extending the word index to 32 bits - invalidateD0Cache() // moveq clobbers d0 - emitLine("moveq #0, d0") - emitLoadD0(idx, IRDataType.WORD) // cache as word; d0.l widens it - if(scale!=1) { - invalidateD0Cache() // scaling transforms d0 - when(scale) { - 2 -> emitLine("add.l d0,d0") - 4 -> emitLine("lsl.l #2, d0") - 8 -> emitLine("lsl.l #3, d0") - else -> emitLine("muls.w #$scale, d0") - } - } - emitLine("lea $target, a0") - emitLine($$"fmovecr #$0f, fp0") - emitLine("fmove.s fp0, (0, a0, d0.l)") - } - - Opcode.STOREHFACZERO -> { + Opcode.STOREZM, Opcode.STOREZI, Opcode.STOREZX -> { + val target = resolveMemory(insn.requireMemory(), forFloat = true) emitLine($$"fmovecr #$0f, fp0") emitLine("fmove.s fp0, $target") invalidateD0CacheForAddress(target) } Opcode.STOREIM -> { - val value = immFp ?: error("STOREIM.f needs immediateFp value") - val native = nativeFloatConst(value) - if (native != null) { - emitLine("fmovecr #$native, fp0") - } else { - val lbl = makeFloatConstLabel(value) - emitLine("lea $lbl, a0") - emitLine("fmove.s (a0), fp0") - } + val target = resolveMemory(insn.requireMemory(), forFloat = true) + emitFloadConstantTo("fp0", insn.requireImmediateFloat()) emitLine("fmove.s fp0, $target") invalidateD0CacheForAddress(target) } - else -> { - val fp1 = fpReg1 ?: error("float op needs fpReg1 for ${insn.opcode}") - when (insn.opcode) { - Opcode.LOAD -> when { - immFp != null -> { - if (suppressRegfileStore) - return - emitFloadConstantToAcc(immFp) - emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(fp1)}") - } - label != null -> { - val resolved = resolveSymbolRef(label) - val symOff = if (offset != null) "$resolved+$offset" else resolved - emitLine("lea $symOff, a0") - emitLine("fmove.s (a0), $FP_ACC") - emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(fp1)}") - } - else -> error("FLOAT LOAD needs immediateFp or labelSymbol") + Opcode.LOAD -> { + val dst = insn.requireFloatDest().floatNumber + when (val value = insn.requireImmediate()) { + is ImmediateOperand.FloatValue -> { + if (suppressRegfileStore) + return + emitFloadConstantToAcc(value.value) + emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(dst)}") } - - Opcode.LOADM -> { - emitLine("fmove.s $target, $FP_ACC") - emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(fp1)}") - } - - Opcode.LOADR -> { - val src = fpReg2 ?: error("LOADR.f needs fpReg2") - emitLine("fmove.s ${floatRegFileAddr(src)}, $FP_ACC") - emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(fp1)}") - } - - Opcode.LOADX -> { - val idx = r1 ?: error("LOADX.f needs reg1 (index)") - val scale = insn.scale - // full-width d0.l indexing requires zero-extending the word index to 32 bits - invalidateD0Cache() // moveq clobbers d0 - emitLine("moveq #0, d0") - emitLoadD0(idx, IRDataType.WORD) // cache as word; d0.l widens it - if(scale!=1) { - invalidateD0Cache() // scaling transforms d0 - when(scale) { - 2 -> emitLine("add.l d0,d0") - 4 -> emitLine("lsl.l #2, d0") - 8 -> emitLine("lsl.l #3, d0") - else -> emitLine("muls.w #$scale, d0") - } - } - emitLine("lea $target, a0") - emitLine("fmove.s (0, a0, d0.l), $FP_ACC") - emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(fp1)}") - } - - Opcode.LOADHR -> { - val slot = imm ?: error("LOADHR.f needs slot immediate") - val hwReg = m68kSlotRegister(CallingConventionSlot(slot)) - emitLine("fmove $hwReg, $FP_ACC") - emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(fp1)}") - } - - Opcode.LOADI -> { - val base = r1 ?: error("LOADI.f needs reg1 (base)") - val off = imm ?: 0 - loadPointerToA0(base) - if (off != 0) addIndirectOffset(off) + is ImmediateOperand.SymbolAddress -> { + val resolved = resolveSymbolRef(value.symbol) + val symOff = if (value.offset != 0) "$resolved+${value.offset}" else resolved + emitLine("lea $symOff, a0") emitLine("fmove.s (a0), $FP_ACC") - emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(fp1)}") + emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(dst)}") } - - Opcode.STOREM -> { - emitLine("fmove.s ${floatRegFileAddr(fp1)}, $FP_ACC") - emitLine("fmove.s $FP_ACC, $target") - invalidateD0CacheForAddress(target) - } - - Opcode.STOREX -> { - val idx = r1 ?: error("STOREX.f needs reg1 (index)") - val scale = insn.scale - // full-width d0.l indexing requires zero-extending the word index to 32 bits - invalidateD0Cache() // moveq clobbers d0 - emitLine("moveq #0, d0") - emitLoadD0(idx, IRDataType.WORD) // cache as word; d0.l widens it - if(scale!=1) { - invalidateD0Cache() // scaling transforms d0 - when(scale) { - 2 -> emitLine("add.l d0,d0") - 4 -> emitLine("lsl.l #2, d0") - 8 -> emitLine("lsl.l #3, d0") - else -> emitLine("muls.w #$scale, d0") - } - } - emitLine("lea $target, a0") - emitLine("fmove.s ${floatRegFileAddr(fp1)}, $FP_ACC") - emitLine("fmove.s $FP_ACC, (0, a0, d0.l)") - } - - Opcode.STOREHR -> { - val slot = imm ?: error("STOREHR.f needs slot immediate") - val hwReg = m68kSlotRegister(CallingConventionSlot(slot)) - emitLine("fmove.s ${floatRegFileAddr(fp1)}, $FP_ACC") - emitLine("fmove $FP_ACC, $hwReg") - } - - Opcode.STOREI -> { - val base = r1 ?: error("STOREI.f needs reg1 (base)") - val off = imm ?: 0 - loadPointerToA0(base) - if (off != 0) addIndirectOffset(off) - emitLine("fmove.s ${floatRegFileAddr(fp1)}, $FP_ACC") - emitLine("fmove.s $FP_ACC, (a0)") - } - - Opcode.LOADHFACZERO -> { - emitLine($$"fmovecr #$0f, $$FP_ACC") - emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(fp1)}") - } - Opcode.LOADHFACONE -> { - emitLine("fmovecr #$32, $FP_ACC") - emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(fp1)}") - } - Opcode.STOREHFACONE -> { - emitLine("fmovecr #$32, $FP_ACC") - emitLine("fmove.s $FP_ACC, $target") - invalidateD0CacheForAddress(target) - } - - else -> error("Unknown float load/store opcode: ${insn.opcode}") + is ImmediateOperand.Integer -> error("LOAD.f: integer immediate for a float register") } } + + Opcode.LOADM, Opcode.LOADX, Opcode.LOADI -> { + val dst = insn.requireFloatDest().floatNumber + val source = resolveMemory(insn.requireMemory(), forFloat = true) + emitLine("fmove.s $source, $FP_ACC") + emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(dst)}") + } + + Opcode.LOADR -> { + val dst = insn.requireFloatDest().floatNumber + val src = insn.requireFloatSourceA().floatNumber + emitLine("fmove.s ${floatRegFileAddr(src)}, $FP_ACC") + emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(dst)}") + } + + Opcode.LOADHR -> { + val dst = insn.requireFloatDest().floatNumber + val hwReg = m68kSlotRegister(insn.requireHardwareSlot().slot) + emitLine("fmove $hwReg, $FP_ACC") + emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(dst)}") + } + + Opcode.STOREM, Opcode.STOREX, Opcode.STOREI -> { + val src = insn.requireFloatSourceA().floatNumber + val target = resolveMemory(insn.requireMemory(), forFloat = true) + emitLine("fmove.s ${floatRegFileAddr(src)}, $FP_ACC") + emitLine("fmove.s $FP_ACC, $target") + invalidateD0CacheForAddress(target) + } + + Opcode.STOREHR -> { + val src = insn.requireFloatSourceA().floatNumber + val hwReg = m68kSlotRegister(insn.requireHardwareSlot().slot) + emitLine("fmove.s ${floatRegFileAddr(src)}, $FP_ACC") + emitLine("fmove $FP_ACC, $hwReg") + } + + // The 'FAC' hardware float registers only exist on the 6502 targets. On m68k they are + // modelled with the two FPU scratch registers; loading them yields the 0.0 / 1.0 constants + // that the 6502 targets keep in FAC1 / FAC2. + Opcode.LOADHFACZERO -> { + val dst = insn.requireFloatDest().floatNumber + emitLine($$"fmovecr #$0f, $$FP_ACC") + emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(dst)}") + } + Opcode.LOADHFACONE -> { + val dst = insn.requireFloatDest().floatNumber + emitLine("fmovecr #$32, $FP_ACC") + emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(dst)}") + } + Opcode.STOREHFACZERO -> { + val src = insn.requireFloatSourceA().floatNumber + emitLine("fmove.s ${floatRegFileAddr(src)}, $FP_ACC") + } + Opcode.STOREHFACONE -> { + val src = insn.requireFloatSourceA().floatNumber + emitLine("fmove.s ${floatRegFileAddr(src)}, $FP_SRC") + } + + else -> error("Unknown float load/store opcode: ${insn.opcode}") } } diff --git a/codeGenM68k/src/prog8/codegen/m68k/InstrSyscall.kt b/codeGenM68k/src/prog8/codegen/m68k/InstrSyscall.kt index 5c7758aa8..dd214310b 100644 --- a/codeGenM68k/src/prog8/codegen/m68k/InstrSyscall.kt +++ b/codeGenM68k/src/prog8/codegen/m68k/InstrSyscall.kt @@ -7,35 +7,43 @@ package prog8.codegen.m68k -import prog8.intermediate.FunctionCallArgs +import prog8.intermediate.CallSite import prog8.intermediate.IMSyscall import prog8.intermediate.IRDataType +import prog8.intermediate.RegisterOperand +import prog8.intermediate.intNumber // === SYSCALL dispatch === -internal fun AsmGen.translateSyscall(num: Int, args: FunctionCallArgs) { +internal fun AsmGen.translateSyscall(num: Int, site: CallSite) { when (num) { - IMSyscall.WORDARRAY_CONTAINS.number -> translateSyscallWordarrayContains(args) - IMSyscall.COMPARE_STRINGS.number -> translateSyscallStringCompare(args) - IMSyscall.CLAMP_BYTE.number -> translateSyscallClamp(args, IRDataType.BYTE, true) - IMSyscall.CLAMP_UBYTE.number -> translateSyscallClamp(args, IRDataType.BYTE, false) - IMSyscall.CLAMP_WORD.number -> translateSyscallClamp(args, IRDataType.WORD, true) - IMSyscall.CLAMP_UWORD.number -> translateSyscallClamp(args, IRDataType.WORD, false) - IMSyscall.CLAMP_LONG.number -> translateSyscallClamp(args, IRDataType.LONG, true) + IMSyscall.WORDARRAY_CONTAINS.number -> translateSyscallWordarrayContains(site) + IMSyscall.COMPARE_STRINGS.number -> translateSyscallStringCompare(site) + IMSyscall.CLAMP_BYTE.number -> translateSyscallClamp(site, IRDataType.BYTE, true) + IMSyscall.CLAMP_UBYTE.number -> translateSyscallClamp(site, IRDataType.BYTE, false) + IMSyscall.CLAMP_WORD.number -> translateSyscallClamp(site, IRDataType.WORD, true) + IMSyscall.CLAMP_UWORD.number -> translateSyscallClamp(site, IRDataType.WORD, false) + IMSyscall.CLAMP_LONG.number -> translateSyscallClamp(site, IRDataType.LONG, true) else -> TODO("syscall $num on m68k") } } +private fun CallSite.argument(index: Int): RegisterOperand = + arguments.getOrNull(index)?.source ?: error("syscall is missing argument $index") + +private fun CallSite.resultRegister(index: Int): RegisterOperand = + results.getOrNull(index)?.destination ?: error("syscall is missing result $index") + // Compare two strings by delegating to the library routine prog8_lib.strcmp, // which already implements an efficient case-sensitive comparison that // correctly handles 32 bits pointers (the pointer type on M68k targets). -private fun AsmGen.translateSyscallStringCompare(args: FunctionCallArgs) { - val reg1 = args.arguments[0].reg.registerNum.value - val reg2 = args.arguments[1].reg.registerNum.value - val resultReg = args.returns[0].registerNum.value - emitLoadD0(reg1, args.arguments[0].reg.dt) - loadStringArg(reg2, args.arguments[1].reg.dt, "d1") +private fun AsmGen.translateSyscallStringCompare(site: CallSite) { + val arg1 = site.argument(0) + val arg2 = site.argument(1) + val resultReg = site.resultRegister(0).intNumber + emitLoadD0(arg1.intNumber, arg1.type) + loadStringArg(arg2.intNumber, arg2.type, "d1") invalidateD0Cache() // d0 is caller-saved across the subroutine call emitLine("bsr prog8_lib.strcmp") emitStoreD0(resultReg, IRDataType.BYTE) @@ -51,11 +59,11 @@ private fun AsmGen.loadStringArg(reg: Int, dt: IRDataType, dreg: String) { } } -private fun AsmGen.translateSyscallWordarrayContains(args: FunctionCallArgs) { - val regElem = args.arguments[0].reg.registerNum.value - val regArr = args.arguments[1].reg.registerNum.value - val regLen = args.arguments[2].reg.registerNum.value - val resultReg = args.returns[0].registerNum.value +private fun AsmGen.translateSyscallWordarrayContains(site: CallSite) { + val regElem = site.argument(0).intNumber + val regArr = site.argument(1).intNumber + val regLen = site.argument(2).intNumber + val resultReg = site.resultRegister(0).intNumber val labelLoop = makeLabel(".wac_loop") val labelFound = makeLabel(".wac_found") @@ -78,11 +86,11 @@ private fun AsmGen.translateSyscallWordarrayContains(args: FunctionCallArgs) { emitRaw("$labelDone:") } -private fun AsmGen.translateSyscallClamp(args: FunctionCallArgs, dt: IRDataType, signed: Boolean) { - val valueReg = args.arguments[0].reg.registerNum.value - val minReg = args.arguments[1].reg.registerNum.value - val maxReg = args.arguments[2].reg.registerNum.value - val resultReg = args.returns[0].registerNum.value +private fun AsmGen.translateSyscallClamp(site: CallSite, dt: IRDataType, signed: Boolean) { + val valueReg = site.argument(0).intNumber + val minReg = site.argument(1).intNumber + val maxReg = site.argument(2).intNumber + val resultReg = site.resultRegister(0).intNumber val labelCheckMax = makeLabel(".clamp_max") val labelDone = makeLabel(".clamp_done") diff --git a/codeGenM68k/src/prog8/codegen/m68k/M68kCodeGenerator.kt b/codeGenM68k/src/prog8/codegen/m68k/M68kCodeGenerator.kt index d35f2f92c..325305036 100644 --- a/codeGenM68k/src/prog8/codegen/m68k/M68kCodeGenerator.kt +++ b/codeGenM68k/src/prog8/codegen/m68k/M68kCodeGenerator.kt @@ -7,7 +7,9 @@ import prog8.code.ast.PtProgram import prog8.code.core.CompilationOptions import prog8.code.core.IErrorReporter import prog8.codegen.intermediate.IRCodeGen +import prog8.intermediate.IRDataType import prog8.intermediate.IRFileWriter +import prog8.intermediate.VirtualRegister import prog8.intermediate.dumpVariables class M68kCodeGenerator(val retainSSA: Boolean): ICodeGeneratorBackend { @@ -20,7 +22,11 @@ class M68kCodeGenerator(val retainSSA: Boolean): ICodeGeneratorBackend { val irCodeGen = IRCodeGen(program, symbolTable, options, errors, retainSSA) val irProgram = irCodeGen.generate() - irProgram.verifyRegisterTypes(irCodeGen.registerTypes()) + val virtualRegisterTypes = irCodeGen.registerTypes().entries.associate { (num, type) -> + val register: VirtualRegister = if (type == IRDataType.FLOAT) VirtualRegister.float(num.value) else VirtualRegister.int(num.value) + register to type + } + irProgram.verifyRegisterTypes(virtualRegisterTypes) if (options.dumpVariables) dumpVariables(irProgram) diff --git a/codeGenM68k/test/TestInstructionSelectionOptimizations.kt b/codeGenM68k/test/TestInstructionSelectionOptimizations.kt index ff2bc79fd..157346e5f 100644 --- a/codeGenM68k/test/TestInstructionSelectionOptimizations.kt +++ b/codeGenM68k/test/TestInstructionSelectionOptimizations.kt @@ -51,12 +51,27 @@ class TestInstructionSelectionOptimizations : FunSpec({ return generateAsmChunks(outputDir, listOf(chunk), target) } + fun hwArg(register: Int, type: IRDataType, slot: Int): CallArgument = + Calls.argument(register, type, CallLocation.HardwareRegister(CallingConventionSlot(slot))) + + fun hwResult(register: Int, type: IRDataType, slot: Int): CallResult = + Calls.result(register, type, CallLocation.HardwareRegister(CallingConventionSlot(slot))) + + fun flagResult(register: Int, flag: Statusflag): CallResult = + Calls.result(register, IRDataType.BYTE, CallLocation.StatusFlag(flag)) + + fun callSite( + label: String, + arguments: List = emptyList(), + results: List = emptyList() + ): CallSite = CallSite(CallTarget.Direct(codeLabel(label)), arguments, results) + test("uses quick address adjustments and preserves large offsets") { val lines = generateAsm( tempRoot.resolve("test-m68k-offsets"), listOf( - IRInstruction(Opcode.STOREZI, IRDataType.FLOAT, reg1 = 1, immediate = 1), - IRInstruction(Opcode.STOREZI, IRDataType.FLOAT, reg1 = 1, immediate = 65535) + IRInstructions.storeZero(Opcode.STOREZI, IRDataType.FLOAT, IRMemory.indirect(1, 1)), + IRInstructions.storeZero(Opcode.STOREZI, IRDataType.FLOAT, IRMemory.indirect(1, 65535)) ) ) @@ -68,10 +83,10 @@ class TestInstructionSelectionOptimizations : FunSpec({ val lines = generateAsm( tempRoot.resolve("test-m68k-control"), listOf( - IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1 = 2, reg2 = 1), - IRInstruction(Opcode.EXT, IRDataType.WORD, reg1 = 3, reg2 = 2), - IRInstruction(Opcode.RETURNI, IRDataType.BYTE, immediate = 42), - IRInstruction(Opcode.RETURNI, IRDataType.BYTE, immediate = 255) + IRInstructions.binary(Opcode.EXT, IRDataType.BYTE, 2, 1), + IRInstructions.binary(Opcode.EXT, IRDataType.WORD, 3, 2), + IRInstructions.returnImmediate(IRDataType.BYTE, 42), + IRInstructions.returnImmediate(IRDataType.BYTE, 255) ) ) @@ -83,7 +98,7 @@ class TestInstructionSelectionOptimizations : FunSpec({ test("compares a virtual register directly against memory") { val lines = generateAsm( tempRoot.resolve("test-m68k-compare"), - listOf(IRInstruction(Opcode.CMP, IRDataType.BYTE, reg1 = 1, reg2 = 2)) + listOf(IRInstructions.compare(IRDataType.BYTE, 1, 2)) ) lines.any { it == "move.b p8_regfile+0,d0" } shouldBe true @@ -96,8 +111,8 @@ class TestInstructionSelectionOptimizations : FunSpec({ val lines = generateAsm( tempRoot.resolve("test-m68k-byte-mod"), listOf( - IRInstruction(Opcode.MODR, IRDataType.BYTE, reg1 = 1, reg2 = 2), - IRInstruction(Opcode.MODSR, IRDataType.BYTE, reg1 = 3, reg2 = 4) + IRInstructions.binary(Opcode.MODR, IRDataType.BYTE, 1, 2), + IRInstructions.binary(Opcode.MODSR, IRDataType.BYTE, 3, 4) ) ) @@ -111,8 +126,8 @@ class TestInstructionSelectionOptimizations : FunSpec({ val lines = generateAsm( tempRoot.resolve("test-m68k-mul-memsrc"), listOf( - IRInstruction(Opcode.MULR, IRDataType.WORD, reg1 = 1, reg2 = 2), - IRInstruction(Opcode.MULR, IRDataType.LONG, reg1 = 3, reg2 = 4) + IRInstructions.binary(Opcode.MULR, IRDataType.WORD, 1, 2), + IRInstructions.binary(Opcode.MULR, IRDataType.LONG, 3, 4) ) ) @@ -126,8 +141,8 @@ class TestInstructionSelectionOptimizations : FunSpec({ val lines = generateAsm( tempRoot.resolve("test-m68k-indirect"), listOf( - IRInstruction(Opcode.JUMPI, reg1 = 1), - IRInstruction(Opcode.CALLI, reg1 = 2) + IRInstructions.jumpIndirect(1), + IRInstructions.call(Opcode.CALLI, CallSite(CallTarget.Direct(codeIndirect(2)))) ) ) @@ -140,8 +155,8 @@ class TestInstructionSelectionOptimizations : FunSpec({ val lines = generateAsm( tempRoot.resolve("test-m68k-divmod-word"), listOf( - IRInstruction(Opcode.DIVMODR, IRDataType.WORD, reg1 = 1, reg2 = 2), - IRInstruction(Opcode.SDIVMODR, IRDataType.WORD, reg1 = 3, reg2 = 4) + IRInstructions.divmodRegister(Opcode.DIVMODR, IRDataType.WORD, 1, 2), + IRInstructions.divmodRegister(Opcode.SDIVMODR, IRDataType.WORD, 3, 4) ) ) @@ -155,14 +170,14 @@ class TestInstructionSelectionOptimizations : FunSpec({ val lines = generateAsm( tempRoot.resolve("test-m68k-extract"), listOf( - IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = 1, reg2 = 2), - IRInstruction(Opcode.MSIGB, IRDataType.LONG, reg1 = 3, reg2 = 4), - IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = 5, reg2 = 6), - IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1 = 7, reg2 = 8), - IRInstruction(Opcode.MSIGW, IRDataType.LONG, reg1 = 9, reg2 = 10), - IRInstruction(Opcode.LSIGW, IRDataType.LONG, reg1 = 11, reg2 = 12), - IRInstruction(Opcode.BSIGB, IRDataType.LONG, reg1 = 13, reg2 = 14), - IRInstruction(Opcode.MIDB, IRDataType.LONG, reg1 = 15, reg2 = 16) + IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, 1, 2), + IRInstructions.binary(Opcode.MSIGB, IRDataType.LONG, 3, 4), + IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, 5, 6), + IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, 7, 8), + IRInstructions.binary(Opcode.MSIGW, IRDataType.LONG, 9, 10), + IRInstructions.binary(Opcode.LSIGW, IRDataType.LONG, 11, 12), + IRInstructions.binary(Opcode.BSIGB, IRDataType.LONG, 13, 14), + IRInstructions.binary(Opcode.MIDB, IRDataType.LONG, 15, 16) ) ) @@ -176,10 +191,10 @@ class TestInstructionSelectionOptimizations : FunSpec({ val lines = generateAsm( tempRoot.resolve("test-m68k-lsb-msb"), listOf( - IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = 1, reg2 = 2), - IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1 = 3, reg2 = 4), - IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = 5, reg2 = 6), - IRInstruction(Opcode.MSIGB, IRDataType.LONG, reg1 = 7, reg2 = 8) + IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, 1, 2), + IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, 3, 4), + IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, 5, 6), + IRInstructions.binary(Opcode.MSIGB, IRDataType.LONG, 7, 8) ) ) @@ -192,8 +207,8 @@ class TestInstructionSelectionOptimizations : FunSpec({ val lines = generateAsm( tempRoot.resolve("test-m68k-lsw-msw"), listOf( - IRInstruction(Opcode.LSIGW, IRDataType.LONG, reg1 = 1, reg2 = 2), - IRInstruction(Opcode.MSIGW, IRDataType.LONG, reg1 = 3, reg2 = 4) + IRInstructions.binary(Opcode.LSIGW, IRDataType.LONG, 1, 2), + IRInstructions.binary(Opcode.MSIGW, IRDataType.LONG, 3, 4) ) ) @@ -207,9 +222,9 @@ class TestInstructionSelectionOptimizations : FunSpec({ val lines = generateAsm( tempRoot.resolve("test-m68k-lmh"), listOf( - IRInstruction(Opcode.BSIGB, IRDataType.LONG, reg1 = 1, reg2 = 2), - IRInstruction(Opcode.MIDB, IRDataType.LONG, reg1 = 3, reg2 = 4), - IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1 = 5, reg2 = 6) + IRInstructions.binary(Opcode.BSIGB, IRDataType.LONG, 1, 2), + IRInstructions.binary(Opcode.MIDB, IRDataType.LONG, 3, 4), + IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, 5, 6) ) ) @@ -218,21 +233,12 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("forwards an immediate load into a following hardware-register call argument") { - val args = FunctionCallArgs( - listOf( - FunctionCallArgs.ArgumentSpec( - "", - null, - FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(1), CallingConventionSlot(10), null) - ) - ), - emptyList() - ) + val site = callSite("copper.move", arguments = listOf(hwArg(1, IRDataType.WORD, 10))) val lines = generateAsm( tempRoot.resolve("test-m68k-call-forward"), listOf( - IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = 1, immediate = 384), - IRInstruction(Opcode.CALL, labelSymbol = "copper.move", fcallArgs = args) + IRInstructions.load(IRDataType.WORD, 1, 384), + IRInstructions.call(site) ) ) @@ -242,19 +248,13 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("forwards all immediate loads for a multi-argument call") { - val args = FunctionCallArgs( - listOf( - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(1), CallingConventionSlot(10), null)), - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(2), CallingConventionSlot(11), null)) - ), - emptyList() - ) + val site = callSite("copper.move", arguments = listOf(hwArg(1, IRDataType.WORD, 10), hwArg(2, IRDataType.WORD, 11))) val lines = generateAsm( tempRoot.resolve("test-m68k-call-forward-multi"), listOf( - IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = 1, immediate = 384), - IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = 2, immediate = 1220), - IRInstruction(Opcode.CALL, labelSymbol = "copper.move", fcallArgs = args) + IRInstructions.load(IRDataType.WORD, 1, 384), + IRInstructions.load(IRDataType.WORD, 2, 1220), + IRInstructions.call(site) ) ) @@ -265,18 +265,12 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("does not partially forward a multi-argument call") { - val args = FunctionCallArgs( - listOf( - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(1), CallingConventionSlot(10), null)), - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(2), CallingConventionSlot(11), null)) - ), - emptyList() - ) + val site = callSite("copper.move", arguments = listOf(hwArg(1, IRDataType.WORD, 10), hwArg(2, IRDataType.WORD, 11))) val lines = generateAsm( tempRoot.resolve("test-m68k-call-forward-atomic"), listOf( - IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = 1, immediate = 384), - IRInstruction(Opcode.CALL, labelSymbol = "copper.move", fcallArgs = args) + IRInstructions.load(IRDataType.WORD, 1, 384), + IRInstructions.call(site) ) ) @@ -285,17 +279,15 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("retains immediate loads for named call arguments") { - val args = FunctionCallArgs( - listOf( - FunctionCallArgs.ArgumentSpec("value", null, FunctionCallArgs.RegSpec(IRDataType.BYTE, RegisterNum(1), null, null)) - ), - emptyList() + val site = callSite( + "callee", + arguments = listOf(Calls.argument(1, IRDataType.BYTE, CallLocation.ParameterMemory("value"))) ) val lines = generateAsm( tempRoot.resolve("test-m68k-call-named-argument"), listOf( - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = 1, immediate = 42), - IRInstruction(Opcode.CALL, labelSymbol = "callee", fcallArgs = args) + IRInstructions.load(IRDataType.BYTE, 1, 42), + IRInstructions.call(site) ) ) @@ -304,16 +296,13 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("retains the register-file store when the value is used after the call") { - val args = FunctionCallArgs( - listOf(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(1), CallingConventionSlot(10), null))), - emptyList() - ) + val site = callSite("copper.move", arguments = listOf(hwArg(1, IRDataType.WORD, 10))) val lines = generateAsm( tempRoot.resolve("test-m68k-call-forward-live"), listOf( - IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = 1, immediate = 384), - IRInstruction(Opcode.CALL, labelSymbol = "copper.move", fcallArgs = args), - IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1 = 1, labelSymbol = "p8b_test.p8v_value") + IRInstructions.load(IRDataType.WORD, 1, 384), + IRInstructions.call(site), + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.WORD, 1, IRMemory.direct("p8b_test.p8v_value")) ) ) @@ -322,19 +311,13 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("uses moveq for forwarded small immediate call arguments") { - val args = FunctionCallArgs( - listOf( - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(1), CallingConventionSlot(10), null)), - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.BYTE, RegisterNum(2), CallingConventionSlot(11), null)) - ), - emptyList() - ) + val site = callSite("copper.wait", arguments = listOf(hwArg(1, IRDataType.WORD, 10), hwArg(2, IRDataType.BYTE, 11))) val lines = generateAsm( tempRoot.resolve("test-m68k-call-forward-moveq"), listOf( - IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = 1, immediate = 100), - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = 2, immediate = 48), - IRInstruction(Opcode.CALL, labelSymbol = "copper.wait", fcallArgs = args) + IRInstructions.load(IRDataType.WORD, 1, 100), + IRInstructions.load(IRDataType.BYTE, 2, 48), + IRInstructions.call(site) ) ) @@ -345,17 +328,12 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("maps byte values 128-255 to signed moveq range for forwarded arguments") { - val args = FunctionCallArgs( - listOf( - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.BYTE, RegisterNum(1), CallingConventionSlot(10), null)) - ), - emptyList() - ) + val site = callSite("copper.wait", arguments = listOf(hwArg(1, IRDataType.BYTE, 10))) val lines = generateAsm( tempRoot.resolve("test-m68k-call-forward-moveq-signed"), listOf( - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = 1, immediate = 200), - IRInstruction(Opcode.CALL, labelSymbol = "copper.wait", fcallArgs = args) + IRInstructions.load(IRDataType.BYTE, 1, 200), + IRInstructions.call(site) ) ) @@ -364,17 +342,12 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("keeps move.w for forwarded immediates that do not fit moveq range") { - val args = FunctionCallArgs( - listOf( - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(1), CallingConventionSlot(10), null)) - ), - emptyList() - ) + val site = callSite("copper.move", arguments = listOf(hwArg(1, IRDataType.WORD, 10))) val lines = generateAsm( tempRoot.resolve("test-m68k-call-forward-nomoveq"), listOf( - IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = 1, immediate = 384), - IRInstruction(Opcode.CALL, labelSymbol = "copper.move", fcallArgs = args) + IRInstructions.load(IRDataType.WORD, 1, 384), + IRInstructions.call(site) ) ) @@ -385,21 +358,12 @@ class TestInstructionSelectionOptimizations : FunSpec({ // === Floating-point immediate call-argument forwarding and dead-store removal === test("forwards an immediate float load into a following FPU hardware-register call argument") { - val args = FunctionCallArgs( - listOf( - FunctionCallArgs.ArgumentSpec( - "", - null, - FunctionCallArgs.RegSpec(IRDataType.FLOAT, RegisterNum(1), CallingConventionSlot(25), null) - ) - ), - emptyList() - ) + val site = callSite("math.func", arguments = listOf(hwArg(1, IRDataType.FLOAT, 25))) val lines = generateAsm( tempRoot.resolve("test-m68k-call-forward-float"), listOf( - IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = RegisterNum(1), immediateFp = 1.0), - IRInstruction(Opcode.CALL, labelSymbol = "math.func", fcallArgs = args) + IRInstructions.loadFloat(1, 1.0), + IRInstructions.call(site) ) ) @@ -409,21 +373,12 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("forwards an immediate float load into a different FPU register and drops the dead fregfile store") { - val args = FunctionCallArgs( - listOf( - FunctionCallArgs.ArgumentSpec( - "", - null, - FunctionCallArgs.RegSpec(IRDataType.FLOAT, RegisterNum(1), CallingConventionSlot(26), null) - ) - ), - emptyList() - ) + val site = callSite("math.func", arguments = listOf(hwArg(1, IRDataType.FLOAT, 26))) val lines = generateAsm( tempRoot.resolve("test-m68k-call-forward-float-fp1"), listOf( - IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = RegisterNum(1), immediateFp = 100.0), - IRInstruction(Opcode.CALL, labelSymbol = "math.func", fcallArgs = args) + IRInstructions.loadFloat(1, 100.0), + IRInstructions.call(site) ) ) @@ -433,19 +388,13 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("forwards both integer and float immediate loads for a mixed-argument call") { - val args = FunctionCallArgs( - listOf( - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(1), CallingConventionSlot(10), null)), - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, RegisterNum(2), CallingConventionSlot(25), null)) - ), - emptyList() - ) + val site = callSite("mixed.func", arguments = listOf(hwArg(1, IRDataType.WORD, 10), hwArg(2, IRDataType.FLOAT, 25))) val lines = generateAsm( tempRoot.resolve("test-m68k-call-forward-mixed"), listOf( - IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = 1, immediate = 384), - IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = RegisterNum(2), immediateFp = 1.0), - IRInstruction(Opcode.CALL, labelSymbol = "mixed.func", fcallArgs = args) + IRInstructions.load(IRDataType.WORD, 1, 384), + IRInstructions.loadFloat(2, 1.0), + IRInstructions.call(site) ) ) @@ -457,18 +406,13 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("retains the fregfile store when a forwarded float value is used after the call") { - val args = FunctionCallArgs( - listOf( - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, RegisterNum(1), CallingConventionSlot(25), null)) - ), - emptyList() - ) + val site = callSite("math.func", arguments = listOf(hwArg(1, IRDataType.FLOAT, 25))) val lines = generateAsm( tempRoot.resolve("test-m68k-call-forward-float-live"), listOf( - IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = RegisterNum(1), immediateFp = 1.0), - IRInstruction(Opcode.CALL, labelSymbol = "math.func", fcallArgs = args), - IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = RegisterNum(1), labelSymbol = "p8b_test.p8v_value") + IRInstructions.loadFloat(1, 1.0), + IRInstructions.call(site), + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.FLOAT, 1, IRMemory.direct("p8b_test.p8v_value")) ) ) @@ -478,27 +422,24 @@ class TestInstructionSelectionOptimizations : FunSpec({ // === sys.memcopy inlining === - fun memcopyArgs(): FunctionCallArgs { - return FunctionCallArgs( - listOf( - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.LONG, RegisterNum(1), CallingConventionSlot(18), null)), - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.LONG, RegisterNum(2), CallingConventionSlot(19), null)), - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.LONG, RegisterNum(3), CallingConventionSlot(10), null)) - ), - emptyList() + fun memcopySite(): CallSite = callSite( + "sys.memcopy", + arguments = listOf( + hwArg(1, IRDataType.LONG, 18), + hwArg(2, IRDataType.LONG, 19), + hwArg(3, IRDataType.LONG, 10) ) - } + ) fun generateMemcopyAsm(outputDir: Path, srcImm: UInt?, tgtImm: UInt?, countImm: UInt?): List { - val args = memcopyArgs() val instructions = mutableListOf() if (srcImm != null) - instructions.add(IRInstruction(Opcode.LOAD, IRDataType.LONG, reg1 = 1, immediate = srcImm.toInt())) + instructions.add(IRInstructions.load(IRDataType.LONG, 1, srcImm.toInt())) if (tgtImm != null) - instructions.add(IRInstruction(Opcode.LOAD, IRDataType.LONG, reg1 = 2, immediate = tgtImm.toInt())) + instructions.add(IRInstructions.load(IRDataType.LONG, 2, tgtImm.toInt())) if (countImm != null) - instructions.add(IRInstruction(Opcode.LOAD, IRDataType.LONG, reg1 = 3, immediate = countImm.toInt())) - instructions.add(IRInstruction(Opcode.CALL, labelSymbol = "sys.memcopy", fcallArgs = args)) + instructions.add(IRInstructions.load(IRDataType.LONG, 3, countImm.toInt())) + instructions.add(IRInstructions.call(memcopySite())) return generateAsm(outputDir, instructions) } @@ -557,15 +498,14 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("calls sys.memcopy when count is not a forwarded immediate") { - val args = memcopyArgs() val lines = generateAsm( tempRoot.resolve("test-m68k-memcopy-variable"), listOf( - IRInstruction(Opcode.LOAD, IRDataType.LONG, reg1 = 1, immediate = 0x1000), - IRInstruction(Opcode.LOAD, IRDataType.LONG, reg1 = 2, immediate = 0x2000), + IRInstructions.load(IRDataType.LONG, 1, 0x1000), + IRInstructions.load(IRDataType.LONG, 2, 0x2000), // no LOAD for r3, count comes from elsewhere (simulated by loading from a made-up value) - IRInstruction(Opcode.LOADM, IRDataType.LONG, reg1 = 3, labelSymbol = "p8b_test.p8v_count"), - IRInstruction(Opcode.CALL, labelSymbol = "sys.memcopy", fcallArgs = args) + IRInstructions.loadMemory(Opcode.LOADM, IRDataType.LONG, 3, IRMemory.direct("p8b_test.p8v_count")), + IRInstructions.call(memcopySite()) ) ) @@ -580,18 +520,12 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("does not partially forward a mixed-argument call when a float arg has no immediate load") { - val args = FunctionCallArgs( - listOf( - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(1), CallingConventionSlot(10), null)), - FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, RegisterNum(2), CallingConventionSlot(25), null)) - ), - emptyList() - ) + val site = callSite("mixed.func", arguments = listOf(hwArg(1, IRDataType.WORD, 10), hwArg(2, IRDataType.FLOAT, 25))) val lines = generateAsm( tempRoot.resolve("test-m68k-call-forward-mixed-atomic"), listOf( - IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = 1, immediate = 384), - IRInstruction(Opcode.CALL, labelSymbol = "mixed.func", fcallArgs = args) + IRInstructions.load(IRDataType.WORD, 1, 384), + IRInstructions.call(site) ) ) @@ -603,7 +537,7 @@ class TestInstructionSelectionOptimizations : FunSpec({ val lines = generateAsm( tempRoot.resolve("test-m68k-pointer-a0"), listOf( - IRInstruction(Opcode.STOREZI, IRDataType.WORD, reg1 = 1, immediate = 1) + IRInstructions.storeZero(Opcode.STOREZI, IRDataType.WORD, IRMemory.indirect(1, 1)) ) ) @@ -613,9 +547,9 @@ class TestInstructionSelectionOptimizations : FunSpec({ test("ADDR.P zero-extends a word source") { val chunk = IRCodeChunk(null, null) - chunk.instructions.add(IRInstruction(Opcode.LOAD, IRDataType.POINTER, reg1 = 1, immediate = 0x10000)) - chunk.instructions.add(IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = 2, immediate = 10)) - chunk.instructions.add(IRInstruction(Opcode.ADDR, IRDataType.POINTER, reg1 = 1, reg2 = 2)) + chunk.instructions.add(IRInstructions.load(IRDataType.POINTER, 1, 0x10000)) + chunk.instructions.add(IRInstructions.load(IRDataType.WORD, 2, 10)) + chunk.instructions.add(IRInstructions.binary(Opcode.ADDR, IRDataType.POINTER, 1, 2)) val lines = generateAsmChunks( tempRoot.resolve("test-m68k-addr-p-word-src"), listOf(chunk) @@ -628,9 +562,9 @@ class TestInstructionSelectionOptimizations : FunSpec({ test("ADDR.P does not zero-extend a long source") { val chunk = IRCodeChunk(null, null) - chunk.instructions.add(IRInstruction(Opcode.LOAD, IRDataType.POINTER, reg1 = 1, immediate = 0x10000)) - chunk.instructions.add(IRInstruction(Opcode.LOAD, IRDataType.LONG, reg1 = 2, immediate = 10)) - chunk.instructions.add(IRInstruction(Opcode.ADDR, IRDataType.POINTER, reg1 = 1, reg2 = 2)) + chunk.instructions.add(IRInstructions.load(IRDataType.POINTER, 1, 0x10000)) + chunk.instructions.add(IRInstructions.load(IRDataType.LONG, 2, 10)) + chunk.instructions.add(IRInstructions.binary(Opcode.ADDR, IRDataType.POINTER, 1, 2)) val lines = generateAsmChunks( tempRoot.resolve("test-m68k-addr-p-long-src"), listOf(chunk) @@ -642,9 +576,9 @@ class TestInstructionSelectionOptimizations : FunSpec({ test("SUBR.P zero-extends a word source") { val chunk = IRCodeChunk(null, null) - chunk.instructions.add(IRInstruction(Opcode.LOAD, IRDataType.POINTER, reg1 = 1, immediate = 0x10000)) - chunk.instructions.add(IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = 2, immediate = 10)) - chunk.instructions.add(IRInstruction(Opcode.SUBR, IRDataType.POINTER, reg1 = 1, reg2 = 2)) + chunk.instructions.add(IRInstructions.load(IRDataType.POINTER, 1, 0x10000)) + chunk.instructions.add(IRInstructions.load(IRDataType.WORD, 2, 10)) + chunk.instructions.add(IRInstructions.binary(Opcode.SUBR, IRDataType.POINTER, 1, 2)) val lines = generateAsmChunks( tempRoot.resolve("test-m68k-subr-p-word-src"), listOf(chunk) @@ -656,22 +590,13 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("does not forward across an intervening instruction") { - val args = FunctionCallArgs( - listOf( - FunctionCallArgs.ArgumentSpec( - "", - null, - FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(1), CallingConventionSlot(10), null) - ) - ), - emptyList() - ) + val site = callSite("copper.move", arguments = listOf(hwArg(1, IRDataType.WORD, 10))) val lines = generateAsm( tempRoot.resolve("test-m68k-call-forward-boundary"), listOf( - IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = 1, immediate = 384), - IRInstruction(Opcode.NOP), - IRInstruction(Opcode.CALL, labelSymbol = "copper.move", fcallArgs = args) + IRInstructions.load(IRDataType.WORD, 1, 384), + IRInstructions.simple(Opcode.NOP), + IRInstructions.call(site) ) ) @@ -680,24 +605,15 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("retains the register-file store when the value is read via a backward jump") { - val args = FunctionCallArgs( - listOf( - FunctionCallArgs.ArgumentSpec( - "", - null, - FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(1), CallingConventionSlot(10), null) - ) - ), - emptyList() - ) + val site = callSite("copper.move", arguments = listOf(hwArg(1, IRDataType.WORD, 10))) val loopHead = IRCodeChunk("test.start.loop", null) loopHead.instructions.add( - IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1 = 1, labelSymbol = "p8b_test.p8v_out") + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.WORD, 1, IRMemory.direct("p8b_test.p8v_out")) ) val loopBody = IRCodeChunk(null, null) - loopBody.instructions.add(IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = 1, immediate = 384)) - loopBody.instructions.add(IRInstruction(Opcode.CALL, labelSymbol = "copper.move", fcallArgs = args)) - loopBody.instructions.add(IRInstruction(Opcode.JUMP, labelSymbol = "test.start.loop")) + loopBody.instructions.add(IRInstructions.load(IRDataType.WORD, 1, 384)) + loopBody.instructions.add(IRInstructions.call(site)) + loopBody.instructions.add(IRInstructions.jump(codeLabel("test.start.loop"))) val lines = generateAsmChunks( tempRoot.resolve("test-m68k-call-forward-backjump"), listOf(loopHead, loopBody) @@ -708,22 +624,13 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("suppresses the register-file stores inside a loop when the registers are only used by the call") { - val args = FunctionCallArgs( - listOf( - FunctionCallArgs.ArgumentSpec( - "", - null, - FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(1), CallingConventionSlot(10), null) - ) - ), - emptyList() - ) + val site = callSite("copper.move", arguments = listOf(hwArg(1, IRDataType.WORD, 10))) val loopHead = IRCodeChunk("test.start.loop", null) - loopHead.instructions.add(IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = 2, immediate = 1)) + loopHead.instructions.add(IRInstructions.load(IRDataType.WORD, 2, 1)) val loopBody = IRCodeChunk(null, null) - loopBody.instructions.add(IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = 1, immediate = 384)) - loopBody.instructions.add(IRInstruction(Opcode.CALL, labelSymbol = "copper.move", fcallArgs = args)) - loopBody.instructions.add(IRInstruction(Opcode.JUMP, labelSymbol = "test.start.loop")) + loopBody.instructions.add(IRInstructions.load(IRDataType.WORD, 1, 384)) + loopBody.instructions.add(IRInstructions.call(site)) + loopBody.instructions.add(IRInstructions.jump(codeLabel("test.start.loop"))) val lines = generateAsmChunks( tempRoot.resolve("test-m68k-call-forward-loop-singleuse"), listOf(loopHead, loopBody) @@ -734,19 +641,10 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("retains the register-file store when the value is read by a later inline asm chunk") { - val args = FunctionCallArgs( - listOf( - FunctionCallArgs.ArgumentSpec( - "", - null, - FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(1), CallingConventionSlot(10), null) - ) - ), - emptyList() - ) + val site = callSite("copper.move", arguments = listOf(hwArg(1, IRDataType.WORD, 10))) val chunk = IRCodeChunk(null, null) - chunk.instructions.add(IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = 1, immediate = 384)) - chunk.instructions.add(IRInstruction(Opcode.CALL, labelSymbol = "copper.move", fcallArgs = args)) + chunk.instructions.add(IRInstructions.load(IRDataType.WORD, 1, 384)) + chunk.instructions.add(IRInstructions.call(site)) val inlineAsm = IRInlineAsmChunk(null, "move.w p8_regfile+0,d1", false, null) val lines = generateAsmChunks( tempRoot.resolve("test-m68k-call-forward-inlineasm"), @@ -758,19 +656,10 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("still suppresses the register-file store when an inline asm chunk does not read the register file") { - val args = FunctionCallArgs( - listOf( - FunctionCallArgs.ArgumentSpec( - "", - null, - FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(1), CallingConventionSlot(10), null) - ) - ), - emptyList() - ) + val site = callSite("copper.move", arguments = listOf(hwArg(1, IRDataType.WORD, 10))) val chunk = IRCodeChunk(null, null) - chunk.instructions.add(IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = 1, immediate = 384)) - chunk.instructions.add(IRInstruction(Opcode.CALL, labelSymbol = "copper.move", fcallArgs = args)) + chunk.instructions.add(IRInstructions.load(IRDataType.WORD, 1, 384)) + chunk.instructions.add(IRInstructions.call(site)) val inlineAsm = IRInlineAsmChunk(null, "move.w custom.INTREQR,d1", false, null) val lines = generateAsmChunks( tempRoot.resolve("test-m68k-call-forward-inlineasm-harmless"), @@ -783,10 +672,10 @@ class TestInstructionSelectionOptimizations : FunSpec({ test("removes jmp to immediately following label") { val chunk1 = IRCodeChunk(null, null) - chunk1.instructions.add(IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = 1, immediate = 42)) - chunk1.instructions.add(IRInstruction(Opcode.JUMP, labelSymbol = "test.end")) + chunk1.instructions.add(IRInstructions.load(IRDataType.BYTE, 1, 42)) + chunk1.instructions.add(IRInstructions.jump(codeLabel("test.end"))) val chunk2 = IRCodeChunk("test.end", null) - chunk2.instructions.add(IRInstruction(Opcode.RETURN)) + chunk2.instructions.add(IRInstructions.returnVoid()) val lines = generateAsmChunks( tempRoot.resolve("test-m68k-jmp-to-next-label"), listOf(chunk1, chunk2) @@ -798,11 +687,11 @@ class TestInstructionSelectionOptimizations : FunSpec({ test("removes jmp to immediately following label but keeps label on jmp line") { val chunk1 = IRCodeChunk(null, null) - chunk1.instructions.add(IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = 1, immediate = 42)) + chunk1.instructions.add(IRInstructions.load(IRDataType.BYTE, 1, 42)) val chunk2 = IRCodeChunk("test.mid", null) - chunk2.instructions.add(IRInstruction(Opcode.JUMP, labelSymbol = "test.end")) + chunk2.instructions.add(IRInstructions.jump(codeLabel("test.end"))) val chunk3 = IRCodeChunk("test.end", null) - chunk3.instructions.add(IRInstruction(Opcode.RETURN)) + chunk3.instructions.add(IRInstructions.returnVoid()) val lines = generateAsmChunks( tempRoot.resolve("test-m68k-jmp-to-next-label-with-label"), listOf(chunk1, chunk2, chunk3) @@ -817,10 +706,10 @@ class TestInstructionSelectionOptimizations : FunSpec({ // When bsr+rts is followed immediately by the target label, both bsr and rts are removed // (optimizeJmpToNextLabel removes the bra that optimizeTailCall created) val chunk1 = IRCodeChunk(null, null) - chunk1.instructions.add(IRInstruction(Opcode.CALL, labelSymbol = "test.target")) - chunk1.instructions.add(IRInstruction(Opcode.RETURN)) + chunk1.instructions.add(IRInstructions.call(callSite("test.target"))) + chunk1.instructions.add(IRInstructions.returnVoid()) val chunk2 = IRCodeChunk("test.target", null) - chunk2.instructions.add(IRInstruction(Opcode.RETURN)) + chunk2.instructions.add(IRInstructions.returnVoid()) val lines = generateAsmChunks( tempRoot.resolve("test-m68k-tail-call"), listOf(chunk1, chunk2) @@ -838,12 +727,12 @@ class TestInstructionSelectionOptimizations : FunSpec({ test("optimizes bsr+rts to bra when target is not immediately following") { // When there's code between the bsr+rts and the target label, the bra is kept val chunk1 = IRCodeChunk(null, null) - chunk1.instructions.add(IRInstruction(Opcode.CALL, labelSymbol = "test.target")) - chunk1.instructions.add(IRInstruction(Opcode.RETURN)) + chunk1.instructions.add(IRInstructions.call(callSite("test.target"))) + chunk1.instructions.add(IRInstructions.returnVoid()) val chunk2 = IRCodeChunk(null, null) - chunk2.instructions.add(IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = 1, immediate = 99)) + chunk2.instructions.add(IRInstructions.load(IRDataType.BYTE, 1, 99)) val chunk3 = IRCodeChunk("test.target", null) - chunk3.instructions.add(IRInstruction(Opcode.RETURN)) + chunk3.instructions.add(IRInstructions.returnVoid()) val lines = generateAsmChunks( tempRoot.resolve("test-m68k-tail-call-with-gap"), listOf(chunk1, chunk2, chunk3) @@ -859,14 +748,14 @@ class TestInstructionSelectionOptimizations : FunSpec({ test("optimizes bsr+rts to bra but keeps label on bsr line") { val chunk1 = IRCodeChunk(null, null) - chunk1.instructions.add(IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = 1, immediate = 42)) + chunk1.instructions.add(IRInstructions.load(IRDataType.BYTE, 1, 42)) val chunk2 = IRCodeChunk("test.caller", null) - chunk2.instructions.add(IRInstruction(Opcode.CALL, labelSymbol = "test.target")) - chunk2.instructions.add(IRInstruction(Opcode.RETURN)) + chunk2.instructions.add(IRInstructions.call(callSite("test.target"))) + chunk2.instructions.add(IRInstructions.returnVoid()) val chunk3 = IRCodeChunk(null, null) - chunk3.instructions.add(IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = 2, immediate = 99)) + chunk3.instructions.add(IRInstructions.load(IRDataType.BYTE, 2, 99)) val chunk4 = IRCodeChunk("test.target", null) - chunk4.instructions.add(IRInstruction(Opcode.RETURN)) + chunk4.instructions.add(IRInstructions.returnVoid()) val lines = generateAsmChunks( tempRoot.resolve("test-m68k-tail-call-with-label"), listOf(chunk1, chunk2, chunk3, chunk4) @@ -936,17 +825,10 @@ class TestInstructionSelectionOptimizations : FunSpec({ // Bug fix: single-return expression calls with @A0-A6/@FP0-FP7 returns // should emit the store instruction because the IR doesn't generate LOADHR // for single-return calls. - val args = FunctionCallArgs( - emptyList(), - listOf( - FunctionCallArgs.RegSpec(IRDataType.POINTER, RegisterNum(0), CallingConventionSlot(18), null) - ) - ) + val site = callSite("test.asmsub", results = listOf(hwResult(0, IRDataType.POINTER, 18))) val lines = generateAsm( tempRoot.resolve("test-m68k-single-return-a0"), - listOf( - IRInstruction(Opcode.CALL, labelSymbol = "test.asmsub", fcallArgs = args) - ) + listOf(IRInstructions.call(site)) ) // Should emit a store from a0 to virtual register lines.any { it.contains("move.l") && it.contains("a0") && it.contains("p8_regfile") } shouldBe true @@ -955,17 +837,10 @@ class TestInstructionSelectionOptimizations : FunSpec({ test("single-return expression call with @FP0 (slot 25) emits store instruction") { // Bug fix: single-return expression calls with float register returns // should emit the store instruction. - val args = FunctionCallArgs( - emptyList(), - listOf( - FunctionCallArgs.RegSpec(IRDataType.FLOAT, RegisterNum(0), CallingConventionSlot(25), null) - ) - ) + val site = callSite("test.asmsub", results = listOf(hwResult(0, IRDataType.FLOAT, 25))) val lines = generateAsm( tempRoot.resolve("test-m68k-single-return-fp0"), - listOf( - IRInstruction(Opcode.CALL, labelSymbol = "test.asmsub", fcallArgs = args) - ) + listOf(IRInstructions.call(site)) ) // Should emit fmove.s from fp0 to virtual register lines.any { it.contains("fmove.s") && it.contains("fp0") } shouldBe true @@ -975,18 +850,13 @@ class TestInstructionSelectionOptimizations : FunSpec({ // Bug fix: in multi-assign context, slot returns should be skipped because // the IR generates LOADHR for them. Emitting move here would clobber CPU flags // before the IR's branch pattern can read them. - val args = FunctionCallArgs( - emptyList(), - listOf( - FunctionCallArgs.RegSpec(IRDataType.BYTE, RegisterNum(0), CallingConventionSlot(10), null), - FunctionCallArgs.RegSpec(IRDataType.BYTE, RegisterNum(1), null, Statusflag.Pz) - ) + val site = callSite( + "test.asmsub", + results = listOf(hwResult(0, IRDataType.BYTE, 10), flagResult(1, Statusflag.Pz)) ) val lines = generateAsm( tempRoot.resolve("test-m68k-multi-assign-d0-pz"), - listOf( - IRInstruction(Opcode.CALL, labelSymbol = "test.asmsub", fcallArgs = args) - ) + listOf(IRInstructions.call(site)) ) // Find the bsr line val jsrIndex = lines.indexOfFirst { it.startsWith("bsr") && it.contains("test.asmsub") } @@ -1003,23 +873,13 @@ class TestInstructionSelectionOptimizations : FunSpec({ test("CALLFAR multi-assign with slot + flag return does not emit move between jsr and branch") { // Bug fix: CALLFAR in multi-assign context should also skip slot returns. // CALLFAR only works for amiga targets, so we need a separate helper. - val args = FunctionCallArgs( - emptyList(), - listOf( - FunctionCallArgs.RegSpec(IRDataType.POINTER, RegisterNum(0), CallingConventionSlot(18), null), - FunctionCallArgs.RegSpec(IRDataType.BYTE, RegisterNum(1), null, Statusflag.Pz) - ) + // For amiga CALLFAR the target is an automatic library call: library number 1 (exec.library) + // and a negative LVO offset into its jump table. + val callfarSite = CallSite( + CallTarget.AmigaLibrary(library = 1, lvo = -30, name = "test.libfunc"), + results = listOf(hwResult(0, IRDataType.POINTER, 18), flagResult(1, Statusflag.Pz)) ) - // For amiga CALLFAR, address is the LVO offset (negative as Int, but stored as UInt bits) - // -30 as Int = 0xFFFFFFE2 as UInt bits - val negativeOffset = (-30).toUInt() - val callfarInsn = IRInstruction( - Opcode.CALLFAR, - labelSymbol = "test.libfunc", - fcallArgs = args, - address = MemoryAddress(negativeOffset), - immediate = 1 // bank number (1=exec.library) - ).apply { extSubName = "test.libfunc" } + val callfarInsn = IRInstructions.call(Opcode.CALLFAR, callfarSite) val target = Amiga500Target() val options = CompilationOptions.builder(target) @@ -1061,7 +921,7 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("dbra via IRLoopChunk for repeat word loop 1000") { - val body = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.INCM, IRDataType.WORD, labelSymbol="p8b_main.p8v_sum") } + val body = IRCodeChunk(null, null).also { it += IRInstructions.memoryOp(Opcode.INCM, IRDataType.WORD, IRMemory.direct("p8b_main.p8v_sum")) } val loop = IRLoopChunk("p8_label_gen_2", 1000, mutableListOf(body)) val lines = generateAsmChunks(tempRoot, listOf(loop)) lines.any { it.contains("move.w #999,d7") } shouldBe true @@ -1069,7 +929,7 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("dbra via IRLoopChunk for repeat byte loop 100") { - val body = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.INCM, IRDataType.WORD, labelSymbol="p8b_main.p8v_sum") } + val body = IRCodeChunk(null, null).also { it += IRInstructions.memoryOp(Opcode.INCM, IRDataType.WORD, IRMemory.direct("p8b_main.p8v_sum")) } val loop = IRLoopChunk("p8_label_gen_1", 100, mutableListOf(body)) val lines = generateAsmChunks(tempRoot, listOf(loop)) lines.any { it.contains("move.w #99,d7") } shouldBe true @@ -1077,7 +937,7 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("dbra via IRLoopChunk for repeat 256 special case") { - val body = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.INCM, IRDataType.WORD, labelSymbol="p8b_main.p8v_sum") } + val body = IRCodeChunk(null, null).also { it += IRInstructions.memoryOp(Opcode.INCM, IRDataType.WORD, IRMemory.direct("p8b_main.p8v_sum")) } val loop = IRLoopChunk("p8_label_gen_1", 256, mutableListOf(body)) val lines = generateAsmChunks(tempRoot, listOf(loop)) lines.any { it.contains("move.w #255,d7") } shouldBe true @@ -1109,7 +969,7 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("dbra via IRLoopChunk preserves label on init line") { - val body = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.INCM, IRDataType.WORD, labelSymbol="p8b_main.p8v_sum") } + val body = IRCodeChunk(null, null).also { it += IRInstructions.memoryOp(Opcode.INCM, IRDataType.WORD, IRMemory.direct("p8b_main.p8v_sum")) } val outerChunk = IRCodeChunk("outer", null) val loop = IRLoopChunk("p8_label_gen_2", 100, mutableListOf(body)) val lines = generateAsmChunks(tempRoot, listOf(outerChunk, loop)) @@ -1119,7 +979,7 @@ class TestInstructionSelectionOptimizations : FunSpec({ } test("dbra via IRLoopChunk preserves label on loop") { - val body = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.INCM, IRDataType.WORD, labelSymbol="p8b_main.p8v_sum") } + val body = IRCodeChunk(null, null).also { it += IRInstructions.memoryOp(Opcode.INCM, IRDataType.WORD, IRMemory.direct("p8b_main.p8v_sum")) } val loop = IRLoopChunk("p8_label_gen_2", 100, mutableListOf(body)) val lines = generateAsmChunks(tempRoot, listOf(loop)) lines.any { it.contains("p8_label_gen_2:") } shouldBe true @@ -1128,7 +988,7 @@ class TestInstructionSelectionOptimizations : FunSpec({ test("IRLoopChunk saves d7 around sqrt helper call") { val body = IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.SQRT, IRDataType.BYTE, reg1=1, reg2=2) + it += IRInstructions.binary(Opcode.SQRT, IRDataType.BYTE, 1, 2) } val loop = IRLoopChunk("p8_label_gen_2", 5, mutableListOf(body)) val lines = generateAsmChunks(tempRoot, listOf(loop)) @@ -1152,7 +1012,7 @@ class TestInstructionSelectionOptimizations : FunSpec({ program.options.outputDir = outputDir val sub = IRSubroutine("test.start", emptyList(), emptyList(), Position.DUMMY) val body = IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.MUL, IRDataType.LONG, reg1=1, immediate=3) + it += IRInstructions.binaryImmediate(Opcode.MUL, IRDataType.LONG, 1, 3) } sub.chunks.add(IRLoopChunk("p8_label_gen_2", 5, mutableListOf(body))) val block = IRBlock("test", false, IRBlock.Options(), Position.DUMMY) @@ -1261,8 +1121,8 @@ class TestInstructionSelectionOptimizations : FunSpec({ val lines = generateAsm( tempRoot.resolve("test-m68k-d0-cache-skip"), listOf( - IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1 = 3, reg2 = 1), - IRInstruction(Opcode.ADDM, IRDataType.WORD, reg1 = 1, labelSymbol = "p8b_test.p8v_value") + IRInstructions.binary(Opcode.ADDR, IRDataType.WORD, 3, 1), + IRInstructions.memoryOp(Opcode.ADDM, IRDataType.WORD, IRMemory.direct("p8b_test.p8v_value"), source = 1) ) ) @@ -1274,9 +1134,9 @@ class TestInstructionSelectionOptimizations : FunSpec({ val lines = generateAsm( tempRoot.resolve("test-m68k-d0-cache-invalidate-slot"), listOf( - IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1 = 3, reg2 = 1), - IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = 1), - IRInstruction(Opcode.ADDM, IRDataType.WORD, reg1 = 1, labelSymbol = "p8b_test.p8v_value") + IRInstructions.binary(Opcode.ADDR, IRDataType.WORD, 3, 1), + IRInstructions.unary(Opcode.INC, IRDataType.WORD, 1), + IRInstructions.memoryOp(Opcode.ADDM, IRDataType.WORD, IRMemory.direct("p8b_test.p8v_value"), source = 1) ) ) @@ -1288,9 +1148,9 @@ class TestInstructionSelectionOptimizations : FunSpec({ val lines = generateAsm( tempRoot.resolve("test-m68k-d0-cache-invalidate-d0"), listOf( - IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1 = 3, reg2 = 1), - IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1 = 4, reg2 = 2), - IRInstruction(Opcode.ADDM, IRDataType.WORD, reg1 = 1, labelSymbol = "p8b_test.p8v_value") + IRInstructions.binary(Opcode.ADDR, IRDataType.WORD, 3, 1), + IRInstructions.binary(Opcode.ADDR, IRDataType.WORD, 4, 2), + IRInstructions.memoryOp(Opcode.ADDM, IRDataType.WORD, IRMemory.direct("p8b_test.p8v_value"), source = 1) ) ) @@ -1300,9 +1160,9 @@ class TestInstructionSelectionOptimizations : FunSpec({ test("d0 cache is invalidated at chunk boundaries") { val chunk1 = IRCodeChunk(null, null) - chunk1.instructions.add(IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1 = 3, reg2 = 1)) + chunk1.instructions.add(IRInstructions.binary(Opcode.ADDR, IRDataType.WORD, 3, 1)) val chunk2 = IRCodeChunk("test.next", null) - chunk2.instructions.add(IRInstruction(Opcode.ADDM, IRDataType.WORD, reg1 = 1, labelSymbol = "p8b_test.p8v_value")) + chunk2.instructions.add(IRInstructions.memoryOp(Opcode.ADDM, IRDataType.WORD, IRMemory.direct("p8b_test.p8v_value"), source = 1)) val lines = generateAsmChunks( tempRoot.resolve("test-m68k-d0-cache-boundary"), listOf(chunk1, chunk2) @@ -1312,12 +1172,95 @@ class TestInstructionSelectionOptimizations : FunSpec({ lines.count { it.startsWith("move.w p8_regfile+") && it.endsWith(",d0") } shouldBe 2 } + // === centralized MemoryReference lowering (Direct/Indexed/Indirect) === + + test("indexed memory lowering scales the index in the addressing mode on 68020") { + val lines = generateAsm( + tempRoot.resolve("test-m68k-indexed-68020"), + listOf( + IRInstructions.loadMemory(Opcode.LOADX, IRDataType.WORD, 6, IRMemory.indexed("arr", 3, IRDataType.WORD, 2, 4)), + IRInstructions.storeMemory(Opcode.STOREX, IRDataType.WORD, 8, IRMemory.indexed("arr", 3, IRDataType.WORD, 4)), + IRInstructions.storeZero(Opcode.STOREZX, IRDataType.BYTE, IRMemory.indexed("arr", 3, IRDataType.WORD, 8)) + ) + ) + + lines.any { it == "lea arr+4,a0" } shouldBe true + lines.any { it == "move.w (a0,d0.w*2),d0" } shouldBe true + // the stored value is staged in d1 because a0/d0 hold the effective address + lines.any { it == "move.w d1,(a0,d0.w*4)" } shouldBe true + lines.any { it == "clr.b (a0,d0.w*8)" } shouldBe true + } + + test("indexed memory lowering scales the index by hand on 68000") { + val lines = generateAsm( + tempRoot.resolve("test-m68k-indexed-68000"), + listOf( + IRInstructions.loadMemory(Opcode.LOADX, IRDataType.WORD, 6, IRMemory.indexed("arr", 3, IRDataType.WORD, 2)), + IRInstructions.storeMemory(Opcode.STOREX, IRDataType.WORD, 8, IRMemory.indexed("arr", 3, IRDataType.WORD, 4)), + IRInstructions.storeZero(Opcode.STOREZX, IRDataType.BYTE, IRMemory.indexed("arr", 3, IRDataType.WORD, 8)) + ), + target = Amiga500Target() + ) + + lines.any { it == "add.w d0,d0" } shouldBe true + lines.any { it == "lsl.w #2,d0" } shouldBe true + lines.any { it == "muls.w #8,d0" } shouldBe true + lines.count { it.contains("(a0,d0.w*") } shouldBe 0 + } + + test("indirect memory lowering picks displacement addressing or explicit pointer adjustment") { + val lines = generateAsm( + tempRoot.resolve("test-m68k-indirect-displacement"), + listOf( + IRInstructions.loadMemory(Opcode.LOADI, IRDataType.WORD, 9, IRMemory.indirect(4, 0)), + IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, 11, IRMemory.indirect(4, 6)), + IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, 10, IRMemory.indirect(4, 40000)) + ) + ) + + lines.any { it.startsWith("movea.l p8_regfile") && it.endsWith(",a0") } shouldBe true + lines.any { it == "move.w (a0),d0" } shouldBe true + lines.any { it.endsWith(",(6,a0)") } shouldBe true + // displacements beyond the signed 16-bit range need an explicit adda + lines.any { it == "adda.l #40000,a0" } shouldBe true + } + + test("float indexed memory lowering uses full width d0.l indexing") { + val lines = generateAsm( + tempRoot.resolve("test-m68k-indexed-float"), + listOf( + IRInstructions.loadMemory(Opcode.LOADX, IRDataType.FLOAT, 1, IRMemory.indexed("arr", 2, IRDataType.WORD, 4)), + IRInstructions.storeMemory(Opcode.STOREX, IRDataType.FLOAT, 1, IRMemory.indexed("arr", 2, IRDataType.WORD, 8)) + ) + ) + + lines.any { it == "lsl.l #2,d0" } shouldBe true + lines.any { it == "lsl.l #3,d0" } shouldBe true + lines.any { it == "fmove.s (0,a0,d0.l),fp0" } shouldBe true + lines.any { it == "fmove.s fp0,(0,a0,d0.l)" } shouldBe true + } + + test("direct memory lowering resolves symbol displacements and absolute addresses") { + val lines = generateAsm( + tempRoot.resolve("test-m68k-direct-memory"), + listOf( + IRInstructions.loadMemory(Opcode.LOADM, IRDataType.WORD, 12, IRMemory.direct("var", 4)), + IRInstructions.storeMemory(Opcode.STOREM, IRDataType.WORD, 12, IRMemory.direct(0xdff180u)), + IRInstructions.storeImmediate(IRDataType.WORD, 1234, IRMemory.direct("var")) + ) + ) + + lines.any { it == "move.w var+4,p8_regfile+0" } shouldBe true + lines.any { it == "move.w p8_regfile+0,\$00dff180" } shouldBe true + lines.any { it == "move.w #1234,var" } shouldBe true + } + // === extb.l gate robustification === test("M68020 sign-extends byte to long with extb.l") { val lines = generateAsm( tempRoot.resolve("test-m68k-extbl-qemu"), - listOf(IRInstruction(Opcode.EXTLS, IRDataType.BYTE, reg1 = 1, reg2 = 2)) + listOf(IRInstructions.binary(Opcode.EXTLS, IRDataType.BYTE, 1, 2)) ) lines.count { it == "extb.l d0" } shouldBe 1 @@ -1328,7 +1271,7 @@ class TestInstructionSelectionOptimizations : FunSpec({ test("M68000 sign-extends byte to long with ext.w plus ext.l") { val lines = generateAsm( tempRoot.resolve("test-m68k-extbl-amiga"), - listOf(IRInstruction(Opcode.EXTLS, IRDataType.BYTE, reg1 = 1, reg2 = 2)), + listOf(IRInstructions.binary(Opcode.EXTLS, IRDataType.BYTE, 1, 2)), target = Amiga500Target() ) diff --git a/codeGenNew6502/src/prog8/codegen/new6502/AsmGen.kt b/codeGenNew6502/src/prog8/codegen/new6502/AsmGen.kt index 3fe62f045..42b1ca239 100644 --- a/codeGenNew6502/src/prog8/codegen/new6502/AsmGen.kt +++ b/codeGenNew6502/src/prog8/codegen/new6502/AsmGen.kt @@ -95,7 +95,7 @@ internal class AsmGen(val program: IRProgram, private val target: ICompilationTa private data class RegFileLayout(val offsets: Map, val totalSize: Int) private val regFileLayout: RegFileLayout by lazy { - val allRegs = regsUsed.regsTypes + val allRegs = regsUsed.intRegsTypes val offsets = mutableMapOf() var currentOffset = 0 for ((regNum, type) in allRegs.entries.sortedBy { it.key.value }) { @@ -264,7 +264,7 @@ internal class AsmGen(val program: IRProgram, private val target: ICompilationTa // === FP register file layout === private val fpRegFileLayout: RegFileLayout by lazy { - val allFpNums = (regsUsed.readFpRegs.keys + regsUsed.writeFpRegs.keys).map { it.value }.distinct().sorted() + val allFpNums = (regsUsed.floatRegsRead.keys + regsUsed.floatRegsWritten.keys).map { it.value }.distinct().sorted() val offsets = mutableMapOf() var currentOffset = 0 val floatSize = target.FLOAT_MEM_SIZE.toInt() @@ -865,10 +865,9 @@ internal class AsmGen(val program: IRProgram, private val target: ICompilationTa private fun translateInstruction(insn: IRInstruction) { emitRaw(" ; $insn") - if (insn.opcode == Opcode.LOADHR || insn.opcode == Opcode.STOREHR) { + insn.hardwareSlot?.let { hw -> val slotNames = mapOf(0 to "A", 1 to "X", 2 to "Y", 3 to "AX", 4 to "AY", 5 to "XY") - val slot = insn.immediate - slot?.let { slotNames[it]?.let { name -> emitRaw(" ; slot s$it == $name") } } + slotNames[hw.slot.value]?.let { name -> emitRaw(" ; slot ${hw.slot} == $name") } } when (insn.opcode) { Opcode.NOP -> {} diff --git a/codeGenNew6502/src/prog8/codegen/new6502/InstrArithmetic.kt b/codeGenNew6502/src/prog8/codegen/new6502/InstrArithmetic.kt index c3de1e7d5..a99ad93d2 100644 --- a/codeGenNew6502/src/prog8/codegen/new6502/InstrArithmetic.kt +++ b/codeGenNew6502/src/prog8/codegen/new6502/InstrArithmetic.kt @@ -21,160 +21,128 @@ package prog8.codegen.new6502 import prog8.code.core.AssemblyError -import prog8.intermediate.IRDataType -import prog8.intermediate.IRInstruction -import prog8.intermediate.Opcode +import prog8.intermediate.* internal fun AsmGen.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 - val offset = insn.labelSymbolOffset - // FLOAT operations use fpReg1/fpReg2 instead of reg1/reg2 + // FLOAT operations use the float register file instead of the integer one if (type == IRDataType.FLOAT) { translateFloatArithmetic(insn) return } when (insn.opcode) { - Opcode.INC -> incrementRegister(r1 ?: error("INC needs reg1"), type) - Opcode.INCM -> incrementMemory(resolveAddress(addr, label, offset), type) - Opcode.DEC -> decrementRegister(r1 ?: error("DEC needs reg1"), type) - Opcode.DECM -> decrementMemory(resolveAddress(addr, label, offset), type) - Opcode.NEG -> negateRegister(r1 ?: error("NEG needs reg1"), type) - Opcode.NEGM -> negateMemory(resolveAddress(addr, label, offset), type) + Opcode.INC -> incrementRegister(insn.requireIntDest().intNumber, type) + Opcode.INCM -> incrementMemory(resolveAddress(insn.requireMemory()), type) + Opcode.DEC -> decrementRegister(insn.requireIntDest().intNumber, type) + Opcode.DECM -> decrementMemory(resolveAddress(insn.requireMemory()), type) + Opcode.NEG -> negateRegister(insn.requireIntDest().intNumber, type) + Opcode.NEGM -> negateMemory(resolveAddress(insn.requireMemory()), type) Opcode.ADDR -> { - val r2val = r2 ?: error("ADDR needs reg2") - addRegisters(r1 ?: error("ADDR needs reg1"), r2val, type) + addRegisters(insn.requireIntDest().intNumber, insn.requireIntSourceA().intNumber, type) } Opcode.ADD -> { - val value = imm ?: error("ADD needs immediate") - addImmediate(r1 ?: error("ADD needs reg1"), value, type) + addImmediate(insn.requireIntDest().intNumber, insn.requireImmediateInt(), type) } Opcode.ADDM -> { - val target = resolveAddress(addr, label, offset) - addMemory(r1 ?: error("ADDM needs reg1"), target, type) + val target = resolveAddress(insn.requireMemory()) + addMemory(insn.requireIntSourceA().intNumber, target, type) } Opcode.ADDIM -> { - val target = resolveAddress(addr, label, offset) - addMemoryImm(imm ?: error("ADDIM needs immediate"), target, type) + val target = resolveAddress(insn.requireMemory()) + addMemoryImm(insn.requireImmediateInt(), target, type) } Opcode.SUBR -> { - val r2val = r2 ?: error("SUBR needs reg2") - subRegisters(r1 ?: error("SUBR needs reg1"), r2val, type) + subRegisters(insn.requireIntDest().intNumber, insn.requireIntSourceA().intNumber, type) } Opcode.SUB -> { - val value = imm ?: error("SUB needs immediate") - subImmediate(r1 ?: error("SUB needs reg1"), value, type) + subImmediate(insn.requireIntDest().intNumber, insn.requireImmediateInt(), type) } Opcode.SUBM -> { - val target = resolveAddress(addr, label, offset) - subMemory(r1 ?: error("SUBM needs reg1"), target, type) + val target = resolveAddress(insn.requireMemory()) + subMemory(insn.requireIntSourceA().intNumber, target, type) } Opcode.SUBIM -> { - val target = resolveAddress(addr, label, offset) - subMemoryImm(imm ?: error("SUBIM needs imm"), target, type) + val target = resolveAddress(insn.requireMemory()) + subMemoryImm(insn.requireImmediateInt(), target, type) } Opcode.MULR -> { - val r2val = r2 ?: error("MULR needs reg2") - mulRegisters(r1 ?: error("MULR needs reg1"), r2val, type) + mulRegisters(insn.requireIntDest().intNumber, insn.requireIntSourceA().intNumber, type) } Opcode.MUL -> { - val value = imm ?: error("MUL needs immediate") - mulImmediate(r1 ?: error("MUL needs reg1"), value, type) + mulImmediate(insn.requireIntDest().intNumber, insn.requireImmediateInt(), type) } Opcode.MULM -> { - val target = resolveAddress(addr, label, offset) - mulMemory(r1 ?: error("MULM needs reg1"), target, type) + val target = resolveAddress(insn.requireMemory()) + mulMemory(insn.requireIntSourceA().intNumber, target, type) } Opcode.MULSR -> { - val r2val = r2 ?: error("MULSR needs reg2") - mulSignedRegisters(r1 ?: error("MULSR needs reg1"), r2val, type) + mulSignedRegisters(insn.requireIntDest().intNumber, insn.requireIntSourceA().intNumber, type) } Opcode.MULS -> { - val value = imm ?: error("MULS needs immediate") - mulSignedImmediate(r1 ?: error("MULS needs reg1"), value, type) + mulSignedImmediate(insn.requireIntDest().intNumber, insn.requireImmediateInt(), type) } Opcode.MULSM -> { - val target = resolveAddress(addr, label, offset) - mulSignedMemory(r1 ?: error("MULSM needs reg1"), target, type) + val target = resolveAddress(insn.requireMemory()) + mulSignedMemory(insn.requireIntSourceA().intNumber, target, type) } Opcode.DIVR -> { - val r2val = r2 ?: error("DIVR needs reg2") - divRegisters(r1 ?: error("DIVR needs reg1"), r2val, type) + divRegisters(insn.requireIntDest().intNumber, insn.requireIntSourceA().intNumber, type) } Opcode.DIV -> { - val value = imm ?: error("DIV needs immediate") - divImmediate(r1 ?: error("DIV needs reg1"), value, type) + divImmediate(insn.requireIntDest().intNumber, insn.requireImmediateInt(), type) } Opcode.DIVM -> { - val target = resolveAddress(addr, label, offset) - divMemory(r1 ?: error("DIVM needs reg1"), target, type) + val target = resolveAddress(insn.requireMemory()) + divMemory(insn.requireIntSourceA().intNumber, target, type) } Opcode.DIVSR -> { - val r2val = r2 ?: error("DIVSR needs reg2") - divSignedRegisters(r1 ?: error("DIVSR needs reg1"), r2val, type) + divSignedRegisters(insn.requireIntDest().intNumber, insn.requireIntSourceA().intNumber, type) } Opcode.DIVS -> { - val value = imm ?: error("DIVS needs immediate") - divSignedImmediate(r1 ?: error("DIVS needs reg1"), value, type) + divSignedImmediate(insn.requireIntDest().intNumber, insn.requireImmediateInt(), type) } Opcode.DIVSM -> { - val target = resolveAddress(addr, label, offset) - divSignedMemory(r1 ?: error("DIVSM needs reg1"), target, type) + val target = resolveAddress(insn.requireMemory()) + divSignedMemory(insn.requireIntSourceA().intNumber, target, type) } Opcode.MODR -> { - val r2val = r2 ?: error("MODR needs reg2") - modRegisters(r1 ?: error("MODR needs reg1"), r2val, type) + modRegisters(insn.requireIntDest().intNumber, insn.requireIntSourceA().intNumber, type) } Opcode.MOD -> { - val value = imm ?: error("MOD needs immediate") - modImmediate(r1 ?: error("MOD needs reg1"), value, type) + modImmediate(insn.requireIntDest().intNumber, insn.requireImmediateInt(), type) } Opcode.MODSR -> { - val r2val = r2 ?: error("MODSR needs reg2") - modSignedRegisters(r1 ?: error("MODSR needs reg1"), r2val, type) + modSignedRegisters(insn.requireIntDest().intNumber, insn.requireIntSourceA().intNumber, type) } Opcode.MODS -> { - val value = imm ?: error("MODS needs immediate") - modSignedImmediate(r1 ?: error("MODS needs reg1"), value, type) + modSignedImmediate(insn.requireIntDest().intNumber, insn.requireImmediateInt(), type) } Opcode.DIVMODR -> { - val r2val = r2 ?: error("DIVMODR needs reg2") - divModRegisters(r1 ?: error("DIVMODR needs reg1"), r2val, type) + divModRegisters(insn.requireIntDest().intNumber, insn.requireDestB().intNumber, type) } Opcode.DIVMOD -> { - val value = imm ?: error("DIVMOD needs immediate") - val r2val = r2 ?: error("DIVMOD needs reg2") - divModImmediate(r1 ?: error("DIVMOD needs reg1"), r2val, value, type) + divModImmediate(insn.requireIntDest().intNumber, insn.requireDestB().intNumber, insn.requireImmediateInt(), type) } Opcode.SDIVMODR -> { - val r2val = r2 ?: error("SDIVMODR needs reg2") - sdivModRegisters(r1 ?: error("SDIVMODR needs reg1"), r2val, type) + sdivModRegisters(insn.requireIntDest().intNumber, insn.requireDestB().intNumber, type) } Opcode.SDIVMOD -> { - val value = imm ?: error("SDIVMOD needs immediate") - val r2val = r2 ?: error("SDIVMOD needs reg2") - sdivModImmediate(r1 ?: error("SDIVMOD needs reg1"), r2val, value, type) + sdivModImmediate(insn.requireIntDest().intNumber, insn.requireDestB().intNumber, insn.requireImmediateInt(), type) } Opcode.CMP -> { - val r2val = r2 ?: error("CMP needs reg2") - compareRegisters(r1 ?: error("CMP needs reg1"), r2val, type) + compareRegisters(insn.requireIntSourceA().intNumber, insn.requireSrcB().intNumber, type) } Opcode.CMPI -> { - val value = imm ?: error("CMPI needs immediate") - compareImmediate(r1 ?: error("CMPI needs reg1"), value, type) + compareImmediate(insn.requireIntSourceA().intNumber, insn.requireImmediateInt(), type) } else -> error("Unknown arithmetic opcode: ${insn.opcode}") @@ -1304,17 +1272,10 @@ internal fun AsmGen.compareImmediate(r1: Int, value: Int, type: IRDataType) { // === Float arithmetic === internal fun AsmGen.translateFloatArithmetic(insn: IRInstruction) { - val fr1 = insn.fpReg1 - val fr2 = insn.fpReg2 - val immFp = insn.immediateFp - val addr = insn.address - val label = insn.labelSymbol - val offset = insn.labelSymbolOffset - when (insn.opcode) { Opcode.INC -> { // fr1 += 1.0 - val fpReg = fr1 ?: error("INC.f needs fpReg1") + val fpReg = insn.requireFloatDest().floatNumber emitLine("lda #<${fpRegAddr(fpReg.value)}") emitLine("ldy #>${fpRegAddr(fpReg.value)}") emitLine("jsr floats.MOVFM") @@ -1330,7 +1291,7 @@ internal fun AsmGen.translateFloatArithmetic(insn: IRInstruction) { Opcode.DEC -> { // fr1 -= 1.0 // NOTE: FSUB does FAC1 = memory - FAC1, so we must use push/pop + FSUBT instead - val fpReg = fr1 ?: error("DEC.f needs fpReg1") + val fpReg = insn.requireFloatDest().floatNumber emitLine("lda #<${fpRegAddr(fpReg.value)}") emitLine("ldy #>${fpRegAddr(fpReg.value)}") emitLine("jsr floats.MOVFM") @@ -1349,7 +1310,7 @@ internal fun AsmGen.translateFloatArithmetic(insn: IRInstruction) { Opcode.NEG -> { // fr1 = -fr1 - val fpReg = fr1 ?: error("NEG.f needs fpReg1") + val fpReg = insn.requireFloatDest().floatNumber emitLine("lda #<${fpRegAddr(fpReg.value)}") emitLine("ldy #>${fpRegAddr(fpReg.value)}") emitLine("jsr floats.MOVFM") @@ -1361,8 +1322,8 @@ internal fun AsmGen.translateFloatArithmetic(insn: IRInstruction) { Opcode.ADDR -> { // fr1 += fr2 - val dstReg = fr1 ?: error("ADDR.f needs fpReg1") - val srcReg = fr2 ?: error("ADDR.f needs fpReg2") + val dstReg = insn.requireFloatDest().floatNumber + val srcReg = insn.requireFloatSourceA().floatNumber if (useC64PushPopOperands) { // On C64/PET32, FAC2 must be loaded last via CONUPK for correct arisgn setup emitLine("lda #<${fpRegAddr(srcReg.value)}") @@ -1388,8 +1349,8 @@ internal fun AsmGen.translateFloatArithmetic(insn: IRInstruction) { Opcode.SUBR -> { // fr1 -= fr2 => FSUBT = FAC2 - FAC1, need FAC2=fr1, FAC1=fr2 - val dstReg = fr1 ?: error("SUBR.f needs fpReg1") - val srcReg = fr2 ?: error("SUBR.f needs fpReg2") + val dstReg = insn.requireFloatDest().floatNumber + val srcReg = insn.requireFloatSourceA().floatNumber if (useC64PushPopOperands) { emitLine("lda #<${fpRegAddr(dstReg.value)}") emitLine("ldy #>${fpRegAddr(dstReg.value)}") @@ -1414,8 +1375,8 @@ internal fun AsmGen.translateFloatArithmetic(insn: IRInstruction) { Opcode.MULR -> { // fr1 *= fr2 - val dstReg = fr1 ?: error("MULR.f needs fpReg1") - val srcReg = fr2 ?: error("MULR.f needs fpReg2") + val dstReg = insn.requireFloatDest().floatNumber + val srcReg = insn.requireFloatSourceA().floatNumber if (useC64PushPopOperands) { emitLine("lda #<${fpRegAddr(srcReg.value)}") emitLine("ldy #>${fpRegAddr(srcReg.value)}") @@ -1440,8 +1401,8 @@ internal fun AsmGen.translateFloatArithmetic(insn: IRInstruction) { Opcode.DIVR, Opcode.DIVSR -> { // fr1 /= fr2 => FDIVT = FAC2 / FAC1, need FAC2=fr1, FAC1=fr2 - val dstReg = fr1 ?: error("DIVR.f needs fpReg1") - val srcReg = fr2 ?: error("DIVR.f needs fpReg2") + val dstReg = insn.requireFloatDest().floatNumber + val srcReg = insn.requireFloatSourceA().floatNumber if (useC64PushPopOperands) { emitLine("lda #<${fpRegAddr(dstReg.value)}") emitLine("ldy #>${fpRegAddr(dstReg.value)}") @@ -1465,8 +1426,8 @@ internal fun AsmGen.translateFloatArithmetic(insn: IRInstruction) { } Opcode.ADD, Opcode.MUL, Opcode.MULS -> { - val dstReg = fr1 ?: error("${insn.opcode}.f needs fpReg1") - val value = immFp ?: error("${insn.opcode}.f needs immediateFp") + val dstReg = insn.requireFloatDest().floatNumber + val value = insn.requireImmediateFloat() val constLabel = getFloatConstLabel(value) emitLine("lda #<${fpRegAddr(dstReg.value)}") emitLine("ldy #>${fpRegAddr(dstReg.value)}") @@ -1486,8 +1447,8 @@ internal fun AsmGen.translateFloatArithmetic(insn: IRInstruction) { Opcode.SUB -> { // NOTE: FSUB does FAC1 = memory - FAC1, so we must use push/pop + FSUBT instead - val dstReg = fr1 ?: error("SUB.f needs fpReg1") - val value = immFp ?: error("SUB.f needs immediateFp") + val dstReg = insn.requireFloatDest().floatNumber + val value = insn.requireImmediateFloat() val constLabel = getFloatConstLabel(value) emitLine("lda #<${fpRegAddr(dstReg.value)}") emitLine("ldy #>${fpRegAddr(dstReg.value)}") @@ -1506,8 +1467,8 @@ internal fun AsmGen.translateFloatArithmetic(insn: IRInstruction) { Opcode.DIV, Opcode.DIVS -> { // NOTE: FDIV does FAC1 = memory / FAC1, so we must use push/pop + FDIVT instead - val dstReg = fr1 ?: error("${insn.opcode}.f needs fpReg1") - val value = immFp ?: error("${insn.opcode}.f needs immediateFp") + val dstReg = insn.requireFloatDest().floatNumber + val value = insn.requireImmediateFloat() val constLabel = getFloatConstLabel(value) emitLine("lda #<${fpRegAddr(dstReg.value)}") emitLine("ldy #>${fpRegAddr(dstReg.value)}") @@ -1525,8 +1486,8 @@ internal fun AsmGen.translateFloatArithmetic(insn: IRInstruction) { } Opcode.ADDM -> { - val srcReg = fr1 ?: error("ADDM.f needs fpReg1") - val target = resolveAddress(addr, label, offset) + val srcReg = insn.requireFloatSourceA().floatNumber + val target = resolveAddress(insn.requireMemory()) if (useC64PushPopOperands) { emitLine("lda #<$target") emitLine("ldy #>$target") @@ -1550,8 +1511,8 @@ internal fun AsmGen.translateFloatArithmetic(insn: IRInstruction) { } Opcode.SUBM -> { - val srcReg = fr1 ?: error("SUBM.f needs fpReg1") - val target = resolveAddress(addr, label, offset) + val srcReg = insn.requireFloatSourceA().floatNumber + val target = resolveAddress(insn.requireMemory()) if (useC64PushPopOperands) { emitLine("lda #<$target") emitLine("ldy #>$target") @@ -1575,7 +1536,7 @@ internal fun AsmGen.translateFloatArithmetic(insn: IRInstruction) { } Opcode.INCM -> { - val target = resolveAddress(addr, label, offset) + val target = resolveAddress(insn.requireMemory()) emitLine("lda #<$target") emitLine("ldy #>$target") emitLine("jsr floats.MOVFM") @@ -1589,7 +1550,7 @@ internal fun AsmGen.translateFloatArithmetic(insn: IRInstruction) { } Opcode.DECM -> { - val target = resolveAddress(addr, label, offset) + val target = resolveAddress(insn.requireMemory()) emitLine("lda #<$target") emitLine("ldy #>$target") emitLine("jsr floats.MOVFM") @@ -1607,7 +1568,7 @@ internal fun AsmGen.translateFloatArithmetic(insn: IRInstruction) { } Opcode.NEGM -> { - val target = resolveAddress(addr, label, offset) + val target = resolveAddress(insn.requireMemory()) emitLine("lda #<$target") emitLine("ldy #>$target") emitLine("jsr floats.MOVFM") @@ -1618,8 +1579,8 @@ internal fun AsmGen.translateFloatArithmetic(insn: IRInstruction) { } Opcode.MULM, Opcode.MULSM -> { - val srcReg = fr1 ?: error("${insn.opcode}.f needs fpReg1") - val target = resolveAddress(addr, label, offset) + val srcReg = insn.requireFloatSourceA().floatNumber + val target = resolveAddress(insn.requireMemory()) if (useC64PushPopOperands) { emitLine("lda #<$target") emitLine("ldy #>$target") @@ -1643,8 +1604,8 @@ internal fun AsmGen.translateFloatArithmetic(insn: IRInstruction) { } Opcode.DIVM, Opcode.DIVSM -> { - val srcReg = fr1 ?: error("${insn.opcode}.f needs fpReg1") - val target = resolveAddress(addr, label, offset) + val srcReg = insn.requireFloatSourceA().floatNumber + val target = resolveAddress(insn.requireMemory()) if (useC64PushPopOperands) { emitLine("lda #<$target") emitLine("ldy #>$target") @@ -1672,8 +1633,8 @@ internal fun AsmGen.translateFloatArithmetic(insn: IRInstruction) { Opcode.MULSR -> { // fr1 *= fr2 (signed - same as unsigned for floats) - val dstReg = fr1 ?: error("MULSR.f needs fpReg1") - val srcReg = fr2 ?: error("MULSR.f needs fpReg2") + val dstReg = insn.requireFloatDest().floatNumber + val srcReg = insn.requireFloatSourceA().floatNumber if (useC64PushPopOperands) { emitLine("lda #<${fpRegAddr(srcReg.value)}") emitLine("ldy #>${fpRegAddr(srcReg.value)}") diff --git a/codeGenNew6502/src/prog8/codegen/new6502/InstrBitwise.kt b/codeGenNew6502/src/prog8/codegen/new6502/InstrBitwise.kt index 1cb053ae4..fb11a0670 100644 --- a/codeGenNew6502/src/prog8/codegen/new6502/InstrBitwise.kt +++ b/codeGenNew6502/src/prog8/codegen/new6502/InstrBitwise.kt @@ -15,119 +15,111 @@ package prog8.codegen.new6502 -import prog8.intermediate.IRDataType -import prog8.intermediate.IRInstruction -import prog8.intermediate.Opcode +import prog8.intermediate.* internal fun AsmGen.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 - val offset = insn.labelSymbolOffset when (insn.opcode) { Opcode.ANDR -> { - val r2val = r2 ?: error("ANDR needs reg2") - andRegisters(r1 ?: error("ANDR needs reg1"), r2val, type) + val dst = insn.requireIntDest().intNumber + val src = insn.requireIntSourceA().intNumber + andRegisters(dst, src, type) } Opcode.AND -> { - val value = imm ?: error("AND needs immediate") - andImmediate(r1 ?: error("AND needs reg1"), value, type) + val dst = insn.requireIntDest().intNumber + val value = insn.requireImmediateInt() + andImmediate(dst, value, type) } Opcode.ANDM -> { - val sourceAddress = resolveAddress(addr, label, offset) - andMemory(r1 ?: error("ANDM needs reg1"), sourceAddress, type) + val src = insn.requireIntSourceA().intNumber + val sourceAddress = resolveAddress(insn.requireMemory()) + andMemory(src, sourceAddress, type) } Opcode.ORR -> { - val r2val = r2 ?: error("ORR needs reg2") - orRegisters(r1 ?: error("ORR needs reg1"), r2val, type) + val dst = insn.requireIntDest().intNumber + val src = insn.requireIntSourceA().intNumber + orRegisters(dst, src, type) } Opcode.OR -> { - val value = imm ?: error("OR needs immediate") - orImmediate(r1 ?: error("OR needs reg1"), value, type) + val dst = insn.requireIntDest().intNumber + val value = insn.requireImmediateInt() + orImmediate(dst, value, type) } Opcode.ORM -> { - val sourceAddress = resolveAddress(addr, label, offset) - orMemory(r1 ?: error("ORM needs reg1"), sourceAddress, type) + val src = insn.requireIntSourceA().intNumber + val sourceAddress = resolveAddress(insn.requireMemory()) + orMemory(src, sourceAddress, type) } Opcode.XORR -> { - val r2val = r2 ?: error("XORR needs reg2") - xorRegisters(r1 ?: error("XORR needs reg1"), r2val, type) + val dst = insn.requireIntDest().intNumber + val src = insn.requireIntSourceA().intNumber + xorRegisters(dst, src, type) } Opcode.XOR -> { - val value = imm ?: error("XOR needs immediate") - xorImmediate(r1 ?: error("XOR needs reg1"), value, type) + val dst = insn.requireIntDest().intNumber + val value = insn.requireImmediateInt() + xorImmediate(dst, value, type) } Opcode.XORM -> { - val sourceAddress = resolveAddress(addr, label, offset) - xorMemory(r1 ?: error("XORM needs reg1"), sourceAddress, type) + val src = insn.requireIntSourceA().intNumber + val sourceAddress = resolveAddress(insn.requireMemory()) + xorMemory(src, sourceAddress, type) } - Opcode.INV -> invertRegister(r1 ?: error("INV needs reg1"), type) - Opcode.INVM -> invertMemory(resolveAddress(addr, label, offset), type) + Opcode.INV -> invertRegister(insn.requireIntDest().intNumber, type) + Opcode.INVM -> invertMemory(resolveAddress(insn.requireMemory()), type) - // Shift by register count (ASRN/LSRN/LSLN: r2 = shift count register) + // Shift by register count (ASRN/LSRN/LSLN: srcA = shift count register) Opcode.ASRN -> { - val countReg = r2 ?: error("ASRN needs reg2 for shift count") - arithmeticShiftRightVar(r1 ?: error("ASRN needs reg1"), countReg, type) + val dst = insn.requireIntDest().intNumber + val countReg = insn.requireIntSourceA().intNumber + arithmeticShiftRightVar(dst, countReg, type) } Opcode.LSRN -> { - val countReg = r2 ?: error("LSRN needs reg2 for shift count") - logicalShiftRightVar(r1 ?: error("LSRN needs reg1"), countReg, type) + val dst = insn.requireIntDest().intNumber + val countReg = insn.requireIntSourceA().intNumber + logicalShiftRightVar(dst, countReg, type) } Opcode.LSLN -> { - val countReg = r2 ?: error("LSLN needs reg2 for shift count") - logicalShiftLeftVar(r1 ?: error("LSLN needs reg1"), countReg, type) + val dst = insn.requireIntDest().intNumber + val countReg = insn.requireIntSourceA().intNumber + logicalShiftLeftVar(dst, countReg, type) } - // Shift memory by register count (ASRNM/LSRNM/LSLNM: r1 = count reg, addr = target) - Opcode.ASRNM -> shiftMemoryVar(resolveAddress(addr, label, offset), r1 ?: error("ASRNM needs reg1"), type, isArithmetic = true) - Opcode.LSRNM -> shiftMemoryVar(resolveAddress(addr, label, offset), r1 ?: error("LSRNM needs reg1"), type, isArithmetic = false) - Opcode.LSLNM -> shiftMemoryLeftVar(resolveAddress(addr, label, offset), r1 ?: error("LSLNM needs reg1"), type) + // Shift memory by register count (ASRNM/LSRNM/LSLNM: srcA = count reg, memory = target) + Opcode.ASRNM -> shiftMemoryVar(resolveAddress(insn.requireMemory()), insn.requireIntSourceA().intNumber, type, isArithmetic = true) + Opcode.LSRNM -> shiftMemoryVar(resolveAddress(insn.requireMemory()), insn.requireIntSourceA().intNumber, type, isArithmetic = false) + Opcode.LSLNM -> shiftMemoryLeftVar(resolveAddress(insn.requireMemory()), insn.requireIntSourceA().intNumber, type) // Shift by 1 (ASR/LSR/LSL: single-bit shift, no count operand) - Opcode.ASR -> arithmeticShiftRight(r1 ?: error("ASR needs reg1"), 1, type) - Opcode.ASRM -> arithmeticShiftRightMemory(resolveAddress(addr, label, offset), 1, type) - Opcode.LSR -> logicalShiftRight(r1 ?: error("LSR needs reg1"), 1, type) - Opcode.LSRM -> logicalShiftRightMemory(resolveAddress(addr, label, offset), 1, type) - Opcode.LSL -> logicalShiftLeft(r1 ?: error("LSL needs reg1"), 1, type) - Opcode.LSLM -> logicalShiftLeftMemory(resolveAddress(addr, label, offset), 1, type) + Opcode.ASR -> arithmeticShiftRight(insn.requireIntDest().intNumber, 1, type) + Opcode.ASRM -> arithmeticShiftRightMemory(resolveAddress(insn.requireMemory()), 1, type) + Opcode.LSR -> logicalShiftRight(insn.requireIntDest().intNumber, 1, type) + Opcode.LSRM -> logicalShiftRightMemory(resolveAddress(insn.requireMemory()), 1, type) + Opcode.LSL -> logicalShiftLeft(insn.requireIntDest().intNumber, 1, type) + Opcode.LSLM -> logicalShiftLeftMemory(resolveAddress(insn.requireMemory()), 1, type) // Immediate-count shifts (LSLI/LSRI/ASRI): unroll the count into N 1-bit shifts - Opcode.ASRI -> arithmeticShiftRight(r1 ?: error("ASRI needs reg1"), imm ?: error("ASRI needs immediate count"), type) - Opcode.LSRI -> logicalShiftRight(r1 ?: error("LSRI needs reg1"), imm ?: error("LSRI needs immediate count"), type) - Opcode.LSLI -> logicalShiftLeft(r1 ?: error("LSLI needs reg1"), imm ?: error("LSLI needs immediate count"), type) + Opcode.ASRI -> arithmeticShiftRight(insn.requireIntDest().intNumber, insn.requireImmediateInt(), type) + Opcode.LSRI -> logicalShiftRight(insn.requireIntDest().intNumber, insn.requireImmediateInt(), type) + Opcode.LSLI -> logicalShiftLeft(insn.requireIntDest().intNumber, insn.requireImmediateInt(), type) - Opcode.ROR -> rotateRight(r1 ?: error("ROR needs reg1"), type) - Opcode.RORM -> rotateRightMemory(resolveAddress(addr, label, offset), type) - Opcode.ROL -> rotateLeft(r1 ?: error("ROL needs reg1"), type) - Opcode.ROLM -> rotateLeftMemory(resolveAddress(addr, label, offset), type) - Opcode.ROXR -> rotateRightThroughCarry(r1 ?: error("ROXR needs reg1"), type) - Opcode.ROXRM -> rotateRightThroughCarryMemory(resolveAddress(addr, label, offset), type) - Opcode.ROXL -> rotateLeftThroughCarry(r1 ?: error("ROXL needs reg1"), type) - Opcode.ROXLM -> rotateLeftThroughCarryMemory(resolveAddress(addr, label, offset), type) + Opcode.ROR -> rotateRight(insn.requireIntDest().intNumber, type) + Opcode.RORM -> rotateRightMemory(resolveAddress(insn.requireMemory()), type) + Opcode.ROL -> rotateLeft(insn.requireIntDest().intNumber, type) + Opcode.ROLM -> rotateLeftMemory(resolveAddress(insn.requireMemory()), type) + Opcode.ROXR -> rotateRightThroughCarry(insn.requireIntDest().intNumber, type) + Opcode.ROXRM -> rotateRightThroughCarryMemory(resolveAddress(insn.requireMemory()), type) + Opcode.ROXL -> rotateLeftThroughCarry(insn.requireIntDest().intNumber, type) + Opcode.ROXLM -> rotateLeftThroughCarryMemory(resolveAddress(insn.requireMemory()), type) - Opcode.BITTST -> { - val bit = imm ?: error("BITTST needs bit number") - bitTest(r1 ?: error("BITTST needs reg1"), bit) - } - Opcode.BITSET -> { - val bit = imm ?: error("BITSET needs bit number") - bitSet(r1 ?: error("BITSET needs reg1"), bit) - } - Opcode.BITCLR -> { - val bit = imm ?: error("BITCLR needs bit number") - bitClear(r1 ?: error("BITCLR needs reg1"), bit) - } - Opcode.BITTOG -> { - val bit = imm ?: error("BITTOG needs bit number") - bitToggle(r1 ?: error("BITTOG needs reg1"), bit) - } + Opcode.BITTST -> bitTest(insn.requireIntSourceA().intNumber, insn.requireImmediateInt()) + Opcode.BITSET -> bitSet(insn.requireIntDest().intNumber, insn.requireImmediateInt()) + Opcode.BITCLR -> bitClear(insn.requireIntDest().intNumber, insn.requireImmediateInt()) + Opcode.BITTOG -> bitToggle(insn.requireIntDest().intNumber, insn.requireImmediateInt()) else -> error("Unknown bitwise opcode: ${insn.opcode}") } diff --git a/codeGenNew6502/src/prog8/codegen/new6502/InstrBranch.kt b/codeGenNew6502/src/prog8/codegen/new6502/InstrBranch.kt index acbd77979..17d32addc 100644 --- a/codeGenNew6502/src/prog8/codegen/new6502/InstrBranch.kt +++ b/codeGenNew6502/src/prog8/codegen/new6502/InstrBranch.kt @@ -36,19 +36,11 @@ package prog8.codegen.new6502 -import prog8.intermediate.IRDataType -import prog8.intermediate.IRInstruction -import prog8.intermediate.Opcode +import prog8.intermediate.* +import prog8.code.core.toHex internal fun AsmGen.translateBranch(insn: IRInstruction) { - val target = insn.branchTarget - val ls = insn.labelSymbol - - val label: String = when { - target?.label != null -> target.label!! - ls != null -> ls - else -> "unknown_target" - } + val label = targetLabel(insn) when (insn.opcode) { Opcode.BSTCC -> emitLine("bcc $label") @@ -80,14 +72,22 @@ internal fun AsmGen.translateBranch(insn: IRInstruction) { } } +/** the label (or fixed address) a branch instruction targets */ +internal fun AsmGen.targetLabel(insn: IRInstruction): String = + when (val target = insn.requireTarget()) { + is CodeReference.Label -> if (target.offset != 0) "${target.name}+${target.offset}" else target.name + is CodeReference.Absolute -> target.address.value.toHex() + is CodeReference.Indirect -> error("$insn: branch target cannot be indirect") + } + private fun AsmGen.translateCmpBranchUnsigned(insn: IRInstruction, label: String, greaterThan: Boolean = false, greaterOrEqual: Boolean = false, lessThan: Boolean = false, lessOrEqual: Boolean = false) { val type = insn.type ?: IRDataType.BYTE - val reg = insn.reg1 ?: error("branch needs reg1") - val imm = insn.immediate ?: error("unsigned branch needs immediate") + val reg = insn.requireIntSourceA().intNumber + val imm = insn.requireImmediateInt() when (type) { IRDataType.BYTE -> { @@ -161,8 +161,8 @@ private fun AsmGen.translateCmpBranchLongUnsigned(insn: IRInstruction, label: St greaterOrEqual: Boolean, lessThan: Boolean, lessOrEqual: Boolean) { - val reg = insn.reg1 ?: error("branch needs reg1") - val imm = insn.immediate ?: error("branch needs immediate") + val reg = insn.requireIntSourceA().intNumber + val imm = insn.requireImmediateInt() when { greaterThan -> { // a > imm -> imm < a: swap operands @@ -227,8 +227,8 @@ private fun AsmGen.translateCmpBranchRegUnsigned(insn: IRInstruction, label: Str greaterThan: Boolean = false, greaterOrEqual: Boolean = false) { val type = insn.type ?: IRDataType.BYTE - val reg1 = insn.reg1 ?: error("branch needs reg1") - val reg2 = insn.reg2 ?: error("reg branch needs reg2") + val reg1 = insn.requireIntSourceA().intNumber + val reg2 = insn.requireSrcB().intNumber when (type) { IRDataType.BYTE -> { @@ -301,8 +301,8 @@ private fun AsmGen.translateCmpBranchSigned(insn: IRInstruction, label: String, lessThan: Boolean = false, lessOrEqual: Boolean = false) { val type = insn.type ?: IRDataType.BYTE - val reg = insn.reg1 ?: error("branch needs reg1") - val imm = insn.immediate ?: error("signed branch needs immediate") + val reg = insn.requireIntSourceA().intNumber + val imm = insn.requireImmediateInt() when (type) { IRDataType.BYTE -> { @@ -488,8 +488,8 @@ private fun AsmGen.translateCmpBranchRegSigned(insn: IRInstruction, label: Strin lessThan: Boolean = false, lessOrEqual: Boolean = false) { val type = insn.type ?: IRDataType.BYTE - val reg1 = insn.reg1 ?: error("branch needs reg1") - val reg2 = insn.reg2 ?: error("reg branch needs reg2") + val reg1 = insn.requireIntSourceA().intNumber + val reg2 = insn.requireSrcB().intNumber when (type) { IRDataType.BYTE -> { diff --git a/codeGenNew6502/src/prog8/codegen/new6502/InstrControl.kt b/codeGenNew6502/src/prog8/codegen/new6502/InstrControl.kt index 10a6ed12b..5f043815c 100644 --- a/codeGenNew6502/src/prog8/codegen/new6502/InstrControl.kt +++ b/codeGenNew6502/src/prog8/codegen/new6502/InstrControl.kt @@ -28,20 +28,13 @@ import prog8.code.core.toHex import prog8.intermediate.* internal fun AsmGen.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?.value?.toHex() ?: error("JUMP needs target") - emitUnconditionalBranch(target) + emitUnconditionalBranch(targetLabel(insn)) } Opcode.JUMPI -> { - val reg = r1 ?: error("JUMPI needs reg1") + val reg = (insn.requireTarget() as CodeReference.Indirect).pointer.intNumber // NOTE: `jmp (ptr)` has the 6502 page-wrap bug on plain 6502 // (if the pointer's address ends in $FF the high byte is read // from the same page instead of the next). 65C02 is fine. @@ -52,13 +45,17 @@ internal fun AsmGen.translateControl(insn: IRInstruction) { } Opcode.CALL -> { - val fnLabel = label ?: addr?.value?.toHex() ?: error("CALL needs label or address") - val args = insn.fcallArgs - translateCall(fnLabel, args) + val site = insn.requireCallSite() + val fnLabel = when (val ref = site.codeReference) { + is CodeReference.Label -> ref.name + is CodeReference.Absolute -> ref.address.value.toHex() + else -> error("CALL needs a direct label or address target") + } + translateCall(fnLabel, site) } Opcode.CALLI -> { - val reg = r1 ?: error("CALLI needs reg1") + val reg = ((insn.requireCallSite().target as CallTarget.Direct).reference as CodeReference.Indirect).pointer.intNumber // NOTE: same 6502 page-wrap pitfall as JUMPI above: `jmp (ptr)` // misbehaves on plain 6502 if the pointer (here a register in // p8_regfile) lands at $xxFF. 65C02 is fine. Same hazard exists @@ -72,67 +69,40 @@ internal fun AsmGen.translateControl(insn: IRInstruction) { } Opcode.CALLFAR -> { - val target = label ?: (addr?.value ?: 0u).toHex() - val bank = imm ?: 0 + val site = insn.requireCallSite() + val bankedTarget = site.target as? CallTarget.Banked + ?: error("CALLFAR amiga library calls are not supported on 6502 targets") + val target = codeReferenceLabel(bankedTarget.reference) + val bank = bankedTarget.bank val jsrfar = jsrfarRoutine() - val args = insn.fcallArgs - if (args != null) { - for ((index, arg) in args.arguments.withIndex()) { - if (arg.reg.callingConventionSlot == null) - translateArgument(arg, index, null) - } - val slotArgs = args.arguments.withIndex().filter { it.value.reg.callingConventionSlot != null } - val orderedSlotArgs = slotArgs.sortedWith(compareBy> { - val slot = it.value.reg.callingConventionSlot!!.value - when (slot) { - 3, 4, 5 -> 0 - 2 -> 1 - 1 -> 2 - 0 -> 3 - 6, 7 -> 4 - else -> 5 - } - }.thenByDescending { -it.index }) - for ((index, arg) in orderedSlotArgs) { + for ((index, arg) in site.arguments.withIndex()) { + if (arg.hardwareSlot == null) translateArgument(arg, index, null) - } + } + for ((index, arg) in site.arguments.inSlotLoadOrder()) { + translateArgument(arg, index, null) } emitLine("jsr $jsrfar") emitLine(".word $target") emitLine(".byte $bank") - if (args != null) { - for (ret in args.returns) { - translateReturnValue(ret) - } + for (ret in site.results) { + translateReturnValue(ret) } } Opcode.CALLFARVB -> { - val target = label ?: (addr?.value ?: 0u).toHex() - val bankReg = r1 ?: error("CALLFARVB needs reg1") + val site = insn.requireCallSite() + val bankedTarget = site.target as CallTarget.BankedVariable + val target = codeReferenceLabel(bankedTarget.reference) + val bankReg = bankedTarget.bankRegister.intNumber val jsrfar = jsrfarRoutine() val patchLabel = makeLabel("callfarvb_patch") - val args = insn.fcallArgs - if (args != null) { - for ((index, arg) in args.arguments.withIndex()) { - if (arg.reg.callingConventionSlot == null) - translateArgument(arg, index, null) - } - val slotArgs = args.arguments.withIndex().filter { it.value.reg.callingConventionSlot != null } - val orderedSlotArgs = slotArgs.sortedWith(compareBy> { - val slot = it.value.reg.callingConventionSlot!!.value - when (slot) { - 3, 4, 5 -> 0 - 2 -> 1 - 1 -> 2 - 0 -> 3 - 6, 7 -> 4 - else -> 5 - } - }.thenByDescending { -it.index }) - for ((index, arg) in orderedSlotArgs) { + for ((index, arg) in site.arguments.withIndex()) { + if (arg.hardwareSlot == null) translateArgument(arg, index, null) - } + } + for ((index, arg) in site.arguments.inSlotLoadOrder()) { + translateArgument(arg, index, null) } emitLine("lda ${regAddrLo(bankReg)}") emitLine("sta ${patchLabel}+2") @@ -140,31 +110,29 @@ internal fun AsmGen.translateControl(insn: IRInstruction) { emitLabel(patchLabel) emitLine(".word $target") emitLine(".byte 0") - if (args != null) { - for (ret in args.returns) { - translateReturnValue(ret) - } + for (ret in site.results) { + translateReturnValue(ret) } } Opcode.SYSCALL -> { - val args = insn.fcallArgs - translateSyscall(insn, args) + translateSyscall(insn) } Opcode.RETURN -> { emitLine("rts") } + Opcode.RETURNR -> { val type = insn.type ?: IRDataType.BYTE if (type == IRDataType.FLOAT) { - val fpReg = insn.fpReg1 ?: error("RETURNR.f needs fpReg1") + val fpReg = insn.requireFloatSourceA().floatNumber emitLine("lda #<${fpRegAddr(fpReg.value)}") emitLine("ldy #>${fpRegAddr(fpReg.value)}") emitLine("jsr floats.MOVFM") } else { - val reg = r1 ?: error("RETURNR needs reg1") + val reg = insn.requireIntSourceA().intNumber when (type) { IRDataType.BYTE -> { emitLine("lda ${regAddrLo(reg)}") @@ -191,13 +159,13 @@ internal fun AsmGen.translateControl(insn: IRInstruction) { Opcode.RETURNI -> { val type = insn.type ?: IRDataType.BYTE if (type == IRDataType.FLOAT) { - val value = insn.immediateFp ?: error("RETURNI.f needs immediateFp") + val value = insn.requireImmediateFloat() val constLabel = getFloatConstLabel(value) emitLine("lda #<$constLabel") emitLine("ldy #>$constLabel") emitLine("jsr floats.MOVFM") } else { - val value = imm ?: error("RETURNI needs immediate") + val value = insn.requireImmediateInt() when (type) { IRDataType.BYTE -> { emitLine("lda #${value and 0xff}") @@ -224,13 +192,13 @@ internal fun AsmGen.translateControl(insn: IRInstruction) { Opcode.PUSH -> { val type = insn.type ?: IRDataType.BYTE if (type == IRDataType.FLOAT) { - val fpReg = insn.fpReg1 ?: error("PUSH.f needs fpReg1") + val fpReg = insn.requireFloatSourceA().floatNumber emitLine("lda #<${fpRegAddr(fpReg.value)}") emitLine("ldy #>${fpRegAddr(fpReg.value)}") emitLine("jsr floats.MOVFM") emitLine("jsr floats.pushFAC1") } else { - val reg = r1 ?: error("PUSH needs reg1") + val reg = insn.requireIntSourceA().intNumber when (type) { IRDataType.BYTE -> { emitLine("lda ${regAddrLo(reg)}") @@ -261,14 +229,14 @@ internal fun AsmGen.translateControl(insn: IRInstruction) { Opcode.POP -> { val type = insn.type ?: IRDataType.BYTE if (type == IRDataType.FLOAT) { - val fpReg = insn.fpReg1 ?: error("POP.f needs fpReg1") + val fpReg = insn.requireFloatDest().floatNumber emitLine("clc") emitLine("jsr floats.popFAC") emitLine("ldx #<${fpRegAddr(fpReg.value)}") emitLine("ldy #>${fpRegAddr(fpReg.value)}") emitLine("jsr floats.MOVMF") } else { - val reg = r1 ?: error("POP needs reg1") + val reg = insn.requireIntDest().intNumber when (type) { IRDataType.BYTE -> { emitLine("pla") @@ -310,22 +278,22 @@ internal fun AsmGen.translateControl(insn: IRInstruction) { Opcode.SEI -> emitLine("sei") Opcode.ALIGN -> { - val alignment = imm ?: 256 + val alignment = insn.requireImmediateInt() if (alignment > 1) { emitLine(".align ${alignment.toUInt().toHex()}") } } Opcode.LSIGB -> { - val dest = r1 ?: error("LSIGB needs reg1") - val src = r2 ?: error("LSIGB needs reg2") + val dest = insn.requireIntDest().intNumber + val src = insn.requireIntSourceA().intNumber emitLine("lda ${regAddrLo(src)}") emitLine("sta ${regAddrLo(dest)}") } Opcode.LSIGW -> { - val dest = r1 ?: error("LSIGW needs reg1") - val src = r2 ?: error("LSIGW needs reg2") + val dest = insn.requireIntDest().intNumber + val src = insn.requireIntSourceA().intNumber emitLine("lda ${regAddrLo(src)}") emitLine("sta ${regAddrLo(dest)}") emitLine("lda ${regAddrHi(src)}") @@ -333,8 +301,8 @@ internal fun AsmGen.translateControl(insn: IRInstruction) { } Opcode.MSIGB -> { - val dest = r1 ?: error("MSIGB needs reg1") - val src = r2 ?: error("MSIGB needs reg2") + val dest = insn.requireIntDest().intNumber + val src = insn.requireIntSourceA().intNumber val type = insn.type ?: IRDataType.WORD when (type) { IRDataType.WORD -> { @@ -350,8 +318,8 @@ internal fun AsmGen.translateControl(insn: IRInstruction) { } Opcode.MSIGW -> { - val dest = r1 ?: error("MSIGW needs reg1") - val src = r2 ?: error("MSIGW needs reg2") + val dest = insn.requireIntDest().intNumber + val src = insn.requireIntSourceA().intNumber // dest is WORD (bytes 0-1 only), no need to zero bytes 2-3 emitLine("lda ${regAddrByte(src, 2)}") emitLine("sta ${regAddrLo(dest)}") @@ -360,57 +328,57 @@ internal fun AsmGen.translateControl(insn: IRInstruction) { } Opcode.BSIGB -> { - val dest = r1 ?: error("BSIGB needs reg1") - val src = r2 ?: error("BSIGB needs reg2") + val dest = insn.requireIntDest().intNumber + val src = insn.requireIntSourceA().intNumber emitLine("lda ${regAddrByte(src, 2)}") emitLine("sta ${regAddrLo(dest)}") } Opcode.MIDB -> { - val dest = r1 ?: error("MIDB needs reg1") - val src = r2 ?: error("MIDB needs reg2") + val dest = insn.requireIntDest().intNumber + val src = insn.requireIntSourceA().intNumber emitLine("lda ${regAddrHi(src)}") emitLine("sta ${regAddrLo(dest)}") } Opcode.CONCAT -> { val type = insn.type ?: IRDataType.BYTE - val r1 = r1 ?: error("CONCAT needs reg1") - val r2 = r2 ?: error("CONCAT needs reg2") - val r3 = insn.reg3 ?: error("CONCAT needs reg3") + val dest = insn.requireIntDest().intNumber + val msb = insn.requireIntSourceA().intNumber + val lsb = insn.requireSrcB().intNumber when (type) { IRDataType.BYTE -> { - // r1 = WORD(r2 as MSB, r3 as LSB) - emitLine("lda ${regAddrLo(r2)}") - emitLine("sta ${regAddrHi(r1)}") - emitLine("lda ${regAddrLo(r3)}") - emitLine("sta ${regAddrLo(r1)}") + // dest = WORD(msb, lsb) + emitLine("lda ${regAddrLo(msb)}") + emitLine("sta ${regAddrHi(dest)}") + emitLine("lda ${regAddrLo(lsb)}") + emitLine("sta ${regAddrLo(dest)}") } IRDataType.WORD -> { - // r1 = LONG(r2 as MSW, r3 as LSW) - // Save r2 (msw) first in case r1 overlaps with r2 or r2==r3 - emitLine("lda ${regAddrLo(r2)}") + // dest = LONG(msb as MSW, lsb as LSW) + // Save msb first in case dest overlaps with msb or msb==lsb + emitLine("lda ${regAddrLo(msb)}") emitLine("sta $ZP_TEMP") - emitLine("lda ${regAddrHi(r2)}") + emitLine("lda ${regAddrHi(msb)}") emitLine("sta ${ZP_TEMP}+1") - // Copy r3 (lsw) to r1+0, r1+1 - emitLine("lda ${regAddrLo(r3)}") - emitLine("sta ${regAddrLo(r1)}") - emitLine("lda ${regAddrHi(r3)}") - emitLine("sta ${regAddrHi(r1)}") - // Copy saved msw to r1+2, r1+3 + // Copy lsb (lsw) to dest+0, dest+1 + emitLine("lda ${regAddrLo(lsb)}") + emitLine("sta ${regAddrLo(dest)}") + emitLine("lda ${regAddrHi(lsb)}") + emitLine("sta ${regAddrHi(dest)}") + // Copy saved msw to dest+2, dest+3 emitLine("lda $ZP_TEMP") - emitLine("sta ${regAddrByte(r1, 2)}") + emitLine("sta ${regAddrByte(dest, 2)}") emitLine("lda ${ZP_TEMP}+1") - emitLine("sta ${regAddrByte(r1, 3)}") + emitLine("sta ${regAddrByte(dest, 3)}") } else -> TODO("CONCAT ${type.name}") } } Opcode.EXT -> { - val reg = r1 ?: error("EXT needs reg1") - val srcReg = r2 ?: error("EXT needs reg2") + val reg = insn.requireIntDest().intNumber + val srcReg = insn.requireIntSourceA().intNumber if (reg != srcReg) { emitLine("lda ${regAddrLo(srcReg)}") emitLine("sta ${regAddrLo(reg)}") @@ -419,8 +387,8 @@ internal fun AsmGen.translateControl(insn: IRInstruction) { } Opcode.EXTS -> { - val dest = r1 ?: error("EXTS needs reg1") - val src = r2 ?: error("EXTS needs reg2") + val dest = insn.requireIntDest().intNumber + val src = insn.requireIntSourceA().intNumber emitLine("lda ${regAddrLo(src)}") emitLine("sta ${regAddrLo(dest)}") emitLine("and #128") @@ -434,8 +402,8 @@ internal fun AsmGen.translateControl(insn: IRInstruction) { } Opcode.EXTL -> { - val dest = r1 ?: error("EXTL needs reg1") - val src = r2 ?: error("EXTL needs reg2") + val dest = insn.requireIntDest().intNumber + val src = insn.requireIntSourceA().intNumber if (dest != src) { emitLine("lda ${regAddrLo(src)}") emitLine("sta ${regAddrLo(dest)}") @@ -446,8 +414,8 @@ internal fun AsmGen.translateControl(insn: IRInstruction) { } Opcode.EXTLS -> { - val dest = r1 ?: error("EXTLS needs reg1") - val src = r2 ?: error("EXTLS needs reg2") + val dest = insn.requireIntDest().intNumber + val src = insn.requireIntSourceA().intNumber emitLine("lda ${regAddrLo(src)}") emitLine("sta ${regAddrLo(dest)}") emitLine("and #128") @@ -508,61 +476,33 @@ internal fun AsmGen.translateControl(insn: IRInstruction) { // === Call handling === -private fun AsmGen.translateCall(fnLabel: String, args: FunctionCallArgs?) { - // Check if this is an inline ASMSUB - must be inlined at call site, not called with jsr - val inlineAsmSub = findInlineAsmSub(fnLabel) - if (inlineAsmSub != null) { - // Inline the assembly body directly at the call site (no jsr, no rts) - if (args != null) { - // Process non-slot arguments first - for ((index, arg) in args.arguments.withIndex()) { - if (arg.reg.callingConventionSlot == null) - translateArgument(arg, index, fnLabel) - } - // Process slot arguments - val slotArgs = args.arguments.withIndex().filter { it.value.reg.callingConventionSlot != null } - val orderedSlotArgs = slotArgs.sortedWith(compareBy> { - val slot = it.value.reg.callingConventionSlot!!.value - when (slot) { - 3, 4, 5 -> 0 - 2 -> 1 - 1 -> 2 - 0 -> 3 - 6, 7 -> 4 - else -> 5 - } - }.thenByDescending { -it.index }) - for ((index, arg) in orderedSlotArgs) { - translateArgument(arg, index, fnLabel) - } - } - emitRaw(" ; inlined: $fnLabel") - inlineAsmSub.asmChunk.assembly.lineSequence().forEach { line -> - if (line.isNotBlank()) emitRaw(" $line") - } - emitRaw(" ; end inlined: $fnLabel") - if (args != null) { - for (ret in args.returns) { - translateReturnValue(ret) - } - } - return - } +/** the label (or fixed address) a static code reference denotes */ +internal fun codeReferenceLabel(reference: CodeReference): String = when (reference) { + is CodeReference.Label -> if (reference.offset != 0) "${reference.name}+${reference.offset}" else reference.name + is CodeReference.Absolute -> reference.address.value.toHex() + is CodeReference.Indirect -> error("code reference must be a static one, not $reference") +} - if (args != null) { - // Process non-slot arguments first (they use A as temp to store to memory/registers) - for ((index, arg) in args.arguments.withIndex()) { - if (arg.reg.callingConventionSlot == null) - translateArgument(arg, index, fnLabel) - } - // Process slot arguments in optimal order to avoid register clobbering. - // Order matches old 6502 codegen: paired regs first (AX/AY/XY), then single regs (Y, X, A), - // then float regs, then status flags. This ensures that loading a paired register - // (which clobbers two hardware regs) doesn't overwrite a value needed for a later argument. - val slotArgs = args.arguments.withIndex().filter { it.value.reg.callingConventionSlot != null } - val orderedSlotArgs = slotArgs.sortedWith(compareBy> { - val slot = it.value.reg.callingConventionSlot!!.value - when (slot) { +/** the calling convention slot this argument is passed in, if it is passed in a cpu hardware register */ +internal val CallArgument.hardwareSlot: CallingConventionSlot? + get() = (location as? CallLocation.HardwareRegister)?.slot + +/** the calling convention slot this result is delivered in, if it is delivered in a cpu hardware register */ +internal val CallResult.hardwareSlot: CallingConventionSlot? + get() = (location as? CallLocation.HardwareRegister)?.slot + +/** + * The slot-based arguments, in the order they must be loaded to avoid register clobbering. + * Order matches the old 6502 codegen: paired regs first (AX/AY/XY), then single regs (Y, X, A), + * then float regs, then status flags. This ensures that loading a paired register + * (which clobbers two hardware regs) doesn't overwrite a value needed for a later argument. + * Within each group the original argument order is kept. + */ +private fun List.inSlotLoadOrder(): List> = + withIndex() + .filter { it.value.hardwareSlot != null } + .sortedWith(compareBy> { + when (it.value.hardwareSlot!!.value) { 3, 4, 5 -> 0 // paired CPU regs (AX, AY, XY) - load first 2 -> 1 // Y - load before X and A 1 -> 2 // X - load before A @@ -570,34 +510,59 @@ private fun AsmGen.translateCall(fnLabel: String, args: FunctionCallArgs?) { 6, 7 -> 4 // float regs (FAC1, FAC2) else -> 5 // status flags } - }.thenByDescending { - // Within each group, process in original index order (stable sort) - -it.index - }) - for ((index, arg) in orderedSlotArgs) { + }.thenBy { it.index }) + +private fun AsmGen.translateCall(fnLabel: String, site: CallSite) { + // Check if this is an inline ASMSUB - must be inlined at call site, not called with jsr + val inlineAsmSub = findInlineAsmSub(fnLabel) + if (inlineAsmSub != null) { + // Inline the assembly body directly at the call site (no jsr, no rts) + // Process non-slot arguments first + for ((index, arg) in site.arguments.withIndex()) { + if (arg.hardwareSlot == null) + translateArgument(arg, index, fnLabel) + } + // Process slot arguments + for ((index, arg) in site.arguments.inSlotLoadOrder()) { translateArgument(arg, index, fnLabel) } + emitRaw(" ; inlined: $fnLabel") + inlineAsmSub.asmChunk.assembly.lineSequence().forEach { line -> + if (line.isNotBlank()) emitRaw(" $line") + } + emitRaw(" ; end inlined: $fnLabel") + for (ret in site.results) { + translateReturnValue(ret) + } + return + } + + // Process non-slot arguments first (they use A as temp to store to memory/registers) + for ((index, arg) in site.arguments.withIndex()) { + if (arg.hardwareSlot == null) + translateArgument(arg, index, fnLabel) + } + for ((index, arg) in site.arguments.inSlotLoadOrder()) { + translateArgument(arg, index, fnLabel) } emitLine("jsr $fnLabel") // Move return values back to virtual registers. // Skip status flag returns: always handled by IR's branch pattern (bsteq/bmi etc). - // In multi-assign context (returns.size > 1), also skip slot-based returns: + // In multi-assign context (results.size > 1), also skip slot-based returns: // the IR generates LOADHR for them. // In single-return expression context, process slot returns normally // (the IR doesn't generate LOADHR for single-return calls). // LIMITATION: multiple status flag returns in one multi-assign (e.g. -> bool @Pz, bool @Pc) // are not supported - codegen limitation: the first flag's extraction clobbers the state for subsequent flags. - if (args != null) { - val isMultiReturn = args.returns.size > 1 - for (ret in args.returns) { - if (ret.statusflag != null) - continue - if (isMultiReturn) - continue - translateReturnValue(ret) - } + val isMultiReturn = site.results.size > 1 + for (ret in site.results) { + if (ret.location is CallLocation.StatusFlag) + continue + if (isMultiReturn) + continue + translateReturnValue(ret) } } @@ -616,12 +581,11 @@ fun IRProgram.findInlineAsmSub(label: String): IRAsmSubroutine? { return null } -private fun AsmGen.translateArgument(arg: FunctionCallArgs.ArgumentSpec, argIndex: Int = -1, fnLabel: String? = null) { - val regSpec = arg.reg - val slot = regSpec.callingConventionSlot - val regNum = regSpec.registerNum.value +private fun AsmGen.translateArgument(arg: CallArgument, argIndex: Int = -1, fnLabel: String? = null) { + val source = arg.source + val regNum = source.register.num - when (slot?.value) { + when (arg.hardwareSlot?.value) { 0 -> { emitLine("lda ${regAddrLo(regNum)}") } @@ -657,160 +621,108 @@ private fun AsmGen.translateArgument(arg: FunctionCallArgs.ArgumentSpec, argInde emitLine("jsr floats.MOVAF") } null -> { - val flag = regSpec.statusflag - if (flag != null) { - // Status flag argument - load value and set the appropriate flag - when (flag) { - Statusflag.Pc -> { - emitLine("lda ${regAddrLo(regNum)}") - emitLine("cmp #0") - emitLine("beq +") - emitLine("sec") - emitLabel("+") - } + when (val location = arg.location) { + is CallLocation.StatusFlag -> { + // Status flag argument - load value and set the appropriate flag + when (location.flag) { + Statusflag.Pc -> { + emitLine("lda ${regAddrLo(regNum)}") + emitLine("cmp #0") + emitLine("beq +") + emitLine("sec") + emitLabel("+") + } - Statusflag.Pv -> TODO("status flag Pv for argument") - else -> TODO("status flag $flag") - } - return - } - val address = arg.address - if (address != null) { - when (regSpec.dt) { - IRDataType.BYTE -> { - emitLine("lda ${regAddrLo(regNum)}") - emitLine("sta ${address.toHex()}") - } - IRDataType.WORD, IRDataType.POINTER -> { - emitLine("lda ${regAddrLo(regNum)}") - emitLine("sta ${address.toHex()}") - emitLine("lda ${regAddrHi(regNum)}") - emitLine("sta ${address.toHex()}+1") - } - IRDataType.LONG -> { - val a = address.toHex() - val base = regAddrByte(regNum, 0) - emitLine("ldy #3") - emitLine("- lda $base,y") - emitLine("sta $a,y") - emitLine("dey") - emitLine("bpl -") - } - IRDataType.FLOAT -> { - emitLine("lda #<${fpRegAddr(regNum)}") - emitLine("ldy #>${fpRegAddr(regNum)}") - emitLine("jsr floats.MOVFM") - emitLine("ldx #<${address.toHex()}") - emitLine("ldy #>${address.toHex()}") - emitLine("jsr floats.MOVMF") + Statusflag.Pv -> TODO("status flag Pv for argument") + else -> TODO("status flag ${location.flag}") } } - } else { - val name = arg.name - if (name.isNotEmpty()) { - // Check if this argument maps to an asmsub's cx16 virtual register parameter - val asmTarget = if (fnLabel != null && argIndex >= 0) - this.asmSubParamTarget(fnLabel, argIndex) else null - if (asmTarget != null) { - when (regSpec.dt) { - IRDataType.BYTE -> { - emitLine("lda ${regAddrLo(regNum)}") - emitLine("sta $asmTarget") - } - IRDataType.WORD, IRDataType.POINTER -> { - emitLine("lda ${regAddrLo(regNum)}") - emitLine("sta $asmTarget") - emitLine("lda ${regAddrHi(regNum)}") - emitLine("sta ${asmTarget}+1") - } - IRDataType.LONG -> { - val base = regAddrByte(regNum, 0) - emitLine("ldy #3") - emitLine("- lda $base,y") - emitLine("sta $asmTarget,y") - emitLine("dey") - emitLine("bpl -") - } - IRDataType.FLOAT -> { - emitLine("lda #<${fpRegAddr(regNum)}") - emitLine("ldy #>${fpRegAddr(regNum)}") - emitLine("jsr floats.MOVFM") - emitLine("ldx #<$asmTarget") - emitLine("ldy #>$asmTarget") - emitLine("jsr floats.MOVMF") - } - } + is CallLocation.ParameterMemory -> { + val address = location.address + if (address != null) { + storeArgumentTo(address.toHex(), regNum, source.type) } else { - // The slot is null, so this is NOT an extsub parameter (extsub - // parameters have a non-null callingConventionSlot for A/X/Y register - // passing). The parameter is accessed by name in the inline asm - // (e.g. `lda # { - emitLine("lda ${regAddrLo(regNum)}") - emitLine("sta $target") - } - IRDataType.WORD, IRDataType.POINTER -> { - emitLine("lda ${regAddrLo(regNum)}") - emitLine("sta $target") - emitLine("lda ${regAddrHi(regNum)}") - emitLine("sta ${target}+1") - } - IRDataType.LONG -> { - val base = regAddrByte(regNum, 0) - emitLine("ldy #3") - emitLine("- lda $base,y") - emitLine("sta $target,y") - emitLine("dey") - emitLine("bpl -") - } - IRDataType.FLOAT -> { - emitLine("lda #<${fpRegAddr(regNum)}") - emitLine("ldy #>${fpRegAddr(regNum)}") - emitLine("jsr floats.MOVFM") - emitLine("ldx #<$target") - emitLine("ldy #>$target") - emitLine("jsr floats.MOVMF") - } + // Check if this argument maps to an asmsub's cx16 virtual register parameter + val asmTarget = if (fnLabel != null && argIndex >= 0) + this.asmSubParamTarget(fnLabel, argIndex) else null + if (asmTarget != null) { + storeArgumentTo(asmTarget, regNum, source.type) + } else { + // The argument is not passed in a cpu hardware register, so this is NOT + // an extsub parameter (extsub parameters have a HardwareRegister location + // for A/X/Y register passing). The parameter is accessed by name in the + // inline asm (e.g. `lda # { // Syscall argument - value is already in the register file, // the syscall handler reads it from there. } + is CallLocation.HardwareRegister -> throw IllegalStateException("slot already handled") } } + else -> TODO("calling convention slot ${arg.hardwareSlot} on 6502") + } +} + +/** store an argument that lives in virtual register [regNum] to a fixed memory location */ +private fun AsmGen.storeArgumentTo(target: String, regNum: Int, type: IRDataType) { + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddrLo(regNum)}") + emitLine("sta $target") + } + IRDataType.WORD, IRDataType.POINTER -> { + emitLine("lda ${regAddrLo(regNum)}") + emitLine("sta $target") + emitLine("lda ${regAddrHi(regNum)}") + emitLine("sta ${target}+1") + } + IRDataType.LONG -> { + val base = regAddrByte(regNum, 0) + emitLine("ldy #3") + emitLine("- lda $base,y") + emitLine("sta $target,y") + emitLine("dey") + emitLine("bpl -") + } + IRDataType.FLOAT -> { + emitLine("lda #<${fpRegAddr(regNum)}") + emitLine("ldy #>${fpRegAddr(regNum)}") + emitLine("jsr floats.MOVFM") + emitLine("ldx #<$target") + emitLine("ldy #>$target") + emitLine("jsr floats.MOVMF") + } } } -private fun AsmGen.translateReturnValue(ret: FunctionCallArgs.RegSpec) { +private fun AsmGen.translateReturnValue(ret: CallResult) { // Status flag returns are handled by the IR's branch pattern. // Do not extract them here - the IR emits bsteq/bmi etc after the call. - if (ret.statusflag != null) { + if (ret.location is CallLocation.StatusFlag) { return } - val slot = ret.callingConventionSlot - val regNum = ret.registerNum.value + val destination = ret.destination + if (destination == null) { + emitLine("; uncaptured return value at ${ret.location}") + return + } + val regNum = destination.register.num - when (slot?.value) { + when (ret.hardwareSlot?.value) { 0 -> { emitLine("sta ${regAddrLo(regNum)}") } @@ -846,70 +758,73 @@ private fun AsmGen.translateReturnValue(ret: FunctionCallArgs.RegSpec) { emitLine("jsr floats.MOVMF") } null -> { - if (regNum >= 0) { - when (ret.dt) { - IRDataType.BYTE -> { - emitLine("sta ${regAddrLo(regNum)}") - } - IRDataType.WORD, IRDataType.POINTER -> { - emitLine("sta ${regAddrLo(regNum)}") - emitLine("sty ${regAddrHi(regNum)}") - } - IRDataType.LONG -> { - emitLine("lda cx16.r14") - emitLine("sta ${regAddrLo(regNum)}") - emitLine("lda cx16.r14+1") - emitLine("sta ${regAddrHi(regNum)}") - emitLine("lda cx16.r15") - emitLine("sta ${regAddrByte(regNum, 2)}") - emitLine("lda cx16.r15+1") - emitLine("sta ${regAddrByte(regNum, 3)}") - } - IRDataType.FLOAT -> { - emitLine("ldx #<${fpRegAddr(regNum)}") - emitLine("ldy #>${fpRegAddr(regNum)}") - emitLine("jsr floats.MOVMF") - } + when (destination.type) { + IRDataType.BYTE -> { + emitLine("sta ${regAddrLo(regNum)}") + } + IRDataType.WORD, IRDataType.POINTER -> { + emitLine("sta ${regAddrLo(regNum)}") + emitLine("sty ${regAddrHi(regNum)}") + } + IRDataType.LONG -> { + emitLine("lda cx16.r14") + emitLine("sta ${regAddrLo(regNum)}") + emitLine("lda cx16.r14+1") + emitLine("sta ${regAddrHi(regNum)}") + emitLine("lda cx16.r15") + emitLine("sta ${regAddrByte(regNum, 2)}") + emitLine("lda cx16.r15+1") + emitLine("sta ${regAddrByte(regNum, 3)}") + } + IRDataType.FLOAT -> { + emitLine("ldx #<${fpRegAddr(regNum)}") + emitLine("ldy #>${fpRegAddr(regNum)}") + emitLine("jsr floats.MOVMF") } - } else { - emitLine("; return value to r$regNum (slot/flag not set)") } } + else -> TODO("calling convention slot ${ret.hardwareSlot} on 6502") } } // === Syscall handling === -private fun AsmGen.translateSyscall(insn: IRInstruction, args: FunctionCallArgs?) { - val syscallNum = insn.immediate ?: error("SYSCALL must have immediate(syscall number)") - val argsNonNull = args ?: error("SYSCALL $syscallNum requires arguments") +private fun CallSite.argumentOperand(index: Int, what: String): RegisterOperand = + arguments.getOrNull(index)?.source ?: error("syscall needs $what as argument $index") + +private fun CallSite.argumentRegister(index: Int, what: String): Int = + argumentOperand(index, what).register.num + +private fun AsmGen.translateSyscall(insn: IRInstruction) { + val site = insn.requireCallSite() + val syscallNum = (site.target as CallTarget.SystemCall).number when (syscallNum) { - IMSyscall.CLAMP_UBYTE.number -> translateSyscallClampUbyte(argsNonNull) - IMSyscall.CLAMP_BYTE.number -> translateSyscallClampByte(argsNonNull) - IMSyscall.CLAMP_UWORD.number -> translateSyscallClampUword(argsNonNull) - IMSyscall.CLAMP_WORD.number -> translateSyscallClampWord(argsNonNull) - IMSyscall.CLAMP_LONG.number -> translateSyscallClampLong(argsNonNull) - IMSyscall.COMPARE_STRINGS.number -> translateSyscallStringCompare(argsNonNull) - IMSyscall.STRING_CONTAINS.number -> translateSyscallStringContains(argsNonNull) - IMSyscall.BYTEARRAY_CONTAINS.number -> translateSyscallBytearrayContains(argsNonNull) - IMSyscall.WORDARRAY_CONTAINS.number -> translateSyscallWordarrayContains(argsNonNull) - IMSyscall.SPLIT_WORDARRAY_CONTAINS.number -> translateSyscallSplitWordarrayContains(argsNonNull) - IMSyscall.LONGARRAY_CONTAINS.number -> translateSyscallLongarrayContains(argsNonNull) - IMSyscall.FLOATARRAY_CONTAINS.number -> translateSyscallFloatarrayContains(argsNonNull) - IMSyscall.CALLFAR.number -> translateSyscallCallfar(argsNonNull) - IMSyscall.CALLFAR2.number -> translateSyscallCallfar2(argsNonNull) - IMSyscall.MEMCOPY.number -> translateSyscallMemcopy(argsNonNull) + IMSyscall.CLAMP_UBYTE.number -> translateSyscallClampUbyte(site) + IMSyscall.CLAMP_BYTE.number -> translateSyscallClampByte(site) + IMSyscall.CLAMP_UWORD.number -> translateSyscallClampUword(site) + IMSyscall.CLAMP_WORD.number -> translateSyscallClampWord(site) + IMSyscall.CLAMP_LONG.number -> translateSyscallClampLong(site) + IMSyscall.COMPARE_STRINGS.number -> translateSyscallStringCompare(site) + IMSyscall.STRING_CONTAINS.number -> translateSyscallStringContains(site) + IMSyscall.BYTEARRAY_CONTAINS.number -> translateSyscallBytearrayContains(site) + IMSyscall.WORDARRAY_CONTAINS.number -> translateSyscallWordarrayContains(site) + IMSyscall.SPLIT_WORDARRAY_CONTAINS.number -> translateSyscallSplitWordarrayContains(site) + IMSyscall.LONGARRAY_CONTAINS.number -> translateSyscallLongarrayContains(site) + IMSyscall.FLOATARRAY_CONTAINS.number -> translateSyscallFloatarrayContains(site) + IMSyscall.CALLFAR.number -> translateSyscallCallfar(site) + IMSyscall.CALLFAR2.number -> translateSyscallCallfar2(site) + IMSyscall.MEMCOPY.number -> translateSyscallMemcopy(site) else -> TODO("unknown SYSCALL number $syscallNum") } - for (ret in argsNonNull.returns) { + for (ret in site.results) { translateReturnValue(ret) } } -private fun AsmGen.translateSyscallClampUbyte(args: FunctionCallArgs) { - val regValue = args.arguments.getOrNull(0)?.reg?.registerNum?.value ?: error("need value reg") - val regMin = args.arguments.getOrNull(1)?.reg?.registerNum?.value ?: error("need min reg") - val regMax = args.arguments.getOrNull(2)?.reg?.registerNum?.value ?: error("need max reg") +private fun AsmGen.translateSyscallClampUbyte(site: CallSite) { + val regValue = site.argumentRegister(0, "value reg") + val regMin = site.argumentRegister(1, "min reg") + val regMax = site.argumentRegister(2, "max reg") emitLine("lda ${regAddrLo(regMin)}") emitLine("sta P8ZP_SCRATCH_W1") emitLine("lda ${regAddrLo(regMax)}") @@ -919,10 +834,10 @@ private fun AsmGen.translateSyscallClampUbyte(args: FunctionCallArgs) { emitLine("sta ${regAddrLo(regValue)}") } -private fun AsmGen.translateSyscallClampByte(args: FunctionCallArgs) { - val regValue = args.arguments.getOrNull(0)?.reg?.registerNum?.value ?: error("need value reg") - val regMin = args.arguments.getOrNull(1)?.reg?.registerNum?.value ?: error("need min reg") - val regMax = args.arguments.getOrNull(2)?.reg?.registerNum?.value ?: error("need max reg") +private fun AsmGen.translateSyscallClampByte(site: CallSite) { + val regValue = site.argumentRegister(0, "value reg") + val regMin = site.argumentRegister(1, "min reg") + val regMax = site.argumentRegister(2, "max reg") emitLine("lda ${regAddrLo(regMin)}") emitLine("sta P8ZP_SCRATCH_W1") emitLine("lda ${regAddrLo(regMax)}") @@ -932,10 +847,10 @@ private fun AsmGen.translateSyscallClampByte(args: FunctionCallArgs) { emitLine("sta ${regAddrLo(regValue)}") } -private fun AsmGen.translateSyscallClampUword(args: FunctionCallArgs) { - val regValue = args.arguments.getOrNull(0)?.reg?.registerNum?.value ?: error("need value reg") - val regMin = args.arguments.getOrNull(1)?.reg?.registerNum?.value ?: error("need min reg") - val regMax = args.arguments.getOrNull(2)?.reg?.registerNum?.value ?: error("need max reg") +private fun AsmGen.translateSyscallClampUword(site: CallSite) { + val regValue = site.argumentRegister(0, "value reg") + val regMin = site.argumentRegister(1, "min reg") + val regMax = site.argumentRegister(2, "max reg") // min in P8ZP_SCRATCH_W1, max in P8ZP_SCRATCH_W2, value in AY, result in AY emitLine("lda ${regAddrLo(regMin)}") emitLine("sta P8ZP_SCRATCH_W1") @@ -952,10 +867,10 @@ private fun AsmGen.translateSyscallClampUword(args: FunctionCallArgs) { emitLine("sty ${regAddrHi(regValue)}") } -private fun AsmGen.translateSyscallClampWord(args: FunctionCallArgs) { - val regValue = args.arguments.getOrNull(0)?.reg?.registerNum?.value ?: error("need value reg") - val regMin = args.arguments.getOrNull(1)?.reg?.registerNum?.value ?: error("need min reg") - val regMax = args.arguments.getOrNull(2)?.reg?.registerNum?.value ?: error("need max reg") +private fun AsmGen.translateSyscallClampWord(site: CallSite) { + val regValue = site.argumentRegister(0, "value reg") + val regMin = site.argumentRegister(1, "min reg") + val regMax = site.argumentRegister(2, "max reg") emitLine("lda ${regAddrLo(regMin)}") emitLine("sta P8ZP_SCRATCH_W1") emitLine("lda ${regAddrHi(regMin)}") @@ -971,10 +886,10 @@ private fun AsmGen.translateSyscallClampWord(args: FunctionCallArgs) { emitLine("sty ${regAddrHi(regValue)}") } -private fun AsmGen.translateSyscallClampLong(args: FunctionCallArgs) { - val regValue = args.arguments.getOrNull(0)?.reg?.registerNum?.value ?: error("need value reg") - val regMin = args.arguments.getOrNull(1)?.reg?.registerNum?.value ?: error("need min reg") - val regMax = args.arguments.getOrNull(2)?.reg?.registerNum?.value ?: error("need max reg") +private fun AsmGen.translateSyscallClampLong(site: CallSite) { + val regValue = site.argumentRegister(0, "value reg") + val regMin = site.argumentRegister(1, "min reg") + val regMax = site.argumentRegister(2, "max reg") // value in R14:R15, min in R10:R11, max in R12:R13, result in R14:R15 emitLine("lda ${regAddrLo(regMin)}") emitLine("sta cx16.r10L") @@ -1003,10 +918,10 @@ private fun AsmGen.translateSyscallClampLong(args: FunctionCallArgs) { emitLine("jsr prog8_lib.func_clamp_long") } -private fun AsmGen.translateSyscallMemcopy(args: FunctionCallArgs) { - val regSrc = args.arguments.getOrNull(0)?.reg?.registerNum?.value ?: error("need src reg") - val regDst = args.arguments.getOrNull(1)?.reg?.registerNum?.value ?: error("need dst reg") - val regCount = args.arguments.getOrNull(2)?.reg?.registerNum?.value ?: error("need count reg") +private fun AsmGen.translateSyscallMemcopy(site: CallSite) { + val regSrc = site.argumentRegister(0, "src reg") + val regDst = site.argumentRegister(1, "dst reg") + val regCount = site.argumentRegister(2, "count reg") // use existing library routine: memcopy_small // P8ZP_SCRATCH_W1 = source, P8ZP_SCRATCH_W2 = dest, Y = count (0 = 256) emitLine("lda ${regAddrLo(regSrc)}") @@ -1026,10 +941,10 @@ private fun AsmGen.translateSyscallMemcopy(args: FunctionCallArgs) { emitLabel("+") } -private fun AsmGen.translateSyscallCallfar(args: FunctionCallArgs) { - val regBank = args.arguments[0].reg.registerNum.value - val regAddress = args.arguments[1].reg.registerNum.value - val regArg = args.arguments[2].reg.registerNum.value +private fun AsmGen.translateSyscallCallfar(site: CallSite) { + val regBank = site.argumentRegister(0, "bank reg") + val regAddress = site.argumentRegister(1, "address reg") + val regArg = site.argumentRegister(2, "argument reg") val jsrfar = jsrfarRoutine() val label = makeLabel("callfar_patch") emitLine("lda ${regAddrLo(regBank)}") @@ -1046,13 +961,13 @@ private fun AsmGen.translateSyscallCallfar(args: FunctionCallArgs) { emitLine(".byte 0") } -private fun AsmGen.translateSyscallCallfar2(args: FunctionCallArgs) { - val regBank = args.arguments[0].reg.registerNum.value - val regAddress = args.arguments[1].reg.registerNum.value - val regA = args.arguments[2].reg.registerNum.value - val regX = args.arguments[3].reg.registerNum.value - val regY = args.arguments[4].reg.registerNum.value - val regCarry = args.arguments[5].reg.registerNum.value +private fun AsmGen.translateSyscallCallfar2(site: CallSite) { + val regBank = site.argumentRegister(0, "bank reg") + val regAddress = site.argumentRegister(1, "address reg") + val regA = site.argumentRegister(2, "A reg") + val regX = site.argumentRegister(3, "X reg") + val regY = site.argumentRegister(4, "Y reg") + val regCarry = site.argumentRegister(5, "carry reg") val jsrfar = jsrfarRoutine() val label = makeLabel("callfar2_patch") emitLine("lda ${regAddrLo(regBank)}") @@ -1080,9 +995,9 @@ private fun AsmGen.translateSyscallCallfar2(args: FunctionCallArgs) { emitLine(".byte 0") } -private fun AsmGen.translateSyscallStringCompare(args: FunctionCallArgs) { - val regStr1 = args.arguments[0].reg.registerNum.value - val regStr2 = args.arguments[1].reg.registerNum.value +private fun AsmGen.translateSyscallStringCompare(site: CallSite) { + val regStr1 = site.argumentRegister(0, "string 1 reg") + val regStr2 = site.argumentRegister(1, "string 2 reg") emitLine("lda ${regAddrLo(regStr2)}") emitLine("sta P8ZP_SCRATCH_W2") emitLine("lda ${regAddrHi(regStr2)}") @@ -1092,10 +1007,10 @@ private fun AsmGen.translateSyscallStringCompare(args: FunctionCallArgs) { emitLine("jsr prog8_lib.strcmp_mem") } -private fun AsmGen.translateSyscallBytearrayContains(args: FunctionCallArgs) { - val regElem = args.arguments[0].reg.registerNum.value - val regArr = args.arguments[1].reg.registerNum.value - val regLen = args.arguments[2].reg.registerNum.value +private fun AsmGen.translateSyscallBytearrayContains(site: CallSite) { + val regElem = site.argumentRegister(0, "element reg") + val regArr = site.argumentRegister(1, "array reg") + val regLen = site.argumentRegister(2, "length reg") emitLine("lda ${regAddrLo(regLen)}") emitLine("sta P8ZP_SCRATCH_W2") emitLine("lda ${regAddrLo(regArr)}") @@ -1107,10 +1022,10 @@ private fun AsmGen.translateSyscallBytearrayContains(args: FunctionCallArgs) { emitLine("jsr prog8_lib.containment_bytearray") } -private fun AsmGen.translateSyscallWordarrayContains(args: FunctionCallArgs) { - val regElem = args.arguments[0].reg.registerNum.value - val regArr = args.arguments[1].reg.registerNum.value - val regLen = args.arguments[2].reg.registerNum.value +private fun AsmGen.translateSyscallWordarrayContains(site: CallSite) { + val regElem = site.argumentRegister(0, "element reg") + val regArr = site.argumentRegister(1, "array reg") + val regLen = site.argumentRegister(2, "length reg") emitLine("lda ${regAddrLo(regArr)}") emitLine("ldy ${regAddrHi(regArr)}") emitLine("sta P8ZP_SCRATCH_W2") @@ -1123,10 +1038,10 @@ private fun AsmGen.translateSyscallWordarrayContains(args: FunctionCallArgs) { emitLine("jsr prog8_lib.containment_linearwordarray") } -private fun AsmGen.translateSyscallSplitWordarrayContains(args: FunctionCallArgs) { - val regElem = args.arguments[0].reg.registerNum.value - val regArr = args.arguments[1].reg.registerNum.value - val regLen = args.arguments[2].reg.registerNum.value +private fun AsmGen.translateSyscallSplitWordarrayContains(site: CallSite) { + val regElem = site.argumentRegister(0, "element reg") + val regArr = site.argumentRegister(1, "array reg") + val regLen = site.argumentRegister(2, "length reg") emitLine("lda ${regAddrLo(regArr)}") emitLine("ldy ${regAddrHi(regArr)}") emitLine("sta P8ZP_SCRATCH_W2") @@ -1139,10 +1054,10 @@ private fun AsmGen.translateSyscallSplitWordarrayContains(args: FunctionCallArgs emitLine("jsr prog8_lib.containment_splitwordarray") } -private fun AsmGen.translateSyscallLongarrayContains(args: FunctionCallArgs) { - val regVal = args.arguments[0].reg.registerNum.value - val regArr = args.arguments[1].reg.registerNum.value - val regLen = args.arguments[2].reg.registerNum.value +private fun AsmGen.translateSyscallLongarrayContains(site: CallSite) { + val regVal = site.argumentRegister(0, "value reg") + val regArr = site.argumentRegister(1, "array reg") + val regLen = site.argumentRegister(2, "length reg") val labelFound = makeLabel("lac_found") val labelNotFound = makeLabel("lac_notfound") val labelLoop = makeLabel("lac_loop") @@ -1198,9 +1113,9 @@ private fun AsmGen.translateSyscallLongarrayContains(args: FunctionCallArgs) { emitLabel(labelDone) } -private fun AsmGen.translateSyscallStringContains(args: FunctionCallArgs) { - val regChar = args.arguments[0].reg.registerNum.value - val regStr = args.arguments[1].reg.registerNum.value +private fun AsmGen.translateSyscallStringContains(site: CallSite) { + val regChar = site.argumentRegister(0, "character reg") + val regStr = site.argumentRegister(1, "string reg") val labelFound = makeLabel("sc_found") val labelNotFound = makeLabel("sc_notfound") val labelDone = makeLabel("sc_done") @@ -1226,10 +1141,10 @@ private fun AsmGen.translateSyscallStringContains(args: FunctionCallArgs) { emitLabel(labelDone) } -private fun AsmGen.translateSyscallFloatarrayContains(args: FunctionCallArgs) { - val regNeedleFp = args.arguments.getOrNull(0)?.reg?.registerNum?.value ?: error("need needle fp reg") - val regArr = args.arguments.getOrNull(1)?.reg?.registerNum?.value ?: error("need array reg") - val regLen = args.arguments.getOrNull(2)?.reg?.registerNum?.value ?: error("need length reg") +private fun AsmGen.translateSyscallFloatarrayContains(site: CallSite) { + val regNeedleFp = site.argumentRegister(0, "needle fp reg") + val regArr = site.argumentRegister(1, "array reg") + val regLen = site.argumentRegister(2, "length reg") // Load needle value from fp register into FAC1 emitLine("lda #<${fpRegAddr(regNeedleFp)}") emitLine("ldy #>${fpRegAddr(regNeedleFp)}") @@ -1247,8 +1162,8 @@ private fun AsmGen.translateSyscallFloatarrayContains(args: FunctionCallArgs) { // === Float operations === private fun AsmGen.translateFloatFromInt(insn: IRInstruction) { - val r1 = insn.reg1 ?: error("${insn.opcode} needs reg1 (int input)") - val fpReg = insn.fpReg1 ?: error("${insn.opcode} needs fpReg1 (float output)") + val r1 = insn.requireIntSourceA().intNumber + val fpReg = insn.requireFloatDest().floatNumber when (insn.opcode) { Opcode.FFROMUB -> { emitLine("ldy ${regAddrLo(r1)}") @@ -1277,8 +1192,8 @@ private fun AsmGen.translateFloatFromInt(insn: IRInstruction) { } private fun AsmGen.translateFloatToInt(insn: IRInstruction) { - val r1 = insn.reg1 ?: error("${insn.opcode} needs reg1 (int output)") - val fpReg = insn.fpReg1 ?: error("${insn.opcode} needs fpReg1 (float input)") + val r1 = insn.requireIntDest().intNumber + val fpReg = insn.requireFloatSourceA().floatNumber emitLine("lda #<${fpRegAddr(fpReg.value)}") emitLine("ldy #>${fpRegAddr(fpReg.value)}") when (insn.opcode) { @@ -1310,8 +1225,8 @@ private fun AsmGen.translateFloatToInt(insn: IRInstruction) { } private fun AsmGen.translateFloatUnary(insn: IRInstruction, routine: String) { - val src = insn.fpReg2 ?: error("${insn.opcode} needs fpReg2 (float input)") - val dst = insn.fpReg1 ?: error("${insn.opcode} needs fpReg1 (float output)") + val src = insn.requireFloatSourceA().floatNumber + val dst = insn.requireFloatDest().floatNumber emitLine("lda #<${fpRegAddr(src.value)}") emitLine("ldy #>${fpRegAddr(src.value)}") emitLine("jsr floats.MOVFM") @@ -1322,9 +1237,9 @@ private fun AsmGen.translateFloatUnary(insn: IRInstruction, routine: String) { } private fun AsmGen.translateFloatPower(insn: IRInstruction) { - val src = insn.fpReg2 ?: error("FPOW needs fpReg2") - val dst = insn.fpReg1 ?: error("FPOW needs fpReg1") - // FPOW: fr1 = fr1 ^ fr2 + val src = insn.requireFloatSourceA().floatNumber + val dst = insn.requireFloatDest().floatNumber + // FPOW: dst = dst ^ src // KERNAL FPWRT: FAC1 = FAC2 ^ FAC1 // Need FAC2=fr1, FAC1=fr2 emitLine("lda #<${fpRegAddr(dst.value)}") @@ -1341,8 +1256,8 @@ private fun AsmGen.translateFloatPower(insn: IRInstruction) { } private fun AsmGen.translateFloatCeil(insn: IRInstruction) { - val src = insn.fpReg2 ?: error("FCEIL needs fpReg2") - val dst = insn.fpReg1 ?: error("FCEIL needs fpReg1") + val src = insn.requireFloatSourceA().floatNumber + val dst = insn.requireFloatDest().floatNumber // ceil(x) = -floor(-x) emitLine("lda #<${fpRegAddr(src.value)}") emitLine("ldy #>${fpRegAddr(src.value)}") @@ -1356,8 +1271,8 @@ private fun AsmGen.translateFloatCeil(insn: IRInstruction) { } private fun AsmGen.translateFloatFromSignedLong(insn: IRInstruction) { - val r1 = insn.reg1 ?: error("FFROMSL needs reg1 (long input)") - val fpReg = insn.fpReg1 ?: error("FFROMSL needs fpReg1 (float output)") + val r1 = insn.requireIntSourceA().intNumber + val fpReg = insn.requireFloatDest().floatNumber val regAddr = regAddr(r1) // Convert 4-byte signed long to float by splitting into high and low words. // float = (float)(signed high_word) * 65536.0 + (float)(unsigned low_word) @@ -1387,8 +1302,8 @@ private fun AsmGen.translateFloatFromSignedLong(insn: IRInstruction) { } private fun AsmGen.translateFloatToSignedLong(insn: IRInstruction) { - val r1 = insn.reg1 ?: error("FTOSL needs reg1 (long output)") - val fpReg = insn.fpReg1 ?: error("FTOSL needs fpReg1 (float input)") + val r1 = insn.requireIntDest().intNumber + val fpReg = insn.requireFloatSourceA().floatNumber val regAddr = regAddr(r1) val facho = "floats.FAC_ADDR+1" // first mantissa byte after exponent // Load float from FP register into FAC1 @@ -1413,9 +1328,9 @@ private fun AsmGen.translateFloatToSignedLong(insn: IRInstruction) { } private fun AsmGen.translateFloatCompare(insn: IRInstruction) { - val r1 = insn.reg1 ?: error("FCOMP needs reg1 (int output)") - val fr1 = insn.fpReg1 ?: error("FCOMP needs fpReg1") - val fr2 = insn.fpReg2 ?: error("FCOMP needs fpReg2") + val r1 = insn.requireIntDest().intNumber + val fr1 = insn.requireFloatSourceA().floatNumber + val fr2 = insn.requireSrcB().floatNumber // Compare fr1 with fr2 using subtraction instead of KERNAL FCOMP. // KERNAL FCOMP on some CX16 ROM versions gives wrong results for certain value pairs. // Using FSUBT + SIGN is more reliable: compute fr1 - fr2, then check SIGN. @@ -1431,8 +1346,8 @@ private fun AsmGen.translateFloatCompare(insn: IRInstruction) { } private fun AsmGen.translateIntSqrt(insn: IRInstruction) { - val src = insn.reg2 ?: error("SQRT needs reg2") - val dst = insn.reg1 ?: error("SQRT needs reg1") + val src = insn.requireIntSourceA().intNumber + val dst = insn.requireIntDest().intNumber when (insn.type) { IRDataType.BYTE -> { emitLine("lda ${regAddr(src)}") @@ -1459,8 +1374,8 @@ private fun AsmGen.translateIntSqrt(insn: IRInstruction) { } private fun AsmGen.translateIntSquare(insn: IRInstruction) { - val src = insn.reg2 ?: error("SQUARE needs reg2") - val dst = insn.reg1 ?: error("SQUARE needs reg1") + val src = insn.requireIntSourceA().intNumber + val dst = insn.requireIntDest().intNumber when (insn.type) { IRDataType.BYTE -> { emitLine("lda ${regAddr(src)}") @@ -1485,9 +1400,9 @@ private fun AsmGen.translateIntSquare(insn: IRInstruction) { } private fun AsmGen.translateFloatSquare(insn: IRInstruction) { - val src = insn.fpReg2 ?: error("SQUARE.f needs fpReg2") - val dst = insn.fpReg1 ?: error("SQUARE.f needs fpReg1") - // fr1 = fr2^2 = fr2 * fr2 + val src = insn.requireFloatSourceA().floatNumber + val dst = insn.requireFloatDest().floatNumber + // dst = src^2 = src * src emitLine("lda #<${fpRegAddr(src.value)}") emitLine("ldy #>${fpRegAddr(src.value)}") emitLine("jsr floats.MOVFM") @@ -1502,9 +1417,9 @@ private fun AsmGen.translateFloatSquare(insn: IRInstruction) { } private fun AsmGen.translateFloatSign(insn: IRInstruction) { - val r1 = insn.reg1 ?: error("SGN.f needs reg1 (int output)") - val fpReg = insn.fpReg1 ?: error("SGN.f needs fpReg1 (float input)") - // SGN: reg1 = sign(fr1) as integer (-1, 0, 1) + val r1 = insn.requireIntDest().intNumber + val fpReg = insn.requireFloatSourceA().floatNumber + // SGN: dest = sign(src) as integer (-1, 0, 1) emitLine("lda #<${fpRegAddr(fpReg.value)}") emitLine("ldy #>${fpRegAddr(fpReg.value)}") emitLine("jsr floats.MOVFM") diff --git a/codeGenNew6502/src/prog8/codegen/new6502/InstrLoadStore.kt b/codeGenNew6502/src/prog8/codegen/new6502/InstrLoadStore.kt index 9be96d09c..8f0732b3e 100644 --- a/codeGenNew6502/src/prog8/codegen/new6502/InstrLoadStore.kt +++ b/codeGenNew6502/src/prog8/codegen/new6502/InstrLoadStore.kt @@ -27,21 +27,12 @@ package prog8.codegen.new6502 import prog8.code.core.toHex -import prog8.intermediate.IRDataType -import prog8.intermediate.IRInstruction -import prog8.intermediate.MemoryAddress -import prog8.intermediate.Opcode +import prog8.intermediate.* internal fun AsmGen.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 - val offset = insn.labelSymbolOffset - // FLOAT operations use fpReg1/fpReg2 instead of reg1/reg2 + // FLOAT operations use the float register file instead of the integer one if (type == IRDataType.FLOAT) { translateFloatLoadStore(insn) return @@ -49,92 +40,101 @@ internal fun AsmGen.translateLoadStore(insn: IRInstruction) { 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") + val dst = insn.requireIntDest().intNumber + when (val value = insn.requireImmediate()) { + is ImmediateOperand.Integer -> loadImmediate(dst, value.value, type) + is ImmediateOperand.SymbolAddress -> loadSymbolAddress(dst, value.symbol, value.offset.takeIf { it != 0 }, type) + is ImmediateOperand.FloatValue -> error("LOAD.$type has a float immediate") } } Opcode.LOADM -> { - val target = resolveAddress(addr, label, offset) - loadFromMemory(r1 ?: error("LOADM needs reg1"), target, type) + val dst = insn.requireIntDest().intNumber + val target = resolveAddress(insn.requireMemory()) + loadFromMemory(dst, target, type) } Opcode.LOADR -> { - val srcReg = r2 ?: error("LOADR needs reg2") - copyRegister(r1 ?: error("LOADR needs reg1"), srcReg, type) + val dst = insn.requireIntDest().intNumber + val srcReg = insn.requireIntSourceA().intNumber + copyRegister(dst, srcReg, type) } Opcode.LOADX -> { - val idxReg = r2 ?: error("LOADX needs reg2") - val baseAddress = resolveAddress(addr, label, offset) - val scale = insn.scale - scaleIndexRegIfNeeded(idxReg, scale) - indexedLoad(r1 ?: error("LOADX needs reg1"), idxReg, baseAddress, type) + val dst = insn.requireIntDest().intNumber + val mem = insn.requireMemory() as MemoryReference.Indexed + val idxReg = mem.index.intNumber + val baseAddress = resolveAddress(mem.base, mem.displacement) + scaleIndexRegIfNeeded(idxReg, mem.scale) + indexedLoad(dst, idxReg, baseAddress, type) } Opcode.LOADHR -> { - loadFromHardwareReg(r1 ?: error("LOADHR needs reg1"), imm ?: error("LOADHR needs slot"), type) + val dst = insn.requireIntDest().intNumber + val slot = insn.requireHardwareSlot().slot.value + loadFromHardwareReg(dst, 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) + val dst = insn.requireIntDest().intNumber + val mem = insn.requireMemory() as MemoryReference.Indirect + indirectLoad(dst, mem.pointer.intNumber, mem.displacement, type) } Opcode.STOREM -> { - val target = resolveAddress(addr, label, offset) - storeToMemory(r1 ?: error("STOREM needs reg1"), target, type) + val src = insn.requireIntSourceA().intNumber + val target = resolveAddress(insn.requireMemory()) + storeToMemory(src, target, type) } Opcode.STOREX -> { - val r2val = r2 ?: error("STOREX needs reg2") - val target = resolveAddress(addr, label, offset) - val scale = insn.scale - scaleIndexRegIfNeeded(r2val, scale) - storeExchange(r1 ?: error("STOREX needs reg1"), r2val, target, type) + val src = insn.requireIntSourceA().intNumber + val mem = insn.requireMemory() as MemoryReference.Indexed + val idxReg = mem.index.intNumber + val target = resolveAddress(mem.base, mem.displacement) + scaleIndexRegIfNeeded(idxReg, mem.scale) + storeExchange(src, idxReg, target, type) } Opcode.STOREZM -> { - val target = resolveAddress(addr, label, offset) + val target = resolveAddress(insn.requireMemory()) zeroMemory(target, type) } Opcode.STOREIM -> { - val target = resolveAddress(addr, label, offset) - val value = imm ?: error("STOREIM needs immediate value") + val target = resolveAddress(insn.requireMemory()) + val value = insn.requireImmediateInt() storeImmediateToMemory(value, target, type) } Opcode.STOREZI -> { - val offset = insn.immediate ?: 0 - zeroIndexed(r1 ?: error("STOREZI needs reg1"), offset, type) + val mem = insn.requireMemory() as MemoryReference.Indirect + zeroIndexed(mem.pointer.intNumber, mem.displacement, type) } Opcode.STOREZX -> { - val target = resolveAddress(addr, label, offset) - val scale = insn.scale - scaleIndexRegIfNeeded(r1 ?: error("STOREZX needs reg1"), scale) - zeroMemoryIndexed(r1, target, type) + val mem = insn.requireMemory() as MemoryReference.Indexed + val idxReg = mem.index.intNumber + val target = resolveAddress(mem.base, mem.displacement) + scaleIndexRegIfNeeded(idxReg, mem.scale) + zeroMemoryIndexed(idxReg, target, type) } Opcode.STOREHR -> { - storeToHardwareReg(r1 ?: error("STOREHR needs reg1"), imm ?: error("STOREHR needs slot")) + val src = insn.requireIntSourceA().intNumber + val slot = insn.requireHardwareSlot().slot.value + storeToHardwareReg(src, slot) } Opcode.STOREI -> { - val baseReg = r2 ?: error("STOREI needs reg2") - val offset = insn.immediate ?: 0 - indirectStore(r1 ?: error("STOREI needs reg1"), baseReg, offset, type) + val src = insn.requireIntSourceA().intNumber + val mem = insn.requireMemory() as MemoryReference.Indirect + indirectStore(src, mem.pointer.intNumber, mem.displacement, type) } Opcode.LOADP_INC -> { - val dst = r1 ?: error("LOADP_INC needs reg1") - val ptrVar = resolveAddress(addr, label, offset) + val dst = insn.requireIntDest().intNumber + val ptrVar = resolveAddress(insn.requireMemory()) // r1 = *ptrVar; ptrVar += sizeof(type) (post-inc) // Use $22/$23 as temp pointer emitLine("lda $ptrVar") @@ -190,8 +190,8 @@ internal fun AsmGen.translateLoadStore(insn: IRInstruction) { } Opcode.STOREP_INC -> { - val src = r1 ?: error("STOREP_INC needs reg1") - val ptrVar = resolveAddress(addr, label, offset) + val src = insn.requireIntSourceA().intNumber + val ptrVar = resolveAddress(insn.requireMemory()) // *ptrVar = r1; ptrVar += sizeof(type) emitLine("lda $ptrVar") emitLine("sta $$22") @@ -350,23 +350,12 @@ private fun AsmGen.emitFloatIndexScaled(idxReg: Int, scale: Int, ptr: String) { } private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) { - val fpReg1 = insn.fpReg1 - val fpReg2 = insn.fpReg2 - val imm = insn.immediate - val immFp = insn.immediateFp - val addr = insn.address - val label = insn.labelSymbol - val offset = insn.labelSymbolOffset - val r1 = insn.reg1 // index register for LOADX/LOADI - when (insn.opcode) { Opcode.LOAD -> { - val value = insn.immediateFp - val sym = insn.labelSymbol - when { - value != null -> { - val fpReg = fpReg1 ?: error("FLOAT LOAD needs fpReg1") - val constLabel = getFloatConstLabel(value) + val fpReg = insn.requireFloatDest().floatNumber + when (val value = insn.requireImmediate()) { + is ImmediateOperand.FloatValue -> { + val constLabel = getFloatConstLabel(value.value) emitLine("lda #<$constLabel") emitLine("ldy #>$constLabel") emitLine("jsr floats.MOVFM") @@ -374,10 +363,9 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) { emitLine("ldy #>${fpRegAddr(fpReg.value)}") emitLine("jsr floats.MOVMF") } - sym != null -> { - val fpReg = fpReg1 ?: error("FLOAT LOAD needs fpReg1") - val resolved = resolveSymbolRef(sym) - val symWithOffset = if (offset != null) "$resolved+$offset" else resolved + is ImmediateOperand.SymbolAddress -> { + val resolved = resolveSymbolRef(value.symbol) + val symWithOffset = if (value.offset != 0) "$resolved+${value.offset}" else resolved emitLine("lda #<$symWithOffset") emitLine("ldy #>$symWithOffset") emitLine("jsr floats.MOVFM") @@ -385,13 +373,13 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) { emitLine("ldy #>${fpRegAddr(fpReg.value)}") emitLine("jsr floats.MOVMF") } - else -> error("FLOAT LOAD needs immediate or labelSymbol") + is ImmediateOperand.Integer -> error("FLOAT LOAD has an integer immediate") } } Opcode.LOADM -> { - val target = resolveAddress(addr, label, offset) - val fpReg = fpReg1 ?: error("LOADM.f needs fpReg1") + val target = resolveAddress(insn.requireMemory()) + val fpReg = insn.requireFloatDest().floatNumber emitLine("lda #<$target") emitLine("ldy #>$target") emitLine("jsr floats.MOVFM") @@ -401,8 +389,8 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) { } Opcode.LOADR -> { - val srcReg = fpReg2 ?: error("LOADR.f needs fpReg2") - val dstReg = fpReg1 ?: error("LOADR.f needs fpReg1") + val srcReg = insn.requireFloatSourceA().floatNumber + val dstReg = insn.requireFloatDest().floatNumber emitLine("lda #<${fpRegAddr(srcReg.value)}") emitLine("ldy #>${fpRegAddr(srcReg.value)}") emitLine("jsr floats.MOVFM") @@ -412,8 +400,8 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) { } Opcode.STOREM -> { - val target = resolveAddress(addr, label, offset) - val fpReg = fpReg1 ?: error("STOREM.f needs fpReg1") + val target = resolveAddress(insn.requireMemory()) + val fpReg = insn.requireFloatSourceA().floatNumber emitLine("lda #<${fpRegAddr(fpReg.value)}") emitLine("ldy #>${fpRegAddr(fpReg.value)}") emitLine("jsr floats.MOVFM") @@ -423,8 +411,8 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) { } Opcode.STOREIM -> { - val target = resolveAddress(addr, label, offset) - val value = immFp ?: error("STOREIM.f needs immediateFp value") + val target = resolveAddress(insn.requireMemory()) + val value = insn.requireImmediateFloat() val constLabel = getFloatConstLabel(value) emitLine("lda #<$constLabel") emitLine("ldy #>$constLabel") @@ -435,8 +423,8 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) { } Opcode.LOADHR -> { - val slot = imm ?: error("LOADHR.f needs slot") - val fpReg = fpReg1 ?: error("LOADHR.f needs fpReg1") + val slot = insn.requireHardwareSlot().slot.value + val fpReg = insn.requireFloatDest().floatNumber when (slot) { 6 -> { emitLine("ldx #<${fpRegAddr(fpReg.value)}") @@ -454,8 +442,8 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) { } Opcode.STOREHR -> { - val slot = imm ?: error("STOREHR.f needs slot") - val fpReg = fpReg1 ?: error("STOREHR.f needs fpReg1") + val slot = insn.requireHardwareSlot().slot.value + val fpReg = insn.requireFloatSourceA().floatNumber when (slot) { 6 -> { emitLine("lda #<${fpRegAddr(fpReg.value)}") @@ -473,10 +461,11 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) { } Opcode.LOADX -> { - val idxReg = r1 ?: error("LOADX.f needs reg1 (index)") - val baseAddress = resolveAddress(addr, label, offset) - val fpReg = fpReg1 ?: error("LOADX.f needs fpReg1") - val scale = insn.scale + val mem = insn.requireMemory() as MemoryReference.Indexed + val idxReg = mem.index.intNumber + val baseAddress = resolveAddress(mem.base, mem.displacement) + val fpReg = insn.requireFloatDest().floatNumber + val scale = mem.scale val ptr = ZP_TEMP emitLine("lda #<$baseAddress") emitLine("sta $ptr") @@ -493,9 +482,10 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) { } Opcode.LOADI -> { - val baseReg = r1 ?: error("LOADI.f needs reg1 (baseAddress ptr)") - val offsetVal = insn.immediate ?: 0 - val fpReg = fpReg1 ?: error("LOADI.f needs fpReg1") + val mem = insn.requireMemory() as MemoryReference.Indirect + val baseReg = mem.pointer.intNumber + val offsetVal = mem.displacement + val fpReg = insn.requireFloatDest().floatNumber val ptr = ZP_TEMP emitLine("lda ${regAddrLo(baseReg)}") emitLine("clc") @@ -513,9 +503,10 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) { } Opcode.STOREI -> { - val baseReg = r1 ?: error("STOREI.f needs reg1 (baseAddress ptr)") - val offsetVal = insn.immediate ?: 0 - val fpReg = fpReg1 ?: error("STOREI.f needs fpReg1") + val mem = insn.requireMemory() as MemoryReference.Indirect + val baseReg = mem.pointer.intNumber + val offsetVal = mem.displacement + val fpReg = insn.requireFloatSourceA().floatNumber val ptr = ZP_TEMP emitLine("lda ${regAddrLo(baseReg)}") emitLine("clc") @@ -533,10 +524,11 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) { } Opcode.STOREX -> { - val idxReg = r1 ?: error("STOREX.f needs reg1 (index)") - val baseAddress = resolveAddress(addr, label, offset) - val fpReg = fpReg1 ?: error("STOREX.f needs fpReg1") - val scale = insn.scale + val mem = insn.requireMemory() as MemoryReference.Indexed + val idxReg = mem.index.intNumber + val baseAddress = resolveAddress(mem.base, mem.displacement) + val fpReg = insn.requireFloatSourceA().floatNumber + val scale = mem.scale val ptr = ZP_TEMP emitLine("lda #<$baseAddress") emitLine("sta $ptr") @@ -552,7 +544,7 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) { } Opcode.STOREZM -> { - val memTarget = resolveAddress(addr, label, offset) + val memTarget = resolveAddress(insn.requireMemory()) val n = floatMemSize - 1 // Note: the 65C02 STZ instruction does NOT support Y-indexed addressing // (only zp, zp,X, abs, abs,X). We use lda #0 / sta for both 6502 and 65C02 @@ -566,8 +558,9 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) { } Opcode.STOREZI -> { - val baseReg = r1 ?: error("STOREZI.f needs reg1 (baseAddress ptr)") - val offsetVal = insn.immediate ?: 0 + val mem = insn.requireMemory() as MemoryReference.Indirect + val baseReg = mem.pointer.intNumber + val offsetVal = mem.displacement val ptr = ZP_TEMP emitLine("lda ${regAddrLo(baseReg)}") emitLine("clc") @@ -585,9 +578,10 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) { } Opcode.STOREZX -> { - val idxReg = r1 ?: error("STOREZX.f needs reg1 (index)") - val baseAddress = resolveAddress(addr, label, offset) - val scale = insn.scale + val mem = insn.requireMemory() as MemoryReference.Indexed + val idxReg = mem.index.intNumber + val baseAddress = resolveAddress(mem.base, mem.displacement) + val scale = mem.scale val ptr = ZP_TEMP emitLine("lda #<$baseAddress") emitLine("sta $ptr") @@ -604,7 +598,7 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) { Opcode.LOADHFACZERO -> { // Load hardware FAC1 (main accumulator) into fp reg - val fpReg = fpReg1 ?: error("LOADHFACZERO needs fpReg1") + val fpReg = insn.requireFloatDest().floatNumber emitLine("ldx #<${fpRegAddr(fpReg.value)}") emitLine("ldy #>${fpRegAddr(fpReg.value)}") emitLine("jsr floats.MOVMF") @@ -613,7 +607,7 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) { Opcode.LOADHFACONE -> { // Load hardware FAC2 (argument register) into fp reg // First copy FAC2 to FAC1, then store - val fpReg = fpReg1 ?: error("LOADHFACONE needs fpReg1") + val fpReg = insn.requireFloatDest().floatNumber emitLine("jsr floats.MOVFA") emitLine("ldx #<${fpRegAddr(fpReg.value)}") emitLine("ldy #>${fpRegAddr(fpReg.value)}") @@ -622,7 +616,7 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) { Opcode.STOREHFACZERO -> { // Store fp reg into hardware FAC1 (main accumulator) - val fpReg = fpReg1 ?: error("STOREHFACZERO needs fpReg1") + val fpReg = insn.requireFloatSourceA().floatNumber emitLine("lda #<${fpRegAddr(fpReg.value)}") emitLine("ldy #>${fpRegAddr(fpReg.value)}") emitLine("jsr floats.MOVFM") @@ -630,7 +624,7 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) { Opcode.STOREHFACONE -> { // Store fp reg into hardware FAC2 (argument register) - val fpReg = fpReg1 ?: error("STOREHFACONE needs fpReg1") + val fpReg = insn.requireFloatSourceA().floatNumber emitLine("lda #<${fpRegAddr(fpReg.value)}") emitLine("ldy #>${fpRegAddr(fpReg.value)}") emitLine("jsr floats.MOVFM") @@ -947,37 +941,37 @@ internal fun AsmGen.indirectLoad(dstReg: Int, baseReg: Int, offset: Int, type: I } } -internal fun AsmGen.storeExchange(reg: Int, reg2: Int, target: String, type: IRDataType) { - // STOREX: indexed store -- mem[target + reg2] = reg1 +internal fun AsmGen.storeExchange(valueReg: Int, indexReg: Int, target: String, type: IRDataType) { + // STOREX: indexed store -- mem[target + index] = value // TODO can this be simplified? when (type) { IRDataType.BYTE -> { - emitLine("ldx ${regAddrLo(reg2)}") - emitLine("lda ${regAddrLo(reg)}") + emitLine("ldx ${regAddrLo(indexReg)}") + emitLine("lda ${regAddrLo(valueReg)}") emitLine("sta $target,x") } IRDataType.WORD -> { // Index register is a BYTE (pre-multiplied by 2). Keep X once for both bytes. - emitLine("ldx ${regAddrLo(reg2)}") - emitLine("lda ${regAddrLo(reg)}") + emitLine("ldx ${regAddrLo(indexReg)}") + emitLine("lda ${regAddrLo(valueReg)}") emitLine("sta $target,x") - emitLine("lda ${regAddrHi(reg)}") + emitLine("lda ${regAddrHi(valueReg)}") emitLine("sta ${target}+1,x") } IRDataType.LONG -> { // Index register is a BYTE (pre-multiplied by 4). Keep X once for all 4 bytes. - emitLine("ldx ${regAddrLo(reg2)}") - emitLine("lda ${regAddrLo(reg)}") + emitLine("ldx ${regAddrLo(indexReg)}") + emitLine("lda ${regAddrLo(valueReg)}") emitLine("sta $target,x") - emitLine("lda ${regAddrHi(reg)}") + emitLine("lda ${regAddrHi(valueReg)}") emitLine("sta ${target}+1,x") - emitLine("lda ${regAddrByte(reg, 2)}") + emitLine("lda ${regAddrByte(valueReg, 2)}") emitLine("sta ${target}+2,x") - emitLine("lda ${regAddrByte(reg, 3)}") + emitLine("lda ${regAddrByte(valueReg, 3)}") emitLine("sta ${target}+3,x") } else -> { - TODO("STOREX r$reg, r$reg2, $target ${type.name}") + TODO("STOREX r$valueReg, r$indexReg, $target ${type.name}") } } } @@ -1110,13 +1104,20 @@ internal fun AsmGen.copyRegisterLong(dstReg: Int, srcReg: Int) { emitLine("bpl -") } -internal fun AsmGen.resolveAddress(addr: MemoryAddress?, label: String?, offset: Int? = null): String { - return when { - label != null -> { - val resolved = resolveSymbolRef(label) - if (offset != null && offset != 0) "$resolved+$offset" else resolved +/** the address of a [base] (+ [displacement], for a Symbol base only - matches the previous behavior + * of ignoring any offset that came together with a fixed numeric address) */ +internal fun AsmGen.resolveAddress(base: AddressBase, displacement: Int = 0): String { + return when (base) { + is AddressBase.Symbol -> { + val resolved = resolveSymbolRef(base.name) + if (displacement != 0) "$resolved+$displacement" else resolved } - addr != null -> addr.value.toHex() - else -> "0" + is AddressBase.Absolute -> base.address.value.toHex() } } + +/** the address of a DIRECT memory reference (LOADM/STOREM/STOREZM/STOREIM/memory-op opcodes) */ +internal fun AsmGen.resolveAddress(memory: MemoryReference): String { + val direct = memory as? MemoryReference.Direct ?: error("expected a direct memory reference: $memory") + return resolveAddress(direct.base, direct.displacement) +} diff --git a/codeGenNew6502/src/prog8/codegen/new6502/Main.kt b/codeGenNew6502/src/prog8/codegen/new6502/Main.kt index 11e7b1d0a..88ad36cf0 100644 --- a/codeGenNew6502/src/prog8/codegen/new6502/Main.kt +++ b/codeGenNew6502/src/prog8/codegen/new6502/Main.kt @@ -24,31 +24,9 @@ fun main(args: Array) { program.options.asmListfile = true val target = program.options.compTarget - // compute metrics (matching IRFileWriter counting) - val used = program.registersUsed() - val numRegs = (used.readRegs.keys + used.writeRegs.keys).size + (used.readFpRegs.keys + used.writeFpRegs.keys).size - var numChunks = 0 - var numInstr = 0 - for (block in program.blocks) { - for (child in block.children) { - when (child) { - is prog8.intermediate.IRSubroutine -> { - for (chunk in child.chunks) { - numChunks++ - if (chunk is prog8.intermediate.IRCodeChunk) - numInstr += chunk.instructions.size - } - } - is prog8.intermediate.IRCodeChunk -> { - // counted in numInstr but NOT in numChunks (matches IRFileWriter) - numInstr += child.instructions.size - } - else -> {} - } - } - } - // globalInits: counted in numInstr but NOT in numChunks (matches IRFileWriter) - numInstr += program.globalInits.instructions.size + // program metrics, via the canonical IRProgram counters + val numRegs = program.countUsedRegisters() + val (numInstr, numChunks) = program.countCodeElements() System.err.println("Loaded IR program: ${program.name}") System.err.println("Target system and CPU: ${target.name} / ${target.cpu}") System.err.println("($numInstr instructions in $numChunks chunks, $numRegs registers)") diff --git a/codeGenNew6502/src/prog8/codegen/new6502/New6502CodeGenerator.kt b/codeGenNew6502/src/prog8/codegen/new6502/New6502CodeGenerator.kt index d51bf7df1..a4c0cbe50 100644 --- a/codeGenNew6502/src/prog8/codegen/new6502/New6502CodeGenerator.kt +++ b/codeGenNew6502/src/prog8/codegen/new6502/New6502CodeGenerator.kt @@ -8,7 +8,9 @@ import prog8.code.ast.PtProgram import prog8.code.core.CompilationOptions import prog8.code.core.IErrorReporter import prog8.codegen.intermediate.IRCodeGen +import prog8.intermediate.IRDataType import prog8.intermediate.IRFileWriter +import prog8.intermediate.VirtualRegister class New6502CodeGenerator(val retainSSA: Boolean, private val preassignedCallSiteIds: Map = emptyMap() @@ -24,8 +26,13 @@ class New6502CodeGenerator(val retainSSA: Boolean, // but you can also use the Intermediate Representation to build a codegen on: val irCodeGen = IRCodeGen(program, symbolTable, options, errors, retainSSA, preassignedCallSiteIds) val irProgram = irCodeGen.generate() - if (!irCodeGen.wasPackingApplied) - irProgram.verifyRegisterTypes(irCodeGen.registerTypes()) + if (!irCodeGen.wasPackingApplied) { + val virtualRegisterTypes = irCodeGen.registerTypes().entries.associate { (num, type) -> + val register: VirtualRegister = if (type == IRDataType.FLOAT) VirtualRegister.float(num.value) else VirtualRegister.int(num.value) + register to type + } + irProgram.verifyRegisterTypes(virtualRegisterTypes) + } IRFileWriter(irProgram, null).write() diff --git a/codeGenNew6502/src/prog8/codegen/new6502/ZeropageAllocator.kt b/codeGenNew6502/src/prog8/codegen/new6502/ZeropageAllocator.kt index 3a4504c47..9a5fbb42b 100644 --- a/codeGenNew6502/src/prog8/codegen/new6502/ZeropageAllocator.kt +++ b/codeGenNew6502/src/prog8/codegen/new6502/ZeropageAllocator.kt @@ -96,7 +96,9 @@ class ZeropageAllocator( val varNames = allVars.associateBy { it.name } fun scoreInstruction(insn: IRInstruction) { - val label = insn.labelSymbol ?: return + val label = insn.memory?.symbolName + ?: (insn.immediate as? ImmediateOperand.SymbolAddress)?.symbol + ?: return if (label !in varNames) return val weight = when { insn.type == IRDataType.WORD -> 2 diff --git a/codeGenNew6502/src/prog8/codegen/new6502/optimization/PeepholeOptimizer.kt b/codeGenNew6502/src/prog8/codegen/new6502/optimization/PeepholeOptimizer.kt index 871cfc042..ae32ea01c 100644 --- a/codeGenNew6502/src/prog8/codegen/new6502/optimization/PeepholeOptimizer.kt +++ b/codeGenNew6502/src/prog8/codegen/new6502/optimization/PeepholeOptimizer.kt @@ -27,12 +27,13 @@ internal class PeepholeOptimizer(private val program: IRProgram) { while (i < instructions.size - 1) { val cur = instructions[i] val next = instructions[i + 1] - val constImm = cur.immediate + val constImm = cur.immediate as? ImmediateOperand.Integer + val nextCallSite = next.callSite + val indirectPointer = ((nextCallSite?.target as? CallTarget.Direct)?.reference as? CodeReference.Indirect)?.pointer if (cur.opcode == Opcode.LOAD && cur.type == IRDataType.WORD && constImm != null - && next.opcode == Opcode.CALLI && next.reg1 == cur.reg1) { - val constAddr = constImm.toUInt() - val callReplacement = - IRInstruction(Opcode.CALL, address = constAddr.toAddress(), fcallArgs = next.fcallArgs) + && next.opcode == Opcode.CALLI && indirectPointer != null && indirectPointer.register == cur.requireDest().register) { + val constAddr = constImm.value.toUInt() + val callReplacement = IRInstructions.call(nextCallSite.withTarget(codeAddress(constAddr))) instructions[i] = callReplacement instructions.removeAt(i + 1) } diff --git a/codeGenNew6502/test/TestAsmsubReturns.kt b/codeGenNew6502/test/TestAsmsubReturns.kt index 24c990047..5abf63a37 100644 --- a/codeGenNew6502/test/TestAsmsubReturns.kt +++ b/codeGenNew6502/test/TestAsmsubReturns.kt @@ -1,6 +1,7 @@ package prog8tests.codegen.new6502 import io.kotest.core.spec.style.FunSpec +import io.kotest.engine.spec.tempdir import io.kotest.matchers.ints.shouldBeGreaterThan import io.kotest.matchers.shouldBe import prog8.code.core.* @@ -9,14 +10,15 @@ import prog8.codegen.new6502.AsmGen import prog8.intermediate.* import prog8tests.helpers.ErrorReporterForTests import java.nio.file.Path -import kotlin.io.path.Path import kotlin.io.path.exists import kotlin.io.path.readText class TestAsmsubReturns : FunSpec({ + val tempRoot = tempdir().toPath() + fun buildTestProgramWithCall( - returns: List + results: List ): Pair { val target = Cx16Target() val options = CompilationOptions.builder(target) @@ -30,10 +32,11 @@ class TestAsmsubReturns : FunSpec({ val st = IRSymbolTable() val program = IRProgram("test", st, options, DummyStringEncoder) - // Convert FunctionCallArgs.RegSpec to IRAsmParam for the asmsub definition - val asmParams = returns.map { ret -> + // Derive the asmsub's return declarations from the structured call results + val asmParams = results.map { ret -> + val location = ret.location val regOrFlag = RegisterOrStatusflag( - registerOrPair = when (ret.callingConventionSlot?.value) { + registerOrPair = when ((location as? CallLocation.HardwareRegister)?.slot?.value) { 0 -> RegisterOrPair.A 1 -> RegisterOrPair.X 2 -> RegisterOrPair.Y @@ -44,14 +47,15 @@ class TestAsmsubReturns : FunSpec({ 7 -> RegisterOrPair.FAC2 else -> null }, - statusflag = ret.statusflag + statusflag = (location as? CallLocation.StatusFlag)?.flag ) - val dt = when (ret.dt) { + val dt = when (ret.type) { IRDataType.BYTE -> DataType.forDt(BaseDataType.BOOL) IRDataType.WORD -> DataType.forDt(BaseDataType.UWORD) IRDataType.POINTER -> DataType.forDt(BaseDataType.POINTER) IRDataType.LONG -> DataType.forDt(BaseDataType.LONG) IRDataType.FLOAT -> DataType.forDt(BaseDataType.FLOAT) + null -> DataType.forDt(BaseDataType.BOOL) } IRAsmSubroutine.IRAsmParam(regOrFlag, dt) } @@ -70,9 +74,9 @@ class TestAsmsubReturns : FunSpec({ ) // Create the main subroutine that calls the asmsub - val callArgs = FunctionCallArgs(emptyList(), returns) + val callSite = CallSite(CallTarget.Direct(codeLabel("main.testsub")), emptyList(), results) val callChunk = IRCodeChunk(null, null) - callChunk.instructions.add(IRInstruction(Opcode.CALL, labelSymbol = "main.testsub", fcallArgs = callArgs)) + callChunk.instructions.add(IRInstructions.call(callSite)) val mainSub = IRSubroutine("main.start", emptyList(), emptyList(), Position.DUMMY) mainSub.chunks.add(callChunk) @@ -95,12 +99,12 @@ class TestAsmsubReturns : FunSpec({ test("multi-assign with @A + @Pz does not extract status flag in backend") { // Bug fix: the IR now emits branch patterns for status flags, // so the backend should NOT extract them with php/pla/and sequences. - val returns = listOf( - FunctionCallArgs.RegSpec(IRDataType.BYTE, RegisterNum(0), CallingConventionSlot(0), null), - FunctionCallArgs.RegSpec(IRDataType.BYTE, RegisterNum(1), null, Statusflag.Pz) + val results = listOf( + Calls.result(0, IRDataType.BYTE, CallLocation.HardwareRegister(CallingConventionSlot(0))), + Calls.result(1, IRDataType.BYTE, CallLocation.StatusFlag(Statusflag.Pz)) ) - val (program, target) = buildTestProgramWithCall(returns) - val outputDir = Path("/tmp/test-new6502-multi-assign-pz") + val (program, target) = buildTestProgramWithCall(results) + val outputDir = tempRoot.resolve("test-new6502-multi-assign-pz") outputDir.toFile().deleteRecursively() outputDir.toFile().mkdirs() program.options.outputDir = outputDir @@ -122,12 +126,12 @@ class TestAsmsubReturns : FunSpec({ test("multi-assign with @A + @Pn does not extract status flag in backend") { // Bug fix: same as Pz test but for Pn (negative flag) - val returns = listOf( - FunctionCallArgs.RegSpec(IRDataType.BYTE, RegisterNum(0), CallingConventionSlot(0), null), - FunctionCallArgs.RegSpec(IRDataType.BYTE, RegisterNum(1), null, Statusflag.Pn) + val results = listOf( + Calls.result(0, IRDataType.BYTE, CallLocation.HardwareRegister(CallingConventionSlot(0))), + Calls.result(1, IRDataType.BYTE, CallLocation.StatusFlag(Statusflag.Pn)) ) - val (program, target) = buildTestProgramWithCall(returns) - val outputDir = Path("/tmp/test-new6502-multi-assign-pn") + val (program, target) = buildTestProgramWithCall(results) + val outputDir = tempRoot.resolve("test-new6502-multi-assign-pn") outputDir.toFile().deleteRecursively() outputDir.toFile().mkdirs() program.options.outputDir = outputDir @@ -147,11 +151,11 @@ class TestAsmsubReturns : FunSpec({ test("single-return expression call with @A emits store instruction") { // Single-return expression calls should still emit the store instruction // because the IR doesn't generate LOADHR for single-return calls. - val returns = listOf( - FunctionCallArgs.RegSpec(IRDataType.BYTE, RegisterNum(0), CallingConventionSlot(0), null) + val results = listOf( + Calls.result(0, IRDataType.BYTE, CallLocation.HardwareRegister(CallingConventionSlot(0))) ) - val (program, target) = buildTestProgramWithCall(returns) - val outputDir = Path("/tmp/test-new6502-single-return-a") + val (program, target) = buildTestProgramWithCall(results) + val outputDir = tempRoot.resolve("test-new6502-single-return-a") outputDir.toFile().deleteRecursively() outputDir.toFile().mkdirs() program.options.outputDir = outputDir diff --git a/codeGenNew6502/test/TestControlCodegen.kt b/codeGenNew6502/test/TestControlCodegen.kt new file mode 100644 index 000000000..63f953d88 --- /dev/null +++ b/codeGenNew6502/test/TestControlCodegen.kt @@ -0,0 +1,217 @@ +package prog8tests.codegen.new6502 + +import io.kotest.core.spec.style.FunSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.ints.shouldBeGreaterThan +import io.kotest.matchers.ints.shouldBeLessThan +import io.kotest.matchers.shouldBe +import prog8.code.core.* +import prog8.code.target.Cx16Target +import prog8.codegen.new6502.AsmGen +import prog8.intermediate.* +import prog8tests.helpers.ErrorReporterForTests +import java.nio.file.Path +import kotlin.io.path.exists +import kotlin.io.path.readText + +/** + * Tests for the control flow / call translations, which read their operands from the + * structured call site (arguments, results, locations) and from the typed register slots. + */ +class TestControlCodegen : FunSpec({ + + val tempRoot = tempdir().toPath() + + fun generateAsm( + instructions: List, + extraElements: List = emptyList(), + name: String + ): List { + val target = Cx16Target() + val options = CompilationOptions.builder(target) + .output(OutputType.RAW) + .zeropage(ZeropageType.FLOATSAFE) + .floats(true) + .compilerVersion("test") + .memtopAddress(0xffffu) + .build() + val program = IRProgram("test", IRSymbolTable(), options, DummyStringEncoder) + val outputDir: Path = tempRoot.resolve(name) + outputDir.toFile().deleteRecursively() + outputDir.toFile().mkdirs() + program.options.outputDir = outputDir + + val chunk = IRCodeChunk(null, null) + chunk.instructions.addAll(instructions) + val sub = IRSubroutine("main.start", emptyList(), emptyList(), Position.DUMMY) + sub.chunks.add(chunk) + val block = IRBlock("main", false, IRBlock.Options(), Position.DUMMY) + block.children.add(sub) + extraElements.forEach { block.children.add(it) } + program.blocks.add(block) + + AsmGen(program, target, ErrorReporterForTests()).generate() + val asmFile = outputDir.resolve("test.asm") + check(asmFile.exists()) { "Assembly file not written: $asmFile" } + // drop the '; ' trace comments so only real instructions remain + return asmFile.readText().lines().map { it.trim() }.filter { !it.startsWith(";") } + } + + fun regularSub(label: String): IRSubroutine { + val sub = IRSubroutine(label, emptyList(), emptyList(), Position.DUMMY) + sub.chunks.add(IRCodeChunk(null, null).also { it += IRInstructions.returnVoid() }) + return sub + } + + test("call arguments in hardware slots are loaded in slot order, pairs before singles") { + // the A-slot argument comes first in the argument list but must be loaded LAST + val site = CallSite( + CallTarget.Direct(codeLabel("main.testsub")), + listOf( + Calls.argument(1, IRDataType.BYTE, CallLocation.HardwareRegister(CallingConventionSlot(0))), + Calls.argument(2, IRDataType.WORD, CallLocation.HardwareRegister(CallingConventionSlot(3))) + ) + ) + val lines = generateAsm( + listOf(IRInstructions.call(site)), + listOf(regularSub("main.testsub")), + "call-slot-order" + ) + val jsrIndex = lines.indexOfFirst { it == "jsr main.testsub" } + jsrIndex shouldBeGreaterThan -1 + // the AX pair (slot 3, register r2) is loaded with lda+ldx, the A slot (r1) with lda + val ldxIndex = lines.indexOfFirst { it.startsWith("ldx p8_regfile") } + ldxIndex shouldBeGreaterThan -1 + ldxIndex shouldBeLessThan jsrIndex + val lastLdaBeforeJsr = (jsrIndex - 1 downTo 0).first { lines[it].startsWith("lda p8_regfile") } + lastLdaBeforeJsr shouldBeGreaterThan ldxIndex + } + + test("call argument passed in parameter memory is stored to the resolved symbol") { + val site = CallSite( + CallTarget.Direct(codeLabel("main.testsub")), + listOf(Calls.argument(1, IRDataType.WORD, CallLocation.ParameterMemory("value"))) + ) + val lines = generateAsm( + listOf(IRInstructions.call(site)), + listOf(regularSub("main.testsub")), + "call-param-memory" + ) + lines.any { it == "sta main.testsub.value" } shouldBe true + lines.any { it == "sta main.testsub.value+1" } shouldBe true + lines.indexOfFirst { it == "jsr main.testsub" } shouldBeGreaterThan -1 + } + + test("single call result in a hardware slot is stored back into the virtual register") { + val site = CallSite( + CallTarget.Direct(codeLabel("main.testsub")), + emptyList(), + listOf(Calls.result(1, IRDataType.WORD, CallLocation.HardwareRegister(CallingConventionSlot(4)))) + ) + val lines = generateAsm( + listOf(IRInstructions.call(site)), + listOf(regularSub("main.testsub")), + "call-result-slot" + ) + val jsrIndex = lines.indexOfFirst { it == "jsr main.testsub" } + jsrIndex shouldBeGreaterThan -1 + val after = lines.drop(jsrIndex + 1) + after.any { it.startsWith("sta p8_regfile") } shouldBe true + after.any { it.startsWith("sty p8_regfile") } shouldBe true + } + + test("status flag call results are not extracted by the backend") { + val site = CallSite( + CallTarget.Direct(codeLabel("main.testsub")), + emptyList(), + listOf(Calls.result(1, IRDataType.BYTE, CallLocation.StatusFlag(Statusflag.Pc))) + ) + val lines = generateAsm( + listOf(IRInstructions.call(site)), + listOf(regularSub("main.testsub")), + "call-result-flag" + ) + val jsrIndex = lines.indexOfFirst { it == "jsr main.testsub" } + jsrIndex shouldBeGreaterThan -1 + lines.drop(jsrIndex + 1).any { it == "php" } shouldBe false + } + + test("syscall reads its argument registers from the call site") { + val syscall = IRInstructions.syscall( + IMSyscall.MEMCOPY.number, + listOf( + Calls.argument(1, IRDataType.WORD), + Calls.argument(2, IRDataType.WORD), + Calls.argument(3, IRDataType.WORD) + ) + ) + val lines = generateAsm(listOf(syscall), name = "syscall-memcopy") + lines.any { it == "jsr prog8_lib.memcopy_small" } shouldBe true + lines.any { it == "sta P8ZP_SCRATCH_W1" } shouldBe true + lines.any { it == "sta P8ZP_SCRATCH_W2" } shouldBe true + } + + test("returnr reads the value from the source register slot") { + val lines = generateAsm( + listOf(IRInstructions.returnRegister(IRDataType.WORD, 3)), + name = "returnr-word" + ) + val ldaIndex = lines.indexOfFirst { it.startsWith("lda p8_regfile") } + ldaIndex shouldBeGreaterThan -1 + lines[ldaIndex + 1].startsWith("ldy p8_regfile") shouldBe true + lines[ldaIndex + 2] shouldBe "rts" + } + + test("returni.f loads the float immediate from a generated constant") { + val lines = generateAsm( + listOf(IRInstructions.returnImmediateFloat(3.25)), + name = "returni-float" + ) + lines.any { it == "lda #, instructions: List @@ -86,10 +88,10 @@ class TestRegfileSection : FunSpec({ // Use register r1, which lives at offset 0 in the variable-size // regfile layout. If the label is misplaced this access overlaps // the dirty vars that share the same BSS_NOCLEAR section. - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = 1, immediate = 0) + IRInstructions.load(IRDataType.BYTE, 1, 0) ) ) - val outputDir = Path("/tmp/test-regfile-section-1") + val outputDir = tempRoot.resolve("test-regfile-section-1") outputDir.toFile().deleteRecursively() outputDir.toFile().mkdirs() program.options.outputDir = outputDir @@ -149,11 +151,11 @@ class TestRegfileSection : FunSpec({ makeDirtyVar("main.dirtyvar1", DataType.UBYTE) ), instructions = listOf( - IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = 1, immediate = 0), - IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = 2, immediate = 0) + IRInstructions.load(IRDataType.BYTE, 1, 0), + IRInstructions.load(IRDataType.WORD, 2, 0) ) ) - val outputDir = Path("/tmp/test-regfile-section-2") + val outputDir = tempRoot.resolve("test-regfile-section-2") outputDir.toFile().deleteRecursively() outputDir.toFile().mkdirs() program.options.outputDir = outputDir diff --git a/codeGenNew6502/test/TestZeropageAllocator.kt b/codeGenNew6502/test/TestZeropageAllocator.kt index 39be9f3b5..74c0976dd 100644 --- a/codeGenNew6502/test/TestZeropageAllocator.kt +++ b/codeGenNew6502/test/TestZeropageAllocator.kt @@ -122,9 +122,9 @@ class TestZeropageAllocator : FunSpec({ val instructions = mutableListOf() repeat(10) { - instructions.add(IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=0, labelSymbol="main.high")) + instructions.add(IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, 0, IRMemory.direct("main.high"))) } - instructions.add(IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=0, labelSymbol="main.low")) + instructions.add(IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, 0, IRMemory.direct("main.low"))) val (program, target) = createTestProgram( vars = listOf(lowFreqVar, highFreqVar), @@ -142,8 +142,8 @@ class TestZeropageAllocator : FunSpec({ val instructions = mutableListOf() repeat(5) { - instructions.add(IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=0, labelSymbol="main.bytevar")) - instructions.add(IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1=0, labelSymbol="main.wordvar")) + instructions.add(IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, 0, IRMemory.direct("main.bytevar"))) + instructions.add(IRInstructions.loadMemory(Opcode.LOADM, IRDataType.WORD, 0, IRMemory.direct("main.wordvar"))) } val (program, target) = createTestProgram( diff --git a/compiler/res/prog8lib/virtual/conv.p8 b/compiler/res/prog8lib/virtual/conv.p8 index 845278746..d32c13972 100644 --- a/compiler/res/prog8lib/virtual/conv.p8 +++ b/compiler/res/prog8lib/virtual/conv.p8 @@ -177,10 +177,11 @@ sub str_w (word value) -> str { sub str_l (long value) -> str { ; ---- convert the (signed) long into decimal string form, without left padding 0's %ir {{ - loadm.l r99200,conv.str_l.value - load.w r99000,conv.string_out - syscall 60 (r99200.l, r99000.w) : r99000.w - returnr.w r99000 + loadm.l r99200.l,[conv.str_l.value] + load.l r99201.l,#conv.string_out + syscall 60 (r99200.l, r99201.l) + load.w r99000.w,#conv.string_out + returnr.w r99000.w }} } @@ -244,9 +245,9 @@ sub str2uword(str string) -> uword { ; the number may NOT be preceded by a + sign and may NOT contain spaces ; (any non-digit character will terminate the number string that is parsed) %ir {{ - loadm.l r99200,conv.str2uword.string + loadm.l r99200.l,[conv.str2uword.string] syscall 11 (r99200.l) : r99000.w - returnr.w r99000 + returnr.w r99000.w }} } @@ -255,9 +256,9 @@ sub str2word(str string) -> word { ; the number may be preceded by a + or - sign but may NOT contain spaces ; (any non-digit character will terminate the number string that is parsed) %ir {{ - loadm.l r99200,conv.str2word.string + loadm.l r99200.l,[conv.str2word.string] syscall 12 (r99200.l) : r99000.w - returnr.w r99000 + returnr.w r99000.w }} } diff --git a/compiler/res/prog8lib/virtual/diskio.p8 b/compiler/res/prog8lib/virtual/diskio.p8 index 25c1e3188..8664cd244 100644 --- a/compiler/res/prog8lib/virtual/diskio.p8 +++ b/compiler/res/prog8lib/virtual/diskio.p8 @@ -14,7 +14,7 @@ diskio { ; -- Prints the directory contents to the screen. Returns success. %ir {{ syscall 45 (): r99100.b - returnr.b r99100 + returnr.b r99100.b }} } @@ -52,9 +52,9 @@ diskio { ; if you're going to read from it yourself instead of using f_read()! %ir {{ - loadm.l r99200,diskio.f_open.filenameptr + loadm.l r99200.l,[diskio.f_open.filenameptr] syscall 52 (r99200.l): r99100.b - returnr.b r99100 + returnr.b r99100.b }} } @@ -65,7 +65,7 @@ diskio { repeat num_bytes { %ir {{ syscall 54 (): r99000.w - storem.w r99000,$ff0000 + storem.w r99000.w,[$ff0000] }} if cx16.r0H==0 return actual @@ -84,7 +84,7 @@ diskio { repeat { %ir {{ syscall 54 (): r99000.w - storem.w r99000,$ff0000 + storem.w r99000.w,[$ff0000] }} if cx16.r0H==0 return actual @@ -104,7 +104,7 @@ diskio { repeat { %ir {{ syscall 54 (): r99000.w - storem.w r99000,$ff0000 + storem.w r99000.w,[$ff0000] }} if cx16.r0H==0 { @@ -146,9 +146,9 @@ diskio { ; To be 100% sure if this call was successful, you have to use status() ; and check the drive's status message! %ir {{ - loadm.w r99000,diskio.f_open_w.filenameptr - syscall 53 (r99000.w): r99100.b - returnr.b r99100 + loadm.l r99200.l,[diskio.f_open_w.filenameptr] + syscall 53 (r99200.l): r99100.b + returnr.b r99100.b }} } @@ -157,10 +157,10 @@ diskio { ; you can call this multiple times to append more data repeat num_bytes { %ir {{ - loadm.w r99000,diskio.f_write.bufferpointer - loadi.b r99100,r99000,#0 - syscall 55 (r99100.b): r99100.b - storem.b r99100,$ff0000 + loadm.w r99000.w,[diskio.f_write.bufferpointer] + loadi.b r99100.b,[r99000.w] + syscall 55 (r99100.b): r99101.b + storem.b r99101.b,[$ff0000] }} if cx16.r0L==0 return false @@ -183,7 +183,7 @@ diskio { sub chdir(str path) { ; -- change current directory. %ir {{ - loadm.l r99200,diskio.chdir.path + loadm.l r99200.l,[diskio.chdir.path] syscall 50 (r99200.l) }} } @@ -191,7 +191,7 @@ diskio { sub mkdir(str name) { ; -- make a new subdirectory. %ir {{ - loadm.l r99200,diskio.mkdir.name + loadm.l r99200.l,[diskio.mkdir.name] syscall 49 (r99200.l) }} } @@ -199,7 +199,7 @@ diskio { sub rmdir(str name) { ; -- remove a subdirectory. %ir {{ - loadm.l r99200,diskio.rmdir.name + loadm.l r99200.l,[diskio.rmdir.name] syscall 51 (r99200.l) }} } @@ -208,7 +208,7 @@ diskio { ; return current directory name or 0 if error %ir {{ syscall 48 (): r99000.w - returnr.w r99000 + returnr.w r99000.w }} } @@ -225,24 +225,24 @@ diskio { sub save(str filenameptr, pointer start_address, uword savesize) -> bool { %ir {{ - load.b r99100,#0 - loadm.l r99200,diskio.save.filenameptr - loadm.l r99201,diskio.save.start_address - loadm.w r99002,diskio.save.savesize - syscall 42 (r99100.b, r99200.l, r99201.l, r99002.w): r99100.b - returnr.b r99100 + load.b r99100.b,#0.b + loadm.l r99200.l,[diskio.save.filenameptr] + loadm.l r99201.l,[diskio.save.start_address] + loadm.w r99002.w,[diskio.save.savesize] + syscall 42 (r99100.b, r99200.l, r99201.l, r99002.w): r99101.b + returnr.b r99101.b }} } ; like save() but omits the 2 byte prg header. sub save_raw(str filenameptr, pointer start_address, uword savesize) -> bool { %ir {{ - load.b r99100,#1 - loadm.l r99200,diskio.save_raw.filenameptr - loadm.l r99201,diskio.save_raw.start_address - loadm.w r99002,diskio.save_raw.savesize - syscall 42 (r99100.b, r99200.l, r99201.l, r99002.w): r99100.b - returnr.b r99100 + load.b r99100.b,#1.b + loadm.l r99200.l,[diskio.save_raw.filenameptr] + loadm.l r99201.l,[diskio.save_raw.start_address] + loadm.w r99002.w,[diskio.save_raw.savesize] + syscall 42 (r99100.b, r99200.l, r99201.l, r99002.w): r99101.b + returnr.b r99101.b }} } @@ -254,10 +254,10 @@ diskio { ; Returns the end load address+1 if successful or 0 if a load error occurred. sub load(str filenameptr, pointer address_override) -> uword { %ir {{ - loadm.l r99200,diskio.load.filenameptr - loadm.l r99201,diskio.load.address_override + loadm.l r99200.l,[diskio.load.filenameptr] + loadm.l r99201.l,[diskio.load.address_override] syscall 40 (r99200.l, r99201.l): r99002.w - returnr.w r99002 + returnr.w r99002.w }} } @@ -266,17 +266,17 @@ diskio { ; See comments on load() for more details. sub load_raw(str filenameptr, pointer start_address) -> uword { %ir {{ - loadm.l r99200,diskio.load_raw.filenameptr - loadm.l r99201,diskio.load_raw.start_address + loadm.l r99200.l,[diskio.load_raw.filenameptr] + loadm.l r99201.l,[diskio.load_raw.start_address] syscall 41 (r99200.l, r99201.l): r99002.w - returnr.w r99002 + returnr.w r99002.w }} } sub delete(str filenameptr) { ; -- delete a file on the drive %ir {{ - loadm.l r99200,diskio.delete.filenameptr + loadm.l r99200.l,[diskio.delete.filenameptr] syscall 43 (r99200.l) }} } @@ -284,8 +284,8 @@ diskio { sub rename(str oldfileptr, str newfileptr) { ; -- rename a file on the drive %ir {{ - loadm.l r99200,diskio.rename.oldfileptr - loadm.l r99201,diskio.rename.newfileptr + loadm.l r99200.l,[diskio.rename.oldfileptr] + loadm.l r99201.l,[diskio.rename.newfileptr] syscall 44 (r99200.l, r99201.l) }} } @@ -314,9 +314,9 @@ diskio { ; -- seek in the reading file opened with f_open, to the given 32-bits position ; Returns true if successful, false if the position is invalid or file not open. %ir {{ - loadm.l r99200,diskio.f_seek.position + loadm.l r99200.l,[diskio.f_seek.position] syscall 63 (r99200.l): r99100.b - returnr.b r99100 + returnr.b r99100.b }} } @@ -325,9 +325,9 @@ diskio { long @shared pos, size %ir {{ syscall 64 (): r99200.l - storem.l r99200,diskio.f_tell.pos + storem.l r99200.l,[diskio.f_tell.pos] syscall 65 (): r99200.l - storem.l r99200,diskio.f_tell.size + storem.l r99200.l,[diskio.f_tell.size] }} return pos, size } diff --git a/compiler/res/prog8lib/virtual/floats.p8 b/compiler/res/prog8lib/virtual/floats.p8 index 91f45e9c3..0fbe1fe38 100644 --- a/compiler/res/prog8lib/virtual/floats.p8 +++ b/compiler/res/prog8lib/virtual/floats.p8 @@ -35,7 +35,7 @@ floats { sub print(float value) { ; ---- prints the floating point value (without a newline and no leading spaces). %ir {{ - loadm.f fr99000,floats.print.value + loadm.f fr99000.f,[floats.print.value] syscall 15 (fr99000.f) return }} @@ -45,61 +45,61 @@ sub tostr(float value) -> str { ; ---- converts the floating point value to a string (no leading spaces) str @shared buffer=" "*20 %ir {{ - load.l r99200,floats.tostr.buffer - loadm.f fr99000,floats.tostr.value + load.l r99200.l,#floats.tostr.buffer + loadm.f fr99000.f,[floats.tostr.value] syscall 34 (r99200.l, fr99000.f) - load.w r99000,floats.tostr.buffer - returnr.w r99000 + load.w r99000.w,#floats.tostr.buffer + returnr.w r99000.w }} } sub parse(str value) -> float { ; -- parse a string value of a number to float %ir {{ - loadm.l r99200,floats.parse.value + loadm.l r99200.l,[floats.parse.value] syscall 32 (r99200.l): fr99000.f - returnr.f fr99000 + returnr.f fr99000.f }} } sub pow(float value, float power) -> float { %ir {{ - loadm.f fr0,floats.pow.value - loadm.f fr1,floats.pow.power - fpow.f fr0,fr1 - returnr.f fr0 + loadm.f fr0.f,[floats.pow.value] + loadm.f fr1.f,[floats.pow.power] + fpow.f fr0.f,fr1.f + returnr.f fr0.f }} } sub sin(float angle) -> float { %ir {{ - loadm.f fr0,floats.sin.angle - fsin.f fr0,fr0 - returnr.f fr0 + loadm.f fr0.f,[floats.sin.angle] + fsin.f fr1.f,fr0.f + returnr.f fr1.f }} } sub cos(float angle) -> float { %ir {{ - loadm.f fr0,floats.cos.angle - fcos.f fr0,fr0 - returnr.f fr0 + loadm.f fr0.f,[floats.cos.angle] + fcos.f fr1.f,fr0.f + returnr.f fr1.f }} } sub tan(float value) -> float { %ir {{ - loadm.f fr0,floats.tan.value - ftan.f fr0,fr0 - returnr.f fr0 + loadm.f fr0.f,[floats.tan.value] + ftan.f fr1.f,fr0.f + returnr.f fr1.f }} } sub atan(float value) -> float { %ir {{ - loadm.f fr0,floats.atan.value - fatan.f fr0,fr0 - returnr.f fr0 + loadm.f fr0.f,[floats.atan.value] + fatan.f fr1.f,fr0.f + returnr.f fr1.f }} } @@ -119,17 +119,17 @@ sub cot(float value) -> float { return 1.0 / tan(value) } sub ln(float value) -> float { %ir {{ - loadm.f fr0,floats.ln.value - fln.f fr0,fr0 - returnr.f fr0 + loadm.f fr0.f,[floats.ln.value] + fln.f fr1.f,fr0.f + returnr.f fr1.f }} } sub log2(float value) -> float { %ir {{ - loadm.f fr0,floats.log2.value - flog.f fr0,fr0 - returnr.f fr0 + loadm.f fr0.f,[floats.log2.value] + flog.f fr1.f,fr0.f + returnr.f fr1.f }} } @@ -145,39 +145,39 @@ sub deg(float angle) -> float { sub round(float value) -> float { %ir {{ - loadm.f fr0,floats.round.value - fround.f fr0,fr0 - returnr.f fr0 + loadm.f fr0.f,[floats.round.value] + fround.f fr1.f,fr0.f + returnr.f fr1.f }} } sub floor(float value) -> float { %ir {{ - loadm.f fr0,floats.floor.value - ffloor.f fr0,fr0 - returnr.f fr0 + loadm.f fr0.f,[floats.floor.value] + ffloor.f fr1.f,fr0.f + returnr.f fr1.f }} } sub ceil(float value) -> float { ; -- ceil: tr = int(f); if tr==f -> return else return tr+1 %ir {{ - loadm.f fr0,floats.ceil.value - fceil.f fr0,fr0 - returnr.f fr0 + loadm.f fr0.f,[floats.ceil.value] + fceil.f fr1.f,fr0.f + returnr.f fr1.f }} } sub rnd() -> float { %ir {{ syscall 22 () : fr99000.f - returnr.f fr99000 + returnr.f fr99000.f }} } sub rndseed(float seed) { %ir {{ - loadm.f fr99000,floats.rndseed.seed + loadm.f fr99000.f,[floats.rndseed.seed] syscall 19 (fr99000.f) return }} diff --git a/compiler/res/prog8lib/virtual/math.p8 b/compiler/res/prog8lib/virtual/math.p8 index 3485a394e..dc96e48f3 100644 --- a/compiler/res/prog8lib/virtual/math.p8 +++ b/compiler/res/prog8lib/virtual/math.p8 @@ -165,14 +165,14 @@ math { sub rnd() -> ubyte { %ir {{ syscall 20 (): r99100.b - returnr.b r99100 + returnr.b r99100.b }} } sub rndw() -> uword { %ir {{ syscall 21 (): r99000.w - returnr.w r99000 + returnr.w r99000.w }} } @@ -193,8 +193,8 @@ math { sub rndseed(uword seed1, uword seed2) { ; -- reset the pseudo RNG's seed values. Defaults are: $a55a, $7653. %ir {{ - loadm.w r99000,math.rndseed.seed1 - loadm.w r99001,math.rndseed.seed2 + loadm.w r99000.w,[math.rndseed.seed1] + loadm.w r99001.w,[math.rndseed.seed2] syscall 18 (r99000.w, r99001.w) return }} @@ -279,12 +279,12 @@ math { ;; Calculate the angle, in a 256-degree circle, between two points into A. ;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the cartesian plane! %ir {{ - loadm.b r99100,math.atan2.x1 - loadm.b r99101,math.atan2.y1 - loadm.b r99102,math.atan2.x2 - loadm.b r99103,math.atan2.y2 - syscall 31 (r99100.b, r99101.b, r99102.b, r99103.b): r99100.b - returnr.b r99100 + loadm.b r99100.b,[math.atan2.x1] + loadm.b r99101.b,[math.atan2.y1] + loadm.b r99102.b,[math.atan2.x2] + loadm.b r99103.b,[math.atan2.y2] + syscall 31 (r99100.b, r99101.b, r99102.b, r99103.b): r99104.b + returnr.b r99104.b }} } @@ -300,7 +300,7 @@ math { ; as soon as a negative word value (or 2) was used in the multiplication, these upper 16 bits are not valid!! %ir {{ syscall 33 (): r99000.w - returnr.w r99000 + returnr.w r99000.w }} } diff --git a/compiler/res/prog8lib/virtual/strings.p8 b/compiler/res/prog8lib/virtual/strings.p8 index 8bb84e69a..0a73a4987 100644 --- a/compiler/res/prog8lib/virtual/strings.p8 +++ b/compiler/res/prog8lib/virtual/strings.p8 @@ -107,11 +107,11 @@ strings { ; Copy a string to another, overwriting that one. ; Returns the length of the string that was copied. %ir {{ - loadm.l r99200,strings.copy.source - loadm.l r99201,strings.copy.target - load.b r99100,#255 - syscall 39 (r99200.l, r99201.l, r99100.b): r99100.b - returnr.b r99100 + loadm.l r99200.l,[strings.copy.source] + loadm.l r99201.l,[strings.copy.target] + load.b r99100.b,#255.b + syscall 39 (r99200.l, r99201.l, r99100.b): r99101.b + returnr.b r99101.b }} } @@ -119,11 +119,11 @@ strings { ; Copy a string to another, overwriting that one, but limited to the given length. ; Returns the length of the string that was copied. %ir {{ - loadm.l r99200,strings.ncopy.source - loadm.l r99201,strings.ncopy.target - loadm.b r99100,strings.ncopy.maxlength - syscall 39 (r99200.l, r99201.l, r99100.b): r99100.b - returnr.b r99100 + loadm.l r99200.l,[strings.ncopy.source] + loadm.l r99201.l,[strings.ncopy.target] + loadm.b r99100.b,[strings.ncopy.maxlength] + syscall 39 (r99200.l, r99201.l, r99100.b): r99101.b + returnr.b r99101.b }} } @@ -150,10 +150,10 @@ strings { ; Note that you can also directly compare strings and string values with each other using ; comparison operators ==, < etcetera (this will use strcmp automatically). %ir {{ - loadm.l r99200,strings.compare.st1 - loadm.l r99201,strings.compare.st2 + loadm.l r99200.l,[strings.compare.st1] + loadm.l r99201.l,[strings.compare.st2] syscall 16 (r99200.l, r99201.l) : r99100.b - returnr.b r99100 + returnr.b r99100.b }} } @@ -163,10 +163,10 @@ strings { ; Note that you can also directly compare strings and string values with each other using ; comparison operators ==, < etcetera (this will use strcmp automatically). %ir {{ - loadm.l r99200,strings.compare_nocase.st1 - loadm.l r99201,strings.compare_nocase.st2 + loadm.l r99200.l,[strings.compare_nocase.st1] + loadm.l r99201.l,[strings.compare_nocase.st2] syscall 58 (r99200.l, r99201.l) : r99100.b - returnr.b r99100 + returnr.b r99100.b }} } diff --git a/compiler/res/prog8lib/virtual/syslib.p8 b/compiler/res/prog8lib/virtual/syslib.p8 index 2c5816f08..8ccab06f9 100644 --- a/compiler/res/prog8lib/virtual/syslib.p8 +++ b/compiler/res/prog8lib/virtual/syslib.p8 @@ -43,11 +43,11 @@ sys { ; -- kill the program by jumping into the debugger/monitor (if available). Status code is in register A, a pointer to the death message is in X,Y. str @shared warning = iso:"\n\nPROGRAM DIED: " %ir {{ - load.l r99200,sys.die.warning + load.l r99200.l,#sys.die.warning syscall 3 (r99200.l) - loadm.l r99200,sys.die.message + loadm.l r99200.l,[sys.die.message] syscall 3 (r99200.l) - load.b r99100,10 + load.b r99100.b,#10.b syscall 2 (r99100.b) }} exit(code) @@ -56,7 +56,7 @@ sys { sub wait(uword jiffies) { ; --- wait approximately the given number of jiffies (1/60th seconds) %ir {{ - loadm.w r99000,sys.wait.jiffies + loadm.w r99000.w,[sys.wait.jiffies] syscall 13 (r99000.w) }} } @@ -70,27 +70,27 @@ sys { sub memcopy(pointer source, pointer tgt, uword count) { %ir {{ - loadm.l r99200,sys.memcopy.source - loadm.l r99201,sys.memcopy.tgt - loadm.w r99002,sys.memcopy.count + loadm.l r99200.l,[sys.memcopy.source] + loadm.l r99201.l,[sys.memcopy.tgt] + loadm.w r99002.w,[sys.memcopy.count] syscall 36 (r99200.l, r99201.l, r99002.w) }} } sub memset(pointer mem, uword numbytes, ubyte value) { %ir {{ - loadm.l r99200,sys.memset.mem - loadm.w r99001,sys.memset.numbytes - loadm.b r99100,sys.memset.value + loadm.l r99200.l,[sys.memset.mem] + loadm.w r99001.w,[sys.memset.numbytes] + loadm.b r99100.b,[sys.memset.value] syscall 37 (r99200.l, r99001.w, r99100.b) }} } sub memsetw(pointer mem, uword numwords, uword value) { %ir {{ - loadm.l r99200,sys.memsetw.mem - loadm.w r99001,sys.memsetw.numwords - loadm.w r99002,sys.memsetw.value + loadm.l r99200.l,[sys.memsetw.mem] + loadm.w r99001.w,[sys.memsetw.numwords] + loadm.w r99002.w,[sys.memsetw.value] syscall 38 (r99200.l, r99001.w, r99002.w) }} } @@ -99,18 +99,18 @@ sys { ; Compares two blocks of memory of up to 65535 bytes in size ; Returns -1 (255), 0 or 1, meaning: block 1 sorts before, equal or after block 2. %ir {{ - loadm.l r99200,sys.memcmp.address1 - loadm.l r99201,sys.memcmp.address2 - loadm.w r99002,sys.memcmp.size + loadm.l r99200.l,[sys.memcmp.address1] + loadm.l r99201.l,[sys.memcmp.address2] + loadm.w r99002.w,[sys.memcmp.size] syscall 47 (r99200.l, r99201.l, r99002.w) : r99100.b - returnr.b r99100 + returnr.b r99100.b }} } sub exit(ubyte returnvalue) { ; -- exit the program with a return code. All active defers in the call chain are unwound program-wide (LIFO) before system cleanup. sys.reset_system() and poweroff_system() do not run defers. %ir {{ - loadm.b r99100,sys.exit.returnvalue + loadm.b r99100.b,[sys.exit.returnvalue] syscall 1 (r99100.b) }} } @@ -157,42 +157,42 @@ sys { sub gfx_enable(ubyte mode) { %ir {{ - loadm.b r99100,sys.gfx_enable.mode + loadm.b r99100.b,[sys.gfx_enable.mode] syscall 8 (r99100.b) }} } sub gfx_clear(ubyte color) { %ir {{ - loadm.b r99100,sys.gfx_clear.color + loadm.b r99100.b,[sys.gfx_clear.color] syscall 9 (r99100.b) }} } sub gfx_plot(uword xx, uword yy, ubyte color) { %ir {{ - loadm.w r99000,sys.gfx_plot.xx - loadm.w r99001,sys.gfx_plot.yy - loadm.b r99100,sys.gfx_plot.color + loadm.w r99000.w,[sys.gfx_plot.xx] + loadm.w r99001.w,[sys.gfx_plot.yy] + loadm.b r99100.b,[sys.gfx_plot.color] syscall 10 (r99000.w, r99001.w, r99100.b) }} } sub gfx_getpixel(uword xx, uword yy) -> ubyte { %ir {{ - loadm.w r99000,sys.gfx_getpixel.xx - loadm.w r99001,sys.gfx_getpixel.yy + loadm.w r99000.w,[sys.gfx_getpixel.xx] + loadm.w r99001.w,[sys.gfx_getpixel.yy] syscall 17 (r99000.w, r99001.w): r99100.b - returnr.b r99100 + returnr.b r99100.b }} } sub gfx_text(uword xx, uword yy, str textptr, ubyte color) { %ir {{ - loadm.w r99000,sys.gfx_text.xx - loadm.w r99001,sys.gfx_text.yy - loadm.l r99200,sys.gfx_text.textptr - loadm.b r99100,sys.gfx_text.color + loadm.w r99000.w,[sys.gfx_text.xx] + loadm.w r99001.w,[sys.gfx_text.yy] + loadm.l r99200.l,[sys.gfx_text.textptr] + loadm.b r99100.b,[sys.gfx_text.color] syscall 66 (r99000.w, r99001.w, r99200.l, r99100.b) }} } @@ -433,7 +433,7 @@ cx16 { private sub print_error (str message) { %ir {{ - loadm.l r99200,cx16.print_error.message + loadm.l r99200.l,[cx16.print_error.message] syscall 3 (r99200.l) }} } diff --git a/compiler/res/prog8lib/virtual/textio.p8 b/compiler/res/prog8lib/virtual/textio.p8 index e2cda96b6..5138f63c3 100644 --- a/compiler/res/prog8lib/virtual/textio.p8 +++ b/compiler/res/prog8lib/virtual/textio.p8 @@ -9,8 +9,8 @@ sub width() -> ubyte { ; syscall 46 returns size encoded in a single uword: MSB=rows, LSB=columns %ir {{ syscall 46 (): r99000.w - lsigb.w r99100,r99000 - returnr.b r99100 + lsigb.w r99100.b,r99000.w + returnr.b r99100.b }} } @@ -18,8 +18,8 @@ sub height() -> ubyte { ; syscall 46 returns size encoded in a single uword: MSB=rows, LSB=columns %ir {{ syscall 46 (): r99000.w - msigb.w r99100,r99000 - returnr.b r99100 + msigb.w r99100.b,r99000.w + returnr.b r99100.b }} } @@ -31,7 +31,7 @@ sub size() -> ubyte, ubyte { sub clear_screen() { str @shared sequence = "\x1b[2J\x1B[H" %ir {{ - load.l r99200,txt.clear_screen.sequence + load.l r99200.l,#txt.clear_screen.sequence syscall 3 (r99200.l) }} } @@ -85,7 +85,7 @@ sub color (ubyte txtcol) { sub chrout(ubyte char) { %ir {{ - loadm.b r99100,txt.chrout.char + loadm.b r99100.b,[txt.chrout.char] syscall 2 (r99100.b) }} } @@ -96,7 +96,7 @@ sub bell() { sub print (str text) { %ir {{ - loadm.l r99200,txt.print.text + loadm.l r99200.l,[txt.print.text] syscall 3 (r99200.l) }} } @@ -176,7 +176,7 @@ sub print_w (word value) { sub print_l (long value) { ; ---- print the (signed) long in decimal form, without left padding 0's %ir {{ - loadm.l r99200,txt.print_l.value + loadm.l r99200.l,[txt.print_l.value] syscall 59 (r99200.l) }} } @@ -185,10 +185,10 @@ sub input_chars (str buffer) -> ubyte { ; ---- Input a string (max. 80 chars) from the keyboard. Returns length of input. (string is terminated with a 0 byte as well) ; It assumes the keyboard is selected as I/O channel! %ir {{ - loadm.l r99200,txt.input_chars.buffer - load.b r99100,#80 - syscall 6 (r99200.l, r99100.b): r99100.b - returnr.b r99100 + loadm.l r99200.l,[txt.input_chars.buffer] + load.b r99100.b,#80.b + syscall 6 (r99200.l, r99100.b): r99101.b + returnr.b r99101.b }} } diff --git a/compiler/src/prog8/CompilerMain.kt b/compiler/src/prog8/CompilerMain.kt index adf28128b..8df359b41 100644 --- a/compiler/src/prog8/CompilerMain.kt +++ b/compiler/src/prog8/CompilerMain.kt @@ -600,8 +600,8 @@ data class IrMetrics(val instructions: Int, val chunks: Int, val registers: Int) private fun extractIrMetrics(program: prog8.intermediate.IRProgram): IrMetrics { var instructions = 0 var chunks = 0 - val readRegs = mutableSetOf() - val writeRegs = mutableSetOf() + val readRegs = mutableSetOf() + val writeRegs = mutableSetOf() // Count chunks and instructions from all subroutines (loop-aware via shared traversal) program.allSubs().forEach { sub -> @@ -610,26 +610,15 @@ private fun extractIrMetrics(program: prog8.intermediate.IRProgram): IrMetrics { instructions++ // Extract register usage from each instruction - ins.reg1?.let { readRegs.add(it) } - ins.reg2?.let { readRegs.add(it) } - ins.reg3?.let { - if (ins.opcode == Opcode.CONCAT || ins.opcode == Opcode.EXT || - ins.opcode == Opcode.EXTS) { - readRegs.add(it) - } else { - writeRegs.add(it) - } - } - ins.fpReg1?.let { readRegs.add(it.value) } - ins.fpReg2?.let { readRegs.add(it.value) } + readRegs.addAll(ins.uses) + writeRegs.addAll(ins.definitions) } } // Also count global init chunks program.globalInits.instructions.forEach { ins -> - ins.reg1?.let { readRegs.add(it) } - ins.reg2?.let { readRegs.add(it) } - ins.reg3?.let { writeRegs.add(it) } + readRegs.addAll(ins.uses) + writeRegs.addAll(ins.definitions) } val registers = readRegs.size + writeRegs.size diff --git a/compiler/test/TestLaunchEmu.kt b/compiler/test/TestLaunchEmu.kt index c01ddefd1..95bac379d 100644 --- a/compiler/test/TestLaunchEmu.kt +++ b/compiler/test/TestLaunchEmu.kt @@ -12,7 +12,7 @@ class TestLaunchEmu: FunSpec({ val target = VMTarget() val tmpfile = kotlin.io.path.createTempFile(suffix=".p8ir") tmpfile.writeText(""" - + diff --git a/compiler/test/TestOptimization2.kt b/compiler/test/TestOptimization2.kt index 03cc0ae93..d5b74c2aa 100644 --- a/compiler/test/TestOptimization2.kt +++ b/compiler/test/TestOptimization2.kt @@ -59,7 +59,7 @@ some_block { other_block { sub redherring (uword buffer) { %ir {{ - loadm.w r99000,other_block.redherring.buffer + loadm.w r99000.w,[other_block.redherring.buffer] }} } } diff --git a/compiler/test/ast/TestProg8Parser.kt b/compiler/test/ast/TestProg8Parser.kt index be40af575..b2783e7f9 100644 --- a/compiler/test/ast/TestProg8Parser.kt +++ b/compiler/test/ast/TestProg8Parser.kt @@ -1107,7 +1107,7 @@ main { ; curly braces without newline sub start () { foo() derp() other() } sub foo() { cx16.r0++ } - asmsub derp() { %asm {{ nop rts }} %ir {{ load.b r0,#1 return }} } + asmsub derp() { %asm {{ nop rts }} %ir {{ load.b r0.b,#1.b return }} } ; curly braces on next line sub other() @@ -1131,7 +1131,7 @@ main { { %ir {{ - load.b r0,#1 + load.b r0.b,#1.b return }} } diff --git a/compiler/test/vm/TestCompilerVirtual.kt b/compiler/test/vm/TestCompilerVirtual.kt index 03fef25ab..6729ef988 100644 --- a/compiler/test/vm/TestCompilerVirtual.kt +++ b/compiler/test/vm/TestCompilerVirtual.kt @@ -317,7 +317,7 @@ main { main { sub start() { %ir {{ - incm.b $2000 + incm.b [$2000] return }} } @@ -326,7 +326,7 @@ main { val result = compileText(target, false, src, outputDir, writeAssembly = true)!! val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir") val irSrc = virtfile.readText() - irSrc.shouldContainInOrder("incm.b $2000") + irSrc.shouldContainInOrder("incm.b [$2000]") irSrc.shouldNotContain("") VmRunner().runProgram(irSrc, false) } @@ -372,14 +372,14 @@ other { val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir") val irSrc = virtfile.readText() // the &main must be encoded as a LOAD with the block name as label symbol - irSrc.shouldContain("load.p r1,main") + irSrc.shouldContain("load.p r1.p,#main") // and the block must have an address that a 6502 backend can resolve irSrc.shouldContain($$"""c.instructions } // Find LOADM for 'flag' variable - use contains match since scoped name may vary - val loadmIdx = instructions.indexOfFirst { it.opcode == Opcode.LOADM && it.labelSymbol?.contains("flag") == true } + val loadmIdx = instructions.indexOfFirst { it.opcode == Opcode.LOADM && it.memory?.symbolName?.contains("flag") == true } loadmIdx shouldBeGreaterThan -1 // The VIRTUAL target has statusBitsOnMultiByteOps=false, so the IR generator @@ -463,7 +463,7 @@ main { // explicit compare so the result is correct for any register width (BYTE/WORD/LONG). val nextInstr = instructions[loadmIdx + 1] nextInstr.opcode shouldBe Opcode.CMPI - nextInstr.immediate shouldBe 0 + nextInstr.immediate?.integerValue shouldBe 0 } test("if-statement always emits explicit CMPI when condition sets flags (strict status-bits contract)") { @@ -485,14 +485,14 @@ main { val instructions = start.chunks.flatMap { c->c.instructions } // Find LOADM for 'flag' variable - use contains match since scoped name may vary - val loadmIdx = instructions.indexOfFirst { it.opcode == Opcode.LOADM && it.labelSymbol?.contains("flag") == true } + val loadmIdx = instructions.indexOfFirst { it.opcode == Opcode.LOADM && it.memory?.symbolName?.contains("flag") == true } loadmIdx shouldBeGreaterThan -1 // Strict contract: the CMPI is always present, never optimized away. // See CpuType.statusBitsOnMultiByteOps for the rationale. val nextInstr = instructions[loadmIdx + 1] nextInstr.opcode shouldBe Opcode.CMPI - nextInstr.immediate shouldBe 0 + nextInstr.immediate?.integerValue shouldBe 0 } test("if-expression omits CMPI since CALL now sets flags") { @@ -510,7 +510,7 @@ main { val instructions = start.chunks.flatMap { c->c.instructions } // Should NOT contain CMPI #0 after function call anymore (since CALL effectively sets flags now) - val cmpiInstr = instructions.find { it.opcode == Opcode.CMPI && it.immediate == 0 } + val cmpiInstr = instructions.find { it.opcode == Opcode.CMPI && it.immediate?.integerValue == 0 } cmpiInstr shouldBe null } diff --git a/compiler/test/vm/TestVariableStepForLoopStructure.kt b/compiler/test/vm/TestVariableStepForLoopStructure.kt index 9e39c6bb1..8f0de74a9 100644 --- a/compiler/test/vm/TestVariableStepForLoopStructure.kt +++ b/compiler/test/vm/TestVariableStepForLoopStructure.kt @@ -17,6 +17,7 @@ import prog8.code.ast.walkAst import prog8.code.target.VMTarget import prog8.intermediate.IRFileReader import prog8.intermediate.Opcode +import prog8.intermediate.symbolName import prog8tests.helpers.compileText import kotlin.io.path.readText @@ -81,8 +82,8 @@ class TestVariableStepForLoopStructure: FunSpec({ .flatMap { it.instructions } .toList() - instructions.count { it.opcode == Opcode.CALL && it.labelSymbol?.substringAfterLast('.') == "unsigned_step" } shouldBe 1 - instructions.count { it.opcode == Opcode.CALL && it.labelSymbol?.substringAfterLast('.') == "signed_step" } shouldBe 1 + instructions.count { it.opcode == Opcode.CALL && it.labelTarget?.substringAfterLast('.') == "unsigned_step" } shouldBe 1 + instructions.count { it.opcode == Opcode.CALL && it.labelTarget?.substringAfterLast('.') == "signed_step" } shouldBe 1 instructions.map { it.opcode } shouldContain Opcode.BGTR instructions.map { it.opcode } shouldContain Opcode.BGTSR instructions.count { it.opcode == Opcode.ADDR } shouldBe 2 @@ -136,17 +137,17 @@ class TestVariableStepForLoopStructure: FunSpec({ val irPath = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir") val chunks = IRFileReader().read(irPath.readText()).allSubs().flatMap { it.chunks }.toList() val loopvarSymbol = chunks.flatMap { it.instructions } - .first { it.opcode == Opcode.STOREM && it.labelSymbol?.substringAfterLast('.') == "i" } - .labelSymbol + .first { it.opcode == Opcode.STOREM && it.memory?.symbolName?.substringAfterLast('.') == "i" } + .memory?.symbolName val tailChunks = chunks.filter { chunk -> chunk.label != null && - chunk.instructions.count { it.opcode == Opcode.STOREM && it.labelSymbol == loopvarSymbol } == 1 && + chunk.instructions.count { it.opcode == Opcode.STOREM && it.memory?.symbolName == loopvarSymbol } == 1 && chunk.instructions.count { it.opcode == Opcode.JUMP } == 1 } tailChunks.size shouldBe 1 val tailLabel = tailChunks.single().label tailLabel shouldNotBe null - chunks.flatMap { it.instructions }.count { it.opcode == Opcode.JUMP && it.labelSymbol == tailLabel } shouldBe 2 + chunks.flatMap { it.instructions }.count { it.opcode == Opcode.JUMP && it.labelTarget == tailLabel } shouldBe 2 } }) diff --git a/docs/CODEBASE-KNOWLEDGE-GRAPH.md b/docs/CODEBASE-KNOWLEDGE-GRAPH.md index 628960ea6..cbc850f7a 100644 --- a/docs/CODEBASE-KNOWLEDGE-GRAPH.md +++ b/docs/CODEBASE-KNOWLEDGE-GRAPH.md @@ -193,14 +193,30 @@ Package `prog8.intermediate.*`. | Entity | Kind | Purpose | |--------|------|---------| | `IRProgram` | class | Top-level IR program (holds subroutines, chunks, instructions) | -| `IRInstructions.kt` | file | IR instruction/operand model (`@JvmInline` value classes) | +| `IRInstructions.kt` | file | `IRInstruction` model with structured, typed operands; exposes register `uses` and `definitions` | +| `IROperands.kt` | file | Structured operand types for registers, immediates, memory references, code references, and operand access roles | +| `IRCalls.kt` | file | Structured call sites: targets, typed arguments/results, locations, and call effects | +| `OpcodeSchema.kt` | file | **Semantic source of truth** for opcode slots, operand roles/directions/types, memory/status/control-flow effects | +| `IRTextCodec.kt` | object | Canonical text parser/printer driven by `OpcodeSchema`; round-trips structured IR operands | | `IRSymbolTable` | class | IR-side symbol table | | `CallingConventionSlot` | class | Slot-based calling convention (registers/stack) | | `IMSyscall` | enum | IR-level syscall identifiers | | `IRFormat` | class | Serialization format constants | -| `IRFileReader`, `IRFileWriter` | classes | `.p8ir` file read/write | +| `IRFileReader`, `IRFileWriter` | classes | `.p8ir` file read/write; emit/read `IRFORMAT=2` | | `VariableDump`, `Utils` | helpers | Variable dumping, utilities | +`IRInstruction` deliberately uses structured operands rather than positional nullable +`reg1/reg2/address/immediate` fields. `OpcodeSchema` is the semantic source of truth: +it validates the instruction shape and defines each operand's role, direction (`USE`, +`DEF`, or `USE_DEF`), type rule, and effects. Structured memory references and `CallSite` +values carry their own nested register accesses, so `IRInstruction.uses` and +`IRInstruction.definitions` include registers used/defined through memory and calls. +The IR-consuming backends (`codeGenNew6502`, `codeGenM68k`, and the VM) consume these +typed uses/definitions together with structured memory and call operands, rather than +reconstructing semantics from positional fields. The `.p8ir` reader accepts only +`IRFORMAT=2`; this format is intentionally incompatible with the older positional format, +not a backward-compatible extension. + ### 4.7 `codeGenIntermediate` (IR code generator - incl. virtual backend) Package `prog8.codegen.intermediate.*`, `prog8.codegen.vm.*`. | Entity | Purpose | diff --git a/ideas/m68k-register-allocation.md b/ideas/m68k-register-allocation.md index be5b4fbbc..1cc98c65c 100644 --- a/ideas/m68k-register-allocation.md +++ b/ideas/m68k-register-allocation.md @@ -61,9 +61,12 @@ register-oriented. Root cause, and the constraint it imposes: **The backend's instruction selection is hardwired to memory operands.** Every IR instruction translator (`InstrArithmetic`, `InstrBitwise`, `InstrControl`, -`InstrLoadStore`, `InstrBranch`, `InstrSyscall` — roughly 320 call sites of -`regAddr()` / `floatRegFileAddr()` across ~4,100 lines) emits each vreg as an -*absolute memory operand*: +`InstrLoadStore`, `InstrBranch`, `InstrSyscall`) emits each vreg as an +*absolute memory operand*. (Structured-IR status: translators now consume typed +`dest`/`srcA`/`memory`/`target`/`callSite` operands and memory lowering is +centralized in `AsmGen.resolveMemory()` — that part of Stage 1 is done. What +remains is the vreg side, still lowered via `regAddr()`/`floatRegFileAddr()` +to the `p8_regfile` spill area:) ``` INC -> addq.b #1, p8_regfile+N @@ -200,7 +203,11 @@ The allocatable pool is therefore: 5 callee-saved data (D2–D6), 3 callee-saved address (A2–A4), 6 callee-saved FP (FP2–FP7) — plus the caller-saved D0/D1/ A0/A1/FP0/FP1 usable only for call-free live ranges. Whether this is enough is exactly the register-pressure question the Stage-2 success metrics must -measure on real programs. +measure on real programs. (Open contradiction: `m68k-reg-problems.md` §1 shows +translator scratch use of D0/D1/A0/A1/FP0/FP1 conflicts with allocating +call-free vregs there — Stage 2 must not size pools or pressure estimates on +the above until that choice, reserve scratch vs allocator-aware scratch, is +settled.) ### 2.4 What a CALL means for liveness @@ -321,7 +328,14 @@ operand model, a worse design.) ### 3.1 Intraprocedural (required) A standard per-subroutine register allocator needs correct liveness *within* -each subroutine: +each subroutine. Structured-IR input (already available): derive gen/kill from +`IRInstruction.registerAccesses` with `OperandDirection` (USE/DEF/USE_DEF), +via the `uses`/`definitions` sets keyed on `VirtualRegister` — `IntReg` and +`FloatReg` are strictly separate (`r5 != fr5`), so no shared `RegisterNum` +maps. `registerAccesses` already aggregates the registers inside `memory` +(`Indexed`/`Indirect`), `target` (`Indirect` pointer) and `callSite` +(arguments/results/bank/pointer). Use `RegisterOperand.allocationHint` +(`PREFER_DATA`/`PREFER_ADDRESS`) as the class-constraint input for §4: 1. Build a CFG of the subroutine's code chunks. 2. Compute liveness via gen/kill + iterative dataflow → `liveIn`/`liveOut` per @@ -569,26 +583,35 @@ Each is small, contained, and verifiable on its own. ### Stage 1 — location-agnostic instruction selection (prerequisite; the bulk of the work) Replace every use of `regAddr(reg)` / `floatRegFileAddr(reg)` in *operand -position* with a single indirection on `AsmGen`: +position* with a single indirection on `AsmGen` that takes the typed operand +(no `Int` vreg numbers — int/float are distinct `VirtualRegister`s): ```kotlin -fun operand(reg: Int): String // "d3" if allocated, "p8_regfile+N" if spilled -fun storeOperand(reg: Int): String // same, for destinations -fun fpOperand(fpReg: Int): String // "fp2" / "p8_fregfile+N" +fun operand(op: RegisterOperand): String // "d3" if allocated, "p8_regfile+N" if spilled +fun storeOperand(op: RegisterOperand): String // same, for destinations ``` -All ~320 translator call sites then emit e.g. `add${s} d0, ${operand(dst)}` +(`fpOperand()` is subsumed: a `FloatReg` operand routes to `fpN` / +`p8_fregfile+N` via the same lookup.) Memory operands already go through the +centralized `resolveMemory(MemoryReference)` — only the vreg side still needs +this indirection. + +All translator call sites then emit e.g. `add${s} d0, ${operand(dst)}` without knowing or caring where the value resides. Initially the mapping is empty and every query returns the regfile address — **stage 1 is behavior neutral by construction**, which is exactly what makes it safe to land and review on its own. Notes: - The sites that genuinely require memory (address-of `&`, byte sub-access via - `regAddrByte`, inline-asm chunk references to `p8_regfile`) keep calling - `regAddr()` directly. These define the "must be spilled" set for the - allocator essentially for free. + `regAddrByte`, inline-asm chunk references to `p8_regfile`) keep lowering + through the regfile path directly. These define the "must be spilled" set + for the allocator essentially for free: any vreg used in such a position + (not identified by a call count) must be memory-resident at that point. - The vreg side of `LOADHR`/`STOREHR` also goes through `operand()` so they degrade to register moves or no-ops under allocation (§2.8). +- Model `CALL` boundaries through `CallSite.effects` + (`CallEffects.memoryEffect`/`statusEffect`) plus the `callSite` register + accesses (arguments/results), not via ad-hoc per-opcode kill lists. **Indexed/indirect opcodes are a separate, non-mechanical sub-task.** Most translators fit the "swap `regAddr` for `operand()`" pattern, but a minority do @@ -642,7 +665,8 @@ hardcoded scratch register outside the D0/D1/A0/A1 set. liveness-derived one). 3. **Class-aware greedy colouring**: map vregs → physical D/A/FP registers, treating `CALL` as killing caller-saved registers. -4. **Wire the mapping into `operand()` / `storeOperand()` / `fpOperand()`.** +4. **Wire the mapping into `operand()` / `storeOperand()`** (typed + `RegisterOperand` lookup; float vregs resolve through the same entry point). Register-to-register code (`add.l d1, d0`) now falls out of the existing translators with no per-opcode special cases — this is the payoff of Stage 1. @@ -676,12 +700,12 @@ designing rather than executing. These must be decided before Stage 2 is delegable: 1. **`operand()` / `storeOperand()` width semantics.** The signature is shown - (`fun operand(reg: Int): String`) but the width-marshalling is unspecified: + (`fun operand(op: RegisterOperand): String`) but the width-marshalling is unspecified: when a vreg is register-resident but used at a different width (byte vreg in a word op), does `operand()` zero/sign-extend, and into *which* register? Today `loadRegOrZeroExtendToD0` does this against d0; under allocation the extension has to target a defined register. This is the most-encountered - decision across the ~320 call sites. + decision across the translator call sites. 2. **The class-constraint mechanism (§4.1 says *what*, not *how*).** We know index→DATA and pointer→ADDRESS, but not how the translator *communicates* the constraint to the allocator: a per-vreg constraint recorded in a diff --git a/ideas/m68k-stack-memory-model.md b/ideas/m68k-stack-memory-model.md index 38422c071..76e866690 100644 --- a/ideas/m68k-stack-memory-model.md +++ b/ideas/m68k-stack-memory-model.md @@ -18,6 +18,16 @@ The current IR represents memory variables with fixed addresses. In particular: - `IRSubroutine` describes parameters, returns, and code chunks, but has no frame-size or local-slot metadata. +Structured-IR restriction (now merged in this branch): `MemoryReference` only +knows `Direct`/`Indexed`/`Indirect`, and `AddressBase` only knows +`Symbol`/`Absolute` (`intermediate/src/prog8/intermediate/IROperands.kt`). +`Direct`/`Indexed` require `displacement >= 0`, so a frame slot such as +`-N(A5)` cannot be expressed at all. Likewise `CallSite` / +`CallLocation.ParameterMemory(name, address)` (`IRCalls.kt`) assumes a static +symbol name or absolute address, `IRSubroutine` has no `frameSize` or slot +metadata, `IRTextCodec` has no frame-slot syntax, and `VmProgramLoader`/VM +resolve every symbolic base to an absolute address before execution. + The M68K backend therefore uses static storage for normal subroutine parameters and locals. Its current internal calling convention has the caller write arguments into the callee's parameter variables before `jsr`. @@ -207,26 +217,43 @@ existing AmigaOS ABI. ## 7. IR Changes The IR should describe storage symbolically rather than naming A5 directly. -Possible additions include: +Minimal proposal for the structured IR: -- A stack-variable or frame-slot storage class -- A frame offset and data type for each frame object -- `frameSize` on `IRSubroutine` -- A list or mask describing saved callee registers -- Metadata describing incoming parameter locations -- A list of frame objects requiring zero initialization +- New `AddressBase.FrameSlot(val offset: Int)` variant (alternative name: + `IRFrameSlot` storage class if symbol-table-level marking is preferred, as + mentioned in Section 4). The offset is the frame-relative byte offset + assigned by the frame-layout pass; the data type/size continues to come + from the instruction's `IRDataType` and the variable declaration, plus an + optional slot list on `IRSubroutine` for size/alignment/zero-init metadata. +- New `frameSize: Int` (default 0) on `IRSubroutine`, set by the frame-layout + pass. A zero `frameSize` means "no frame", so all existing IR stays valid. +- Mapping: the M68K backend lowers `Direct(FrameSlot(offset), ...)` to a + signed A5 displacement (`-N(A5)`); the VM lowers the same slot to the + current activation record (per-call storage allocated on call, discarded + on return). Neither backend hard-codes A5 in the IR itself. +- `CallLocation.ParameterMemory` needs a frame-slot form alongside its + current static `(name, address)` form (extra optional slot/offset, or a + separate `CallLocation.FrameSlot` variant), otherwise callers keep + assuming statically addressable parameter variables. +- `OpcodeSchema` validation: memory-slot schemas keep accepting `Symbol` + and `Absolute` unchanged, and additionally accept a `FrameSlot` base for + `Direct` access. The `displacement >= 0` rule stays for symbol/absolute + bases; only the `FrameSlot` base allows negative frame-relative offsets. + `Indexed`/`Indirect` over a frame base are rejected in v1. +- `IRTextCodec` syntax: needs a distinct round-trippable printed form for + the new base, for example `Direct(FrameSlot(-8))` printed as `[frame-8]`. + Symbol (`[main.var]`) and absolute (`[$1234]`) printing stays unchanged. +- `VmProgramLoader`: `pass2replaceLabelsByProgIndex` / + `resolvedSymbolInstruction` currently fold every symbolic base into an + absolute address and assert nothing stays unresolved. Frame-relative + references must be excluded from that resolution and stay symbolic; the VM + resolves them against the active frame at execution time. If VM frame + support is deferred, the loader must explicitly reject frame-based IR + instead of miscompiling it as static storage. -The serialized IR should identify the target and storage class clearly. Existing -6502 IR must continue to use absolute/static storage. - -The IR should not encode `-4(a5)`. Instead, it should encode a frame-relative -slot. The M68K backend maps that slot to A5, while the VM maps it to the current -activation record. - -Whether prologue and epilogue operations are explicit IR instructions or are -generated from `IRSubroutine` metadata is an implementation choice. Backend- -generated prologues are preferable initially because saved-register selection, -instruction forms, and frame-pointer choice are backend details. +Static/absolute addressing (globals, `@shared`, slabs, all 6502 IR) is +unaffected: existing `Symbol`/`Absolute` bases, their codec syntax, and +their loader resolution keep working exactly as today. ## 8. Backend Changes @@ -330,6 +357,13 @@ justified until the rest of the stack model is stable. ## 12. Effects on Other Targets +Static/absolute addressing stays unchanged everywhere it exists today: +block-level variables, globals, `@shared` variables, memory slabs, and all +6502 IR keep using `AddressBase.Symbol`/`Absolute` with the current loader +resolution. Only ordinary M68K subroutine locals/parameters move to the new +`FrameSlot` base. The A5 frame-pointer / A6 library-base reservation from +Section 2 remains in force. + The 6502 targets remain unchanged: - Locals continue to use static allocation. diff --git a/intermediate/src/prog8/intermediate/IRCalls.kt b/intermediate/src/prog8/intermediate/IRCalls.kt new file mode 100644 index 000000000..4ec5a6f9f --- /dev/null +++ b/intermediate/src/prog8/intermediate/IRCalls.kt @@ -0,0 +1,210 @@ +package prog8.intermediate + +import prog8.code.core.Statusflag + +/* +Structured call model. +---------------------- + +Calls (CALL, CALLI, CALLFAR, CALLFARVB, SYSCALL) carry a single CallSite operand that describes +what is called, how the arguments are passed, where the results end up, and what the call does +to memory and the status flags. +*/ + + +/** what is being called */ +sealed interface CallTarget { + /** a regular call to a label, a fixed address, or indirectly through a register */ + data class Direct(val reference: CodeReference, val externalName: String? = null) : CallTarget + + /** a call into another ram/rom bank, with the bank number as a constant */ + data class Banked(val bank: Int, val reference: CodeReference, val externalName: String? = null) : CallTarget + + /** a call into another ram/rom bank, with the bank number in a register */ + data class BankedVariable(val bankRegister: RegisterOperand, val reference: CodeReference, val externalName: String? = null) : CallTarget { + init { + require(!bankRegister.isFloat) { "bank register must be an integer register" } + require(bankRegister.role == OperandRole.VALUE) { "bank register must have VALUE role" } + require(bankRegister.direction == OperandDirection.USE) { "bank register must be a USE operand" } + } + } + + /** amiga library call: library number + (negative) LVO offset into the library's jump table */ + data class AmigaLibrary(val library: Int, val lvo: Int, val name: String? = null) : CallTarget { + init { + require(lvo < 0) { "amiga library call offsets should all be <0: $lvo" } + } + } + + /** a system call of the virtual machine / emulation layer */ + data class SystemCall(val number: Int) : CallTarget +} + + +/** where an argument comes from, or where a result is delivered */ +sealed interface CallLocation { + /** no explicit location: value simply lives in the given virtual register */ + data object Default : CallLocation + + /** the value is passed via the memory location of a subroutine parameter variable */ + data class ParameterMemory(val name: String, val address: MemoryAddress? = null) : CallLocation { + init { + require(name.isNotEmpty() || address != null) { "parameter-memory location needs a name or address" } + if (name.isNotEmpty()) + requireValidIRSymbolName(name) + } + } + + /** the value is passed in a cpu hardware register (abstract calling convention slot) */ + data class HardwareRegister(val slot: CallingConventionSlot) : CallLocation + + /** the value is passed in a cpu status flag */ + data class StatusFlag(val flag: Statusflag) : CallLocation +} + + +/** one argument of a call */ +data class CallArgument(val source: RegisterOperand, val location: CallLocation = CallLocation.Default) { + init { + require(source.role == OperandRole.CALL_ARGUMENT) { "call argument must have CALL_ARGUMENT role" } + require(source.direction == OperandDirection.USE) { "call argument must be a USE operand" } + } +} + + +/** + * One result of a call. The destination register is null if the result isn't captured in a + * virtual register (for instance a status flag result that is only used by a following branch). + */ +data class CallResult(val destination: RegisterOperand?, val location: CallLocation = CallLocation.Default) { + init { + if (destination != null) { + require(destination.role == OperandRole.CALL_RESULT) { "call result must have CALL_RESULT role" } + require(destination.direction == OperandDirection.DEF) { "call result must be a DEF operand" } + } else { + require(location != CallLocation.Default) { "a call result without destination register needs an explicit location" } + } + } + + val type: IRDataType? + get() = destination?.type +} + + +/** what the call does besides producing its results */ +data class CallEffects( + val memoryEffect: MemoryEffect = MemoryEffect.UNKNOWN, + val statusEffect: StatusEffect = StatusEffect.UNKNOWN +) { + companion object { + val DEFAULT = CallEffects() + } +} + + +/** a complete description of a call: target, arguments, results, effects */ +data class CallSite( + val target: CallTarget, + val arguments: List = emptyList(), + val results: List = emptyList(), + val effects: CallEffects = CallEffects.DEFAULT +) { + val registerAccesses: List + get() = buildList { + when (val t = target) { + is CallTarget.Direct -> (t.reference as? CodeReference.Indirect)?.let { add(it.pointer) } + is CallTarget.Banked -> (t.reference as? CodeReference.Indirect)?.let { add(it.pointer) } + is CallTarget.BankedVariable -> { + add(t.bankRegister) + (t.reference as? CodeReference.Indirect)?.let { add(it.pointer) } + } + is CallTarget.AmigaLibrary -> {} + is CallTarget.SystemCall -> {} + } + arguments.forEach { add(it.source) } + results.forEach { r -> r.destination?.let { add(it) } } + } + + /** the code reference this call jumps to, if it is a static one */ + val codeReference: CodeReference? + get() = when (val t = target) { + is CallTarget.Direct -> t.reference + is CallTarget.Banked -> t.reference + is CallTarget.BankedVariable -> t.reference + is CallTarget.AmigaLibrary -> null + is CallTarget.SystemCall -> null + } + + /** the symbolic name of the called routine, if known */ + val externalName: String? + get() = when (val t = target) { + is CallTarget.Direct -> t.externalName + is CallTarget.Banked -> t.externalName + is CallTarget.BankedVariable -> t.externalName + is CallTarget.AmigaLibrary -> t.name + is CallTarget.SystemCall -> null + } + + fun mapRegisters(transform: (VirtualRegister) -> VirtualRegister): CallSite { + fun mapRef(ref: CodeReference): CodeReference = + if (ref is CodeReference.Indirect) ref.copy(pointer = ref.pointer.withRegister(transform(ref.pointer.register))) else ref + + val newTarget = when (val t = target) { + is CallTarget.Direct -> t.copy(reference = mapRef(t.reference)) + is CallTarget.Banked -> t.copy(reference = mapRef(t.reference)) + is CallTarget.BankedVariable -> t.copy( + bankRegister = t.bankRegister.withRegister(transform(t.bankRegister.register)), + reference = mapRef(t.reference) + ) + is CallTarget.AmigaLibrary -> t + is CallTarget.SystemCall -> t + } + return copy( + target = newTarget, + arguments = arguments.map { it.copy(source = it.source.withRegister(transform(it.source.register))) }, + results = results.map { r -> r.copy(destination = r.destination?.let { it.withRegister(transform(it.register)) }) } + ) + } + + fun withTarget(reference: CodeReference): CallSite { + val newTarget = when (val t = target) { + is CallTarget.Direct -> t.copy(reference = reference) + is CallTarget.Banked -> t.copy(reference = reference) + is CallTarget.BankedVariable -> t.copy(reference = reference) + is CallTarget.AmigaLibrary -> throw IllegalArgumentException("cannot set code target on an amiga library call") + is CallTarget.SystemCall -> throw IllegalArgumentException("cannot set code target on a syscall") + } + return copy(target = newTarget) + } +} + + +/** convenience builders for call operands */ +object Calls { + fun argument( + register: VirtualRegister, + type: IRDataType, + location: CallLocation = CallLocation.Default + ) = CallArgument(RegisterOperand(register, type, OperandRole.CALL_ARGUMENT, OperandDirection.USE), location) + + fun argument( + registerNumber: Int, + type: IRDataType, + location: CallLocation = CallLocation.Default + ) = argument(registerFor(registerNumber, type), type, location) + + fun result( + register: VirtualRegister, + type: IRDataType, + location: CallLocation = CallLocation.Default + ) = CallResult(RegisterOperand(register, type, OperandRole.CALL_RESULT, OperandDirection.DEF), location) + + fun result( + registerNumber: Int, + type: IRDataType, + location: CallLocation = CallLocation.Default + ) = result(registerFor(registerNumber, type), type, location) + + fun registerFor(registerNumber: Int, type: IRDataType): VirtualRegister = + if (type == IRDataType.FLOAT) VirtualRegister.float(registerNumber) else VirtualRegister.int(registerNumber) +} diff --git a/intermediate/src/prog8/intermediate/IRFileReader.kt b/intermediate/src/prog8/intermediate/IRFileReader.kt index 590589261..ea00a2598 100644 --- a/intermediate/src/prog8/intermediate/IRFileReader.kt +++ b/intermediate/src/prog8/intermediate/IRFileReader.kt @@ -79,6 +79,10 @@ class IRFileReader { require(start.name.localPart=="PROGRAM") { "missing PROGRAM" } val programName = start.attributes.asSequence().single { it.name.localPart == "NAME" }.value val compilerVersion = start.attributes.asSequence().single { it.name.localPart == "COMPILERVERSION" }.value + val irFormat = start.attributes.asSequence().singleOrNull { it.name.localPart == "IRFORMAT" }?.value + ?: throw IRParseException("missing IRFORMAT") + if(irFormat != IR_FORMAT_VERSION.toString()) + throw IRParseException("unsupported IR format: $irFormat") options = parseOptions(reader, compilerVersion) val asmsymbols = parseAsmSymbols(reader) diff --git a/intermediate/src/prog8/intermediate/IRFileWriter.kt b/intermediate/src/prog8/intermediate/IRFileWriter.kt index 1ba642fb8..63f3a8dcd 100644 --- a/intermediate/src/prog8/intermediate/IRFileWriter.kt +++ b/intermediate/src/prog8/intermediate/IRFileWriter.kt @@ -64,6 +64,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) { xml.writeStartElement("PROGRAM") xml.writeAttribute("NAME", irProgram.name) xml.writeAttribute("COMPILERVERSION", irProgram.options.compilerVersion) + xml.writeAttribute("IRFORMAT", IR_FORMAT_VERSION.toString()) xml.writeCharacters("\n") writeOptions() writeAsmSymbols() @@ -80,7 +81,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) { out.close() val used = irProgram.registersUsed() - val numberUsed = (used.readRegs.keys + used.writeRegs.keys).size + (used.readFpRegs.keys + used.writeFpRegs.keys).size + val numberUsed = (used.readRegs.keys + used.writeRegs.keys).size if(!irProgram.options.quiet) println("($numInstr instructions in $numChunks chunks, $numberUsed registers)") return outfile @@ -171,26 +172,29 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) { } private fun writeCodeChunk(chunk: IRCodeChunk) { - val indexRegType = irProgram.options.compTarget.indexRegType - val usedRegs = chunk.usedRegisters(indexRegType) + val usedRegs = chunk.usedRegisters() val regs = StringBuilder() - if(usedRegs.readRegs.any() || usedRegs.writeRegs.any()) { + val intRead = usedRegs.intRegsRead.toSortedMap() + val intWritten = usedRegs.intRegsWritten.toSortedMap() + val floatRead = usedRegs.floatRegsRead.toSortedMap() + val floatWritten = usedRegs.floatRegsWritten.toSortedMap() + if(intRead.any() || intWritten.any()) { regs.append("\nINT REGS:\n") - if (usedRegs.readRegs.any()) - regs.append(" read: ${usedRegs.readRegs.toSortedMap().map { (reg, amount) -> "r$reg=${amount}" }}\n") - if (usedRegs.writeRegs.any()) - regs.append(" write: ${usedRegs.writeRegs.toSortedMap().map { (reg, amount) -> "r$reg=${amount}" }}\n") + if (intRead.any()) + regs.append(" read: ${intRead.map { (reg, amount) -> "$reg=${amount}" }}\n") + if (intWritten.any()) + regs.append(" write: ${intWritten.map { (reg, amount) -> "$reg=${amount}" }}\n") regs.append(" types:\n") - for ((regnum, type) in usedRegs.regsTypes.toSortedMap()) { - regs.append(" r$regnum -> $type\n") + for ((regnum, type) in usedRegs.intRegsTypes.toSortedMap()) { + regs.append(" $regnum -> $type\n") } } - if(usedRegs.readFpRegs.any() || usedRegs.writeFpRegs.any()) { + if(floatRead.any() || floatWritten.any()) { regs.append("\nFP REGS:\n") - if(usedRegs.readFpRegs.any()) - regs.append(" read: ${usedRegs.readFpRegs.toSortedMap().map { (reg, amount) -> "fr$reg=${amount}" }}\n") - if(usedRegs.writeFpRegs.any()) - regs.append(" write: ${usedRegs.writeFpRegs.toSortedMap().map { (reg, amount) -> "fr$reg=${amount}" }}\n") + if(floatRead.any()) + regs.append(" read: ${floatRead.map { (reg, amount) -> "fr${reg.value}=${amount}" }}\n") + if(floatWritten.any()) + regs.append(" write: ${floatWritten.map { (reg, amount) -> "fr${reg.value}=${amount}" }}\n") } xml.writeStartElement("CHUNK") diff --git a/intermediate/src/prog8/intermediate/IRFormat.kt b/intermediate/src/prog8/intermediate/IRFormat.kt index c393adbb0..e98b722df 100644 --- a/intermediate/src/prog8/intermediate/IRFormat.kt +++ b/intermediate/src/prog8/intermediate/IRFormat.kt @@ -4,6 +4,12 @@ package prog8.intermediate * Shared format definitions for IR file reading and writing. * Keeps the text format synchronized between reader and writer. */ +/** + * The version of the .p8ir file format. Format 2 uses structured instruction operands and is + * intentionally incompatible with the older positional format. + */ +const val IR_FORMAT_VERSION = 2 + object IRFormat { // Regex patterns for parsing IR text lines val VAR_NO_INIT = Regex("(?.+?)(?\\[.+?\\])? (?.+) zp=(?.+?)\\s?(split=(?.+?))?\\s?(align=(?.+?))?\\s?(inBss=(?.+?))?\\s?(readonly=(?.+?))?") diff --git a/intermediate/src/prog8/intermediate/IRInstructions.kt b/intermediate/src/prog8/intermediate/IRInstructions.kt index e097a51da..acaa584f2 100644 --- a/intermediate/src/prog8/intermediate/IRInstructions.kt +++ b/intermediate/src/prog8/intermediate/IRInstructions.kt @@ -96,12 +96,38 @@ Value types: integers (.b=byte=8 bits, .w=word=16 bits, .l=long=32 bits), float There is no distinction between signed and unsigned for many instructions. Instead, a different instruction is used if a distinction should be made (for example div and divs). Floating point operations are just 'f' typed regular instructions, however there are a few unique fp conversion instructions. -NOTE: Labels in source text should always start with an underscore. +Standalone label definitions start with an underscore; structured code references omit it. +CANONICAL TEXT FORMAT +--------------------- +Instructions are written as: opcode[.type] operand,operand,... +The operands are written in the canonical slot order of the instruction's OpcodeSchema +(destinations first, then sources, then immediate/memory/target). Operand syntax: + + r5.w integer register 5, used as a word + fr5.f floating point register 5 + #$1234.w immediate value (with its data type) + #main.items the address of a symbol (optionally with +offset) + [main.items] memory at a symbol (optionally [main.items+4]) + [$d020] memory at a fixed address + [r2.p+8] memory pointed to by a register, with a displacement + [main.items+4+r2.w*2] memory at symbol + displacement + index register * scale + s10.w cpu hardware register, addressed by calling convention slot + main.label a code location (branch/jump/call target), optionally main.label+4 + (r2.p) an indirect code location (address in a register) + +Calls are written as: call target(argument,...) : result,... !memory=effect,status=effect +where an argument is [name=]register[@slot|@statusflag] and a result is +register[@slot|@statusflag] or just @slot / @statusflag when the result isn't captured. +The effect suffix is omitted only when both effects are UNKNOWN. + LOAD/STORE ---------- +The following descriptions use historical positional names solely to describe instruction +semantics. They are not accepted as IRFORMAT=2 text; use the canonical syntax above. + All have type b or w or l or f. load reg1, value - load immediate value into register. If you supply a symbol, loads the *address* of the symbol! (variable values are loaded from memory via the loadm instruction) @@ -657,720 +683,132 @@ enum class IRDataType { POINTER // pointer (size depends on target) } -enum class OperandDirection { - UNUSED, - READ, - WRITE, - READWRITE -} - -data class InstructionFormat(val datatype: IRDataType?, - val reg1: OperandDirection, - val reg2: OperandDirection, - val reg3: OperandDirection, - val fpReg1: OperandDirection, - val fpReg2: OperandDirection, - val address: OperandDirection, - val immediate: Boolean, - val funcCall: Boolean, - val sysCall: Boolean) { - companion object { - fun from(spec: String): Map { - val result = mutableMapOf() - for(part in spec.split('|').map{ it.trim() }) { - var reg1 = OperandDirection.UNUSED - var reg2 = OperandDirection.UNUSED - var reg3 = OperandDirection.UNUSED - var fpreg1 = OperandDirection.UNUSED - var fpreg2 = OperandDirection.UNUSED - var address = OperandDirection.UNUSED - var immediate = false - val splits = part.splitToSequence(',').iterator() - val typespec = splits.next() - var funcCall = false - var sysCall = false - while(splits.hasNext()) { - when(splits.next()) { - " reg1 = OperandDirection.READ - ">r1" -> reg1 = OperandDirection.WRITE - "<>r1" -> reg1 = OperandDirection.READWRITE - " reg2 = OperandDirection.READ - ">r2" -> reg2 = OperandDirection.WRITE - "<>r2" -> reg2 = OperandDirection.READWRITE - " reg3 = OperandDirection.READ - " fpreg1 = OperandDirection.READ - ">fr1" -> fpreg1 = OperandDirection.WRITE - "<>fr1" -> fpreg1 = OperandDirection.READWRITE - " fpreg2 = OperandDirection.READ - ">fr2" -> fpreg2 = OperandDirection.WRITE - "<>fr2" -> fpreg2 = OperandDirection.READWRITE - ">i", "<>i" -> throw IllegalArgumentException("can't write into an immediate value") - " immediate = true - " address = OperandDirection.READ - ">a" -> address = OperandDirection.WRITE - "<>a" -> address = OperandDirection.READWRITE - "call" -> funcCall = true - "syscall" -> sysCall = true - else -> throw IllegalArgumentException(spec) - } - } - - if(typespec=="N") - result[null] = InstructionFormat(null, reg1, reg2, reg3, fpreg1, fpreg2, address, immediate, funcCall, sysCall) - if('B' in typespec) - result[IRDataType.BYTE] = InstructionFormat(IRDataType.BYTE, reg1, reg2, reg3, fpreg1, fpreg2, address, immediate, funcCall, sysCall) - if('W' in typespec) { - result[IRDataType.WORD] = InstructionFormat(IRDataType.WORD, reg1, reg2, reg3, fpreg1, fpreg2, address, immediate, funcCall, sysCall) - result[IRDataType.POINTER] = InstructionFormat(IRDataType.POINTER, reg1, reg2, reg3, fpreg1, fpreg2, address, immediate, funcCall, sysCall) - } - if('L' in typespec) - result[IRDataType.LONG] = InstructionFormat(IRDataType.LONG, reg1, reg2, reg3, fpreg1, fpreg2, address, immediate, funcCall, sysCall) - if('F' in typespec) - result[IRDataType.FLOAT] = InstructionFormat(IRDataType.FLOAT, reg1, reg2, reg3, fpreg1, fpreg2, address, immediate, funcCall, sysCall) - } - return result - } - } -} - -/* - X = X is overwritten with output value (write value) - <>X = X is modified (read + written) - where X is one of: - r0... = integer register - fr0... = fp register - a = memory address - i = immediate value +/** + * A single IR instruction with structured, typed operands. + * + * The operands an instruction has are declared by its [OpcodeSchema]; the schema also declares + * the role, direction (use/def) and data type of every operand. There are no positional + * "reg1/reg2/address/immediate" fields: every operand is a typed value that knows what it is. */ -val instructionFormats = mutableMapOf( - Opcode.NOP to InstructionFormat.from("N"), - Opcode.LOAD to InstructionFormat.from("BWL,>r1,fr1,r1,fr1,r1,fr1,r1,fr1,r1,fr1,r1,fr1,fr1"), - Opcode.LOADHFACONE to InstructionFormat.from("F,>fr1"), - Opcode.STOREM to InstructionFormat.from("BWL,a | F,a"), - Opcode.STOREX to InstructionFormat.from("BWL,a | F,a"), - Opcode.STOREZM to InstructionFormat.from("BWL,>a | F,>a"), - Opcode.STOREZI to InstructionFormat.from("BWL,a | F,a"), - Opcode.STOREZX to InstructionFormat.from("BWL,a | F,a"), - Opcode.STOREHR to InstructionFormat.from("BWL,r1,<>a"), - Opcode.STOREP_INC to InstructionFormat.from("BWL,a"), - Opcode.JUMP to InstructionFormat.from("N,r1 | F,<>fr1"), - Opcode.INCM to InstructionFormat.from("BWL,<>a | F,<>a"), - Opcode.DEC to InstructionFormat.from("BWL,<>r1 | F,<>fr1"), - Opcode.DECM to InstructionFormat.from("BWL,<>a | F,<>a"), - Opcode.NEG to InstructionFormat.from("BWL,<>r1 | F,<>fr1"), - Opcode.NEGM to InstructionFormat.from("BWL,<>a | F,<>a"), - Opcode.ADDR to InstructionFormat.from("BWL,<>r1,fr1,r1,fr1,a | F,a"), - Opcode.ADDIM to InstructionFormat.from("BWL,a | F,a"), - Opcode.SUBR to InstructionFormat.from("BWL,<>r1,fr1,r1,fr1,a | F,a"), - Opcode.MULR to InstructionFormat.from("BWL,<>r1,fr1,r1,fr1,a | F,a"), - Opcode.SUBIM to InstructionFormat.from("BWL,a | F,a"), - Opcode.MULSR to InstructionFormat.from("BWL,<>r1,fr1,r1,fr1,a | F,a"), - Opcode.DIVR to InstructionFormat.from("BWL,<>r1,fr1,r1,fr1,a | F,a"), - Opcode.DIVSR to InstructionFormat.from("BWL,<>r1,fr1,r1,fr1,a | F,a"), - Opcode.SQRT to InstructionFormat.from("BWL,>r1,fr1,r1,fr1,r1,r1,r1,r1,r1,r1,r1,<>r2"), - Opcode.DIVMOD to InstructionFormat.from("BWL,<>r1,>r2,r1,<>r2"), - Opcode.SDIVMOD to InstructionFormat.from("BWL,<>r1,>r2,r1,r1,r1,r1,r1,r1,a"), - Opcode.ORR to InstructionFormat.from("BWL,<>r1,r1,a"), - Opcode.XORR to InstructionFormat.from("BWL,<>r1,r1,a"), - Opcode.INV to InstructionFormat.from("BWL,<>r1"), - Opcode.INVM to InstructionFormat.from("BWL,<>a"), - Opcode.ASRN to InstructionFormat.from("BWL,<>r1,a"), - Opcode.LSRN to InstructionFormat.from("BWL,<>r1,a"), - Opcode.LSLN to InstructionFormat.from("BWL,<>r1,a"), - Opcode.ASRI to InstructionFormat.from("BWL,<>r1,r1,r1,r1"), - Opcode.ASRM to InstructionFormat.from("BWL,<>a"), - Opcode.LSR to InstructionFormat.from("BWL,<>r1"), - Opcode.LSRM to InstructionFormat.from("BWL,<>a"), - Opcode.LSL to InstructionFormat.from("BWL,<>r1"), - Opcode.LSLM to InstructionFormat.from("BWL,<>a"), - Opcode.ROR to InstructionFormat.from("BWL,<>r1"), - Opcode.RORM to InstructionFormat.from("BWL,<>a"), - Opcode.ROXR to InstructionFormat.from("BWL,<>r1"), - Opcode.ROXRM to InstructionFormat.from("BWL,<>a"), - Opcode.ROL to InstructionFormat.from("BWL,<>r1"), - Opcode.ROLM to InstructionFormat.from("BWL,<>a"), - Opcode.ROXL to InstructionFormat.from("BWL,<>r1"), - Opcode.ROXLM to InstructionFormat.from("BWL,<>a"), - Opcode.BITTST to InstructionFormat.from("BWL,r1,r1,r1,fr1,fr1,fr1,fr1,fr1,r1,r1,r1,r1,r1,fr1,fr1,r1,fr1,fr1,fr1,fr1,fr1,fr1,fr1,fr1,fr1,r1,r1,r1,r1,r1,r1,r1 | F,>fr1"), - Opcode.PUSHST to InstructionFormat.from("N"), - Opcode.POPST to InstructionFormat.from("N"), - Opcode.CONCAT to InstructionFormat.from("BW,>r1,, - val returns: List -) { - class RegSpec(val dt: IRDataType, val registerNum: RegisterNum, val callingConventionSlot: CallingConventionSlot?, val statusflag: Statusflag?) { - init { - require(callingConventionSlot==null || statusflag==null) { "at most one of callingConventionSlot and statusflag can be non-null" } - } - } - - class ArgumentSpec(val name: String, val address: UInt?, val reg: RegSpec) -} - data class IRInstruction( val opcode: Opcode, - val type: IRDataType?=null, - val reg1: Int?=null, // 0-99999 - val reg2: Int?=null, // 0-99999 - val reg3: Int?=null, // 0-99999 - val fpReg1: RegisterNum?=null, // 0-99999 - val fpReg2: RegisterNum?=null, // 0-99999 - val immediate: Int?=null, // 0-$ff or $ffff or $ffffffff - val immediateFp: Double?=null, - val address: MemoryAddress? = null, // 0-$ffffff (or $ffffffff) - val labelSymbol: String?=null, // symbolic label name as alternative to address (so only for Branch/jump/call Instructions!) - private val symbolOffset: Int? = null, // offset to add on labelSymbol (used to index into an array variable) - var branchTarget: IRCodeChunkBase? = null, // Will be linked after loading in IRProgram.linkChunks()! This is the chunk that the branch labelSymbol points to. - val fcallArgs: FunctionCallArgs? = null, // will be set for the CALL and SYSCALL instructions. - val scale: Int = 1 // scaling factor for LOADX/STOREX/STOREZX: effective address = base + index*scale + disp + val type: IRDataType? = null, + val dest: RegisterOperand? = null, + val destB: RegisterOperand? = null, + val srcA: RegisterOperand? = null, + val srcB: RegisterOperand? = null, + val immediate: ImmediateOperand? = null, + val hardwareSlot: HardwareSlotOperand? = null, + val memory: MemoryReference? = null, + val target: CodeReference? = null, + val callSite: CallSite? = null ) { - var extSubName: String? = null // optional external subroutine name (for asm comments mainly). NOT part of serialization. - // reg1 and fpreg1 can be IN/OUT/INOUT (all others are readonly INPUT) - // This knowledge is useful in IL assembly optimizers to see how registers are used. - val reg1direction: OperandDirection - val reg2direction: OperandDirection - val reg3direction: OperandDirection - val fpReg1direction: OperandDirection - val fpReg2direction: OperandDirection - val labelSymbolOffset = if(symbolOffset==0) null else symbolOffset - init { - if(labelSymbol!=null) { - require(labelSymbol.first() != '_') { "label/symbol should not start with underscore $labelSymbol" } - require(labelSymbol.all { it.isJavaIdentifierStart() || it.isJavaIdentifierPart() || it=='.' }) { - "label/symbol contains invalid character $labelSymbol" - } - } - if(labelSymbolOffset!=null) require(labelSymbolOffset>0 && labelSymbol!=null) {"labelsymbol offset inconsistency"} - require(reg1==null || reg1 in 0..99999) {"reg1 out of bounds"} - require(reg2==null || reg2 in 0..99999) {"reg2 out of bounds"} - require(reg3==null || reg3 in 0..99999) {"reg3 out of bounds"} - // fpReg1/fpReg2 ranges are validated by RegisterNum init block - // enforce SSA discipline: output register must differ from input registers - // (fpRegs are exempt because float ops always use the same register type and there's no aliasing concern; - // LOADI is also exempt because it saves intermediary registers in pointer chain dereferencing) - if(reg1!=null && reg2!=null) require(reg1!=reg2 || opcode==Opcode.LOADI) {"reg1 must not be same as reg2"} - if(reg1!=null && reg3!=null) require(reg1!=reg3) {"reg1 must not be same as reg3"} - if(reg2!=null && reg3!=null) require(reg2!=reg3) {"reg2 must not be same as reg3"} - - val formats = instructionFormats.getValue(opcode) - require (type != null || formats.containsKey(null)) { "missing type" } - - val format = formats.getOrElse(type) { throw IllegalArgumentException("type $type invalid for $opcode") } - if(format.reg1!=OperandDirection.UNUSED) require(reg1!=null) { "missing reg1" } - if(format.reg2!=OperandDirection.UNUSED) require(reg2!=null) { "missing reg2" } - if(format.reg3!=OperandDirection.UNUSED) require(reg3!=null) { "missing reg3" } - if(format.fpReg1!=OperandDirection.UNUSED) require(fpReg1!=null) { "missing fpReg1" } - if(format.fpReg2!=OperandDirection.UNUSED) require(fpReg2!=null) { "missing fpReg2" } - if(format.reg1==OperandDirection.UNUSED) require(reg1==null) { "invalid reg1" } - if(format.reg2==OperandDirection.UNUSED) require(reg2==null) { "invalid reg2" } - if(format.reg3==OperandDirection.UNUSED) require(reg3==null) { "invalid reg3" } - if(format.fpReg1==OperandDirection.UNUSED) require(fpReg1==null) { "invalid fpReg1" } - if(format.fpReg2==OperandDirection.UNUSED) require(fpReg2==null) { "invalid fpReg2" } - if(format.immediate) { - if(type==IRDataType.FLOAT) { - if(opcode !in setOf(Opcode.LOAD, Opcode.LOADI, Opcode.STOREI, Opcode.STOREZI, Opcode.LOADHR, Opcode.STOREHR)) - requireNotNull(immediateFp) { "missing immediate fp value" } - } - else - require(immediate!=null || labelSymbol!=null) {"missing immediate value or labelsymbol"} - } - if(opcode in setOf(Opcode.LOADI, Opcode.STOREI, Opcode.STOREZI)) { - require(immediate != null) { - "missing immediate value for $opcode" } - } - if(type!=IRDataType.FLOAT) - require(fpReg1==null && fpReg2==null) {"int instruction can't use fp reg"} - if(format.address!=OperandDirection.UNUSED) - require(address!=null || labelSymbol!=null) { - "missing an address or labelsymbol"} - if(format.immediate && (immediate!=null || immediateFp!=null)) { - if(opcode in setOf(Opcode.LOADI, Opcode.STOREI, Opcode.STOREZI)) { - require(immediate in 0..65535) { "immediate value out of range for loadi/storei/storezi: $immediate" } - } else if(opcode!=Opcode.SYSCALL) { - when (type) { - IRDataType.BYTE -> require(immediate in -128..255) { "immediate value out of range for byte: $immediate" } - IRDataType.WORD -> require(immediate in -32768..65535) { "immediate value out of range for word: $immediate" } - IRDataType.LONG, IRDataType.POINTER -> require(immediate in -2147483648..2147483647) { "immediate value out of range for long: $immediate" } - IRDataType.FLOAT, null -> {} - } - } - } - if(format.immediate) { - if(opcode==Opcode.LOAD) - require(immediate != null || immediateFp != null || labelSymbol!=null) { "missing immediate value or labelsymbol" } - else - require(immediate != null || immediateFp != null) { "missing immediate value" } - } - // address range is validated by MemoryAddress init block - require(scale >= 1) { "scale must be >=1, got $scale" } - if(opcode in setOf(Opcode.LOADX, Opcode.STOREX, Opcode.STOREZX)) { - require(scale in 1..65535) { "scale out of range for $opcode: $scale" } - } else { - require(scale == 1) { "scale only allowed for LOADX/STOREX/STOREZX, got $scale for $opcode" } - } - - reg1direction = format.reg1 - reg2direction = format.reg2 - reg3direction = format.reg3 - fpReg1direction = format.fpReg1 - fpReg2direction = format.fpReg2 - - if(opcode==Opcode.SYSCALL) { - requireNotNull(immediate) { "syscall needs immediate integer for the syscall number" } - val callRegisters = fcallArgs?.arguments?.map { it.reg.registerNum } ?: emptyList() - val returnRegisters = fcallArgs?.returns?.map { it.registerNum } ?: emptyList() - - val reused = callRegisters.toSet().intersect(returnRegisters.toSet()) - if(reused.isNotEmpty()) { - for(r in reused) { - val argType = fcallArgs!!.arguments.single { it.reg.registerNum==r }.reg.dt - val returnType = fcallArgs.returns.single { it.registerNum==r }.dt - if (argType!=IRDataType.FLOAT && returnType!=IRDataType.FLOAT) { - if(argType!=returnType) - throw AssemblyError("syscall cannot reuse argument register as return register with different type $this") - } - } - } - } + OpcodeSchemas.validate(this) } - fun addUsedRegistersCounts( - readRegsCounts: MutableMap, - writeRegsCounts: MutableMap, - readFpRegsCounts: MutableMap, - writeFpRegsCounts: MutableMap, - regsTypes: MutableMap, - chunk: IRCodeChunk?, - indexRegType: IRDataType = IRDataType.BYTE - ) { - fun incReadReg(reg: RegisterNum) = readRegsCounts.merge(reg, 1, Int::plus) - fun incWriteReg(reg: RegisterNum) = writeRegsCounts.merge(reg, 1, Int::plus) - fun incReadFp(reg: RegisterNum) = readFpRegsCounts.merge(reg, 1, Int::plus) - fun incWriteFp(reg: RegisterNum) = writeFpRegsCounts.merge(reg, 1, Int::plus) + val schema: OpcodeSchema + get() = OpcodeSchemas.get(opcode, type) - // For LOADX/STOREX/STOREZX the width of the index register is not encoded in the - // instruction itself; it is inferred from the target's pointer size (indexRegType). - // IR generation now guarantees the index register has the canonical width - // (see IMemSizer.indexRegType and IRCodeGen.canonicalizeIndexReg), so the - // inference is correct by construction and no guessing suppression is needed. - fun setRegType(reg: RegisterNum, type: IRDataType) { - val existingType = regsTypes[reg] + /** all register accesses of this instruction, including the ones inside memory references and call sites */ + val registerAccesses: List + get() = buildList { + dest?.let { add(it) } + destB?.let { add(it) } + srcA?.let { add(it) } + srcB?.let { add(it) } + memory?.let { addAll(it.registers) } + (target as? CodeReference.Indirect)?.let { add(it.pointer) } + callSite?.let { addAll(it.registerAccesses) } + } + + /** the registers that are read by this instruction */ + val uses: Set + get() = registerAccesses.filter { it.direction != OperandDirection.DEF }.mapTo(mutableSetOf()) { it.register } + + /** the registers that are written by this instruction */ + val definitions: Set + get() = registerAccesses.filter { it.direction != OperandDirection.USE }.mapTo(mutableSetOf()) { it.register } + + val memoryEffect: MemoryEffect + get() = callSite?.effects?.memoryEffect ?: schema.memoryEffect + + val statusEffect: StatusEffect + get() = callSite?.effects?.statusEffect ?: schema.statusEffect + + val controlFlow: ControlFlowEffect + get() = schema.controlFlow + + /** the code location this instruction transfers control to (branch/jump/call), if it is a static one */ + val codeTarget: CodeReference? + get() = target ?: callSite?.codeReference + + /** the label this instruction branches/jumps/calls to, if any */ + val labelTarget: String? + get() = (codeTarget as? CodeReference.Label)?.name + + /** replace every virtual register in this instruction (registers keep their type, role and direction) */ + fun mapRegisters(transform: (VirtualRegister) -> VirtualRegister): IRInstruction { + fun map(operand: RegisterOperand?) = operand?.withRegister(transform(operand.register)) + return copy( + dest = map(dest), + destB = map(destB), + srcA = map(srcA), + srcB = map(srcB), + memory = memory?.mapRegisters(transform), + target = (target as? CodeReference.Indirect)?.let { it.copy(pointer = it.pointer.withRegister(transform(it.pointer.register))) } ?: target, + callSite = callSite?.mapRegisters(transform) + ) + } + + /** transform the memory reference of this instruction (if it has one) */ + fun mapMemoryReferences(transform: (MemoryReference) -> MemoryReference): IRInstruction = + if (memory == null) this else copy(memory = transform(memory)) + + /** set the code location this instruction transfers control to */ + fun withTarget(reference: CodeReference): IRInstruction = when { + target != null -> copy(target = reference) + callSite != null -> copy(callSite = callSite.withTarget(reference)) + else -> throw IllegalArgumentException("$opcode has no code target") + } + + /** + * Count the register reads and writes of this instruction, and record the data type of every + * integer register that is used. Float registers always have the float type so they're not recorded. + */ + fun addUsedRegistersCounts( + readRegsCounts: MutableMap, + writeRegsCounts: MutableMap, + regsTypes: MutableMap, + chunk: IRCodeChunk? + ) { + fun setRegType(register: VirtualRegister, type: IRDataType) { + if (type == IRDataType.FLOAT) + return + val existingType = regsTypes[register] if (existingType == null) { - regsTypes[reg] = type + regsTypes[register] = type } else if (existingType != type) { // POINTER is compatible with WORD or LONG (size depends on target) - val compatible = (existingType==IRDataType.POINTER && type in setOf(IRDataType.WORD, IRDataType.LONG)) || - (type==IRDataType.POINTER && existingType in setOf(IRDataType.WORD, IRDataType.LONG)) - if(!compatible) - throw IllegalArgumentException("register $reg given multiple types! $existingType and $type while processing $this in $chunk") + val compatible = (existingType == IRDataType.POINTER && type in setOf(IRDataType.WORD, IRDataType.LONG)) || + (type == IRDataType.POINTER && existingType in setOf(IRDataType.WORD, IRDataType.LONG)) + if (!compatible) + throw IllegalArgumentException("register $register given multiple types! $existingType and $type while processing $this in $chunk") } } - when (this.reg1direction) { - OperandDirection.UNUSED -> {} - OperandDirection.READ -> { - incReadReg(RegisterNum(this.reg1!!)) - determineReg1Type(indexRegType)?.let { setRegType(RegisterNum(this.reg1), it) } - } - OperandDirection.WRITE -> { - incWriteReg(RegisterNum(this.reg1!!)) - determineReg1Type(indexRegType)?.let { setRegType(RegisterNum(this.reg1), it) } - } - OperandDirection.READWRITE -> { - incReadReg(RegisterNum(this.reg1!!)) - incWriteReg(RegisterNum(this.reg1)) - determineReg1Type(indexRegType)?.let { setRegType(RegisterNum(this.reg1), it) } - } - } - when (this.reg2direction) { - OperandDirection.UNUSED -> {} - OperandDirection.READ -> { - incReadReg(RegisterNum(this.reg2!!)) - determineReg2Type(indexRegType)?.let { setRegType(RegisterNum(this.reg2), it) } - } - OperandDirection.READWRITE -> { - incReadReg(RegisterNum(this.reg2!!)) - incWriteReg(RegisterNum(this.reg2)) - determineReg2Type(indexRegType)?.let { setRegType(RegisterNum(this.reg2), it) } - } - else -> throw IllegalArgumentException("reg2 can only be read or readwrite") - } - when (this.reg3direction) { - OperandDirection.UNUSED -> {} - OperandDirection.READ -> { - incReadReg(RegisterNum(this.reg3!!)) - determineReg3Type()?.let { setRegType(RegisterNum(this.reg3), it) } - } - else -> throw IllegalArgumentException("reg3 can only be read") - } - when (this.fpReg1direction) { - OperandDirection.UNUSED -> {} - OperandDirection.READ -> incReadFp(this.fpReg1!!) - OperandDirection.WRITE -> incWriteFp(this.fpReg1!!) - OperandDirection.READWRITE -> { incReadFp(this.fpReg1!!); incWriteFp(this.fpReg1) } - } - when (this.fpReg2direction) { - OperandDirection.UNUSED -> {} - OperandDirection.READ -> incReadFp(this.fpReg2!!) - OperandDirection.READWRITE -> { incReadFp(this.fpReg2!!); incWriteFp(this.fpReg2) } - else -> throw IllegalArgumentException("fpReg2 can only be read or readwrite") - } - - if(fcallArgs!=null) { - fcallArgs.returns.forEach { - if (it.dt == IRDataType.FLOAT) - incWriteFp(it.registerNum) - else { - incWriteReg(it.registerNum) - setRegType(it.registerNum, it.dt) - } - } - fcallArgs.arguments.forEach { - if(it.reg.dt==IRDataType.FLOAT) - incReadFp(it.reg.registerNum) - else { - incReadReg(it.reg.registerNum) - setRegType(it.reg.registerNum, it.reg.dt) + for (access in registerAccesses) { + when (access.direction) { + OperandDirection.USE -> readRegsCounts.merge(access.register, 1, Int::plus) + OperandDirection.DEF -> writeRegsCounts.merge(access.register, 1, Int::plus) + OperandDirection.USE_DEF -> { + readRegsCounts.merge(access.register, 1, Int::plus) + writeRegsCounts.merge(access.register, 1, Int::plus) } } + setRegType(access.register, access.type) } } - private fun determineReg1Type(indexRegType: IRDataType): IRDataType? { - if(type==IRDataType.FLOAT) { - // some float instructions have an integer (byte, word, or pointer) register as well in reg1 - return when (opcode) { - Opcode.FFROMUB, - Opcode.FFROMSB, - Opcode.FTOUB, - Opcode.FTOSB, - Opcode.FCOMP, - Opcode.SGN -> IRDataType.BYTE - Opcode.LOADX, - Opcode.STOREX, - Opcode.STOREZX -> indexRegType - Opcode.FFROMSL, Opcode.FTOSL -> IRDataType.LONG - // LOADI/STOREI with float type: reg1 holds the memory address (pointer) - Opcode.LOADI, Opcode.STOREI -> IRDataType.POINTER - else -> IRDataType.WORD - } - } - if(type==IRDataType.WORD) { - // some word instructions have byte reg1 - when (opcode) { - Opcode.SGN, Opcode.SQRT -> return IRDataType.BYTE - Opcode.STOREZX -> return indexRegType - Opcode.EXT, Opcode.EXTS, Opcode.CONCAT -> return IRDataType.LONG - else -> {} - } - } - if(type==IRDataType.LONG) { - if(opcode==Opcode.SGN) - return IRDataType.BYTE - if(opcode==Opcode.SQRT) - return IRDataType.WORD - if(opcode==Opcode.STOREZX) - return indexRegType - } - if(type==IRDataType.BYTE && opcode==Opcode.STOREZX) - return indexRegType - if(opcode in setOf(Opcode.JUMPI, Opcode.CALLI, Opcode.STOREZI)) - return IRDataType.POINTER - if(opcode in setOf(Opcode.LSIGW, Opcode.MSIGW)) - return IRDataType.WORD - if(opcode==Opcode.EXT || opcode==Opcode.EXTS) - return if (type == IRDataType.BYTE) IRDataType.WORD else null - if(opcode==Opcode.EXTL || opcode==Opcode.EXTLS) - return IRDataType.LONG - if(opcode==Opcode.CONCAT) - return if (type == IRDataType.BYTE) IRDataType.WORD else null - if(opcode in setOf(Opcode.ASRNM, Opcode.LSRNM, Opcode.LSLNM, Opcode.SQRT, Opcode.LSIGB, Opcode.MSIGB, Opcode.BSIGB, Opcode.MIDB)) - return IRDataType.BYTE - return this.type - } - - private fun determineReg2Type(indexRegType: IRDataType): IRDataType? { - if(opcode==Opcode.LOADX || opcode==Opcode.STOREX) - return indexRegType - if(opcode==Opcode.LOADI || opcode==Opcode.STOREI) - return IRDataType.POINTER - if(opcode==Opcode.ASRN || opcode==Opcode.LSRN || opcode==Opcode.LSLN) - return IRDataType.BYTE - return this.type - } - - private fun determineReg3Type(): IRDataType? { - return this.type - } - - override fun toString(): String = buildString { - append(opcode.name.lowercase()) - - when(type) { - IRDataType.BYTE -> append(".b ") - IRDataType.WORD -> append(".w ") - IRDataType.LONG -> append(".l ") - IRDataType.FLOAT -> append(".f ") - IRDataType.POINTER -> append(".p ") - else -> append(" ") - } - - if(this@IRInstruction.fcallArgs!=null) { - when (opcode) { - Opcode.SYSCALL -> append(immediate!!.toHex()) - Opcode.CALLFAR -> { - if (immediate != null) { - append("#${immediate.toHex()},") - } - address?.let { - if (it.value > 0x7fffffffu) { - append(it.value.toInt().toString()) // negative decimal for Amiga LVO - } else { - append(it.toHex()) - } - } - } - else -> { - if (labelSymbol != null) { - append(labelSymbol) - if (labelSymbolOffset != null) - append("+$labelSymbolOffset") - } - address?.let { append(it.toHex()) } // romcall - } - } - append("(") - fcallArgs.arguments.forEach { - val location = if(it.address==null) { - if(it.name.isBlank()) "" else it.name+"=" - } else "${it.address}=" - - val cpuReg = when { - it.reg.callingConventionSlot != null -> "@s${it.reg.callingConventionSlot.value}" - it.reg.statusflag != null -> "@"+it.reg.statusflag.toString() - else -> "" - } - - when(it.reg.dt) { - IRDataType.BYTE -> append("${location}r${it.reg.registerNum.value}.b$cpuReg,") - IRDataType.WORD -> append("${location}r${it.reg.registerNum.value}.w$cpuReg,") - IRDataType.LONG -> append("${location}r${it.reg.registerNum.value}.l$cpuReg,") - IRDataType.FLOAT -> append("${location}fr${it.reg.registerNum.value}.f$cpuReg,") - IRDataType.POINTER -> append("${location}r${it.reg.registerNum.value}.p$cpuReg,") - } - } - if(last() == ',') { - setLength(length - 1) - } - append(")") - val returns = fcallArgs.returns - if(returns.isNotEmpty()) { - append(":") - returns.forEachIndexed { index, returnspec -> - if (index > 0) append(",") - val cpuReg = when { - returnspec.callingConventionSlot != null -> "s${returnspec.callingConventionSlot.value}" - returnspec.statusflag != null -> returnspec.statusflag.toString() - else -> "" - } - if (cpuReg.isEmpty()) { - when (returnspec.dt) { - IRDataType.BYTE -> append("r${returnspec.registerNum.value}.b") - IRDataType.WORD -> append("r${returnspec.registerNum.value}.w") - IRDataType.LONG -> append("r${returnspec.registerNum.value}.l") - IRDataType.FLOAT -> append("fr${returnspec.registerNum.value}.f") - IRDataType.POINTER -> append("r${returnspec.registerNum.value}.p") - } - } else { - when (returnspec.dt) { - IRDataType.BYTE -> append("r${returnspec.registerNum.value}.b@$cpuReg") - IRDataType.WORD -> append("r${returnspec.registerNum.value}.w@$cpuReg") - IRDataType.LONG -> append("r${returnspec.registerNum.value}.l@$cpuReg") - IRDataType.FLOAT -> append("fr${returnspec.registerNum.value}.f@$cpuReg") - IRDataType.POINTER -> append("r${returnspec.registerNum.value}.p@$cpuReg") - } - } - } - } - } else { - // Output operands in dest-then-source order for human readability (matches assembly convention) - val formats = instructionFormats.getValue(opcode) - val format = formats.getOrElse(type) { formats.getValue(null) } - - // Pass 1: WRITE destinations - if (format.reg1 == OperandDirection.WRITE) reg1?.let { append("r$it,") } - if (format.reg2 == OperandDirection.WRITE) reg2?.let { append("r$it,") } - if (format.reg3 == OperandDirection.WRITE) reg3?.let { append("r$it,") } - if (format.fpReg1 == OperandDirection.WRITE) fpReg1?.let { append("fr${it.value},") } - if (format.fpReg2 == OperandDirection.WRITE) fpReg2?.let { append("fr${it.value},") } - - // Pass 2: READWRITE (in-place) destinations - if (format.reg1 == OperandDirection.READWRITE) reg1?.let { append("r$it,") } - if (format.reg2 == OperandDirection.READWRITE) reg2?.let { append("r$it,") } - if (format.reg3 == OperandDirection.READWRITE) reg3?.let { append("r$it,") } - if (format.fpReg1 == OperandDirection.READWRITE) fpReg1?.let { append("fr${it.value},") } - if (format.fpReg2 == OperandDirection.READWRITE) fpReg2?.let { append("fr${it.value},") } - - // Pass 3: READ sources — float (values) before int (addresses) for readability - if (format.fpReg1 == OperandDirection.READ) fpReg1?.let { append("fr${it.value},") } - if (format.fpReg2 == OperandDirection.READ) fpReg2?.let { append("fr${it.value},") } - if (format.reg1 == OperandDirection.READ) reg1?.let { append("r$it,") } - if (format.reg2 == OperandDirection.READ) reg2?.let { append("r$it,") } - if (format.reg3 == OperandDirection.READ) reg3?.let { append("r$it,") } - - immediate?.let { - when (opcode) { - Opcode.LOADHR, Opcode.STOREHR -> append("s$it,") - Opcode.BITTST, Opcode.BITSET, Opcode.BITCLR, Opcode.BITTOG -> append("$it,") - else -> append("#${it.toHex()},") - } - } - immediateFp?.let { - append("#${it},") - } - address?.let { - append(it.toHex()) - append(",") - } - labelSymbol?.let { - append(it) - if(labelSymbolOffset!=null) - append("+$labelSymbolOffset") - } - if(opcode in setOf(Opcode.LOADX, Opcode.STOREX, Opcode.STOREZX) && scale != 1) { - if(isNotEmpty() && last() != ' ') append(',') - append("S=$scale") - } - } - if(isNotEmpty() && last() == ',') - setLength(length - 1) - }.trimEnd() + override fun toString(): String = IRTextCodec.print(this) } - diff --git a/intermediate/src/prog8/intermediate/IRInstructionsFactory.kt b/intermediate/src/prog8/intermediate/IRInstructionsFactory.kt new file mode 100644 index 000000000..d2817dc59 --- /dev/null +++ b/intermediate/src/prog8/intermediate/IRInstructionsFactory.kt @@ -0,0 +1,454 @@ +package prog8.intermediate + +/* +Typed construction helpers for IR instructions. + +All IR producers (the IR code generator, the optimizers, the text codec and the tests) build +instructions through these factories. The factories derive the role, direction, register file and +data type of every operand from the OpcodeSchema, so a producer only has to supply the +register numbers and values. +*/ + + +/** builders for memory references */ +object IRMemory { + fun direct(symbol: String, displacement: Int = 0): MemoryReference = + MemoryReference.Direct(AddressBase.Symbol(symbol), displacement) + + fun direct(address: MemoryAddress, displacement: Int = 0): MemoryReference = + MemoryReference.Direct(AddressBase.Absolute(address), displacement) + + fun direct(address: UInt, displacement: Int = 0): MemoryReference = + MemoryReference.Direct(AddressBase.Absolute(MemoryAddress(address)), displacement) + + fun indexed( + base: AddressBase, + indexRegister: Int, + indexType: IRDataType, + scale: Int = 1, + displacement: Int = 0 + ): MemoryReference = + MemoryReference.Indexed(base, indexOperand(indexRegister, indexType), scale, displacement) + + fun indexed( + symbol: String, + indexRegister: Int, + indexType: IRDataType, + scale: Int = 1, + displacement: Int = 0 + ): MemoryReference = indexed(AddressBase.Symbol(symbol), indexRegister, indexType, scale, displacement) + + fun indexed( + address: UInt, + indexRegister: Int, + indexType: IRDataType, + scale: Int = 1, + displacement: Int = 0 + ): MemoryReference = indexed(AddressBase.Absolute(MemoryAddress(address)), indexRegister, indexType, scale, displacement) + + fun indirect(pointerRegister: Int, displacement: Int = 0, pointerType: IRDataType = IRDataType.POINTER): MemoryReference = + MemoryReference.Indirect(pointerOperand(pointerRegister, pointerType), displacement) + + fun indexOperand(register: Int, type: IRDataType): RegisterOperand = + RegisterOperand(VirtualRegister.int(register), type, OperandRole.INDEX, OperandDirection.USE, AllocationHint.PREFER_DATA) + + fun pointerOperand(register: Int, type: IRDataType = IRDataType.POINTER): RegisterOperand = + RegisterOperand(VirtualRegister.int(register), type, OperandRole.POINTER_BASE, OperandDirection.USE, AllocationHint.PREFER_ADDRESS) +} + + +object IRInstructions { + + // ---- generic slot construction ---- + + /** build the register operand for a slot of the given opcode, according to its schema */ + fun operandFor( + opcode: Opcode, + type: IRDataType?, + slot: InstructionSlot, + registerNumber: Int, + registerType: IRDataType? = null + ): RegisterOperand { + val schema = OpcodeSchemas.get(opcode, type) + val slotSchema = schema.registerSlot(slot) + ?: throw IllegalArgumentException("$opcode has no register operand in slot $slot") + val operandType = registerType ?: schema.typeFor(slotSchema.typeRule) + ?: throw IllegalArgumentException("$opcode: the type of slot $slot must be given explicitly") + val register = if (slotSchema.registerFile == RegisterFile.FLOAT) + VirtualRegister.float(registerNumber) else VirtualRegister.int(registerNumber) + return RegisterOperand(register, operandType, slotSchema.role, slotSchema.direction) + } + + /** the immediate operand for the given opcode, with the data type the schema prescribes */ + fun immediateFor(opcode: Opcode, type: IRDataType?, value: Int): ImmediateOperand { + val schema = OpcodeSchemas.get(opcode, type) + val immSchema = schema.immediateSlot ?: throw IllegalArgumentException("$opcode has no immediate operand") + val immType = schema.typeFor(immSchema.typeRule) ?: IRDataType.WORD + return ImmediateOperand.Integer(value, immType) + } + + // ---- typed factories ---- + + /** an instruction without operands (NOP, RETURN, CLC, SEC, PUSHST, ...) */ + fun simple(opcode: Opcode, type: IRDataType? = null) = IRInstruction(opcode, type) + + fun load(destination: RegisterOperand, immediate: ImmediateOperand) = + IRInstruction(Opcode.LOAD, destination.type, dest = destination, immediate = immediate) + + fun load(type: IRDataType, destination: Int, value: Int) = + IRInstruction( + Opcode.LOAD, type, + dest = operandFor(Opcode.LOAD, type, InstructionSlot.DEST, destination), + immediate = ImmediateOperand.Integer(value, type) + ) + + fun loadFloat(destination: Int, value: Double) = + IRInstruction( + Opcode.LOAD, IRDataType.FLOAT, + dest = operandFor(Opcode.LOAD, IRDataType.FLOAT, InstructionSlot.DEST, destination), + immediate = ImmediateOperand.FloatValue(value) + ) + + /** load the ADDRESS of a symbol into a register */ + fun loadAddress(type: IRDataType, destination: Int, symbol: String, offset: Int = 0) = + IRInstruction( + Opcode.LOAD, type, + dest = operandFor(Opcode.LOAD, type, InstructionSlot.DEST, destination), + immediate = ImmediateOperand.SymbolAddress(symbol, offset, if (type == IRDataType.FLOAT) IRDataType.POINTER else type) + ) + + fun loadMemory(destination: RegisterOperand, memory: MemoryReference): IRInstruction { + val opcode = when (memory) { + is MemoryReference.Direct -> Opcode.LOADM + is MemoryReference.Indexed -> Opcode.LOADX + is MemoryReference.Indirect -> Opcode.LOADI + } + return IRInstruction(opcode, destination.type, dest = destination, memory = memory) + } + + fun loadMemory(opcode: Opcode, type: IRDataType, destination: Int, memory: MemoryReference) = + IRInstruction( + opcode, type, + dest = operandFor(opcode, type, InstructionSlot.DEST, destination), + memory = memory + ) + + fun storeMemory(opcode: Opcode, type: IRDataType, source: Int, memory: MemoryReference) = + IRInstruction( + opcode, type, + srcA = operandFor(opcode, type, InstructionSlot.SRC_A, source), + memory = memory + ) + + fun storeMemory(source: RegisterOperand, memory: MemoryReference): IRInstruction { + val opcode = when (memory) { + is MemoryReference.Direct -> Opcode.STOREM + is MemoryReference.Indexed -> Opcode.STOREX + is MemoryReference.Indirect -> Opcode.STOREI + } + return IRInstruction(opcode, source.type, srcA = source, memory = memory) + } + + /** STOREZM / STOREZI / STOREZX: store a zero */ + fun storeZero(opcode: Opcode, type: IRDataType, memory: MemoryReference) = + IRInstruction(opcode, type, memory = memory) + + /** STOREIM: store an immediate value into memory */ + fun storeImmediate(type: IRDataType, value: Int, memory: MemoryReference) = + IRInstruction(Opcode.STOREIM, type, immediate = ImmediateOperand.Integer(value, type), memory = memory) + + fun storeImmediateFloat(value: Double, memory: MemoryReference) = + IRInstruction(Opcode.STOREIM, IRDataType.FLOAT, immediate = ImmediateOperand.FloatValue(value), memory = memory) + + fun move(destination: RegisterOperand, source: RegisterOperand) = + IRInstruction(Opcode.LOADR, destination.type, dest = destination, srcA = source) + + fun move(type: IRDataType, destination: Int, source: Int) = + IRInstruction( + Opcode.LOADR, type, + dest = operandFor(Opcode.LOADR, type, InstructionSlot.DEST, destination), + srcA = operandFor(Opcode.LOADR, type, InstructionSlot.SRC_A, source) + ) + + /** an operation with a single register operand: INC, DEC, NEG, INV, ASR, LSL, ROL, ... */ + fun unary(opcode: Opcode, type: IRDataType, destination: Int) = + IRInstruction(opcode, type, dest = operandFor(opcode, type, InstructionSlot.DEST, destination)) + + /** an operation with a destination register and a source register: ADDR, SUBR, EXT, SGN, FTOUB, ... */ + fun binary(opcode: Opcode, destination: RegisterOperand, source: RegisterOperand): IRInstruction { + val type = OpcodeSchemas.typesFor(opcode) + .filterNotNull() + .filter { candidate -> + val schema = OpcodeSchemas.get(opcode, candidate) + schema.matches(InstructionSlot.DEST, destination) && + schema.matches(InstructionSlot.SRC_A, source) + } + .singleOrNull() + ?: throw IllegalArgumentException("$opcode: cannot infer instruction type from $destination and $source") + return IRInstruction(opcode, type, dest = destination, srcA = source) + } + + fun binary(opcode: Opcode, type: IRDataType, destination: Int, source: Int, sourceType: IRDataType? = null) = + IRInstruction( + opcode, type, + dest = operandFor(opcode, type, InstructionSlot.DEST, destination), + srcA = operandFor(opcode, type, InstructionSlot.SRC_A, source, sourceType) + ) + + /** an operation with a destination register and an immediate value: ADD, SUB, AND, ASRI, BITSET, ... */ + fun binaryImmediate(opcode: Opcode, type: IRDataType, destination: Int, value: Int) = + IRInstruction( + opcode, type, + dest = operandFor(opcode, type, InstructionSlot.DEST, destination), + immediate = immediateFor(opcode, type, value) + ) + + fun binaryImmediate(opcode: Opcode, destination: RegisterOperand, immediate: ImmediateOperand) = + IRInstruction(opcode, destination.type, dest = destination, immediate = immediate) + + fun binaryImmediateFloat(opcode: Opcode, destination: Int, value: Double) = + IRInstruction( + opcode, IRDataType.FLOAT, + dest = operandFor(opcode, IRDataType.FLOAT, InstructionSlot.DEST, destination), + immediate = ImmediateOperand.FloatValue(value) + ) + + /** an operation on memory with an optional register or immediate operand: INCM, ADDM, ADDIM, LSLM, ... */ + fun memoryOp(opcode: Opcode, type: IRDataType, memory: MemoryReference, source: Int? = null) = + IRInstruction( + opcode, type, + srcA = source?.let { operandFor(opcode, type, InstructionSlot.SRC_A, it) }, + memory = memory + ) + + fun memoryOpImmediate(opcode: Opcode, type: IRDataType, memory: MemoryReference, value: Int) = + IRInstruction(opcode, type, immediate = immediateFor(opcode, type, value), memory = memory) + + fun memoryOpImmediateFloat(opcode: Opcode, memory: MemoryReference, value: Double) = + IRInstruction(opcode, IRDataType.FLOAT, immediate = ImmediateOperand.FloatValue(value), memory = memory) + + /** CMP: compare two registers */ + fun compare(type: IRDataType, left: Int, right: Int) = + IRInstruction( + Opcode.CMP, type, + srcA = operandFor(Opcode.CMP, type, InstructionSlot.SRC_A, left), + srcB = operandFor(Opcode.CMP, type, InstructionSlot.SRC_B, right) + ) + + /** CMPI: compare a register with an immediate value */ + fun compareImmediate(type: IRDataType, left: Int, value: Int) = + IRInstruction( + Opcode.CMPI, type, + srcA = operandFor(Opcode.CMPI, type, InstructionSlot.SRC_A, left), + immediate = ImmediateOperand.Integer(value, type) + ) + + /** DIVMODR / SDIVMODR: division with quotient and remainder in two registers */ + fun divmodRegister(opcode: Opcode, type: IRDataType, quotient: Int, remainder: Int) = + IRInstruction( + opcode, type, + dest = operandFor(opcode, type, InstructionSlot.DEST, quotient), + destB = operandFor(opcode, type, InstructionSlot.DEST_B, remainder) + ) + + /** DIVMOD / SDIVMOD: division by an immediate value, with quotient and remainder in two registers */ + fun divmodImmediate(opcode: Opcode, type: IRDataType, quotient: Int, remainder: Int, value: Int) = + IRInstruction( + opcode, type, + dest = operandFor(opcode, type, InstructionSlot.DEST, quotient), + destB = operandFor(opcode, type, InstructionSlot.DEST_B, remainder), + immediate = ImmediateOperand.Integer(value, type) + ) + + /** CONCAT: combine two registers into a wider one */ + fun concat(type: IRDataType, destination: Int, msb: Int, lsb: Int) = + IRInstruction( + Opcode.CONCAT, type, + dest = operandFor(Opcode.CONCAT, type, InstructionSlot.DEST, destination), + srcA = operandFor(Opcode.CONCAT, type, InstructionSlot.SRC_A, msb), + srcB = operandFor(Opcode.CONCAT, type, InstructionSlot.SRC_B, lsb) + ) + + /** FCOMP: compare two float registers, result in an integer register */ + fun floatCompare(destination: Int, left: Int, right: Int) = + IRInstruction( + Opcode.FCOMP, IRDataType.FLOAT, + dest = operandFor(Opcode.FCOMP, IRDataType.FLOAT, InstructionSlot.DEST, destination), + srcA = operandFor(Opcode.FCOMP, IRDataType.FLOAT, InstructionSlot.SRC_A, left), + srcB = operandFor(Opcode.FCOMP, IRDataType.FLOAT, InstructionSlot.SRC_B, right) + ) + + fun jump(target: CodeReference) = + if (target is CodeReference.Indirect) IRInstruction(Opcode.JUMPI, target = target) + else IRInstruction(Opcode.JUMP, target = target) + + fun jumpIndirect(pointerRegister: Int, pointerType: IRDataType = IRDataType.POINTER) = + IRInstruction(Opcode.JUMPI, target = codeIndirect(pointerRegister, pointerType)) + + fun branch( + opcode: Opcode, + target: CodeReference, + srcA: RegisterOperand? = null, + srcB: RegisterOperand? = null, + immediate: ImmediateOperand? = null + ): IRInstruction { + val type = srcA?.type + return IRInstruction(opcode, type, srcA = srcA, srcB = srcB, immediate = immediate, target = target) + } + + /** a status-bit branch (BSTEQ, BSTCC, ...) */ + fun branch(opcode: Opcode, target: CodeReference) = IRInstruction(opcode, target = target) + + /** a comparison branch against a register (BGTR, BGESR, ...) */ + fun branchRegister(opcode: Opcode, type: IRDataType, left: Int, right: Int, target: CodeReference) = + IRInstruction( + opcode, type, + srcA = operandFor(opcode, type, InstructionSlot.SRC_A, left), + srcB = operandFor(opcode, type, InstructionSlot.SRC_B, right), + target = target + ) + + /** a comparison branch against an immediate value (BGT, BLE, ...) */ + fun branchImmediate(opcode: Opcode, type: IRDataType, left: Int, value: Int, target: CodeReference) = + IRInstruction( + opcode, type, + srcA = operandFor(opcode, type, InstructionSlot.SRC_A, left), + immediate = ImmediateOperand.Integer(value, type), + target = target + ) + + fun call(opcode: Opcode, site: CallSite) = IRInstruction(opcode, callSite = site) + + fun call(site: CallSite) = IRInstruction(Opcode.CALL, callSite = site) + + fun syscall(number: Int, arguments: List = emptyList(), results: List = emptyList()) = + IRInstruction(Opcode.SYSCALL, callSite = CallSite(CallTarget.SystemCall(number), arguments, results)) + + fun returnVoid() = IRInstruction(Opcode.RETURN) + + fun returnRegister(type: IRDataType, source: Int) = + IRInstruction(Opcode.RETURNR, type, srcA = operandFor(Opcode.RETURNR, type, InstructionSlot.SRC_A, source)) + + fun returnImmediate(type: IRDataType, value: Int) = + IRInstruction(Opcode.RETURNI, type, immediate = ImmediateOperand.Integer(value, type)) + + fun returnImmediateFloat(value: Double) = + IRInstruction(Opcode.RETURNI, IRDataType.FLOAT, immediate = ImmediateOperand.FloatValue(value)) + + fun push(type: IRDataType, source: Int) = + IRInstruction(Opcode.PUSH, type, srcA = operandFor(Opcode.PUSH, type, InstructionSlot.SRC_A, source)) + + fun pop(type: IRDataType, destination: Int) = + IRInstruction(Opcode.POP, type, dest = operandFor(Opcode.POP, type, InstructionSlot.DEST, destination)) + + /** LOADHR: load a cpu hardware register (calling convention slot) into a virtual register */ + fun hardwareLoad(type: IRDataType, destination: Int, slot: CallingConventionSlot) = + IRInstruction( + Opcode.LOADHR, type, + dest = operandFor(Opcode.LOADHR, type, InstructionSlot.DEST, destination), + hardwareSlot = HardwareSlotOperand(slot, type) + ) + + /** STOREHR: store a virtual register into a cpu hardware register (calling convention slot) */ + fun hardwareStore(type: IRDataType, source: Int, slot: CallingConventionSlot) = + IRInstruction( + Opcode.STOREHR, type, + srcA = operandFor(Opcode.STOREHR, type, InstructionSlot.SRC_A, source), + hardwareSlot = HardwareSlotOperand(slot, type) + ) + + fun align(value: Int) = + IRInstruction(Opcode.ALIGN, immediate = ImmediateOperand.Integer(value, IRDataType.LONG)) +} + +private fun OpcodeSchema.matches(slot: InstructionSlot, operand: RegisterOperand): Boolean { + val slotSchema = registerSlot(slot) ?: return false + if ((slotSchema.registerFile == RegisterFile.FLOAT) != operand.isFloat) + return false + return when (val rule = slotSchema.typeRule) { + TypeRule.InstructionType -> operand.type == type + TypeRule.IndexType, TypeRule.PointerType -> operand.type != IRDataType.FLOAT + is TypeRule.Fixed -> operand.type == rule.type + } +} + + +// ---- convenient code reference builders ---- + +fun codeLabel(name: String, offset: Int = 0): CodeReference = CodeReference.Label(name, offset) + +fun codeAddress(address: UInt): CodeReference = CodeReference.Absolute(MemoryAddress(address)) + +fun codeAddress(address: MemoryAddress): CodeReference = CodeReference.Absolute(address) + +fun codeIndirect(pointerRegister: Int, pointerType: IRDataType = IRDataType.POINTER): CodeReference = + CodeReference.Indirect( + RegisterOperand( + VirtualRegister.int(pointerRegister), pointerType, + OperandRole.INDIRECT_TARGET, OperandDirection.USE, AllocationHint.PREFER_ADDRESS + ) + ) + + +// ---- typed operand extraction helpers, for the IR consumers ---- + +fun IRInstruction.requireDest(): RegisterOperand = + dest ?: throw IllegalArgumentException("$opcode: missing destination operand") + +fun IRInstruction.requireDestB(): RegisterOperand = + destB ?: throw IllegalArgumentException("$opcode: missing secondary destination operand") + +fun IRInstruction.requireSrcA(): RegisterOperand = + srcA ?: throw IllegalArgumentException("$opcode: missing first source operand") + +fun IRInstruction.requireSrcB(): RegisterOperand = + srcB ?: throw IllegalArgumentException("$opcode: missing second source operand") + +fun IRInstruction.requireIntDest(): RegisterOperand = requireDest().also { + require(!it.isFloat) { "$opcode: destination must be an integer register" } +} + +fun IRInstruction.requireFloatDest(): RegisterOperand = requireDest().also { + require(it.isFloat) { "$opcode: destination must be a float register" } +} + +fun IRInstruction.requireIntSourceA(): RegisterOperand = requireSrcA().also { + require(!it.isFloat) { "$opcode: source must be an integer register" } +} + +fun IRInstruction.requireFloatSourceA(): RegisterOperand = requireSrcA().also { + require(it.isFloat) { "$opcode: source must be a float register" } +} + +fun IRInstruction.requireImmediate(): ImmediateOperand = + immediate ?: throw IllegalArgumentException("$opcode: missing immediate operand") + +fun IRInstruction.requireImmediateInt(): Int = + (immediate as? ImmediateOperand.Integer)?.value + ?: throw IllegalArgumentException("$opcode: missing integer immediate operand") + +fun IRInstruction.requireImmediateFloat(): Double = + (immediate as? ImmediateOperand.FloatValue)?.value + ?: throw IllegalArgumentException("$opcode: missing float immediate operand") + +fun IRInstruction.requireMemory(): MemoryReference = + memory ?: throw IllegalArgumentException("$opcode: missing memory operand") + +fun IRInstruction.requireTarget(): CodeReference = + target ?: throw IllegalArgumentException("$opcode: missing code target operand") + +fun IRInstruction.requireCallSite(): CallSite = + callSite ?: throw IllegalArgumentException("$opcode: missing call site operand") + +fun IRInstruction.requireHardwareSlot(): HardwareSlotOperand = + hardwareSlot ?: throw IllegalArgumentException("$opcode: missing hardware slot operand") + +/** the number of an integer register operand */ +val RegisterOperand.intNumber: Int + get() = (register as? VirtualRegister.IntReg)?.number?.value + ?: throw IllegalArgumentException("not an integer register: $register") + +/** the number of a float register operand */ +val RegisterOperand.floatNumber: RegisterNum + get() = (register as? VirtualRegister.FloatReg)?.number + ?: throw IllegalArgumentException("not a float register: $register") diff --git a/intermediate/src/prog8/intermediate/IROperands.kt b/intermediate/src/prog8/intermediate/IROperands.kt new file mode 100644 index 000000000..aaaa33e1c --- /dev/null +++ b/intermediate/src/prog8/intermediate/IROperands.kt @@ -0,0 +1,239 @@ +package prog8.intermediate + +/* +Structured IR operands. +----------------------- + +The IR instruction set uses typed, structured operands instead of positional nullable fields. +Every operand knows what it is (register, immediate, memory reference, code reference, +hardware slot), what data type it has, what semantic role it plays in the instruction, +and whether it is read, written, or both. + +The single source of truth for which operands an opcode has, is OpcodeSchema. +*/ + + +/** + * A virtual register. Integer registers and floating point registers are separate register files: + * r5 and fr5 are two DIFFERENT registers, and comparing them yields false. + */ +sealed interface VirtualRegister { + val number: RegisterNum + + @JvmInline + value class IntReg(override val number: RegisterNum) : VirtualRegister { + override fun toString(): String = "r${number.value}" + } + + @JvmInline + value class FloatReg(override val number: RegisterNum) : VirtualRegister { + override fun toString(): String = "fr${number.value}" + } + + companion object { + fun int(number: Int) = IntReg(RegisterNum(number)) + fun float(number: Int) = FloatReg(RegisterNum(number)) + } +} + +val VirtualRegister.num: Int + get() = number.value + +val VirtualRegister.isFloat: Boolean + get() = this is VirtualRegister.FloatReg + + +/** how an operand is accessed by the instruction */ +enum class OperandDirection { USE, DEF, USE_DEF } + +/** the semantic role an operand plays in the instruction */ +enum class OperandRole { + RESULT, SECONDARY_RESULT, VALUE, LEFT, RIGHT, INDEX, POINTER_BASE, + INDIRECT_TARGET, CALL_ARGUMENT, CALL_RESULT +} + +/** hint for machine register allocators (data register vs address register on m68k, for instance) */ +enum class AllocationHint { NONE, PREFER_DATA, PREFER_ADDRESS } + + +/** access to a virtual register, with its data type, role and direction */ +data class RegisterOperand( + val register: VirtualRegister, + val type: IRDataType, + val role: OperandRole, + val direction: OperandDirection, + val allocationHint: AllocationHint = AllocationHint.NONE +) { + init { + require((register is VirtualRegister.FloatReg) == (type == IRDataType.FLOAT)) { + "float register must have float type and vice versa: $register $type" + } + } + + val number: RegisterNum get() = register.number + val registerNumber: Int get() = register.number.value + val isFloat: Boolean get() = register is VirtualRegister.FloatReg + + fun withRegister(newRegister: VirtualRegister): RegisterOperand = + if (newRegister == register) this else copy(register = newRegister) +} + + +/** an immediate (constant) operand: an integer, a float, or the address of a symbol */ +sealed interface ImmediateOperand { + val type: IRDataType + + data class Integer(val value: Int, override val type: IRDataType) : ImmediateOperand { + init { + require(type != IRDataType.FLOAT) { "integer immediate can't have float type" } + } + } + + data class FloatValue(val value: Double) : ImmediateOperand { + override val type: IRDataType get() = IRDataType.FLOAT + } + + /** the address of a symbol (optionally with a positive offset added to it) */ + data class SymbolAddress(val symbol: String, val offset: Int = 0, override val type: IRDataType = IRDataType.POINTER) : ImmediateOperand { + init { + requireValidIRSymbolName(symbol) + require(offset >= 0) { "symbol address offset must be >=0: $offset" } + require(type != IRDataType.FLOAT) { "symbol address can't have float type" } + } + } +} + +/** the integer value of an immediate, or null if it isn't a plain integer */ +val ImmediateOperand.integerValue: Int? + get() = (this as? ImmediateOperand.Integer)?.value + + +/** a cpu hardware register, addressed via an abstract calling convention slot */ +data class HardwareSlotOperand(val slot: CallingConventionSlot, val type: IRDataType) + + +/** base address of a memory reference: a symbol, or a fixed numeric address */ +sealed interface AddressBase { + data class Symbol(val name: String) : AddressBase { + init { + requireValidIRSymbolName(name) + } + } + + data class Absolute(val address: MemoryAddress) : AddressBase +} + + +/** a reference to a memory location */ +sealed interface MemoryReference { + /** memory at (base + displacement) */ + data class Direct(val base: AddressBase, val displacement: Int = 0) : MemoryReference { + init { + require(displacement >= 0) { "memory displacement must be >=0: $displacement" } + } + } + + /** memory at (base + displacement + index*scale). Index is an element index, scale is the element size. */ + data class Indexed( + val base: AddressBase, + val index: RegisterOperand, + val scale: Int = 1, + val displacement: Int = 0 + ) : MemoryReference { + init { + require(scale in 1..65535) { "index scale out of range: $scale" } + require(displacement >= 0) { "memory displacement must be >=0: $displacement" } + require(!index.isFloat) { "index register must be an integer register" } + require(index.role == OperandRole.INDEX) { "index register must have INDEX role" } + require(index.direction == OperandDirection.USE) { "index register must be a USE operand" } + } + } + + /** memory pointed to by a register, plus a displacement (used for pointer/struct field access) */ + data class Indirect(val pointer: RegisterOperand, val displacement: Int = 0) : MemoryReference { + init { + require(displacement in 0..65535) { "indirect displacement out of range: $displacement" } + require(!pointer.isFloat) { "pointer register must be an integer register" } + require(pointer.role == OperandRole.POINTER_BASE) { "pointer register must have POINTER_BASE role" } + require(pointer.direction == OperandDirection.USE) { "pointer register must be a USE operand" } + } + } +} + +/** the register operands that occur inside a memory reference (index or pointer register) */ +val MemoryReference.registers: List + get() = when (this) { + is MemoryReference.Direct -> emptyList() + is MemoryReference.Indexed -> listOf(index) + is MemoryReference.Indirect -> listOf(pointer) + } + +fun MemoryReference.mapRegisters(transform: (VirtualRegister) -> VirtualRegister): MemoryReference = + when (this) { + is MemoryReference.Direct -> this + is MemoryReference.Indexed -> copy(index = index.withRegister(transform(index.register))) + is MemoryReference.Indirect -> copy(pointer = pointer.withRegister(transform(pointer.register))) + } + +/** the symbol name this memory reference is based on, if any */ +val MemoryReference.symbolName: String? + get() = when (this) { + is MemoryReference.Direct -> (base as? AddressBase.Symbol)?.name + is MemoryReference.Indexed -> (base as? AddressBase.Symbol)?.name + is MemoryReference.Indirect -> null + } + +/** the fixed numeric address this memory reference is based on, if any */ +val MemoryReference.absoluteAddress: MemoryAddress? + get() = when (this) { + is MemoryReference.Direct -> (base as? AddressBase.Absolute)?.address + is MemoryReference.Indexed -> (base as? AddressBase.Absolute)?.address + is MemoryReference.Indirect -> null + } + + +/** a reference to a location in the code (branch/jump/call target) */ +sealed interface CodeReference { + data class Label(val name: String, val offset: Int = 0) : CodeReference { + init { + requireValidIRSymbolName(name) + require(offset >= 0) { "label offset must be >=0: $offset" } + } + } + + data class Absolute(val address: MemoryAddress) : CodeReference + + /** jump/call to the address contained in a register */ + data class Indirect(val pointer: RegisterOperand) : CodeReference { + init { + require(!pointer.isFloat) { "indirect code target register must be an integer register" } + require(pointer.role == OperandRole.INDIRECT_TARGET) { "indirect code target register must have INDIRECT_TARGET role" } + require(pointer.direction == OperandDirection.USE) { "indirect code target register must be a USE operand" } + } + } +} + +val CodeReference.labelName: String? + get() = (this as? CodeReference.Label)?.name + + +/** what an instruction does to memory */ +enum class MemoryEffect { NONE, READ, WRITE, READ_WRITE, UNKNOWN } + +/** what an instruction does to the cpu status flags */ +enum class StatusEffect { NONE, SETS, READS, READS_AND_SETS, UNKNOWN } + +/** what an instruction does to the control flow */ +enum class ControlFlowEffect { FALLTHROUGH, BRANCH, JUMP, CALL, RETURN } + + +internal fun requireValidIRSymbolName(name: String) { + require(name.isNotEmpty()) { "empty symbol name" } + require(name.first().isLetter()) { + "label/symbol should start with a letter: $name" + } + require(name.last() != '.' && ".." !in name) { "label/symbol has an empty name segment: $name" } + require(name.all { (it.isJavaIdentifierStart() || it.isJavaIdentifierPart() || it == '.') && it != '$' }) { + "label/symbol contains invalid character: $name" + } +} diff --git a/intermediate/src/prog8/intermediate/IRProgram.kt b/intermediate/src/prog8/intermediate/IRProgram.kt index d38bc2197..0affa6c50 100644 --- a/intermediate/src/prog8/intermediate/IRProgram.kt +++ b/intermediate/src/prog8/intermediate/IRProgram.kt @@ -107,8 +107,17 @@ class IRProgram(val name: String, fun countUsedRegisters(): Int { val used = registersUsed() - return (used.readRegs.keys + used.writeRegs.keys).size + - (used.readFpRegs.keys + used.writeFpRegs.keys).size + return (used.readRegs.keys + used.writeRegs.keys).size + } + + /** label -> code chunk, filled in by linkChunks() */ + var linkedCodeTargets: Map = emptyMap() + private set + + /** the code chunk that a code reference points to (null if it is not a static label) */ + fun resolveCodeTarget(reference: CodeReference): IRCodeChunkBase? = when(reference) { + is CodeReference.Label -> linkedCodeTargets[reference.name] + is CodeReference.Absolute, is CodeReference.Indirect -> null } fun getChunkWithLabel(label: String): IRCodeChunkBase { @@ -134,11 +143,20 @@ class IRProgram(val name: String, } fun linkChunks() { - fun collectLabels(chunk: IRCodeChunkBase, map: MutableMap) { - chunk.forEachChunkRecursive { map[it.label] = it } + fun addLabel(label: String?, chunk: IRCodeChunkBase, map: MutableMap) { + if (label == null) + return + val previous = map.putIfAbsent(label, chunk) + require(previous == null || previous === chunk) { "duplicate code chunk label: $label" } } - fun getLabeledChunks(): Map { - val result = mutableMapOf() + + fun collectLabels(chunk: IRCodeChunkBase, map: MutableMap) { + chunk.forEachChunkRecursive { addLabel(it.label, it, map) } + } + + fun getLabeledChunks(): Map { + val result = mutableMapOf() + collectLabels(globalInits, result) blocks.forEach { block -> block.children.forEach { child -> when(child) { @@ -150,18 +168,23 @@ class IRProgram(val name: String, is IRSubroutine -> { child.chunks.forEach { collectLabels(it, result) } if (child.chunks.isNotEmpty()) { - result[child.label] = child.chunks.first() - // also map loop bodies inside first chunk if needed already via collectLabels + addLabel(child.label, child.chunks.first(), result) } } } } } - result.remove(null) return result } val labeledChunks = getLabeledChunks() + linkedCodeTargets = labeledChunks + + fun verifyCodeTarget(instruction: IRInstruction) { + val label = instruction.labelTarget ?: return + if(resolveCodeTarget(instruction.codeTarget!!) == null) + throw AssemblyError("Missing jump/call target: $label") + } if(globalInits.isNotEmpty()) { if(globalInits.next==null) { @@ -196,17 +219,8 @@ class IRProgram(val name: String, chunk.next = null } - // link all jump and branching instructions to their target - chunk.instructions.forEach { - if(it.opcode in OpcodesThatBranch && it.opcode!=Opcode.JUMPI && it.opcode!=Opcode.RETURN && it.opcode!=Opcode.RETURNR && it.opcode!=Opcode.RETURNI && it.labelSymbol!=null) { - if(it.labelSymbol.startsWith('$') || it.labelSymbol.first().isDigit()) { - // it's a call to an address (extsub most likely) - requireNotNull(it.address) - } else { - it.branchTarget = labeledChunks[it.labelSymbol] ?: throw AssemblyError("Missing jump/call target: ${it.labelSymbol}") - } - } - } + // verify that all jump and branching instructions have a resolvable target + chunk.instructions.forEach { verifyCodeTarget(it) } } fun linkBaseChunk(chunk: IRCodeChunkBase, next: IRCodeChunkBase?) { @@ -224,15 +238,7 @@ class IRProgram(val name: String, if(bc is IRLoopChunk) { bc.body.forEach { resolveBody(it) } } else { - bc.instructions.forEach { - if(it.opcode in OpcodesThatBranch && it.opcode!=Opcode.JUMPI && it.opcode!=Opcode.RETURN && it.opcode!=Opcode.RETURNR && it.opcode!=Opcode.RETURNI && it.labelSymbol!=null) { - if(it.labelSymbol.startsWith('$') || it.labelSymbol.first().isDigit()) { - requireNotNull(it.address) - } else { - if(it.branchTarget==null) it.branchTarget = labeledChunks[it.labelSymbol] ?: throw AssemblyError("Missing jump/call target: ${it.labelSymbol}") - } - } - } + bc.instructions.forEach { verifyCodeTarget(it) } } } chunk.body.forEach { resolveBody(it) } @@ -274,26 +280,19 @@ class IRProgram(val name: String, } fun validate() { + fun validateCodeTarget(instr: IRInstruction) { + val label = instr.labelTarget ?: return + if(instr.opcode in OpcodesThatBranch) + require(resolveCodeTarget(instr.codeTarget!!) != null) { "branching instruction to label $label should have a resolvable target chunk" } + } + fun validateChunk(chunk: IRCodeChunkBase, sub: IRSubroutine?, emptyChunkIsAllowed: Boolean) { if (chunk is IRLoopChunk) { require(chunk.label!=null) { "loop chunk needs label" } require(chunk.trip in 1..65536) chunk.body.forEach { validateChunk(it, null, false) } // loop's next is validated like normal chunk linking (outside) - chunk.instructions.forEach { instr -> - if(instr.labelSymbol!=null && instr.opcode in OpcodesThatBranch) { - if(instr.opcode==Opcode.JUMPI) { - val symbol = st.lookup(instr.labelSymbol) ?: throw AssemblyError("Missing jump target symbol: ${instr.labelSymbol}") - when(symbol) { - is IRStStaticVariable -> require(symbol.dt.isUnsignedWord) - is IRStMemVar -> require(symbol.dt.isUnsignedWord) - else -> throw AssemblyError("Invalid jump target symbol type: ${instr.labelSymbol}") - } - } - else if(!instr.labelSymbol.startsWith('$') && !instr.labelSymbol.first().isDigit()) - require(instr.branchTarget != null) { "branching instruction to label should have branchTarget set" } - } - } + chunk.instructions.forEach { validateCodeTarget(it) } return } if (chunk is IRCodeChunk) { @@ -312,20 +311,7 @@ class IRProgram(val name: String, if(chunk is IRInlineAsmChunk) require(!chunk.isIR) { "inline IR-asm should have been converted into regular code chunk"} } - chunk.instructions.forEach { instr -> - if(instr.labelSymbol!=null && instr.opcode in OpcodesThatBranch) { - if(instr.opcode==Opcode.JUMPI) { - val symbol = st.lookup(instr.labelSymbol) ?: throw AssemblyError("Missing jump target symbol: ${instr.labelSymbol}") - when(symbol) { - is IRStStaticVariable -> require(symbol.dt.isUnsignedWord) - is IRStMemVar -> require(symbol.dt.isUnsignedWord) - else -> throw AssemblyError("Invalid jump target symbol type: ${instr.labelSymbol}") - } - } - else if(!instr.labelSymbol.startsWith('$') && !instr.labelSymbol.first().isDigit()) - require(instr.branchTarget != null) { "branching instruction to label should have branchTarget set" } - } - } + chunk.instructions.forEach { validateCodeTarget(it) } } validateChunk(globalInits, null, true) @@ -348,17 +334,13 @@ class IRProgram(val name: String, } fun registersUsed(permissive: Boolean = wasPackingApplied): RegistersUsed { - val readRegsCounts = mutableMapOf().withDefault { 0 } - val regsTypes = mutableMapOf() - val readFpRegsCounts = mutableMapOf().withDefault { 0 } - val writeRegsCounts = mutableMapOf().withDefault { 0 } - val writeFpRegsCounts = mutableMapOf().withDefault { 0 } + val readRegsCounts = mutableMapOf().withDefault { 0 } + val writeRegsCounts = mutableMapOf().withDefault { 0 } + val regsTypes = mutableMapOf() fun addUsed(usedRegisters: RegistersUsed, child: IIRBlockElement) { usedRegisters.readRegs.forEach{ (reg, count) -> readRegsCounts[reg] = readRegsCounts.getValue(reg) + count } usedRegisters.writeRegs.forEach{ (reg, count) -> writeRegsCounts[reg] = writeRegsCounts.getValue(reg) + count } - usedRegisters.readFpRegs.forEach{ (reg, count) -> readFpRegsCounts[reg] = readFpRegsCounts.getValue(reg) + count } - usedRegisters.writeFpRegs.forEach{ (reg, count) -> writeFpRegsCounts[reg] = writeFpRegsCounts.getValue(reg) + count } if(permissive) { // After register packing, the same slot number may be used with different types // in different subroutines (the packer's globalSlotTypes mechanism ensures type @@ -383,9 +365,6 @@ class IRProgram(val name: String, } } - // on 32-bit targets LOADX/STOREX/STOREZX use a word index register (0-32767) instead of a byte (0-255) - val indexRegType = options.compTarget.indexRegType - // Aggregate via uniform traversal. Loop containers themselves carry no // instructions; only leaf chunks contribute, so nested bodies are // counted exactly once. Inline-asm chunks contribute via their parsed @@ -393,9 +372,9 @@ class IRProgram(val name: String, forEachChunk { chunk -> if(chunk is IRLoopChunk) return@forEachChunk - addUsed(chunk.usedRegisters(indexRegType), chunk) + addUsed(chunk.usedRegisters(), chunk) } - return RegistersUsed(readRegsCounts, writeRegsCounts, readFpRegsCounts, writeFpRegsCounts, regsTypes) + return RegistersUsed(readRegsCounts, writeRegsCounts, regsTypes) } fun convertAsmChunks() { @@ -550,12 +529,11 @@ class IRProgram(val name: String, * differently-typed (but POINTER-compatible) registers in different subroutines, * which this strict check would reject. Use RegisterPacker.rebuildTypeMap() instead. */ - fun verifyRegisterTypes(registerTypes: Map) { - val indexRegType = options.compTarget.indexRegType + fun verifyRegisterTypes(registerTypes: Map) { forEachChunk { chunk -> if(chunk is IRLoopChunk) return@forEachChunk - chunk.usedRegisters(indexRegType).validate(registerTypes, chunk) + chunk.usedRegisters().validate(registerTypes, chunk) } } } @@ -671,7 +649,7 @@ sealed class IRCodeChunkBase(override val label: String?, var next: IRCodeChunkB abstract override fun isEmpty(): Boolean abstract override fun isNotEmpty(): Boolean - abstract fun usedRegisters(indexRegType: IRDataType = IRDataType.BYTE): RegistersUsed + abstract fun usedRegisters(): RegistersUsed /** * Visit this chunk and, for [IRLoopChunk], all nested body chunks @@ -688,14 +666,12 @@ class IRCodeChunk(label: String?, next: IRCodeChunkBase?): IRCodeChunkBase(label override fun isEmpty() = instructions.isEmpty() override fun isNotEmpty() = instructions.isNotEmpty() - override fun usedRegisters(indexRegType: IRDataType): RegistersUsed { - val readRegsCounts = mutableMapOf().withDefault { 0 } - val regsTypes = mutableMapOf() - val readFpRegsCounts = mutableMapOf().withDefault { 0 } - val writeFpRegsCounts = mutableMapOf().withDefault { 0 } - val writeRegsCounts = mutableMapOf().withDefault { 0 } - instructions.forEach { it.addUsedRegistersCounts(readRegsCounts, writeRegsCounts, readFpRegsCounts, writeFpRegsCounts, regsTypes, this, indexRegType) } - return RegistersUsed(readRegsCounts, writeRegsCounts, readFpRegsCounts, writeFpRegsCounts, regsTypes) + override fun usedRegisters(): RegistersUsed { + val readRegsCounts = mutableMapOf().withDefault { 0 } + val writeRegsCounts = mutableMapOf().withDefault { 0 } + val regsTypes = mutableMapOf() + instructions.forEach { it.addUsedRegistersCounts(readRegsCounts, writeRegsCounts, regsTypes, this) } + return RegistersUsed(readRegsCounts, writeRegsCounts, regsTypes) } operator fun plusAssign(ins: IRInstruction) { @@ -734,7 +710,7 @@ class IRInlineAsmChunk(label: String?, require(!assembly.endsWith('\n') && !assembly.endsWith('\r')) { "inline assembly should be trimmed" } } - override fun usedRegisters(indexRegType: IRDataType) = registersUsed + override fun usedRegisters() = registersUsed } class IRInlineBinaryChunk(label: String?, @@ -743,7 +719,7 @@ class IRInlineBinaryChunk(label: String?, // note: no instructions, data is in the property override fun isEmpty() = data.isEmpty() override fun isNotEmpty() = data.isNotEmpty() - override fun usedRegisters(indexRegType: IRDataType) = RegistersUsed(emptyMap(), emptyMap(), emptyMap(), emptyMap(), emptyMap()) + override fun usedRegisters() = RegistersUsed(emptyMap(), emptyMap(), emptyMap()) } class IRLoopChunk(label: String, val trip: Int, val body: MutableList, next: IRCodeChunkBase? = null): IRCodeChunkBase(label, next) { @@ -754,27 +730,23 @@ class IRLoopChunk(label: String, val trip: Int, val body: MutableList().withDefault { 0 } - val regsTypes = mutableMapOf() - val readFpRegsCounts = mutableMapOf().withDefault { 0 } - val writeRegsCounts = mutableMapOf().withDefault { 0 } - val writeFpRegsCounts = mutableMapOf().withDefault { 0 } + val readRegsCounts = mutableMapOf().withDefault { 0 } + val writeRegsCounts = mutableMapOf().withDefault { 0 } + val regsTypes = mutableMapOf() body.forEach { chunk -> - val used = chunk.usedRegisters(indexRegType) + val used = chunk.usedRegisters() used.readRegs.forEach { (reg, count) -> readRegsCounts[reg] = readRegsCounts.getValue(reg) + count } used.writeRegs.forEach { (reg, count) -> writeRegsCounts[reg] = writeRegsCounts.getValue(reg) + count } - used.readFpRegs.forEach { (reg, count) -> readFpRegsCounts[reg] = readFpRegsCounts.getValue(reg) + count } - used.writeFpRegs.forEach { (reg, count) -> writeFpRegsCounts[reg] = writeFpRegsCounts.getValue(reg) + count } used.regsTypes.forEach { (reg, type) -> regsTypes.putIfAbsent(reg, type) } } - return RegistersUsed(readRegsCounts, writeRegsCounts, readFpRegsCounts, writeFpRegsCounts, regsTypes) + return RegistersUsed(readRegsCounts, writeRegsCounts, regsTypes) } } @@ -797,25 +769,37 @@ internal class IRStructSubtype(val def: IRStStructDef): ISubType { class RegistersUsed( - // register num -> number of uses - val readRegs: Map, - val writeRegs: Map, - val readFpRegs: Map, - val writeFpRegs: Map, - val regsTypes: Map + // virtual register -> number of uses + val readRegs: Map, + val writeRegs: Map, + val regsTypes: Map ) { override fun toString(): String { - return "read=$readRegs, write=$writeRegs, readFp=$readFpRegs, writeFp=$writeFpRegs, types=$regsTypes" + return "read=$readRegs, write=$writeRegs, types=$regsTypes" } - fun isEmpty() = readRegs.isEmpty() && writeRegs.isEmpty() && readFpRegs.isEmpty() && writeFpRegs.isEmpty() + fun isEmpty() = readRegs.isEmpty() && writeRegs.isEmpty() fun isNotEmpty() = !isEmpty() - fun used(register: RegisterNum) = register in readRegs || register in writeRegs - fun usedFp(fpRegister: RegisterNum) = fpRegister in readFpRegs || fpRegister in writeFpRegs + fun used(register: VirtualRegister) = register in readRegs || register in writeRegs + fun used(register: RegisterNum) = used(VirtualRegister.IntReg(register)) + fun usedFp(fpRegister: RegisterNum) = used(VirtualRegister.FloatReg(fpRegister)) - fun validate(allowed: Map, chunk: IRCodeChunkBase?) { + fun typeOf(register: VirtualRegister) = regsTypes[register] + + /** the integer registers that are read, by register number */ + val intRegsRead: Map get() = readRegs.filterKeys { it is VirtualRegister.IntReg }.mapKeys { it.key.number } + /** the integer registers that are written, by register number */ + val intRegsWritten: Map get() = writeRegs.filterKeys { it is VirtualRegister.IntReg }.mapKeys { it.key.number } + /** the float registers that are read, by register number */ + val floatRegsRead: Map get() = readRegs.filterKeys { it is VirtualRegister.FloatReg }.mapKeys { it.key.number } + /** the float registers that are written, by register number */ + val floatRegsWritten: Map get() = writeRegs.filterKeys { it is VirtualRegister.FloatReg }.mapKeys { it.key.number } + /** the data types of the integer registers that are used, by register number */ + val intRegsTypes: Map get() = regsTypes.filterKeys { it is VirtualRegister.IntReg }.mapKeys { it.key.number } + + fun validate(allowed: Map, chunk: IRCodeChunkBase?) { for((reg, type) in regsTypes) { val allowedType = allowed[reg] // can't do this check because %ir {{ .. }} segments may contain registers that the compiler doesn't know about yet. @@ -833,30 +817,20 @@ class RegistersUsed( } private fun registersUsedInAssembly(isIR: Boolean, assembly: String): RegistersUsed { - val readRegsCounts = mutableMapOf().withDefault { 0 } - val regsTypes = mutableMapOf() - val readFpRegsCounts = mutableMapOf().withDefault { 0 } - val writeRegsCounts = mutableMapOf().withDefault { 0 } - val writeFpRegsCounts = mutableMapOf().withDefault { 0 } + val readRegsCounts = mutableMapOf() + val writeRegsCounts = mutableMapOf() + val regsTypes = mutableMapOf() if(isIR) { assembly.lineSequence().forEach { line -> val t = line.trim() if(t.isNotEmpty()) { - val result = parseIRCodeLine(t) - when (result) { - is ParsedIRLine.Instruction -> result.value.addUsedRegistersCounts( - readRegsCounts, - writeRegsCounts, - readFpRegsCounts, - writeFpRegsCounts, - regsTypes, - null - ) + when (val result = parseIRCodeLine(t)) { + is ParsedIRLine.Instruction -> result.value.addUsedRegistersCounts(readRegsCounts, writeRegsCounts, regsTypes, null) is ParsedIRLine.Label -> { /* labels can be skipped */ } } } } } - return RegistersUsed(readRegsCounts, writeRegsCounts, readFpRegsCounts, writeFpRegsCounts, regsTypes) + return RegistersUsed(readRegsCounts, writeRegsCounts, regsTypes) } diff --git a/intermediate/src/prog8/intermediate/IRTextCodec.kt b/intermediate/src/prog8/intermediate/IRTextCodec.kt new file mode 100644 index 000000000..1fa4826ee --- /dev/null +++ b/intermediate/src/prog8/intermediate/IRTextCodec.kt @@ -0,0 +1,662 @@ +package prog8.intermediate + +import prog8.code.core.Statusflag +import prog8.code.core.toHex + +/* +The canonical text codec for IR instructions: it parses and prints the textual form of an +instruction, driven by the OpcodeSchema. The printed form always round-trips back to an +identical instruction. +*/ + + +sealed interface IRToken { + val text: String + + data class Register(override val text: String) : IRToken + data class Immediate(override val text: String) : IRToken + data class Memory(override val text: String) : IRToken + data class CodeTarget(override val text: String) : IRToken + data class HardwareSlot(override val text: String) : IRToken + data class StatusFlag(override val text: String) : IRToken +} + + +object IRTextCodec { + + private val instructionPattern = Regex("""([a-zA-Z_]+)(\.b|\.w|\.l|\.f|\.p)?\s*(.*)""", RegexOption.IGNORE_CASE) + private val typeSuffixPattern = Regex("""(.+)\.([bwlfp])""", RegexOption.IGNORE_CASE) + private val registerPattern = Regex("""(f?)r(\d+)""", RegexOption.IGNORE_CASE) + private val hardwareSlotPattern = Regex("""s(\d+)""", RegexOption.IGNORE_CASE) + + // ---------------------------------------------------------------- printing + + fun print(instruction: IRInstruction): String = buildString { + append(instruction.opcode.name.lowercase()) + instruction.type?.let { append('.' + typeSuffix(it)) } + val schema = instruction.schema + val operands = schema.slots.mapNotNull { slot -> printSlot(instruction, slot) } + if (operands.isNotEmpty()) { + append(' ') + append(operands.joinToString(",")) + } + } + + private fun printSlot(instruction: IRInstruction, slot: SlotSchema): String? = when (slot) { + is RegisterSlotSchema -> when (slot.slot) { + InstructionSlot.DEST -> instruction.dest?.let { printRegister(it) } + InstructionSlot.DEST_B -> instruction.destB?.let { printRegister(it) } + InstructionSlot.SRC_A -> instruction.srcA?.let { printRegister(it) } + InstructionSlot.SRC_B -> instruction.srcB?.let { printRegister(it) } + else -> null + } + is ImmediateSlotSchema -> instruction.immediate?.let { printImmediate(it) } + is MemorySlotSchema -> instruction.memory?.let { printMemory(it) } + is HardwareSlotSchema -> instruction.hardwareSlot?.let { printHardwareSlot(it) } + is TargetSlotSchema -> instruction.target?.let { printCodeReference(it) } + is CallSiteSlotSchema -> instruction.callSite?.let { printCallSite(it) } + } + + fun typeSuffix(type: IRDataType): String = when (type) { + IRDataType.BYTE -> "b" + IRDataType.WORD -> "w" + IRDataType.LONG -> "l" + IRDataType.FLOAT -> "f" + IRDataType.POINTER -> "p" + } + + fun printRegister(operand: RegisterOperand): String = + "${operand.register}.${typeSuffix(operand.type)}" + + fun printImmediate(immediate: ImmediateOperand): String = when (immediate) { + is ImmediateOperand.Integer -> "#${immediate.value.toHex()}.${typeSuffix(immediate.type)}" + is ImmediateOperand.FloatValue -> "#${immediate.value}.f" + is ImmediateOperand.SymbolAddress -> + if (immediate.offset == 0) "#${immediate.symbol}" else "#${immediate.symbol}+${immediate.offset}" + } + + fun printMemory(memory: MemoryReference): String = when (memory) { + is MemoryReference.Direct -> buildString { + append('[') + append(printAddressBase(memory.base)) + if (memory.displacement != 0) append("+${memory.displacement}") + append(']') + } + is MemoryReference.Indexed -> buildString { + append('[') + append(printAddressBase(memory.base)) + if (memory.displacement != 0) append("+${memory.displacement}") + append('+') + append(printRegister(memory.index)) + if (memory.scale != 1) append("*${memory.scale}") + append(']') + } + is MemoryReference.Indirect -> buildString { + append('[') + append(printRegister(memory.pointer)) + if (memory.displacement != 0) append("+${memory.displacement}") + append(']') + } + } + + private fun printAddressBase(base: AddressBase): String = when (base) { + is AddressBase.Symbol -> base.name + is AddressBase.Absolute -> base.address.toHex() + } + + fun printHardwareSlot(operand: HardwareSlotOperand): String = + "s${operand.slot.value}.${typeSuffix(operand.type)}" + + fun printCodeReference(reference: CodeReference): String = when (reference) { + is CodeReference.Label -> if (reference.offset == 0) reference.name else "${reference.name}+${reference.offset}" + is CodeReference.Absolute -> reference.address.toHex() + is CodeReference.Indirect -> "(${printRegister(reference.pointer)})" + } + + fun printCallSite(site: CallSite): String = buildString { + when (val target = site.target) { + is CallTarget.Direct -> { + append(printCodeReference(target.reference)) + target.externalName?.let { append(",$it") } + } + is CallTarget.Banked -> { + append("#${target.bank.toHex()},") + append(printCodeReference(target.reference)) + target.externalName?.let { append(",$it") } + } + is CallTarget.BankedVariable -> { + append(printRegister(target.bankRegister)) + append(',') + append(printCodeReference(target.reference)) + target.externalName?.let { append(",$it") } + } + is CallTarget.AmigaLibrary -> { + append("#${target.library.toHex()},") + append(target.lvo.toString()) + target.name?.let { append(",$it") } + } + is CallTarget.SystemCall -> append(target.number.toHex()) + } + append('(') + append(site.arguments.joinToString(",") { printCallArgument(it) }) + append(')') + if (site.results.isNotEmpty()) { + append(':') + append(site.results.joinToString(",") { printCallResult(it) }) + } + if (site.effects != CallEffects.DEFAULT) { + append(" !memory=") + append(site.effects.memoryEffect.name.lowercase()) + append(",status=") + append(site.effects.statusEffect.name.lowercase()) + } + } + + private fun printCallLocationPrefix(location: CallLocation): String = when (location) { + is CallLocation.ParameterMemory -> + if (location.name.isNotBlank()) "${location.name}=" + else location.address?.let { "${it.toHex()}=" } ?: "" + else -> "" + } + + private fun printCallLocationSuffix(location: CallLocation): String = when (location) { + is CallLocation.HardwareRegister -> "@s${location.slot.value}" + is CallLocation.StatusFlag -> "@${location.flag}" + else -> "" + } + + private fun printCallArgument(argument: CallArgument): String = + printCallLocationPrefix(argument.location) + printRegister(argument.source) + printCallLocationSuffix(argument.location) + + private fun printCallResult(result: CallResult): String { + val destination = result.destination?.let { printRegister(it) } ?: "" + val location = printCallLocationSuffix(result.location) + return if (destination.isEmpty()) location else destination + location + } + + // ---------------------------------------------------------------- parsing + + fun parse(line: String): IRInstruction = try { + parseInternal(line) + } catch (ex: IRParseException) { + throw ex + } catch (ex: IllegalArgumentException) { + throw IRParseException(ex.message ?: "invalid IR instruction: $line") + } + + private fun parseInternal(line: String): IRInstruction { + val trimmed = line.trim() + val match = instructionPattern.matchEntire(trimmed) ?: throw IRParseException("invalid IR instruction: $line") + val (instr, typestr, rest) = match.destructured + val opcode = try { + Opcode.valueOf(instr.uppercase()) + } catch (_: IllegalArgumentException) { + throw IRParseException("invalid IR instruction: $instr") + } + + val availableTypes = OpcodeSchemas.typesFor(opcode) + var type = convertIRType(typestr) + if (type == null) { + if (null !in availableTypes) + type = if (IRDataType.BYTE in availableTypes) IRDataType.BYTE else availableTypes.first() + } + if (type !in availableTypes) + throw IRParseException("invalid type $typestr for $opcode") + val schema = OpcodeSchemas.get(opcode, type) + + if (schema.callSlot != null) + return IRInstruction(opcode, type, callSite = parseCallSite(schema.callSlot.kind, rest, opcode)) + + val tokens = tokenizeOperands(rest) + if (tokens.size != schema.slots.size) + throw IRParseException("$opcode expects ${schema.slots.size} operands but got ${tokens.size} in: $line") + + var dest: RegisterOperand? = null + var destB: RegisterOperand? = null + var srcA: RegisterOperand? = null + var srcB: RegisterOperand? = null + var immediate: ImmediateOperand? = null + var hardwareSlot: HardwareSlotOperand? = null + var memory: MemoryReference? = null + var target: CodeReference? = null + + for ((slotSchema, token) in schema.slots.zip(tokens)) { + when (slotSchema) { + is RegisterSlotSchema -> { + val operand = parseRegisterOperand(token.text, slotSchema, schema) + when (slotSchema.slot) { + InstructionSlot.DEST -> dest = operand + InstructionSlot.DEST_B -> destB = operand + InstructionSlot.SRC_A -> srcA = operand + InstructionSlot.SRC_B -> srcB = operand + else -> throw IRParseException("invalid register slot ${slotSchema.slot}") + } + } + is ImmediateSlotSchema -> immediate = parseImmediate(token.text, slotSchema, schema) + is MemorySlotSchema -> memory = parseMemory(token.text, slotSchema, schema) + is HardwareSlotSchema -> hardwareSlot = parseHardwareSlot(token.text, schema) + is TargetSlotSchema -> target = parseCodeReference(token.text, slotSchema.kind) + is CallSiteSlotSchema -> throw IRParseException("unexpected call operand") + } + } + + return IRInstruction(opcode, type, dest, destB, srcA, srcB, immediate, hardwareSlot, memory, target) + } + + /** Tokenize operands while preserving bracketed memory and parenthesized code targets. */ + private fun tokenizeOperands(rest: String): List { + val trimmed = rest.trim() + if (trimmed.isEmpty()) + return emptyList() + return splitTopLevel(trimmed, ',').map { raw -> + val text = raw.trim() + if (text.isEmpty()) + throw IRParseException("empty IR operand in: $rest") + val (withoutType, _) = stripTypeSuffix(text) + when { + text.startsWith('#') -> IRToken.Immediate(text) + text.startsWith('[') && text.endsWith(']') -> IRToken.Memory(text) + text.startsWith('(') && text.endsWith(')') -> IRToken.CodeTarget(text) + hardwareSlotPattern.matchEntire(withoutType) != null -> IRToken.HardwareSlot(text) + registerPattern.matchEntire(withoutType) != null -> IRToken.Register(text) + else -> IRToken.CodeTarget(text) + } + } + } + + private fun splitTopLevel(text: String, separator: Char): List { + val result = mutableListOf() + val delimiters = ArrayDeque() + val current = StringBuilder() + for (char in text) { + when (char) { + '[', '(' -> { + delimiters.addLast(char) + current.append(char) + } + ']', ')' -> { + val expected = if (char == ']') '[' else '(' + if (delimiters.removeLastOrNull() != expected) + throw IRParseException("unbalanced delimiters in: $text") + current.append(char) + } + separator -> if (delimiters.isEmpty()) { + result.add(current.toString()) + current.clear() + } else current.append(char) + else -> current.append(char) + } + } + if (delimiters.isNotEmpty()) + throw IRParseException("unbalanced delimiters in: $text") + result.add(current.toString()) + return result + } + + private fun stripTypeSuffix(text: String): Pair { + val match = typeSuffixPattern.matchEntire(text) ?: return text to null + return match.groupValues[1] to convertIRType("." + match.groupValues[2].lowercase()) + } + + private fun parseRegisterOperand(token: String, slotSchema: RegisterSlotSchema, schema: OpcodeSchema): RegisterOperand { + val (regtext, explicitType) = stripTypeSuffix(token) + val match = registerPattern.matchEntire(regtext) + ?: throw IRParseException("${schema.opcode}: expected a register operand, got: $token") + val isFloat = match.groupValues[1].isNotEmpty() + val number = match.groupValues[2].toInt() + when (slotSchema.registerFile) { + RegisterFile.INTEGER -> if (isFloat) throw IRParseException("${schema.opcode}: expected an integer register, got: $token") + RegisterFile.FLOAT -> if (!isFloat) throw IRParseException("${schema.opcode}: expected a float register, got: $token") + } + val type = explicitType + ?: throw IRParseException("${schema.opcode}: register operand needs an explicit type: $token") + val register = if (isFloat) VirtualRegister.float(number) else VirtualRegister.int(number) + return RegisterOperand(register, type, slotSchema.role, slotSchema.direction) + } + + private fun parseImmediate(token: String, slotSchema: ImmediateSlotSchema, schema: OpcodeSchema): ImmediateOperand { + if (!token.startsWith('#')) + throw IRParseException("${schema.opcode}: immediate operand must start with #: $token") + val text = token.drop(1).trim() + if (text.isEmpty()) + throw IRParseException("${schema.opcode}: empty immediate operand") + if (text[0].isLetter() || text[0] == '_') { + if (!slotSchema.allowSymbolAddress) + throw IRParseException("${schema.opcode}: symbol address not allowed as immediate: $token") + val plus = text.lastIndexOf('+') + return if (plus > 0) { + val offset = text.substring(plus + 1).toIntOrNull() + ?: throw IRParseException("${schema.opcode}: invalid symbol offset in $token") + ImmediateOperand.SymbolAddress(text.substring(0, plus), offset) + } else ImmediateOperand.SymbolAddress(text) + } + val (valueText, explicitType) = stripTypeSuffix(text) + val type = explicitType + ?: throw IRParseException("${schema.opcode}: immediate operand needs an explicit type: $token") + if (type == IRDataType.FLOAT) + return ImmediateOperand.FloatValue(parseIRValue(valueText)) + val value = parseInteger(valueText, "${schema.opcode}: invalid integer immediate") + return ImmediateOperand.Integer(value, type) + } + + private fun parseHardwareSlot(token: String, schema: OpcodeSchema): HardwareSlotOperand { + val (slotText, explicitType) = stripTypeSuffix(token) + val match = hardwareSlotPattern.matchEntire(slotText) + ?: throw IRParseException("${schema.opcode}: expected a hardware slot operand (s0..s32), got: $token") + val type = explicitType + ?: throw IRParseException("${schema.opcode}: hardware slot needs an explicit type: $token") + return HardwareSlotOperand(CallingConventionSlot(match.groupValues[1].toInt()), type) + } + + private fun parseMemory(token: String, slotSchema: MemorySlotSchema, schema: OpcodeSchema): MemoryReference { + if (!token.startsWith('[') || !token.endsWith(']')) + throw IRParseException("${schema.opcode}: expected a memory reference in brackets, got: $token") + val inner = token.substring(1, token.length - 1).trim() + if (inner.isEmpty()) + throw IRParseException("${schema.opcode}: empty memory reference") + val parts = splitTopLevel(inner, '+').map { it.trim() } + if (parts.any { it.isEmpty() }) + throw IRParseException("${schema.opcode}: invalid memory reference: $token") + + fun parseIndexRegister(text: String): Pair { + val (registerText, scaleText) = if ('*' in text) { + val idx = text.indexOf('*') + text.substring(0, idx).trim() to text.substring(idx + 1).trim() + } else text to "1" + val scale = scaleText.toIntOrNull() ?: throw IRParseException("${schema.opcode}: invalid index scale in $token") + val (regtext, explicitType) = stripTypeSuffix(registerText) + val match = registerPattern.matchEntire(regtext) + ?: throw IRParseException("${schema.opcode}: invalid index register in $token") + if (match.groupValues[1].isNotEmpty()) + throw IRParseException("${schema.opcode}: index register must be an integer register: $token") + val type = explicitType ?: throw IRParseException("${schema.opcode}: index register needs an explicit type: $token") + return IRMemory.indexOperand(match.groupValues[2].toInt(), type) to scale + } + + fun isRegisterText(text: String): Boolean { + val (regtext, _) = stripTypeSuffix(text) + return registerPattern.matchEntire(regtext) != null + } + + if (isRegisterText(parts[0])) { + // indirect: [rX.p] or [rX.p+displacement] + val (regtext, explicitType) = stripTypeSuffix(parts[0]) + val match = registerPattern.matchEntire(regtext)!! + if (match.groupValues[1].isNotEmpty()) + throw IRParseException("${schema.opcode}: pointer register must be an integer register: $token") + val displacement = if (parts.size > 1) + parseNonNegativeInteger(parts[1], "${schema.opcode}: invalid displacement in $token") + else 0 + if (parts.size > 2) + throw IRParseException("${schema.opcode}: invalid memory reference: $token") + val pointerType = explicitType + ?: throw IRParseException("${schema.opcode}: pointer register needs an explicit type: $token") + val reference = IRMemory.indirect(match.groupValues[2].toInt(), displacement, pointerType) + requireKind(reference, slotSchema, schema, token) + return reference + } + + val base: AddressBase = if (parts[0].first().isLetter()) + AddressBase.Symbol(parts[0]) + else + AddressBase.Absolute(parseAddress(parts[0], "${schema.opcode}: invalid memory address")) + + var displacement = 0 + var index: RegisterOperand? = null + var scale = 1 + for (part in parts.drop(1)) { + if (isRegisterText(part.substringBefore('*'))) { + if (index != null) throw IRParseException("${schema.opcode}: more than one index register in $token") + val (indexOperand, indexScale) = parseIndexRegister(part) + index = indexOperand + scale = indexScale + } else { + displacement += parseNonNegativeInteger(part, "${schema.opcode}: invalid displacement in $token") + } + } + + val reference = if (index == null) + MemoryReference.Direct(base, displacement) + else + MemoryReference.Indexed(base, index, scale, displacement) + requireKind(reference, slotSchema, schema, token) + return reference + } + + private fun requireKind(reference: MemoryReference, slotSchema: MemorySlotSchema, schema: OpcodeSchema, token: String) { + val kind = when (reference) { + is MemoryReference.Direct -> MemoryKind.DIRECT + is MemoryReference.Indexed -> MemoryKind.INDEXED + is MemoryReference.Indirect -> MemoryKind.INDIRECT + } + if (kind != slotSchema.kind) + throw IRParseException("${schema.opcode}: needs a ${slotSchema.kind} memory reference, got: $token") + } + + private fun parseInteger(text: String, description: String): Int { + val value = parseIRValue(text) + if (!value.isFinite() || value % 1.0 != 0.0 || value < Int.MIN_VALUE || value > UInt.MAX_VALUE.toDouble()) + throw IRParseException("$description: $text") + return value.toLong().toInt() + } + + private fun parseNonNegativeInteger(text: String, description: String): Int { + val value = parseInteger(text, description) + if (value < 0) + throw IRParseException("$description: $text") + return value + } + + private fun parseAddress(text: String, description: String): MemoryAddress { + val value = parseIRValue(text) + if (!value.isFinite() || value % 1.0 != 0.0 || value < 0.0 || value > UInt.MAX_VALUE.toDouble()) + throw IRParseException("$description: $text") + return MemoryAddress(value.toLong().toUInt()) + } + + private fun parseCodeReference(token: String, kind: TargetKind): CodeReference { + val reference = parseCodeReference(token) + when (kind) { + TargetKind.STATIC -> if (reference is CodeReference.Indirect) throw IRParseException("expected a static code target: $token") + TargetKind.INDIRECT -> if (reference !is CodeReference.Indirect) throw IRParseException("expected an indirect code target: $token") + } + return reference + } + + private fun parseCodeReference(token: String): CodeReference { + if (token.startsWith('(') && token.endsWith(')')) { + val (regtext, explicitType) = stripTypeSuffix(token.substring(1, token.length - 1).trim()) + val match = registerPattern.matchEntire(regtext) ?: throw IRParseException("invalid indirect code target: $token") + if (match.groupValues[1].isNotEmpty()) + throw IRParseException("indirect code target must use an integer register: $token") + val pointerType = explicitType + ?: throw IRParseException("indirect code target needs an explicit register type: $token") + return codeIndirect(match.groupValues[2].toInt(), pointerType) + } + if (token.isEmpty()) + throw IRParseException("empty code target") + if (token[0].isLetter()) { + val plus = token.lastIndexOf('+') + return if (plus > 0) { + val offset = token.substring(plus + 1).toIntOrNull() ?: throw IRParseException("invalid code target offset: $token") + CodeReference.Label(token.substring(0, plus), offset) + } else CodeReference.Label(token) + } + return CodeReference.Absolute(parseAddress(token, "invalid code target")) + } + + // ---- call parsing ---- + + private fun parseCallSite(kind: CallKind, rest: String, opcode: Opcode): CallSite { + val text = rest.trim() + val closeParen = text.lastIndexOf(')') + if (closeParen < 0) + throw IRParseException("$opcode: invalid call, missing argument list: $rest") + val suffix = text.substring(closeParen + 1).trim() + val effectMarker = suffix.indexOf('!') + val resultsText = suffix.substring(0, if (effectMarker >= 0) effectMarker else suffix.length).trim().let { + if (it.isEmpty()) null + else if (it.startsWith(':')) it.drop(1).trim() + else throw IRParseException("$opcode: invalid call result spec: $it") + } + val effects = if (effectMarker >= 0) + parseCallEffects(suffix.substring(effectMarker + 1).trim(), opcode) + else + CallEffects.DEFAULT + // find the matching opening parenthesis of the argument list + var depth = 0 + var openParen = -1 + for (index in closeParen downTo 0) { + when (text[index]) { + ')' -> depth++ + '(' -> { + depth-- + if (depth == 0) openParen = index + } + } + if (openParen >= 0) break + } + if (openParen < 0) + throw IRParseException("$opcode: invalid call, unbalanced parentheses: $rest") + + val targetText = text.substring(0, openParen).trim() + val argumentsText = text.substring(openParen + 1, closeParen).trim() + + val target = parseCallTarget(kind, targetText, opcode) + val arguments = if (argumentsText.isEmpty()) emptyList() else + splitTopLevel(argumentsText, ',').map { + if (it.isBlank()) throw IRParseException("$opcode: empty call argument") + parseCallArgument(it.trim(), opcode) + } + val results = if (resultsText.isNullOrEmpty()) emptyList() else + splitTopLevel(resultsText, ',').map { + if (it.isBlank()) throw IRParseException("$opcode: empty call result") + parseCallResult(it.trim(), opcode) + } + return CallSite(target, arguments, results, effects) + } + + private fun parseCallEffects(text: String, opcode: Opcode): CallEffects { + val specifications = text.split(',') + val parts = specifications.associate { part -> + val keyValue = part.split('=', limit = 2).map { it.trim() } + if (keyValue.size != 2 || keyValue.any { it.isEmpty() }) + throw IRParseException("$opcode: invalid call effect: $part") + keyValue[0] to keyValue[1] + } + if (parts.size != specifications.size || parts.keys != setOf("memory", "status")) + throw IRParseException("$opcode: call effects must specify memory and status") + fun > parseEffect(value: String, values: Array, name: String): T = + values.firstOrNull { it.name.equals(value, ignoreCase = true) } + ?: throw IRParseException("$opcode: invalid $name effect: $value") + return CallEffects( + parseEffect(parts.getValue("memory"), MemoryEffect.entries.toTypedArray(), "memory"), + parseEffect(parts.getValue("status"), StatusEffect.entries.toTypedArray(), "status") + ) + } + + private fun parseCallTarget(kind: CallKind, targetText: String, opcode: Opcode): CallTarget { + if (targetText.isEmpty()) + throw IRParseException("$opcode: missing call target") + return when (kind) { + CallKind.NORMAL -> { + val parts = splitTopLevel(targetText, ',').map { it.trim() } + if (parts.size !in 1..2 || parts.any { it.isEmpty() }) + throw IRParseException("$opcode: invalid direct call target: $targetText") + CallTarget.Direct(parseCodeReference(parts[0]), parts.getOrNull(1)) + } + CallKind.INDIRECT -> CallTarget.Direct(parseCodeReference(targetText)) + CallKind.SYSCALL -> CallTarget.SystemCall(parseInteger(targetText, "$opcode: invalid syscall number")) + CallKind.FAR -> { + val parts = splitTopLevel(targetText, ',').map { it.trim() } + if (parts.size !in 2..3 || parts.any { it.isEmpty() }) + throw IRParseException("$opcode: far call needs a bank and an address") + if (!parts[0].startsWith('#')) + throw IRParseException("$opcode: far call bank must be an immediate: ${parts[0]}") + val bank = parseInteger(parts[0].drop(1), "$opcode: invalid bank") + val name = parts.getOrNull(2) + val lvo = parts[1].toIntOrNull() + if (lvo != null && lvo < 0) + CallTarget.AmigaLibrary(bank, lvo, name) + else + CallTarget.Banked(bank, parseCodeReference(parts[1]), name) + } + CallKind.FAR_VARBANK -> { + val parts = splitTopLevel(targetText, ',').map { it.trim() } + if (parts.size !in 2..3 || parts.any { it.isEmpty() }) + throw IRParseException("$opcode: far call needs a bank register and an address") + val (regtext, explicitType) = stripTypeSuffix(parts[0]) + val match = registerPattern.matchEntire(regtext) + ?: throw IRParseException("$opcode: invalid bank register: ${parts[0]}") + if (match.groupValues[1].isNotEmpty()) + throw IRParseException("$opcode: bank register must be an integer register: ${parts[0]}") + val bankRegister = RegisterOperand( + VirtualRegister.int(match.groupValues[2].toInt()), + explicitType ?: throw IRParseException("$opcode: bank register needs an explicit type: ${parts[0]}"), + OperandRole.VALUE, OperandDirection.USE + ) + CallTarget.BankedVariable(bankRegister, parseCodeReference(parts[1]), parts.getOrNull(2)) + } + } + } + + private fun parseCallLocationSuffix(text: String, opcode: Opcode): Pair { + val at = text.indexOf('@') + if (at < 0) + return text to null + val locationText = text.substring(at + 1) + val location = if (locationText.startsWith('s') && locationText.length > 1 && locationText[1].isDigit()) { + val slot = locationText.drop(1).toIntOrNull() ?: throw IRParseException("$opcode: invalid slot $locationText") + CallLocation.HardwareRegister(CallingConventionSlot(slot)) + } else { + try { + CallLocation.StatusFlag(Statusflag.valueOf(locationText)) + } catch (_: IllegalArgumentException) { + throw IRParseException("$opcode: invalid status flag $locationText") + } + } + return text.substring(0, at) to location + } + + private fun parseCallRegister(text: String, role: OperandRole, direction: OperandDirection, opcode: Opcode): RegisterOperand { + val (regtext, explicitType) = stripTypeSuffix(text) + val match = registerPattern.matchEntire(regtext) + ?: throw IRParseException("$opcode: invalid call register: $text") + val type = explicitType ?: throw IRParseException("$opcode: call register needs a type: $text") + val isFloat = match.groupValues[1].isNotEmpty() + if (isFloat != (type == IRDataType.FLOAT)) + throw IRParseException("$opcode: float register/type mismatch: $text") + val register = if (isFloat) VirtualRegister.float(match.groupValues[2].toInt()) else VirtualRegister.int(match.groupValues[2].toInt()) + return RegisterOperand(register, type, role, direction) + } + + private fun parseCallArgument(text: String, opcode: Opcode): CallArgument { + var remainder = text + var parameterLocation: CallLocation? = null + val equals = remainder.indexOf('=') + if (equals >= 0) { + val locationText = remainder.substring(0, equals) + remainder = remainder.substring(equals + 1) + parameterLocation = if (locationText.isEmpty()) null + else if (locationText[0].isLetter()) CallLocation.ParameterMemory(locationText) + else CallLocation.ParameterMemory("", parseAddress(locationText, "$opcode: invalid parameter address")) + } + val (registerText, hardwareLocation) = parseCallLocationSuffix(remainder, opcode) + if (parameterLocation != null && hardwareLocation != null) + throw IRParseException("$opcode: call argument cannot have both parameter and hardware locations: $text") + val source = parseCallRegister(registerText, OperandRole.CALL_ARGUMENT, OperandDirection.USE, opcode) + val location = hardwareLocation ?: parameterLocation ?: CallLocation.Default + return CallArgument(source, location) + } + + private fun parseCallResult(text: String, opcode: Opcode): CallResult { + val (registerText, location) = parseCallLocationSuffix(text, opcode) + if (registerText.isEmpty()) { + if (location == null) + throw IRParseException("$opcode: invalid call result: $text") + return CallResult(null, location) + } + val destination = parseCallRegister(registerText, OperandRole.CALL_RESULT, OperandDirection.DEF, opcode) + return CallResult(destination, location ?: CallLocation.Default) + } +} diff --git a/intermediate/src/prog8/intermediate/OpcodeSchema.kt b/intermediate/src/prog8/intermediate/OpcodeSchema.kt new file mode 100644 index 000000000..490a273c1 --- /dev/null +++ b/intermediate/src/prog8/intermediate/OpcodeSchema.kt @@ -0,0 +1,752 @@ +package prog8.intermediate + +/* +Opcode schema: the single source of truth for the shape and semantics of every IR instruction. + +For every (opcode, datatype) combination there is exactly one OpcodeSchema that declares: + - which operand slots the instruction has, in canonical (print) order + - for register slots: the register file (integer/float), the semantic role, the direction + (use/def/use+def) and how the register's data type is derived + - what the instruction does to memory, to the status flags, and to the control flow +*/ + + +enum class InstructionSlot { + DEST, DEST_B, SRC_A, SRC_B, IMMEDIATE, HARDWARE_SLOT, MEMORY, TARGET, CALL_SITE +} + +enum class RegisterFile { INTEGER, FLOAT } + +/** how the data type of an operand is determined */ +sealed interface TypeRule { + /** same data type as the instruction itself */ + data object InstructionType : TypeRule + + /** an array index register: any integer type (the width is target specific) */ + data object IndexType : TypeRule + + /** a pointer register: an integer type wide enough to hold an address */ + data object PointerType : TypeRule + + /** a fixed data type regardless of the instruction's type */ + data class Fixed(val type: IRDataType) : TypeRule +} + +sealed interface SlotSchema { + val slot: InstructionSlot +} + +data class RegisterSlotSchema( + override val slot: InstructionSlot, + val role: OperandRole, + val direction: OperandDirection, + val registerFile: RegisterFile, + val typeRule: TypeRule +) : SlotSchema + +data class ImmediateSlotSchema( + val role: OperandRole, + val typeRule: TypeRule, + val allowSymbolAddress: Boolean = false +) : SlotSchema { + override val slot: InstructionSlot get() = InstructionSlot.IMMEDIATE +} + +enum class MemoryKind { DIRECT, INDEXED, INDIRECT } + +data class MemorySlotSchema( + val kind: MemoryKind, + val effect: MemoryEffect +) : SlotSchema { + override val slot: InstructionSlot get() = InstructionSlot.MEMORY +} + +data class HardwareSlotSchema(val typeRule: TypeRule = TypeRule.InstructionType) : SlotSchema { + override val slot: InstructionSlot get() = InstructionSlot.HARDWARE_SLOT +} + +enum class TargetKind { STATIC, INDIRECT } + +data class TargetSlotSchema(val kind: TargetKind) : SlotSchema { + override val slot: InstructionSlot get() = InstructionSlot.TARGET +} + +enum class CallKind { NORMAL, INDIRECT, FAR, FAR_VARBANK, SYSCALL } + +data class CallSiteSlotSchema(val kind: CallKind) : SlotSchema { + override val slot: InstructionSlot get() = InstructionSlot.CALL_SITE +} + + +data class OpcodeSchema( + val opcode: Opcode, + val type: IRDataType?, + val slots: List, + val memoryEffect: MemoryEffect = MemoryEffect.NONE, + val statusEffect: StatusEffect = StatusEffect.NONE, + val controlFlow: ControlFlowEffect = ControlFlowEffect.FALLTHROUGH +) { + val registerSlots: List = slots.filterIsInstance() + val immediateSlot: ImmediateSlotSchema? = slots.filterIsInstance().singleOrNull() + val memorySlot: MemorySlotSchema? = slots.filterIsInstance().singleOrNull() + val hardwareSlotSchema: HardwareSlotSchema? = slots.filterIsInstance().singleOrNull() + val targetSlot: TargetSlotSchema? = slots.filterIsInstance().singleOrNull() + val callSlot: CallSiteSlotSchema? = slots.filterIsInstance().singleOrNull() + + fun registerSlot(slot: InstructionSlot): RegisterSlotSchema? = registerSlots.firstOrNull { it.slot == slot } + + /** the data type an operand in this slot must have */ + fun typeFor(rule: TypeRule): IRDataType? = when (rule) { + is TypeRule.Fixed -> rule.type + TypeRule.InstructionType -> type + TypeRule.IndexType -> null // target specific width, declared by the operand itself + TypeRule.PointerType -> null // target specific width, declared by the operand itself + } +} + + +object OpcodeSchemas { + + private val schemas: Map> = buildSchemas() + + fun schemasFor(opcode: Opcode): List = schemas[opcode]?.values?.toList() ?: emptyList() + + fun find(opcode: Opcode, type: IRDataType?): OpcodeSchema? = schemas[opcode]?.get(type) + + fun get(opcode: Opcode, type: IRDataType?): OpcodeSchema = + find(opcode, type) ?: throw IllegalArgumentException("type $type invalid for $opcode") + + /** the schema for an opcode when the type is not (yet) known: the typeless one, or the byte one */ + fun defaultFor(opcode: Opcode): OpcodeSchema { + val perType = schemas[opcode] ?: throw IllegalArgumentException("unknown opcode $opcode") + return perType[null] ?: perType[IRDataType.BYTE] ?: perType.values.first() + } + + fun typesFor(opcode: Opcode): Set = schemas[opcode]?.keys ?: emptySet() + + fun all(): List = schemas.values.flatMap { it.values } + + val coveredOpcodes: Set get() = schemas.keys + + /** does this opcode require a data type? */ + fun requiresType(opcode: Opcode): Boolean = schemas.getValue(opcode).keys.none { it == null } + + fun validate(instruction: IRInstruction) { + val schema = get(instruction.opcode, instruction.type) + + fun checkRegister(slot: InstructionSlot, operand: RegisterOperand?) { + val slotSchema = schema.registerSlot(slot) + if (slotSchema == null) { + require(operand == null) { "${instruction.opcode}: unexpected operand in slot $slot" } + return + } + requireNotNull(operand) { "${instruction.opcode}: missing operand in slot $slot" } + require(operand.role == slotSchema.role) { + "${instruction.opcode}: operand in slot $slot must have role ${slotSchema.role}, got ${operand.role}" + } + require(operand.direction == slotSchema.direction) { + "${instruction.opcode}: operand in slot $slot must have direction ${slotSchema.direction}, got ${operand.direction}" + } + when (slotSchema.registerFile) { + RegisterFile.INTEGER -> require(!operand.isFloat) { "${instruction.opcode}: slot $slot needs an integer register" } + RegisterFile.FLOAT -> require(operand.isFloat) { "${instruction.opcode}: slot $slot needs a float register" } + } + val requiredType = schema.typeFor(slotSchema.typeRule) + if (requiredType != null) + require(operand.type == requiredType) { + "${instruction.opcode}: slot $slot needs type $requiredType, got ${operand.type}" + } + else + require(operand.type != IRDataType.FLOAT) { "${instruction.opcode}: slot $slot needs an integer type" } + } + + checkRegister(InstructionSlot.DEST, instruction.dest) + checkRegister(InstructionSlot.DEST_B, instruction.destB) + checkRegister(InstructionSlot.SRC_A, instruction.srcA) + checkRegister(InstructionSlot.SRC_B, instruction.srcB) + + val immediateSchema = schema.immediateSlot + if (immediateSchema == null) + require(instruction.immediate == null) { "${instruction.opcode}: unexpected immediate operand" } + else { + val immediate = requireNotNull(instruction.immediate) { "${instruction.opcode}: missing immediate operand" } + if (immediate is ImmediateOperand.SymbolAddress) + require(immediateSchema.allowSymbolAddress) { "${instruction.opcode}: symbol address not allowed here" } + else { + val requiredType = schema.typeFor(immediateSchema.typeRule) + if (requiredType != null) + require(immediate.type == requiredType) { + "${instruction.opcode}: immediate needs type $requiredType, got ${immediate.type}" + } + } + if (immediate is ImmediateOperand.Integer) { + when (immediate.type) { + IRDataType.BYTE -> require(immediate.value in -128..255) { "immediate value out of range for byte: ${immediate.value}" } + IRDataType.WORD -> require(immediate.value in -32768..65535) { "immediate value out of range for word: ${immediate.value}" } + else -> {} + } + } + } + + val memorySchema = schema.memorySlot + if (memorySchema == null) + require(instruction.memory == null) { "${instruction.opcode}: unexpected memory operand" } + else { + val memory = requireNotNull(instruction.memory) { "${instruction.opcode}: missing memory operand" } + val kind = when (memory) { + is MemoryReference.Direct -> MemoryKind.DIRECT + is MemoryReference.Indexed -> MemoryKind.INDEXED + is MemoryReference.Indirect -> MemoryKind.INDIRECT + } + require(kind == memorySchema.kind) { "${instruction.opcode}: needs ${memorySchema.kind} memory reference, got $kind" } + } + + val hardwareSchema = schema.hardwareSlotSchema + if (hardwareSchema == null) + require(instruction.hardwareSlot == null) { "${instruction.opcode}: unexpected hardware slot operand" } + else { + val hardwareSlot = requireNotNull(instruction.hardwareSlot) { "${instruction.opcode}: missing hardware slot operand" } + val requiredType = schema.typeFor(hardwareSchema.typeRule) + if (requiredType != null) { + require(hardwareSlot.type == requiredType) { + "${instruction.opcode}: hardware slot needs type $requiredType, got ${hardwareSlot.type}" + } + } + } + + val targetSchema = schema.targetSlot + if (targetSchema == null) + require(instruction.target == null) { "${instruction.opcode}: unexpected code target operand" } + else { + val target = requireNotNull(instruction.target) { "${instruction.opcode}: missing code target operand" } + when (targetSchema.kind) { + TargetKind.STATIC -> require(target !is CodeReference.Indirect) { "${instruction.opcode}: needs a static code target" } + TargetKind.INDIRECT -> require(target is CodeReference.Indirect) { "${instruction.opcode}: needs an indirect code target" } + } + } + + val callSchema = schema.callSlot + if (callSchema == null) + require(instruction.callSite == null) { "${instruction.opcode}: unexpected call site operand" } + else { + val callSite = requireNotNull(instruction.callSite) { "${instruction.opcode}: missing call site operand" } + val kind = when (val target = callSite.target) { + is CallTarget.Direct -> if (target.reference is CodeReference.Indirect) CallKind.INDIRECT else CallKind.NORMAL + is CallTarget.Banked -> CallKind.FAR + is CallTarget.AmigaLibrary -> CallKind.FAR + is CallTarget.BankedVariable -> CallKind.FAR_VARBANK + is CallTarget.SystemCall -> CallKind.SYSCALL + } + require(kind == callSchema.kind) { "${instruction.opcode}: needs a ${callSchema.kind} call target, got $kind" } + } + + // NOTE: the IR is not SSA, registers are freely reused. A defined register + // may also occur as a used register in the same instruction (e.g. `addr r1,r1`, + // `cmp r1,r1`, `loadx r2,[base+r2*2]`, or a call result reusing an argument + // register), so no distinctness is enforced between USE and DEF operands. + // The only exception is an instruction with two definitions: those must be + // distinct registers, otherwise one result would clobber the other. + if (instruction.dest != null && instruction.destB != null) + require(instruction.dest.register != instruction.destB.register) { + "${instruction.opcode}: the two destination registers must be distinct" + } + } + + + private fun buildSchemas(): Map> { + val result = mutableMapOf>() + + fun expand(typespec: String): List = + if (typespec == "N") listOf(null) + else buildList { + if ('B' in typespec) add(IRDataType.BYTE) + if ('W' in typespec) { add(IRDataType.WORD); add(IRDataType.POINTER) } + if ('L' in typespec) add(IRDataType.LONG) + if ('F' in typespec) add(IRDataType.FLOAT) + } + + fun define( + opcode: Opcode, + typespec: String, + memoryEffect: MemoryEffect = MemoryEffect.NONE, + statusEffect: StatusEffect = StatusEffect.NONE, + controlFlow: ControlFlowEffect = ControlFlowEffect.FALLTHROUGH, + slots: (IRDataType?) -> List + ) { + val perType = result.getOrPut(opcode) { mutableMapOf() } + for (type in expand(typespec)) { + require(type !in perType) { "duplicate schema for $opcode $type" } + perType[type] = OpcodeSchema(opcode, type, slots(type), memoryEffect, statusEffect, controlFlow) + } + } + + // ---- slot construction helpers ---- + + fun ireg(slot: InstructionSlot, role: OperandRole, direction: OperandDirection, rule: TypeRule = TypeRule.InstructionType) = + RegisterSlotSchema(slot, role, direction, RegisterFile.INTEGER, rule) + + fun freg(slot: InstructionSlot, role: OperandRole, direction: OperandDirection) = + RegisterSlotSchema(slot, role, direction, RegisterFile.FLOAT, TypeRule.Fixed(IRDataType.FLOAT)) + + fun reg(file: RegisterFile, slot: InstructionSlot, role: OperandRole, direction: OperandDirection, rule: TypeRule = TypeRule.InstructionType) = + if (file == RegisterFile.FLOAT) freg(slot, role, direction) else ireg(slot, role, direction, rule) + + fun fileOf(type: IRDataType?) = if (type == IRDataType.FLOAT) RegisterFile.FLOAT else RegisterFile.INTEGER + + fun imm(role: OperandRole = OperandRole.VALUE, rule: TypeRule = TypeRule.InstructionType, allowSymbol: Boolean = false) = + ImmediateSlotSchema(role, rule, allowSymbol) + + fun mem(kind: MemoryKind, effect: MemoryEffect) = MemorySlotSchema(kind, effect) + + fun widened(type: IRDataType?): IRDataType = when (type) { + IRDataType.BYTE -> IRDataType.WORD + else -> IRDataType.LONG + } + + // ---- load and store ---- + + define(Opcode.NOP, "N") { emptyList() } + + define(Opcode.LOAD, "BWLF") { t -> + listOf( + reg(fileOf(t), InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF), + imm(OperandRole.VALUE, TypeRule.InstructionType, allowSymbol = true) + ) + } + define(Opcode.LOADM, "BWLF", memoryEffect = MemoryEffect.READ) { t -> + listOf( + reg(fileOf(t), InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF), + mem(MemoryKind.DIRECT, MemoryEffect.READ) + ) + } + define(Opcode.LOADX, "BWLF", memoryEffect = MemoryEffect.READ) { t -> + listOf( + reg(fileOf(t), InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF), + mem(MemoryKind.INDEXED, MemoryEffect.READ) + ) + } + define(Opcode.LOADR, "BWLF") { t -> + listOf( + reg(fileOf(t), InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF), + reg(fileOf(t), InstructionSlot.SRC_A, OperandRole.VALUE, OperandDirection.USE) + ) + } + define(Opcode.LOADHR, "BWLF") { t -> + listOf( + reg(fileOf(t), InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF), + HardwareSlotSchema() + ) + } + define(Opcode.LOADI, "BWLF", memoryEffect = MemoryEffect.READ) { t -> + listOf( + reg(fileOf(t), InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF), + mem(MemoryKind.INDIRECT, MemoryEffect.READ) + ) + } + define(Opcode.LOADHFACZERO, "F") { listOf(freg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF)) } + define(Opcode.LOADHFACONE, "F") { listOf(freg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF)) } + + define(Opcode.STOREM, "BWLF", memoryEffect = MemoryEffect.WRITE) { t -> + listOf( + reg(fileOf(t), InstructionSlot.SRC_A, OperandRole.VALUE, OperandDirection.USE), + mem(MemoryKind.DIRECT, MemoryEffect.WRITE) + ) + } + define(Opcode.STOREX, "BWLF", memoryEffect = MemoryEffect.WRITE) { t -> + listOf( + reg(fileOf(t), InstructionSlot.SRC_A, OperandRole.VALUE, OperandDirection.USE), + mem(MemoryKind.INDEXED, MemoryEffect.WRITE) + ) + } + define(Opcode.STOREZM, "BWLF", memoryEffect = MemoryEffect.WRITE) { listOf(mem(MemoryKind.DIRECT, MemoryEffect.WRITE)) } + define(Opcode.STOREZI, "BWLF", memoryEffect = MemoryEffect.WRITE) { listOf(mem(MemoryKind.INDIRECT, MemoryEffect.WRITE)) } + define(Opcode.STOREZX, "BWLF", memoryEffect = MemoryEffect.WRITE) { listOf(mem(MemoryKind.INDEXED, MemoryEffect.WRITE)) } + define(Opcode.STOREIM, "BWLF", memoryEffect = MemoryEffect.WRITE) { + listOf( + imm(OperandRole.VALUE, TypeRule.InstructionType), + mem(MemoryKind.DIRECT, MemoryEffect.WRITE) + ) + } + define(Opcode.STOREHR, "BWLF") { t -> + listOf( + reg(fileOf(t), InstructionSlot.SRC_A, OperandRole.VALUE, OperandDirection.USE), + HardwareSlotSchema() + ) + } + define(Opcode.STOREI, "BWLF", memoryEffect = MemoryEffect.WRITE) { t -> + listOf( + reg(fileOf(t), InstructionSlot.SRC_A, OperandRole.VALUE, OperandDirection.USE), + mem(MemoryKind.INDIRECT, MemoryEffect.WRITE) + ) + } + define(Opcode.STOREHFACZERO, "F") { listOf(freg(InstructionSlot.SRC_A, OperandRole.VALUE, OperandDirection.USE)) } + define(Opcode.STOREHFACONE, "F") { listOf(freg(InstructionSlot.SRC_A, OperandRole.VALUE, OperandDirection.USE)) } + + define(Opcode.LOADP_INC, "BWL", memoryEffect = MemoryEffect.READ_WRITE) { + listOf( + ireg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF), + mem(MemoryKind.DIRECT, MemoryEffect.READ_WRITE) + ) + } + define(Opcode.STOREP_INC, "BWL", memoryEffect = MemoryEffect.READ_WRITE) { + listOf( + ireg(InstructionSlot.SRC_A, OperandRole.VALUE, OperandDirection.USE), + mem(MemoryKind.DIRECT, MemoryEffect.READ_WRITE) + ) + } + + // ---- control flow ---- + + define(Opcode.JUMP, "N", controlFlow = ControlFlowEffect.JUMP) { listOf(TargetSlotSchema(TargetKind.STATIC)) } + define(Opcode.JUMPI, "N", controlFlow = ControlFlowEffect.JUMP) { listOf(TargetSlotSchema(TargetKind.INDIRECT)) } + define(Opcode.CALLI, "N", memoryEffect = MemoryEffect.UNKNOWN, statusEffect = StatusEffect.UNKNOWN, controlFlow = ControlFlowEffect.CALL) { + listOf(CallSiteSlotSchema(CallKind.INDIRECT)) + } + define(Opcode.CALL, "N", memoryEffect = MemoryEffect.UNKNOWN, statusEffect = StatusEffect.UNKNOWN, controlFlow = ControlFlowEffect.CALL) { + listOf(CallSiteSlotSchema(CallKind.NORMAL)) + } + define(Opcode.CALLFAR, "N", memoryEffect = MemoryEffect.UNKNOWN, statusEffect = StatusEffect.UNKNOWN, controlFlow = ControlFlowEffect.CALL) { + listOf(CallSiteSlotSchema(CallKind.FAR)) + } + define(Opcode.CALLFARVB, "N", memoryEffect = MemoryEffect.UNKNOWN, statusEffect = StatusEffect.UNKNOWN, controlFlow = ControlFlowEffect.CALL) { + listOf(CallSiteSlotSchema(CallKind.FAR_VARBANK)) + } + define(Opcode.SYSCALL, "N", memoryEffect = MemoryEffect.UNKNOWN, statusEffect = StatusEffect.UNKNOWN, controlFlow = ControlFlowEffect.CALL) { + listOf(CallSiteSlotSchema(CallKind.SYSCALL)) + } + define(Opcode.RETURN, "N", controlFlow = ControlFlowEffect.RETURN) { emptyList() } + define(Opcode.RETURNR, "BWLF", controlFlow = ControlFlowEffect.RETURN) { t -> + listOf(reg(fileOf(t), InstructionSlot.SRC_A, OperandRole.VALUE, OperandDirection.USE)) + } + define(Opcode.RETURNI, "BWLF", controlFlow = ControlFlowEffect.RETURN) { listOf(imm()) } + + for (branch in listOf(Opcode.BSTCC, Opcode.BSTCS, Opcode.BSTEQ, Opcode.BSTNE, Opcode.BSTNEG, Opcode.BSTPOS, Opcode.BSTVC, Opcode.BSTVS)) { + define(branch, "N", statusEffect = StatusEffect.READS, controlFlow = ControlFlowEffect.BRANCH) { + listOf(TargetSlotSchema(TargetKind.STATIC)) + } + } + for (branch in listOf(Opcode.BGTR, Opcode.BGTSR, Opcode.BGER, Opcode.BGESR)) { + define(branch, "BWL", controlFlow = ControlFlowEffect.BRANCH) { + listOf( + ireg(InstructionSlot.SRC_A, OperandRole.LEFT, OperandDirection.USE), + ireg(InstructionSlot.SRC_B, OperandRole.RIGHT, OperandDirection.USE), + TargetSlotSchema(TargetKind.STATIC) + ) + } + } + for (branch in listOf(Opcode.BGT, Opcode.BLT, Opcode.BGTS, Opcode.BLTS, Opcode.BGE, Opcode.BLE, Opcode.BGES, Opcode.BLES)) { + define(branch, "BWL", controlFlow = ControlFlowEffect.BRANCH) { + listOf( + ireg(InstructionSlot.SRC_A, OperandRole.LEFT, OperandDirection.USE), + imm(OperandRole.RIGHT), + TargetSlotSchema(TargetKind.STATIC) + ) + } + } + + // ---- arithmetic ---- + + for (opcode in listOf(Opcode.INC, Opcode.DEC, Opcode.NEG)) { + define(opcode, "BWLF") { t -> listOf(reg(fileOf(t), InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.USE_DEF)) } + } + for (opcode in listOf(Opcode.INCM, Opcode.DECM, Opcode.NEGM, Opcode.INVM)) { + val types = if (opcode == Opcode.INVM) "BWL" else "BWLF" + define(opcode, types, memoryEffect = MemoryEffect.READ_WRITE) { listOf(mem(MemoryKind.DIRECT, MemoryEffect.READ_WRITE)) } + } + + // register-register binary operations that modify the destination in place + for (opcode in listOf(Opcode.ADDR, Opcode.SUBR, Opcode.MULR, Opcode.MULSR, Opcode.DIVR, Opcode.DIVSR, Opcode.FPOW)) { + val types = when (opcode) { + Opcode.FPOW -> "F" + else -> "BWLF" + } + define(opcode, types) { t -> + listOf( + reg(fileOf(t), InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.USE_DEF), + reg(fileOf(t), InstructionSlot.SRC_A, OperandRole.RIGHT, OperandDirection.USE) + ) + } + } + for (opcode in listOf(Opcode.MODR, Opcode.MODSR, Opcode.ANDR, Opcode.ORR, Opcode.XORR)) { + define(opcode, "BWL") { + listOf( + ireg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.USE_DEF), + ireg(InstructionSlot.SRC_A, OperandRole.RIGHT, OperandDirection.USE) + ) + } + } + // register-immediate binary operations that modify the destination in place + for (opcode in listOf(Opcode.ADD, Opcode.SUB, Opcode.MUL, Opcode.MULS, Opcode.DIV, Opcode.DIVS)) { + define(opcode, "BWLF") { t -> + listOf( + reg(fileOf(t), InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.USE_DEF), + imm(OperandRole.RIGHT) + ) + } + } + for (opcode in listOf(Opcode.MOD, Opcode.MODS, Opcode.AND, Opcode.OR, Opcode.XOR)) { + define(opcode, "BWL") { + listOf( + ireg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.USE_DEF), + imm(OperandRole.RIGHT) + ) + } + } + // memory operations: register operand + memory destination + for (opcode in listOf(Opcode.ADDM, Opcode.SUBM, Opcode.MULM, Opcode.MULSM, Opcode.DIVM, Opcode.DIVSM)) { + define(opcode, "BWLF", memoryEffect = MemoryEffect.READ_WRITE) { t -> + listOf( + reg(fileOf(t), InstructionSlot.SRC_A, OperandRole.RIGHT, OperandDirection.USE), + mem(MemoryKind.DIRECT, MemoryEffect.READ_WRITE) + ) + } + } + for (opcode in listOf(Opcode.ANDM, Opcode.ORM, Opcode.XORM)) { + define(opcode, "BWL", memoryEffect = MemoryEffect.READ_WRITE) { + listOf( + ireg(InstructionSlot.SRC_A, OperandRole.RIGHT, OperandDirection.USE), + mem(MemoryKind.DIRECT, MemoryEffect.READ_WRITE) + ) + } + } + for (opcode in listOf(Opcode.ADDIM, Opcode.SUBIM)) { + define(opcode, "BWLF", memoryEffect = MemoryEffect.READ_WRITE) { + listOf( + imm(OperandRole.RIGHT), + mem(MemoryKind.DIRECT, MemoryEffect.READ_WRITE) + ) + } + } + + define(Opcode.DIVMODR, "BWL") { + listOf( + ireg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.USE_DEF), + ireg(InstructionSlot.DEST_B, OperandRole.SECONDARY_RESULT, OperandDirection.USE_DEF) + ) + } + define(Opcode.SDIVMODR, "BWL") { + listOf( + ireg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.USE_DEF), + ireg(InstructionSlot.DEST_B, OperandRole.SECONDARY_RESULT, OperandDirection.USE_DEF) + ) + } + for (opcode in listOf(Opcode.DIVMOD, Opcode.SDIVMOD)) { + define(opcode, "BWL") { + listOf( + ireg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.USE_DEF), + ireg(InstructionSlot.DEST_B, OperandRole.SECONDARY_RESULT, OperandDirection.DEF), + imm(OperandRole.RIGHT) + ) + } + } + + define(Opcode.SQRT, "BWLF") { t -> + val destType = when (t) { + IRDataType.LONG -> IRDataType.WORD + IRDataType.FLOAT -> null + else -> IRDataType.BYTE + } + if (t == IRDataType.FLOAT) + listOf( + freg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF), + freg(InstructionSlot.SRC_A, OperandRole.VALUE, OperandDirection.USE) + ) + else + listOf( + ireg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF, TypeRule.Fixed(destType!!)), + ireg(InstructionSlot.SRC_A, OperandRole.VALUE, OperandDirection.USE) + ) + } + define(Opcode.SQUARE, "BWLF") { t -> + listOf( + reg(fileOf(t), InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF), + reg(fileOf(t), InstructionSlot.SRC_A, OperandRole.VALUE, OperandDirection.USE) + ) + } + define(Opcode.SGN, "BWLF", statusEffect = StatusEffect.SETS) { t -> + listOf( + ireg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF, TypeRule.Fixed(IRDataType.BYTE)), + reg(fileOf(t), InstructionSlot.SRC_A, OperandRole.VALUE, OperandDirection.USE) + ) + } + define(Opcode.CMP, "BWL", statusEffect = StatusEffect.SETS) { + listOf( + ireg(InstructionSlot.SRC_A, OperandRole.LEFT, OperandDirection.USE), + ireg(InstructionSlot.SRC_B, OperandRole.RIGHT, OperandDirection.USE) + ) + } + define(Opcode.CMPI, "BWL", statusEffect = StatusEffect.SETS) { + listOf( + ireg(InstructionSlot.SRC_A, OperandRole.LEFT, OperandDirection.USE), + imm(OperandRole.RIGHT) + ) + } + for (opcode in listOf(Opcode.EXT, Opcode.EXTS)) { + define(opcode, "BWL") { t -> + listOf( + ireg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF, TypeRule.Fixed(widened(t))), + ireg(InstructionSlot.SRC_A, OperandRole.VALUE, OperandDirection.USE) + ) + } + } + for (opcode in listOf(Opcode.EXTL, Opcode.EXTLS)) { + define(opcode, "B") { + listOf( + ireg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF, TypeRule.Fixed(IRDataType.LONG)), + ireg(InstructionSlot.SRC_A, OperandRole.VALUE, OperandDirection.USE) + ) + } + } + + // ---- bit operations, shifts and rotates ---- + + define(Opcode.INV, "BWL") { listOf(ireg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.USE_DEF)) } + for (opcode in listOf(Opcode.ASRN, Opcode.LSRN, Opcode.LSLN)) { + define(opcode, "BWL", statusEffect = StatusEffect.SETS) { + listOf( + ireg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.USE_DEF), + ireg(InstructionSlot.SRC_A, OperandRole.RIGHT, OperandDirection.USE, TypeRule.Fixed(IRDataType.BYTE)) + ) + } + } + for (opcode in listOf(Opcode.ASRNM, Opcode.LSRNM, Opcode.LSLNM)) { + define(opcode, "BWL", memoryEffect = MemoryEffect.READ_WRITE, statusEffect = StatusEffect.SETS) { + listOf( + ireg(InstructionSlot.SRC_A, OperandRole.RIGHT, OperandDirection.USE, TypeRule.Fixed(IRDataType.BYTE)), + mem(MemoryKind.DIRECT, MemoryEffect.READ_WRITE) + ) + } + } + for (opcode in listOf(Opcode.ASRI, Opcode.LSRI, Opcode.LSLI)) { + define(opcode, "BWL", statusEffect = StatusEffect.SETS) { + listOf( + ireg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.USE_DEF), + imm(OperandRole.RIGHT) + ) + } + } + for (opcode in listOf(Opcode.ASR, Opcode.LSR, Opcode.LSL, Opcode.ROR, Opcode.ROL)) { + define(opcode, "BWL", statusEffect = StatusEffect.SETS) { + listOf(ireg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.USE_DEF)) + } + } + for (opcode in listOf(Opcode.ROXR, Opcode.ROXL)) { + define(opcode, "BWL", statusEffect = StatusEffect.READS_AND_SETS) { + listOf(ireg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.USE_DEF)) + } + } + for (opcode in listOf(Opcode.ASRM, Opcode.LSRM, Opcode.LSLM, Opcode.RORM, Opcode.ROLM)) { + define(opcode, "BWL", memoryEffect = MemoryEffect.READ_WRITE, statusEffect = StatusEffect.SETS) { + listOf(mem(MemoryKind.DIRECT, MemoryEffect.READ_WRITE)) + } + } + for (opcode in listOf(Opcode.ROXRM, Opcode.ROXLM)) { + define(opcode, "BWL", memoryEffect = MemoryEffect.READ_WRITE, statusEffect = StatusEffect.READS_AND_SETS) { + listOf(mem(MemoryKind.DIRECT, MemoryEffect.READ_WRITE)) + } + } + define(Opcode.BITTST, "BWL", statusEffect = StatusEffect.SETS) { + listOf( + ireg(InstructionSlot.SRC_A, OperandRole.VALUE, OperandDirection.USE), + imm(OperandRole.RIGHT, TypeRule.Fixed(IRDataType.BYTE)) + ) + } + for (opcode in listOf(Opcode.BITSET, Opcode.BITCLR, Opcode.BITTOG)) { + define(opcode, "BWL") { + listOf( + ireg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.USE_DEF), + imm(OperandRole.RIGHT, TypeRule.Fixed(IRDataType.BYTE)) + ) + } + } + + // ---- floating point conversions ---- + + val fromIntegerTypes = mapOf( + Opcode.FFROMUB to IRDataType.BYTE, + Opcode.FFROMSB to IRDataType.BYTE, + Opcode.FFROMUW to IRDataType.WORD, + Opcode.FFROMSW to IRDataType.WORD, + Opcode.FFROMSL to IRDataType.LONG + ) + for ((opcode, intType) in fromIntegerTypes) { + define(opcode, "F") { + listOf( + freg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF), + ireg(InstructionSlot.SRC_A, OperandRole.VALUE, OperandDirection.USE, TypeRule.Fixed(intType)) + ) + } + } + val toIntegerTypes = mapOf( + Opcode.FTOUB to IRDataType.BYTE, + Opcode.FTOSB to IRDataType.BYTE, + Opcode.FTOUW to IRDataType.WORD, + Opcode.FTOSW to IRDataType.WORD, + Opcode.FTOSL to IRDataType.LONG + ) + for ((opcode, intType) in toIntegerTypes) { + define(opcode, "F") { + listOf( + ireg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF, TypeRule.Fixed(intType)), + freg(InstructionSlot.SRC_A, OperandRole.VALUE, OperandDirection.USE) + ) + } + } + for (opcode in listOf(Opcode.FABS, Opcode.FSIN, Opcode.FCOS, Opcode.FTAN, Opcode.FATAN, Opcode.FLN, Opcode.FLOG, + Opcode.FROUND, Opcode.FFLOOR, Opcode.FCEIL)) { + define(opcode, "F") { + listOf( + freg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF), + freg(InstructionSlot.SRC_A, OperandRole.VALUE, OperandDirection.USE) + ) + } + } + define(Opcode.FCOMP, "F") { + listOf( + ireg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF, TypeRule.Fixed(IRDataType.BYTE)), + freg(InstructionSlot.SRC_A, OperandRole.LEFT, OperandDirection.USE), + freg(InstructionSlot.SRC_B, OperandRole.RIGHT, OperandDirection.USE) + ) + } + + // ---- misc ---- + + for (opcode in listOf(Opcode.CLC, Opcode.SEC, Opcode.CLI, Opcode.SEI)) + define(opcode, "N", statusEffect = StatusEffect.SETS) { emptyList() } + define(Opcode.PUSH, "BWLF") { t -> listOf(reg(fileOf(t), InstructionSlot.SRC_A, OperandRole.VALUE, OperandDirection.USE)) } + define(Opcode.POP, "BWLF") { t -> listOf(reg(fileOf(t), InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF)) } + define(Opcode.PUSHST, "N", statusEffect = StatusEffect.READS) { emptyList() } + define(Opcode.POPST, "N", statusEffect = StatusEffect.SETS) { emptyList() } + + val extractions = listOf( + Triple(Opcode.LSIGB, "WL", IRDataType.BYTE), + Triple(Opcode.MSIGB, "WL", IRDataType.BYTE), + Triple(Opcode.LSIGW, "L", IRDataType.WORD), + Triple(Opcode.MSIGW, "L", IRDataType.WORD), + Triple(Opcode.BSIGB, "L", IRDataType.BYTE), + Triple(Opcode.MIDB, "L", IRDataType.BYTE) + ) + for ((opcode, types, destType) in extractions) { + define(opcode, types) { + listOf( + ireg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF, TypeRule.Fixed(destType)), + ireg(InstructionSlot.SRC_A, OperandRole.VALUE, OperandDirection.USE) + ) + } + } + define(Opcode.CONCAT, "BW") { t -> + listOf( + ireg(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.DEF, TypeRule.Fixed(widened(t))), + ireg(InstructionSlot.SRC_A, OperandRole.LEFT, OperandDirection.USE), + ireg(InstructionSlot.SRC_B, OperandRole.RIGHT, OperandDirection.USE) + ) + } + define(Opcode.BREAKPOINT, "N") { emptyList() } + define(Opcode.ALIGN, "N") { listOf(imm(OperandRole.VALUE, TypeRule.Fixed(IRDataType.LONG))) } + + require(result.keys == Opcode.entries.toSet()) { + "opcode schema is incomplete, missing: ${Opcode.entries.toSet() - result.keys}" + } + return result + } +} diff --git a/intermediate/src/prog8/intermediate/Utils.kt b/intermediate/src/prog8/intermediate/Utils.kt index 3d5aafce7..ac65e918d 100644 --- a/intermediate/src/prog8/intermediate/Utils.kt +++ b/intermediate/src/prog8/intermediate/Utils.kt @@ -87,315 +87,20 @@ sealed interface ParsedIRLine { data class Label(val name: String) : ParsedIRLine } -private val instructionPattern = Regex("""([a-z_]+)(\.b|\.w|\.l|\.f|\.p)?(.*)""", RegexOption.IGNORE_CASE) private val labelPattern = Regex("""_([a-zA-Z\d\._]+):""") fun parseIRCodeLine(line: String): ParsedIRLine { val labelmatch = labelPattern.matchEntire(line.trim()) - if(labelmatch!=null) - return ParsedIRLine.Label(labelmatch.groupValues[1]) - - val match = instructionPattern.matchEntire(line) - ?: throw IRParseException("invalid IR instruction: $line") - val (instr, typestr, rest) = match.destructured - val opcode = try { - Opcode.valueOf(instr.uppercase()) - } catch (_: IllegalArgumentException) { - throw IRParseException("invalid IR instruction: $instr") - } - var type: IRDataType? = convertIRType(typestr) - val formats = instructionFormats.getValue(opcode) - val format: InstructionFormat = - if(type !in formats) { - type = IRDataType.BYTE - if(type !in formats) - formats.getValue(null) - else - formats.getValue(type) - } else { - formats.getValue(type) - } - - // parse the operands, detect optional ,S=N suffix for scaled indexing - val operandsRaw = if(rest.isBlank()) emptyList() else rest.split(",").map{ it.trim() }.toMutableList() - var scale = 1 - val operands = operandsRaw.toMutableList() - if(opcode in setOf(Opcode.LOADX, Opcode.STOREX, Opcode.STOREZX) && operands.isNotEmpty()) { - val last = operands.last() - if(last.startsWith("S=", ignoreCase = true)) { - val scaleStr = last.substring(2) - scale = scaleStr.toIntOrNull() ?: throw IRParseException("invalid scale suffix $last") - if(scale < 1) throw IRParseException("scale must be >=1") - operands.removeAt(operands.lastIndex) - } - } - var reg1: Int? = null - var reg2: Int? = null - var reg3: Int? = null - var fpReg1: RegisterNum? = null - var fpReg2: RegisterNum? = null - var immediateInt: Int? = null - var immediateFp: Double? = null - var address: UInt? = null - var labelSymbol: String? = null - - fun parseValueOrPlaceholder(operand: String): Double? { - return if(operand[0].isLetter()) { - null - } else { - parseIRValue(operand) - } - } - if(format.sysCall) { - val call = parseCall(rest) - val syscallNum = call.address?.toInt() ?: (call.target?.let { parseIRValue(it).toInt() } ?: throw IRParseException("Missing syscall number")) - return ParsedIRLine.Instruction(IRInstruction(Opcode.SYSCALL, immediate = syscallNum, fcallArgs = FunctionCallArgs(call.args, call.returns))) - } else if (format.funcCall || opcode in setOf(Opcode.CALLFAR, Opcode.CALLFARVB)) { - val call = parseCall(rest) - val ir = when(opcode) { - Opcode.CALLFAR, Opcode.CALLFARVB -> - IRInstruction(opcode, address = call.address?.toAddress(), immediate = call.immediate?.toInt(), fcallArgs = FunctionCallArgs(call.args, call.returns)) - else -> - IRInstruction(Opcode.CALL, address = call.address?.toAddress(), labelSymbol = call.target, fcallArgs = FunctionCallArgs(call.args, call.returns)) - } - return ParsedIRLine.Instruction(ir) - } else { - operands.forEach { oper -> - if (oper[0] == '&') - throw IRParseException("address-of should be done with normal LOAD ") - else if (isRegisterName(oper)) { - if (reg1 == null) reg1 = oper.substring(1).toInt() - else if (reg2 == null) reg2 = oper.substring(1).toInt() - else if (reg3 == null) reg3 = oper.substring(1).toInt() - else throw IRParseException("too many register operands") - } else if (isFloatRegisterName(oper)) { - if (fpReg1 == null) fpReg1 = RegisterNum(oper.substring(2).toInt()) - else if (fpReg2 == null) fpReg2 = RegisterNum(oper.substring(2).toInt()) - else throw IRParseException("too many fp register operands") - } else if (opcode in setOf(Opcode.LOADHR, Opcode.STOREHR) && oper[0] == 's' && oper.length > 1 && oper[1].isDigit()) { - immediateInt = oper.drop(1).toInt() - } else if (oper[0] in "0123456789$%-#" || oper.startsWith("0x")) { - val value = if(oper[0]=='#') parseIRValue(oper.drop(1)) else parseIRValue(oper) - if (format.immediate) { - if (immediateInt == null && immediateFp == null) { - when (type) { - IRDataType.FLOAT if opcode != Opcode.LOADI && opcode != Opcode.STOREI -> immediateFp = value - IRDataType.LONG -> { - val immediateLong = value.toLong() - immediateInt = if (immediateLong == 0x80000000L) -2147483648 else immediateLong.toInt() - } - else -> immediateInt = value.toInt() - } - } else { - address = value.toUInt() - } - } else { - address = value.toUInt() - } - } else { - if (!oper[0].isLetter()) - throw IRParseException("expected symbol name: $oper") - labelSymbol = oper - val value = parseValueOrPlaceholder(oper) - if (value != null) - address = value.toUInt() - } - } - } - - if(type!=null && type !in formats) - throw IRParseException("invalid type code for $line") - if(format.reg1!=OperandDirection.UNUSED && reg1==null) - throw IRParseException("needs reg1 for $line") - if(format.reg2!=OperandDirection.UNUSED && reg2==null) - throw IRParseException("needs reg2 for $line") - if(format.reg3!=OperandDirection.UNUSED && reg3==null) - throw IRParseException("needs reg3 for $line") - if(format.fpReg1!=OperandDirection.UNUSED && fpReg1==null) - throw IRParseException("needs fpReg1 for $line") - if(format.fpReg2!=OperandDirection.UNUSED && fpReg2==null) - throw IRParseException("needs fpReg2 for $line") - if(format.address!=OperandDirection.UNUSED && address==null && labelSymbol==null) - throw IRParseException("needs address or symbol for $line") - if(format.reg1==OperandDirection.UNUSED && reg1!=null) - throw IRParseException("invalid reg1 for $line") - if(format.reg2==OperandDirection.UNUSED && reg2!=null) - throw IRParseException("invalid reg2 for $line") - if(format.reg3==OperandDirection.UNUSED && reg3!=null) - throw IRParseException("invalid reg3 for $line") - if(format.fpReg1==OperandDirection.UNUSED && fpReg1!=null) - throw IRParseException("invalid fpReg1 for $line") - if(format.fpReg2==OperandDirection.UNUSED && fpReg2!=null) - throw IRParseException("invalid fpReg2 for $line") - if(format.immediate && opcode!=Opcode.SYSCALL) { - if(immediateInt==null && immediateFp==null && labelSymbol==null) - throw IRParseException("needs value or symbol for $line") - if(opcode==Opcode.LOADI || opcode==Opcode.STOREI) { - if(immediateInt !in 0..65535) - throw IRParseException("immediate value out of range for loadi/storei: $immediateInt") - } else { - when (type) { - IRDataType.BYTE -> { - if (immediateInt!=null && (immediateInt < -128 || immediateInt > 255)) - throw IRParseException("immediate value out of range for byte: $immediateInt") - } - IRDataType.WORD -> { - if (immediateInt!=null && (immediateInt < -32768 || immediateInt > 65535)) - throw IRParseException("immediate value out of range for word: $immediateInt") - } - IRDataType.POINTER, IRDataType.LONG -> { - if (immediateInt!=null && (immediateInt.toLong() < -2147483648L || immediateInt.toLong() > 0x7fffffffL)) - throw IRParseException("immediate value out of range for long: $immediateInt") - } - IRDataType.FLOAT -> {} - null -> {} - } - } - } - - if(format.address!=OperandDirection.UNUSED && address==null && labelSymbol==null) - throw IRParseException("requires address or symbol for $line") - - var offset: Int? = null - if(labelSymbol!=null) { - if (labelSymbol[0] == 'r' && labelSymbol[1].isDigit()) - throw IRParseException("labelsymbol confused with register?: $labelSymbol") - if('+' in labelSymbol) { - val offsetStr = labelSymbol.substringAfterLast('+') - if (offsetStr.isNotEmpty()) { - offset = offsetStr.toInt() - labelSymbol = labelSymbol.substringBeforeLast('+') - } - } - } - - return ParsedIRLine.Instruction(IRInstruction(opcode, type, reg1, reg2, reg3, fpReg1, fpReg2, immediateInt, immediateFp, address?.toAddress(), labelSymbol = labelSymbol, symbolOffset = offset, scale = scale)) -} - -private fun isRegisterName(oper: String): Boolean { - if(oper[0] in "rR") { + if(labelmatch!=null) { + val name = labelmatch.groupValues[1] try { - oper.substring(1).toInt() - return true - } catch(_: NumberFormatException) { - return false + requireValidIRSymbolName(name) + } catch (ex: IllegalArgumentException) { + throw IRParseException(ex.message ?: "invalid label: $name") } + return ParsedIRLine.Label(name) } - return false -} - -private fun isFloatRegisterName(oper: String): Boolean { - if(oper[0] in "fF" && oper[1] in "rR") { - try { - oper.substring(2).toInt() - return true - } catch(_: NumberFormatException) { - return false - } - } - return false -} - -private class ParsedCall( - val target: String?, - val address: UInt?, - val args: List, - val returns: List, - val immediate: UInt? = null -) - -private val callPattern = Regex("(?.+?)\\((?.*?)\\)(:(?.+?))?") - -private fun parseCall(rest: String): ParsedCall { - - fun parseRegspec(reg: String): FunctionCallArgs.RegSpec { - val pattern = Regex("f?r([0-9]+)\\.(.)(@.{1,5})?$") - val match = pattern.matchEntire(reg) ?: throw IRParseException("invalid regspec $reg") - val num = match.groups[1]!!.value.toInt() - val type = when(match.groups[2]!!.value) { - "b" -> IRDataType.BYTE - "w" -> IRDataType.WORD - "l" -> IRDataType.LONG - "f" -> IRDataType.FLOAT - "p" -> IRDataType.POINTER - else -> throw IRParseException("invalid type spec in $reg") - } - val (callingConventionSlot, statusflag) = - if(match.groups[3]!=null) { - val cpuRegStr = match.groups[3]!!.value.drop(1) - if (cpuRegStr.startsWith('s') && cpuRegStr.length > 1) { - val slotNum = cpuRegStr.substring(1).toIntOrNull() ?: throw IRParseException("invalid slot $cpuRegStr") - CallingConventionSlot(slotNum) to null - } else { - null to Statusflag.valueOf(cpuRegStr) - } - } else null to null - return FunctionCallArgs.RegSpec(type, RegisterNum(num), callingConventionSlot, statusflag) - } - - fun parseReturnRegspec(regs: String?): List { - if(regs==null) - return emptyList() - return regs.split(',').map { reg-> - if (reg.startsWith('@')) { - val rest = reg.drop(1) - if (rest.startsWith('s') && rest.length > 1) { - val slotNum = rest.substring(1).toIntOrNull() ?: throw IRParseException("invalid slot $rest") - FunctionCallArgs.RegSpec(IRDataType.BYTE, RegisterNum(-1), CallingConventionSlot(slotNum), null) - } else { - FunctionCallArgs.RegSpec(IRDataType.BYTE, RegisterNum(-1), null, Statusflag.valueOf(rest)) - } - } else { - parseRegspec(reg) - } - } - } - - fun parseArgs(args: String): List { - if(args.isBlank()) - return emptyList() - return args.split(',').map { - if(it.contains('=')) { - val (argVar, argReg) = it.split('=') - FunctionCallArgs.ArgumentSpec(argVar, null, parseRegspec(argReg)) // address will be set later - } else { - FunctionCallArgs.ArgumentSpec("", null, parseRegspec(it)) // address will be set later - } - } - } - - val match = callPattern.matchEntire(rest.replace(" ","")) ?: throw IRParseException("invalid call spec $rest") - val target = match.groups["target"]!!.value - val args = match.groups["arglist"]!!.value - val arguments = parseArgs(args) - val returns = match.groups["returns"]?.value - var address: UInt? = null - var actualTarget: String? = null - var immediate: UInt? = null - - if(target.startsWith('#')) { - val parts = target.split(",", limit=2) - immediate = parseIRValue(parts.first().drop(1)).toUInt() - val restTarget = parts.getOrElse(1) { "" } - if(restTarget.startsWith('$') || restTarget.startsWith('-') || restTarget[0].isDigit()) { - val value = parseIRValue(restTarget) - address = if(value<0) value.toInt().toUInt() else value.toUInt() - } else { - actualTarget = restTarget - } - } else if(target.startsWith('$') || target[0].isDigit()) { - address = parseIRValue(target).toUInt() - } else { - actualTarget = target - } - - return ParsedCall( - actualTarget, - address, - arguments, - parseReturnRegspec(returns), - immediate - ) + return ParsedIRLine.Instruction(IRTextCodec.parse(line)) } diff --git a/intermediate/test/TestIRFileInOut.kt b/intermediate/test/TestIRFileInOut.kt index 006009da4..2f874e749 100644 --- a/intermediate/test/TestIRFileInOut.kt +++ b/intermediate/test/TestIRFileInOut.kt @@ -1,3 +1,4 @@ +import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.ints.shouldBeGreaterThan import io.kotest.matchers.shouldBe @@ -8,9 +9,17 @@ import prog8.intermediate.* import kotlin.io.path.* class TestIRFileInOut: FunSpec({ + test("IR reader requires format 2") { + shouldThrow { + IRFileReader().read("""""") + }.message shouldBe "missing IRFORMAT" + shouldThrow { + IRFileReader().read("""""") + }.message shouldBe "unsupported IR format: 1" + } + test("test IR writer") { val target = Cx16Target() - val tempdir = Path(System.getProperty("java.io.tmpdir")) val options = CompilationOptions.builder(target) .output(OutputType.RAW) .zeropage(ZeropageType.DONTUSE) @@ -18,14 +27,14 @@ class TestIRFileInOut: FunSpec({ .compilerVersion("99.99") .loadAddress(target.PROGRAM_LOAD_ADDRESS) .memtopAddress(0xffffu) - .outputDir(tempdir) + .outputDir(Path("")) .build() val program = IRProgram("unittest-irwriter", IRSymbolTable(), options, target) - val writer = IRFileWriter(program, null) + val writer = IRFileWriter(program, Path("intermediate-irwriter-test-output.p8ir")) val generatedFile = writer.write() val lines = generatedFile.readLines() lines[0] shouldBe "" - lines[1] shouldBe "" + lines[1] shouldBe "" lines.last() shouldBe "" generatedFile.deleteExisting() lines.size shouldBeGreaterThan 20 @@ -33,7 +42,7 @@ class TestIRFileInOut: FunSpec({ test("test IR reader") { val source=""" - + compTarget=virtual output=PRG @@ -76,7 +85,7 @@ ubyte main.thing=42 dummy -load.b r1,#42 +load.b r1.b,#42.b @@ -96,7 +105,7 @@ return uword sys.wait.jiffies - loadm.w r0,sys.wait.jiffies + loadm.w r0.w,[sys.wait.jiffies] dummy return @@ -105,10 +114,7 @@ return """ - val tempfile = createTempFile(suffix = ".p8ir") - tempfile.writeText(source) - val program = IRFileReader().read(tempfile) - tempfile.deleteExisting() + val program = IRFileReader().read(source) program.name shouldBe "test-ir-reader" program.blocks.size shouldBe 2 program.st.allVariables().count() shouldBe 3 @@ -123,7 +129,7 @@ return test("test IR reader with struct containing pointer fields") { val source=""" - + compTarget=virtual output=PRG @@ -171,16 +177,13 @@ re.State testinst size=8 values=uword:$0100,^^re.State:0,^^re.State:0,uword:0 dummy -load.b r1,#0 +load.b r1.b,#0.b """ - val tempfile = createTempFile(suffix = ".p8ir") - tempfile.writeText(source) - val program = IRFileReader().read(tempfile) - tempfile.deleteExisting() + val program = IRFileReader().read(source) program.name shouldBe "test-struct-pointer" val struct = program.st.allStructInstances().first() struct.name shouldBe "testinst" @@ -191,7 +194,6 @@ load.b r1,#0 // regression test: struct field floats used to be serialized via .toInt().toHex() // which truncated the decimal portion (e.g. 1463.87 became 1463.0 in the loaded VM memory) val target = Cx16Target() - val tempdir = Path(System.getProperty("java.io.tmpdir")) val options = CompilationOptions.builder(target) .output(OutputType.RAW) .zeropage(ZeropageType.DONTUSE) @@ -199,7 +201,7 @@ load.b r1,#0 .compilerVersion("99.99") .loadAddress(target.PROGRAM_LOAD_ADDRESS) .memtopAddress(0xffffu) - .outputDir(tempdir) + .outputDir(Path("")) .build() val program = IRProgram("unittest-float-struct", IRSymbolTable(), options, target) val structName = "main.Country" @@ -222,7 +224,7 @@ load.b r1,#0 ), 8u )) - val writer = IRFileWriter(program, null) + val writer = IRFileWriter(program, Path("intermediate-float-struct-test-output.p8ir")) val generatedFile = writer.write() val program2 = IRFileReader().read(generatedFile) generatedFile.deleteExisting() @@ -236,7 +238,7 @@ load.b r1,#0 test("test IR reader parses loadhr/storehr sN immediate encoding") { val source=""" - + compTarget=virtual output=PRG @@ -275,70 +277,119 @@ loadAddress=$0000 dummy -loadhr.b r1,s0 -storehr.b r1,s2 +loadhr.b r1.b,s0.b +storehr.b r1.b,s2.b """ - val tempfile = createTempFile(suffix = ".p8ir") - tempfile.writeText(source) - val program = IRFileReader().read(tempfile) - tempfile.deleteExisting() + val program = IRFileReader().read(source) val sub = program.blocks.single().children.single() as IRSubroutine val instructions = sub.chunks.flatMap { it.instructions } instructions.size shouldBe 2 instructions[0].opcode shouldBe Opcode.LOADHR instructions[0].type shouldBe IRDataType.BYTE - instructions[0].reg1 shouldBe 1 - instructions[0].immediate shouldBe 0 + instructions[0].requireDest().registerNumber shouldBe 1 + instructions[0].requireHardwareSlot() shouldBe HardwareSlotOperand(CallingConventionSlot(0), IRDataType.BYTE) instructions[1].opcode shouldBe Opcode.STOREHR instructions[1].type shouldBe IRDataType.BYTE - instructions[1].reg1 shouldBe 1 - instructions[1].immediate shouldBe 2 + instructions[1].requireSrcA().registerNumber shouldBe 1 + instructions[1].requireHardwareSlot() shouldBe HardwareSlotOperand(CallingConventionSlot(2), IRDataType.BYTE) } test("test IR callfar round-trip with negative amiga LVO address") { val target = Amiga500Target() - val tempdir = Path(System.getProperty("java.io.tmpdir")) val options = CompilationOptions.builder(target) .zeropage(ZeropageType.DONTUSE) .noSysInit(true) .compilerVersion("99.99") .loadAddress(target.PROGRAM_LOAD_ADDRESS) .memtopAddress(target.PROGRAM_MEMTOP_ADDRESS) - .outputDir(tempdir) + .outputDir(Path("")) .quiet(true) .build() val program = IRProgram("unittest-callfar-lvo", IRSymbolTable(), options, target) val block = IRBlock("main", library=false, IRBlock.Options(), Position("unittest", 1, 1, 1)) val sub = IRSubroutine("main.start", emptyList(), emptyList(), Position("unittest", 1, 1, 1)) val chunk = IRCodeChunk("main.start", null) - chunk.instructions += IRInstruction( + chunk.instructions += IRInstructions.call( Opcode.CALLFAR, - immediate = 1, - address = MemoryAddress(0xfffffdd8u), - fcallArgs = FunctionCallArgs(emptyList(), emptyList()) + CallSite(CallTarget.AmigaLibrary(1, -552)) ) sub += chunk block += sub program.addBlock(block) - val generatedFile = IRFileWriter(program, null).write() + val generatedFile = IRFileWriter(program, Path("intermediate-callfar-test-output.p8ir")).write() val readProgram = IRFileReader().read(generatedFile) generatedFile.deleteExisting() val readSub = readProgram.blocks.single().children.single() as IRSubroutine val instr = readSub.chunks.flatMap { it.instructions }.single() instr.opcode shouldBe Opcode.CALLFAR - instr.immediate shouldBe 1 - instr.address shouldBe MemoryAddress(0xfffffdd8u) + instr.requireCallSite().target shouldBe CallTarget.AmigaLibrary(1, -552) + } + + test("test IR file round-trip retains structured call details") { + val target = Cx16Target() + val options = CompilationOptions.builder(target) + .output(OutputType.RAW) + .zeropage(ZeropageType.DONTUSE) + .noSysInit(true) + .compilerVersion("99.99") + .loadAddress(target.PROGRAM_LOAD_ADDRESS) + .memtopAddress(0xffffu) + .outputDir(Path("")) + .build() + val program = IRProgram("unittest-structured-calls", IRSymbolTable(), options, target) + val block = IRBlock("main", library = false, IRBlock.Options(), Position.DUMMY) + val start = IRSubroutine("main.start", emptyList(), emptyList(), Position.DUMMY) + val startChunk = IRCodeChunk("main.start", null) + startChunk += IRInstructions.call( + CallSite( + CallTarget.Direct(codeLabel("main.callee")), + arguments = listOf(Calls.argument(1, IRDataType.BYTE, CallLocation.ParameterMemory("main.callee.arg"))) + ) + ) + startChunk += IRInstructions.call( + CallSite( + CallTarget.Direct(codeAddress(0xffd2u), "kernal.chrout"), + arguments = listOf(Calls.argument(1, IRDataType.BYTE, CallLocation.HardwareRegister(CallingConventionSlot(0)))), + results = listOf( + Calls.result(2, IRDataType.BYTE, CallLocation.HardwareRegister(CallingConventionSlot(0))), + CallResult(null, CallLocation.StatusFlag(Statusflag.Pc)) + ), + effects = CallEffects(MemoryEffect.READ, StatusEffect.READS) + ) + ) + startChunk += IRInstructions.returnVoid() + start += startChunk + val callee = IRSubroutine("main.callee", emptyList(), emptyList(), Position.DUMMY) + val calleeChunk = IRCodeChunk("main.callee", null) + calleeChunk += IRInstructions.returnVoid() + callee += calleeChunk + block += start + block += callee + program.addBlock(block) + + val generatedFile = IRFileWriter(program, Path("intermediate-structured-calls-test-output.p8ir")).write() + val readProgram = IRFileReader().read(generatedFile) + generatedFile.deleteExisting() + + val calls = (readProgram.blocks.single().children[0] as IRSubroutine) + .chunks.flatMap { it.instructions }.filter { it.opcode == Opcode.CALL } + calls[0].requireCallSite().arguments.single().location shouldBe CallLocation.ParameterMemory("main.callee.arg") + val asmCall = calls[1].requireCallSite() + (asmCall.target as CallTarget.Direct).externalName shouldBe "kernal.chrout" + asmCall.results[0].location shouldBe CallLocation.HardwareRegister(CallingConventionSlot(0)) + asmCall.results[1] shouldBe CallResult(null, CallLocation.StatusFlag(Statusflag.Pc)) + asmCall.effects shouldBe CallEffects(MemoryEffect.READ, StatusEffect.READS) } test("test IR reader parses block-level CHUNK (label and align)") { val source=""" - + compTarget=virtual output=PRG @@ -374,7 +425,7 @@ loadAddress=$0000 -align #$100 +align #$100.l @@ -388,10 +439,7 @@ return """ - val tempfile = createTempFile(suffix = ".p8ir") - tempfile.writeText(source) - val program = IRFileReader().read(tempfile) - tempfile.deleteExisting() + val program = IRFileReader().read(source) val block = program.blocks.single() // 1 align chunk + 1 label chunk + 1 sub block.children.size shouldBe 3 @@ -399,7 +447,7 @@ return alignChunk.label shouldBe null alignChunk.instructions.size shouldBe 1 alignChunk.instructions[0].opcode shouldBe Opcode.ALIGN - alignChunk.instructions[0].immediate shouldBe 256 + alignChunk.instructions[0].requireImmediateInt() shouldBe 256 val labelChunk = block.children[1] as IRCodeChunk labelChunk.label shouldBe "main.mylabel" } diff --git a/intermediate/test/TestIRTextCodec.kt b/intermediate/test/TestIRTextCodec.kt new file mode 100644 index 000000000..e4cbb41ec --- /dev/null +++ b/intermediate/test/TestIRTextCodec.kt @@ -0,0 +1,170 @@ +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import prog8.code.core.Statusflag +import prog8.intermediate.* + + +class TestIRTextCodec: FunSpec({ + + fun roundtrip(text: String) { + IRTextCodec.print(IRTextCodec.parse(text)) shouldBe text + } + + test("registers and immediates") { + roundtrip("load.w r1.w,#\$1234.w") + roundtrip("load.b r1.b,#\$2a.b") + roundtrip("load.f fr1.f,#3.14.f") + roundtrip("load.p r1.p,#main.items") + roundtrip("load.w r1.w,#main.items+4") + roundtrip("loadr.l r1.l,r2.l") + roundtrip("nop") + roundtrip("return") + } + + test("memory references") { + roundtrip("loadi.b r1.b,[r2.p+8]") + roundtrip("loadi.b r1.b,[r2.p]") + roundtrip("loadx.w r1.w,[main.items+4+r2.w*2]") + roundtrip("loadx.w r1.w,[main.items+r2.w]") + roundtrip("loadm.w r0.w,[sys.wait.jiffies]") + roundtrip("loadm.b r0.b,[\$d020]") + roundtrip("storem.b r0.b,[main.var+2]") + roundtrip("storezm.w [main.var]") + roundtrip("storezx.b [main.array+r3.w*4]") + roundtrip("storeim.b #\$2a.b,[main.var]") + roundtrip("incm.w [main.var]") + roundtrip("addm.b r5.b,[main.var]") + roundtrip("addim.b #3.b,[main.var]") + } + + test("hardware slots") { + roundtrip("loadhr.w r1.w,s10.w") + roundtrip("storehr.b r1.b,s0.b") + roundtrip("loadhfaczero.f fr3.f") + } + + test("branches and jumps") { + roundtrip("jump main.label") + roundtrip("jump \$c000") + roundtrip("jumpi (r5.p)") + roundtrip("bsteq main.label") + roundtrip("bstcc main.label") + roundtrip("bgt.b r1.b,#10.b,main.label") + roundtrip("bgtr.w r1.w,r2.w,main.label") + roundtrip("bgesr.l r1.l,r2.l,main.label") + roundtrip("bles.b r1.b,#-5.b,main.label") + } + + test("two-result division") { + roundtrip("divmod.w r1.w,r2.w,#7.w") + roundtrip("sdivmod.b r1.b,r2.b,#7.b") + roundtrip("divmodr.w r1.w,r2.w") + roundtrip("sdivmodr.w r1.w,r2.w") + } + + test("divmod preserves quotient and remainder register order") { + val original = IRInstructions.divmodImmediate(Opcode.DIVMOD, IRDataType.WORD, quotient = 1, remainder = 2, value = 7) + val parsed = (parseIRCodeLine(original.toString()) as ParsedIRLine.Instruction).value + parsed.requireDest().registerNumber shouldBe 1 + parsed.requireDestB().registerNumber shouldBe 2 + parsed shouldBe original + } + + test("all call opcodes") { + roundtrip("call main.foo()") + roundtrip("call main.foo(main.foo.arg=r1.b,r2.w):r3.w") + roundtrip("call \$ffd2(r5.b@s0)") + roundtrip("call main.foo(r1.b):r2.b@s0,@Pc") + roundtrip("calli (r6.p)()") + roundtrip("callfar #2,\$c000(r1.b)") + roundtrip("callfar #2,-30,Open(r1.l@s10):r2.l@s10") + roundtrip("callfarvb r3.b,\$c000(r1.b)") + roundtrip("syscall 13(r99000.w):r99100.b") + roundtrip("syscall 0()") + roundtrip("call main.foo() !memory=read,status=sets") + } + + test("call site details are preserved") { + val call = IRTextCodec.parse("call main.foo(main.foo.arg=r1.b,r2.w@s3):r3.w,@Pc") + val site = call.requireCallSite() + site.target shouldBe CallTarget.Direct(CodeReference.Label("main.foo")) + site.arguments.size shouldBe 2 + site.arguments[0].location shouldBe CallLocation.ParameterMemory("main.foo.arg") + site.arguments[0].source.registerNumber shouldBe 1 + site.arguments[1].location shouldBe CallLocation.HardwareRegister(CallingConventionSlot(3)) + site.results.size shouldBe 2 + site.results[0].destination!!.registerNumber shouldBe 3 + site.results[1].destination shouldBe null + site.results[1].location shouldBe CallLocation.StatusFlag(Statusflag.Pc) + } + + test("amiga library call keeps the signed lvo") { + val call = IRTextCodec.parse("callfar #2,-30,Open(r1.l@s10)") + val target = call.requireCallSite().target as CallTarget.AmigaLibrary + target.library shouldBe 2 + target.lvo shouldBe -30 + target.name shouldBe "Open" + } + + test("misc instructions") { + roundtrip("push.w r1.w") + roundtrip("pop.f fr1.f") + roundtrip("pushst") + roundtrip("clc") + roundtrip("breakpoint") + roundtrip("align #\$0100.l") + roundtrip("bittst.b r1.b,#3.b") + roundtrip("concat.b r1.w,r2.b,r3.b") + roundtrip("ext.b r1.w,r2.b") + roundtrip("lsigb.w r1.b,r2.w") + roundtrip("fcomp.f r1.b,fr2.f,fr3.f") + roundtrip("ffromub.f fr1.f,r2.b") + roundtrip("sgn.w r1.b,r2.w") + roundtrip("sqrt.l r1.w,r2.l") + } + + test("parse rejects wrong operand kinds") { + shouldThrow { IRTextCodec.parse("load.w r1.w") } // missing immediate + shouldThrow { IRTextCodec.parse("load.w r1.w,#1.w,#2.w") } // too many operands + shouldThrow { IRTextCodec.parse("loadm.w r1.w,main.var") } // memory needs brackets + shouldThrow { IRTextCodec.parse("loadi.w r1.w,[main.var]") } // needs indirect memory + shouldThrow { IRTextCodec.parse("loadm.w [main.var],r1.w") } // wrong slot order + shouldThrow { IRTextCodec.parse("addr.w fr1.f,r2.w") } // float register in int slot + shouldThrow { IRTextCodec.parse("bogus.w r1.w") } // unknown opcode + shouldThrow { IRTextCodec.parse("jump (r1.p)") } // jump needs a static target + } + + test("parse rejects legacy operand spellings and malformed values") { + shouldThrow { IRTextCodec.parse("load.b r1,#42") } + shouldThrow { IRTextCodec.parse("load.b r1.b,42.b") } + shouldThrow { IRTextCodec.parse("loadhr.b r1.b,s0") } + shouldThrow { IRTextCodec.parse("loadi.b r1.b,[r2]") } + shouldThrow { IRTextCodec.parse("jumpi (r2)") } + shouldThrow { IRTextCodec.parse("load.b r100000.b,#1.b") } + shouldThrow { IRTextCodec.parse("load.b r1.b,#1.5.b") } + shouldThrow { IRTextCodec.parse("loadx.b r1.b,[main.items+fr2.f]") } + shouldThrow { IRTextCodec.parse("load.b r1.b,,#1.b") } + } + + test("direct call metadata is preserved") { + val instruction = IRInstructions.call( + CallSite(CallTarget.Direct(codeAddress(0xffd2u), "kernal.chrout")) + ) + val parsed = IRTextCodec.parse(instruction.toString()) + val parsedTarget = parsed.requireCallSite().target as CallTarget.Direct + val originalTarget = instruction.requireCallSite().target as CallTarget.Direct + parsedTarget.externalName shouldBe originalTarget.externalName + ((parsedTarget.reference as CodeReference.Absolute).address.value == + (originalTarget.reference as CodeReference.Absolute).address.value) shouldBe true + (parsedTarget == originalTarget) shouldBe true + (parsed.requireCallSite().effects == instruction.requireCallSite().effects) shouldBe true + (parsed == instruction) shouldBe true + instruction.toString() shouldBe "call \$ffd2,kernal.chrout()" + } + + test("label lines are parsed by parseIRCodeLine") { + val label = parseIRCodeLine("_main.label:") + label shouldBe ParsedIRLine.Label("main.label") + } +}) diff --git a/intermediate/test/TestIRTraversal.kt b/intermediate/test/TestIRTraversal.kt index 56ead0707..2bd989002 100644 --- a/intermediate/test/TestIRTraversal.kt +++ b/intermediate/test/TestIRTraversal.kt @@ -27,24 +27,24 @@ class TestIRTraversal: FunSpec({ fun makeNestedLoopSub(): IRSubroutine { val sub = IRSubroutine("p8b_main.p8s_start", emptyList(), emptyList(), Position.DUMMY) val head = IRCodeChunk("p8b_main.p8s_start", null) - head += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=10) + head += IRInstructions.load(IRDataType.BYTE, 1, 10) sub += head // inner-most body uses r99 exclusively inside nested loop val innerBody = IRCodeChunk(null, null) - innerBody += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=99, immediate=42) - innerBody += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=99, labelSymbol="sym") + innerBody += IRInstructions.load(IRDataType.BYTE, 99, 42) + innerBody += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 99, IRMemory.direct("sym")) val innerLoop = IRLoopChunk("p8_label_inner", 2, mutableListOf(innerBody)) val outerBody1 = IRCodeChunk(null, null) - outerBody1 += IRInstruction(Opcode.NOP) + outerBody1 += IRInstructions.simple(Opcode.NOP) val outerBody2 = IRCodeChunk(null, null) - outerBody2 += IRInstruction(Opcode.NOP) + outerBody2 += IRInstructions.simple(Opcode.NOP) val outerLoop = IRLoopChunk("p8_label_outer", 3, mutableListOf(outerBody1, innerLoop, outerBody2)) sub += outerLoop val tail = IRCodeChunk(null, null) - tail += IRInstruction(Opcode.RETURN) + tail += IRInstructions.returnVoid() sub += tail return sub } @@ -63,41 +63,41 @@ class TestIRTraversal: FunSpec({ test("forEachInstruction finds register used only inside nested loop") { val sub = makeNestedLoopSub() - val regs = mutableSetOf() - sub.forEachInstruction { regs += it.reg1 } - (99 in regs) shouldBe true + val regs = mutableSetOf() + sub.forEachInstruction { regs += it.registerAccesses.map { access -> access.register } } + (VirtualRegister.int(99) in regs) shouldBe true } test("program forEachInstruction and registersUsed find loop-body register") { val prog = makeProgram(makeNestedLoopSub()) - val regs = mutableSetOf() - prog.forEachInstruction { regs += it.reg1 } - (99 in regs) shouldBe true + val regs = mutableSetOf() + prog.forEachInstruction { regs += it.registerAccesses.map { access -> access.register } } + (VirtualRegister.int(99) in regs) shouldBe true val used = prog.registersUsed() - (RegisterNum(99) in used.readRegs || RegisterNum(99) in used.writeRegs) shouldBe true + (VirtualRegister.int(99) in used.readRegs || VirtualRegister.int(99) in used.writeRegs) shouldBe true } test("program forEachInstruction visits top-level chunks and nested loops") { val prog = makeProgram(makeNestedLoopSub()) val block = prog.blocks.first() val topLevel = IRCodeChunk(null, null) - topLevel += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=77, immediate=1) + topLevel += IRInstructions.load(IRDataType.BYTE, 77, 1) block += topLevel val loopBody = IRCodeChunk(null, null) - loopBody += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=88, immediate=2) + loopBody += IRInstructions.load(IRDataType.BYTE, 88, 2) block += IRLoopChunk("p8_label_top_level", 2, mutableListOf(loopBody)) - val regs = mutableSetOf() - prog.forEachInstruction { regs += it.reg1 } + val regs = mutableSetOf() + prog.forEachInstruction { regs += it.registerAccesses.map { access -> access.register } } - (77 in regs) shouldBe true - (88 in regs) shouldBe true + (VirtualRegister.int(77) in regs) shouldBe true + (VirtualRegister.int(88) in regs) shouldBe true } test("chunk recursive helper visits self then body") { val body = IRCodeChunk(null, null) - body += IRInstruction(Opcode.NOP) + body += IRInstructions.simple(Opcode.NOP) val loop = IRLoopChunk("p8_label_gen_1", 5, mutableListOf(body)) val visited = mutableListOf() loop.forEachChunkRecursive { visited += it } @@ -105,4 +105,35 @@ class TestIRTraversal: FunSpec({ (visited[0] is IRLoopChunk) shouldBe true (visited[1] is IRCodeChunk) shouldBe true } + + test("register traversal keeps integer and float files separate") { + val sub = IRSubroutine("main.start", emptyList(), emptyList(), Position.DUMMY) + val chunk = IRCodeChunk("main.start", null) + chunk += IRInstructions.binary(Opcode.ADDR, IRDataType.WORD, 5, 6) + chunk += IRInstructions.binary(Opcode.FSIN, IRDataType.FLOAT, 5, 6) + chunk += IRInstructions.returnVoid() + sub += chunk + val program = makeProgram(sub) + + val used = program.registersUsed() + used.readRegs.keys shouldBe setOf( + VirtualRegister.int(5), VirtualRegister.int(6), VirtualRegister.float(6) + ) + used.writeRegs.keys shouldBe setOf(VirtualRegister.int(5), VirtualRegister.float(5)) + } + + test("program resolves structured code targets") { + val sub = IRSubroutine("main.start", emptyList(), emptyList(), Position.DUMMY) + val source = IRCodeChunk("main.start", null) + source += IRInstructions.jump(codeLabel("main.target")) + val target = IRCodeChunk("main.target", null) + target += IRInstructions.returnVoid() + sub += source + sub += target + val program = makeProgram(sub) + + program.linkChunks() + + program.resolveCodeTarget(CodeReference.Label("main.target")) shouldBe target + } }) diff --git a/intermediate/test/TestInstructions.kt b/intermediate/test/TestInstructions.kt index 196c54793..77d53364b 100644 --- a/intermediate/test/TestInstructions.kt +++ b/intermediate/test/TestInstructions.kt @@ -1,5 +1,4 @@ import io.kotest.assertions.throwables.shouldThrow -import io.kotest.assertions.throwables.shouldThrowWithMessage import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import prog8.intermediate.* @@ -8,142 +7,162 @@ import prog8.intermediate.* class TestInstructions: FunSpec({ test("simple") { - val ins = IRInstruction(Opcode.NOP) + val ins = IRInstructions.simple(Opcode.NOP) ins.opcode shouldBe Opcode.NOP ins.type shouldBe null - ins.reg1direction shouldBe OperandDirection.UNUSED - ins.fpReg1direction shouldBe OperandDirection.UNUSED - ins.reg1 shouldBe null - ins.reg2 shouldBe null - ins.address shouldBe null + ins.dest shouldBe null + ins.srcA shouldBe null + ins.memory shouldBe null ins.immediate shouldBe null - ins.immediateFp shouldBe null - ins.labelSymbol shouldBe null + ins.target shouldBe null + ins.callSite shouldBe null + ins.registerAccesses shouldBe emptyList() ins.toString() shouldBe "nop" } - test("with value") { - val ins = IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, immediate = 0, address = 99u.toAddress()) + test("with immediate value") { + val ins = IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.BYTE, destination = 42, value = 0) ins.opcode shouldBe Opcode.ADD ins.type shouldBe IRDataType.BYTE - ins.reg1direction shouldBe OperandDirection.READWRITE - ins.fpReg1direction shouldBe OperandDirection.UNUSED - ins.reg1 shouldBe 42 - ins.reg2 shouldBe null - ins.address shouldBe 99u.toAddress() - ins.immediate shouldBe 0 - ins.immediateFp shouldBe null - ins.labelSymbol shouldBe null - ins.toString() shouldBe "add.b r42,#0,$63" + ins.requireDest().registerNumber shouldBe 42 + ins.requireDest().direction shouldBe OperandDirection.USE_DEF + ins.requireImmediate() shouldBe ImmediateOperand.Integer(0, IRDataType.BYTE) + ins.toString() shouldBe "add.b r42.b,#0.b" } - test("with label") { - val ins = IRInstruction(Opcode.ADD, IRDataType.WORD, reg1=11, immediate = 0, labelSymbol = "a.b.c") - ins.opcode shouldBe Opcode.ADD - ins.type shouldBe IRDataType.WORD - ins.reg1direction shouldBe OperandDirection.READWRITE - ins.fpReg1direction shouldBe OperandDirection.UNUSED - ins.reg1 shouldBe 11 - ins.reg2 shouldBe null - ins.address shouldBe null - ins.immediate shouldBe 0 - ins.immediateFp shouldBe null - ins.labelSymbol shouldBe "a.b.c" - ins.toString() shouldBe "add.w r11,#0,a.b.c" + test("with symbol address") { + val ins = IRInstructions.loadAddress(IRDataType.WORD, 11, "a.b.c") + ins.opcode shouldBe Opcode.LOAD + ins.requireImmediate() shouldBe ImmediateOperand.SymbolAddress("a.b.c", 0, IRDataType.WORD) + ins.toString() shouldBe "load.w r11.w,#a.b.c" + + val withOffset = IRInstructions.loadAddress(IRDataType.WORD, 11, "a.b.c", 4) + withOffset.toString() shouldBe "load.w r11.w,#a.b.c+4" } test("with output registers") { - val ins = IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1=11, reg2=22) - ins.opcode shouldBe Opcode.ADDR - ins.type shouldBe IRDataType.WORD - ins.reg1direction shouldBe OperandDirection.READWRITE - ins.reg2direction shouldBe OperandDirection.READ - ins.fpReg1direction shouldBe OperandDirection.UNUSED - ins.fpReg2direction shouldBe OperandDirection.UNUSED - ins.reg1 shouldBe 11 - ins.reg2 shouldBe 22 - ins.address shouldBe null - ins.immediate shouldBe null - ins.immediateFp shouldBe null - ins.labelSymbol shouldBe null - ins.toString() shouldBe "addr.w r11,r22" + val ins = IRInstructions.binary(Opcode.ADDR, IRDataType.WORD, destination = 11, source = 22) + ins.requireDest().direction shouldBe OperandDirection.USE_DEF + ins.requireSrcA().direction shouldBe OperandDirection.USE + ins.uses shouldBe setOf(VirtualRegister.int(11), VirtualRegister.int(22)) + ins.definitions shouldBe setOf(VirtualRegister.int(11)) + ins.toString() shouldBe "addr.w r11.w,r22.w" - val ins2 = IRInstruction(Opcode.SQRT, IRDataType.BYTE, reg1=11, reg2=22) - ins2.opcode shouldBe Opcode.SQRT - ins2.type shouldBe IRDataType.BYTE - ins2.reg1direction shouldBe OperandDirection.WRITE - ins2.reg2direction shouldBe OperandDirection.READ - ins2.fpReg1direction shouldBe OperandDirection.UNUSED - ins2.fpReg2direction shouldBe OperandDirection.UNUSED - ins2.reg1 shouldBe 11 - ins2.reg2 shouldBe 22 - ins.address shouldBe null - ins.immediate shouldBe null - ins.immediateFp shouldBe null - ins2.labelSymbol shouldBe null - ins2.toString() shouldBe "sqrt.b r11,r22" + val ins2 = IRInstructions.binary(Opcode.SQRT, IRDataType.BYTE, destination = 11, source = 22) + ins2.requireDest().direction shouldBe OperandDirection.DEF + ins2.requireSrcA().direction shouldBe OperandDirection.USE + ins2.definitions shouldBe setOf(VirtualRegister.int(11)) + ins2.toString() shouldBe "sqrt.b r11.b,r22.b" } test("with float regs") { - val ins = IRInstruction(Opcode.FSIN, IRDataType.FLOAT, fpReg1 = RegisterNum(1), fpReg2 = RegisterNum(2)) - ins.opcode shouldBe Opcode.FSIN + val ins = IRInstructions.binary(Opcode.FSIN, IRDataType.FLOAT, destination = 1, source = 2) ins.type shouldBe IRDataType.FLOAT - ins.reg1direction shouldBe OperandDirection.UNUSED - ins.reg2direction shouldBe OperandDirection.UNUSED - ins.fpReg1direction shouldBe OperandDirection.WRITE - ins.fpReg2direction shouldBe OperandDirection.READ - ins.fpReg1 shouldBe RegisterNum(1) - ins.fpReg2 shouldBe RegisterNum(2) - ins.reg1 shouldBe null - ins.reg2 shouldBe null - ins.address shouldBe null - ins.immediate shouldBe null - ins.immediateFp shouldBe null - ins.labelSymbol shouldBe null - ins.toString() shouldBe "fsin.f fr1,fr2" + ins.requireDest().register shouldBe VirtualRegister.float(1) + ins.requireSrcA().register shouldBe VirtualRegister.float(2) + ins.toString() shouldBe "fsin.f fr1.f,fr2.f" } + test("typed binary factory infers conversion instruction types") { + val sign = IRInstructions.binary( + Opcode.SGN, + IRInstructions.operandFor(Opcode.SGN, IRDataType.WORD, InstructionSlot.DEST, 1), + IRInstructions.operandFor(Opcode.SGN, IRDataType.WORD, InstructionSlot.SRC_A, 2) + ) + sign.type shouldBe IRDataType.WORD + sign.requireDest().type shouldBe IRDataType.BYTE + sign.requireSrcA().type shouldBe IRDataType.WORD + + val floatFromByte = IRInstructions.binary( + Opcode.FFROMUB, + IRInstructions.operandFor(Opcode.FFROMUB, IRDataType.FLOAT, InstructionSlot.DEST, 1), + IRInstructions.operandFor(Opcode.FFROMUB, IRDataType.FLOAT, InstructionSlot.SRC_A, 2) + ) + floatFromByte.type shouldBe IRDataType.FLOAT + floatFromByte.requireDest().register shouldBe VirtualRegister.float(1) + floatFromByte.requireSrcA().type shouldBe IRDataType.BYTE + } test("missing type should fail") { shouldThrow { - IRInstruction(Opcode.ADD, reg1=42, address=99u.toAddress()) + IRInstruction(Opcode.ADD, dest = IRInstructions.operandFor(Opcode.ADD, IRDataType.BYTE, InstructionSlot.DEST, 42)) } } test("missing registers should fail") { - shouldThrowWithMessage("missing reg1") { - IRInstruction(Opcode.ADD, IRDataType.BYTE, immediate = 0, address=99u.toAddress()) + shouldThrow { + IRInstruction(Opcode.ADD, IRDataType.BYTE, immediate = ImmediateOperand.Integer(0, IRDataType.BYTE)) } } - test("missing address should fail") { - shouldThrowWithMessage("missing an address or labelsymbol") { + test("missing memory operand should fail") { + shouldThrow { IRInstruction(Opcode.INCM, IRDataType.BYTE) } } - test("all instructionformats") { - instructionFormats.size shouldBe Opcode.entries.size - Opcode.entries.forEach { - val fmt = instructionFormats.getValue(it) - fmt.values.forEach { format -> - require(format.reg2==OperandDirection.UNUSED || format.reg2==OperandDirection.READ || format.reg2==OperandDirection.WRITE || format.reg2==OperandDirection.READWRITE) {"reg2 can only be used as input, output, or readwrite"} - require(format.fpReg2==OperandDirection.UNUSED || format.fpReg2==OperandDirection.READ || format.fpReg2==OperandDirection.WRITE || format.fpReg2==OperandDirection.READWRITE) {"fpReg2 can only be used as input, output, or readwrite"} - } + test("wrong operand kind should fail") { + shouldThrow { + // LOADM needs a direct memory reference, not an indirect one + IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, 1, IRMemory.indirect(2, 4)) + } + shouldThrow { + // an immediate that doesn't fit the type + IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.BYTE, destination = 1, value = 9999) } } - test("with symbol offset") { - val i1 = IRInstruction(Opcode.ADDM, IRDataType.BYTE, reg1 = 1, labelSymbol = "symbol", symbolOffset = 99) - i1.labelSymbol shouldBe "symbol" - i1.labelSymbolOffset shouldBe 99 + test("baseline instruction shapes") { + val loadx = IRInstructions.loadMemory( + Opcode.LOADX, IRDataType.WORD, 1, + IRMemory.indexed("main.items", 2, IRDataType.WORD, scale = 2, displacement = 4) + ) + loadx.toString() shouldBe "loadx.w r1.w,[main.items+4+r2.w*2]" - val i2 = IRInstruction(Opcode.ADDM, IRDataType.BYTE, reg1 = 1, labelSymbol = "symbol", symbolOffset = 0) - i2.labelSymbol shouldBe "symbol" - i2.labelSymbolOffset shouldBe null + val loadi = IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, 3, IRMemory.indirect(4, 8)) + loadi.toString() shouldBe "loadi.b r3.b,[r4.p+8]" - shouldThrowWithMessage("labelsymbol offset inconsistency") { - IRInstruction(Opcode.ADDR, IRDataType.BYTE, reg1 = 1, reg2 = 2, symbolOffset = 99) - } + val loadhr = IRInstructions.hardwareLoad(IRDataType.WORD, 5, CallingConventionSlot(10)) + loadhr.toString() shouldBe "loadhr.w r5.w,s10.w" + + val calli = IRInstructions.call(Opcode.CALLI, CallSite(CallTarget.Direct(codeIndirect(6)))) + calli.toString() shouldBe "calli (r6.p)()" + } + + test("with symbol offset in a memory reference") { + val i1 = IRInstructions.memoryOp(Opcode.ADDM, IRDataType.BYTE, IRMemory.direct("symbol", 99), source = 1) + val memory = i1.requireMemory() as MemoryReference.Direct + memory.symbolName shouldBe "symbol" + memory.displacement shouldBe 99 + i1.toString() shouldBe "addm.b r1.b,[symbol+99]" + + val i2 = IRInstructions.memoryOp(Opcode.ADDM, IRDataType.BYTE, IRMemory.direct("symbol"), source = 1) + (i2.requireMemory() as MemoryReference.Direct).displacement shouldBe 0 + i2.toString() shouldBe "addm.b r1.b,[symbol]" + } + + test("register accounting uses distinct integer and float identities") { + val reads = mutableMapOf() + val writes = mutableMapOf() + val types = mutableMapOf() + IRInstructions.binary(Opcode.ADDR, IRDataType.WORD, 5, 6).addUsedRegistersCounts(reads, writes, types, null) + IRInstructions.binary(Opcode.FSIN, IRDataType.FLOAT, 5, 6).addUsedRegistersCounts(reads, writes, types, null) + reads.keys shouldBe setOf( + VirtualRegister.int(5), VirtualRegister.int(6), + VirtualRegister.float(6) + ) + writes.keys shouldBe setOf(VirtualRegister.int(5), VirtualRegister.float(5)) + types shouldBe mapOf( + VirtualRegister.int(5) to IRDataType.WORD, + VirtualRegister.int(6) to IRDataType.WORD + ) + } + + test("instructions can be retargeted") { + val jump = IRInstructions.jump(codeLabel("main.first")) + jump.withTarget(codeLabel("main.second")).requireTarget() shouldBe CodeReference.Label("main.second") + + val call = IRInstructions.call(CallSite(CallTarget.Direct(codeLabel("main.first")))) + call.withTarget(codeLabel("main.second")).requireCallSite().codeReference shouldBe CodeReference.Label("main.second") } }) diff --git a/intermediate/test/TestOpcodeSchema.kt b/intermediate/test/TestOpcodeSchema.kt new file mode 100644 index 000000000..862e4289d --- /dev/null +++ b/intermediate/test/TestOpcodeSchema.kt @@ -0,0 +1,152 @@ +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.collections.shouldNotBeEmpty +import io.kotest.matchers.shouldBe +import prog8.intermediate.* + + +class TestOpcodeSchema: FunSpec({ + + test("every opcode has at least one schema") { + Opcode.entries.forEach { opcode -> + OpcodeSchemas.schemasFor(opcode).shouldNotBeEmpty() + } + OpcodeSchemas.coveredOpcodes shouldBe Opcode.entries.toSet() + } + + test("there is exactly one schema per opcode and type") { + OpcodeSchemas.all().groupBy { it.opcode to it.type }.values.all { it.size == 1 } shouldBe true + } + + test("register-register arithmetic shape") { + OpcodeSchemas.get(Opcode.ADDR, IRDataType.WORD).slots shouldBe listOf( + RegisterSlotSchema(InstructionSlot.DEST, OperandRole.RESULT, OperandDirection.USE_DEF, + RegisterFile.INTEGER, TypeRule.InstructionType), + RegisterSlotSchema(InstructionSlot.SRC_A, OperandRole.RIGHT, OperandDirection.USE, + RegisterFile.INTEGER, TypeRule.InstructionType) + ) + OpcodeSchemas.get(Opcode.ADDR, IRDataType.FLOAT).registerSlots.map { it.registerFile } shouldBe + listOf(RegisterFile.FLOAT, RegisterFile.FLOAT) + } + + test("two-result division shape") { + OpcodeSchemas.get(Opcode.DIVMOD, IRDataType.WORD).slots.map { it.slot } shouldBe + listOf(InstructionSlot.DEST, InstructionSlot.DEST_B, InstructionSlot.IMMEDIATE) + OpcodeSchemas.get(Opcode.SDIVMOD, IRDataType.WORD).registerSlots.map { it.role } shouldBe + listOf(OperandRole.RESULT, OperandRole.SECONDARY_RESULT) + OpcodeSchemas.get(Opcode.DIVMODR, IRDataType.BYTE).slots.map { it.slot } shouldBe + listOf(InstructionSlot.DEST, InstructionSlot.DEST_B) + } + + test("float indexed load has an integer index register") { + val schema = OpcodeSchemas.get(Opcode.LOADX, IRDataType.FLOAT) + schema.slots.map { it.slot } shouldBe listOf(InstructionSlot.DEST, InstructionSlot.MEMORY) + schema.registerSlot(InstructionSlot.DEST)!!.registerFile shouldBe RegisterFile.FLOAT + schema.memorySlot!!.kind shouldBe MemoryKind.INDEXED + schema.memoryEffect shouldBe MemoryEffect.READ + } + + test("pointer dereference uses an indirect memory reference") { + val schema = OpcodeSchemas.get(Opcode.LOADI, IRDataType.BYTE) + schema.slots.map { it.slot } shouldBe listOf(InstructionSlot.DEST, InstructionSlot.MEMORY) + schema.memorySlot!!.kind shouldBe MemoryKind.INDIRECT + OpcodeSchemas.get(Opcode.STOREI, IRDataType.BYTE).memorySlot!!.kind shouldBe MemoryKind.INDIRECT + OpcodeSchemas.get(Opcode.STOREZI, IRDataType.BYTE).slots.map { it.slot } shouldBe listOf(InstructionSlot.MEMORY) + } + + test("hardware register transfer uses a hardware slot") { + OpcodeSchemas.get(Opcode.LOADHR, IRDataType.WORD).slots.map { it.slot } shouldBe + listOf(InstructionSlot.DEST, InstructionSlot.HARDWARE_SLOT) + OpcodeSchemas.get(Opcode.STOREHR, IRDataType.WORD).slots.map { it.slot } shouldBe + listOf(InstructionSlot.SRC_A, InstructionSlot.HARDWARE_SLOT) + } + + test("hardware slots must have the instruction type") { + shouldThrow { + IRInstruction( + Opcode.LOADHR, + IRDataType.BYTE, + dest = IRInstructions.operandFor(Opcode.LOADHR, IRDataType.BYTE, InstructionSlot.DEST, 1), + hardwareSlot = HardwareSlotOperand(CallingConventionSlot(0), IRDataType.WORD) + ) + } + } + + test("concat combines two registers into a wider one") { + val schema = OpcodeSchemas.get(Opcode.CONCAT, IRDataType.BYTE) + schema.slots.map { it.slot } shouldBe listOf(InstructionSlot.DEST, InstructionSlot.SRC_A, InstructionSlot.SRC_B) + schema.registerSlot(InstructionSlot.DEST)!!.typeRule shouldBe TypeRule.Fixed(IRDataType.WORD) + OpcodeSchemas.get(Opcode.CONCAT, IRDataType.WORD).registerSlot(InstructionSlot.DEST)!!.typeRule shouldBe + TypeRule.Fixed(IRDataType.LONG) + } + + test("branches and jumps have a code target") { + OpcodeSchemas.get(Opcode.JUMP, null).targetSlot!!.kind shouldBe TargetKind.STATIC + OpcodeSchemas.get(Opcode.JUMPI, null).targetSlot!!.kind shouldBe TargetKind.INDIRECT + OpcodeSchemas.get(Opcode.BSTEQ, null).slots.map { it.slot } shouldBe listOf(InstructionSlot.TARGET) + OpcodeSchemas.get(Opcode.BSTEQ, null).statusEffect shouldBe StatusEffect.READS + OpcodeSchemas.get(Opcode.BGT, IRDataType.BYTE).slots.map { it.slot } shouldBe + listOf(InstructionSlot.SRC_A, InstructionSlot.IMMEDIATE, InstructionSlot.TARGET) + OpcodeSchemas.get(Opcode.BGTR, IRDataType.BYTE).slots.map { it.slot } shouldBe + listOf(InstructionSlot.SRC_A, InstructionSlot.SRC_B, InstructionSlot.TARGET) + OpcodeSchemas.get(Opcode.JUMP, null).controlFlow shouldBe ControlFlowEffect.JUMP + } + + test("returns") { + OpcodeSchemas.get(Opcode.RETURN, null).slots shouldBe emptyList() + OpcodeSchemas.get(Opcode.RETURNR, IRDataType.FLOAT).registerSlot(InstructionSlot.SRC_A)!!.registerFile shouldBe RegisterFile.FLOAT + OpcodeSchemas.get(Opcode.RETURNI, IRDataType.BYTE).slots.map { it.slot } shouldBe listOf(InstructionSlot.IMMEDIATE) + OpcodeSchemas.get(Opcode.RETURN, null).controlFlow shouldBe ControlFlowEffect.RETURN + } + + test("all call opcodes only have a call site operand") { + val callKinds = mapOf( + Opcode.CALL to CallKind.NORMAL, + Opcode.CALLI to CallKind.INDIRECT, + Opcode.CALLFAR to CallKind.FAR, + Opcode.CALLFARVB to CallKind.FAR_VARBANK, + Opcode.SYSCALL to CallKind.SYSCALL + ) + for((opcode, kind) in callKinds) { + val schema = OpcodeSchemas.get(opcode, null) + schema.slots.map { it.slot } shouldBe listOf(InstructionSlot.CALL_SITE) + schema.callSlot!!.kind shouldBe kind + schema.controlFlow shouldBe ControlFlowEffect.CALL + } + } + + test("memory and status effects are declared") { + OpcodeSchemas.get(Opcode.LOADM, IRDataType.BYTE).memoryEffect shouldBe MemoryEffect.READ + OpcodeSchemas.get(Opcode.STOREM, IRDataType.BYTE).memoryEffect shouldBe MemoryEffect.WRITE + OpcodeSchemas.get(Opcode.INCM, IRDataType.BYTE).memoryEffect shouldBe MemoryEffect.READ_WRITE + OpcodeSchemas.get(Opcode.ADDR, IRDataType.BYTE).memoryEffect shouldBe MemoryEffect.NONE + OpcodeSchemas.get(Opcode.CMP, IRDataType.BYTE).statusEffect shouldBe StatusEffect.SETS + OpcodeSchemas.get(Opcode.ROXL, IRDataType.BYTE).statusEffect shouldBe StatusEffect.READS_AND_SETS + OpcodeSchemas.get(Opcode.LOADM, IRDataType.BYTE).statusEffect shouldBe StatusEffect.NONE + } + + test("word schemas also cover the pointer type") { + OpcodeSchemas.typesFor(Opcode.LOADM) shouldBe setOf( + IRDataType.BYTE, IRDataType.WORD, IRDataType.POINTER, IRDataType.LONG, IRDataType.FLOAT + ) + OpcodeSchemas.typesFor(Opcode.NOP) shouldBe setOf(null) + } + + test("every schema is internally consistent") { + OpcodeSchemas.all().forEach { schema -> + // at most one operand per slot kind + schema.slots.groupBy { it.slot }.values.all { it.size == 1 } shouldBe true + // float registers only in float instructions + schema.registerSlots.forEach { slot -> + if(slot.registerFile == RegisterFile.FLOAT) + schema.type shouldBe IRDataType.FLOAT + } + // canonical print order: destinations come before sources + val slotOrder = schema.slots.map { it.slot } + val destIndex = slotOrder.indexOfFirst { it == InstructionSlot.DEST } + val srcIndex = slotOrder.indexOfFirst { it == InstructionSlot.SRC_A } + if(destIndex >= 0 && srcIndex >= 0) + (destIndex < srcIndex) shouldBe true + } + } +}) diff --git a/intermediate/test/TestStructuredCalls.kt b/intermediate/test/TestStructuredCalls.kt new file mode 100644 index 000000000..96847c8c0 --- /dev/null +++ b/intermediate/test/TestStructuredCalls.kt @@ -0,0 +1,129 @@ +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import prog8.code.core.Statusflag +import prog8.intermediate.* + + +class TestStructuredCalls: FunSpec({ + + test("normal call passes arguments through parameter memory") { + val site = CallSite( + CallTarget.Direct(CodeReference.Label("main.foo")), + arguments = listOf( + Calls.argument(1, IRDataType.BYTE, CallLocation.ParameterMemory("main.foo.arg")), + Calls.argument(2, IRDataType.WORD) + ), + results = listOf(Calls.result(3, IRDataType.WORD)) + ) + val call = IRInstructions.call(Opcode.CALL, site) + call.requireCallSite().arguments[0].location shouldBe CallLocation.ParameterMemory("main.foo.arg") + call.requireCallSite().arguments[1].location shouldBe CallLocation.Default + call.uses shouldBe setOf(VirtualRegister.int(1), VirtualRegister.int(2)) + call.definitions shouldBe setOf(VirtualRegister.int(3)) + } + + test("asmsub call uses hardware register slots") { + val site = CallSite( + CallTarget.Direct(CodeReference.Absolute(MemoryAddress(0xffd2u))), + arguments = listOf(Calls.argument(1, IRDataType.BYTE, CallLocation.HardwareRegister(CallingConventionSlot(0)))), + results = listOf(Calls.result(2, IRDataType.WORD, CallLocation.HardwareRegister(CallingConventionSlot(12)))) + ) + site.arguments[0].location shouldBe CallLocation.HardwareRegister(CallingConventionSlot(0)) + site.results[0].location shouldBe CallLocation.HardwareRegister(CallingConventionSlot(12)) + } + + test("call result has exactly one explicit location") { + val destination = RegisterOperand( + VirtualRegister.IntReg(RegisterNum(4)), + IRDataType.WORD, + OperandRole.CALL_RESULT, + OperandDirection.DEF + ) + val result = CallResult( + destination, + CallLocation.HardwareRegister(CallingConventionSlot(12)) + ) + result.location shouldBe CallLocation.HardwareRegister(CallingConventionSlot(12)) + } + + test("multiple results are kept in order") { + val site = CallSite( + CallTarget.Direct(CodeReference.Label("main.foo")), + results = listOf( + Calls.result(1, IRDataType.BYTE, CallLocation.HardwareRegister(CallingConventionSlot(10))), + Calls.result(2, IRDataType.WORD, CallLocation.HardwareRegister(CallingConventionSlot(11))), + CallResult(null, CallLocation.StatusFlag(Statusflag.Pc)) + ) + ) + site.results.map { it.destination?.registerNumber } shouldBe listOf(1, 2, null) + site.results[2].location shouldBe CallLocation.StatusFlag(Statusflag.Pc) + } + + test("a result without destination register needs an explicit location") { + shouldThrow { + CallResult(null, CallLocation.Default) + } + } + + test("indirect call") { + val target = CallTarget.Direct(codeIndirect(6)) + val call = IRInstructions.call(Opcode.CALLI, CallSite(target)) + call.opcode shouldBe Opcode.CALLI + call.uses shouldBe setOf(VirtualRegister.int(6)) + } + + test("Amiga LVO retains signed offset") { + val target = CallTarget.AmigaLibrary(2, -30, "Open") + target.lvo shouldBe -30 + target.library shouldBe 2 + shouldThrow { CallTarget.AmigaLibrary(2, 30, "Open") } + } + + test("banked call keeps bank and address") { + val banked = CallTarget.Banked(3, CodeReference.Absolute(MemoryAddress(0xc000u)), "ext.sub") + val call = IRInstructions.call(Opcode.CALLFAR, CallSite(banked)) + (call.requireCallSite().target as CallTarget.Banked).bank shouldBe 3 + call.requireCallSite().externalName shouldBe "ext.sub" + + val varbanked = CallTarget.BankedVariable( + RegisterOperand(VirtualRegister.int(5), IRDataType.BYTE, OperandRole.VALUE, OperandDirection.USE), + CodeReference.Absolute(MemoryAddress(0xc000u)) + ) + val call2 = IRInstructions.call(Opcode.CALLFARVB, CallSite(varbanked)) + call2.uses shouldBe setOf(VirtualRegister.int(5)) + } + + test("syscall") { + val call = IRInstructions.syscall( + 13, + arguments = listOf(Calls.argument(99000, IRDataType.WORD)), + results = listOf(Calls.result(99100, IRDataType.BYTE)) + ) + call.opcode shouldBe Opcode.SYSCALL + (call.requireCallSite().target as CallTarget.SystemCall).number shouldBe 13 + call.uses shouldBe setOf(VirtualRegister.int(99000)) + call.definitions shouldBe setOf(VirtualRegister.int(99100)) + } + + test("call site registers can be remapped") { + val site = CallSite( + CallTarget.Direct(CodeReference.Label("main.foo")), + arguments = listOf(Calls.argument(1, IRDataType.BYTE)), + results = listOf(Calls.result(2, IRDataType.BYTE)) + ) + val mapped = site.mapRegisters { if(it == VirtualRegister.int(1)) VirtualRegister.int(9) else it } + mapped.arguments[0].source.registerNumber shouldBe 9 + mapped.results[0].destination!!.registerNumber shouldBe 2 + mapped.arguments[0].location shouldBe CallLocation.Default + } + + test("call sites are only allowed on call opcodes") { + shouldThrow { + IRInstructions.call(Opcode.CALL, CallSite(CallTarget.SystemCall(1))) + } + shouldThrow { + IRInstructions.call(Opcode.SYSCALL, CallSite(CallTarget.Direct(CodeReference.Label("main.foo")))) + } + } +}) diff --git a/intermediate/test/TestStructuredOperands.kt b/intermediate/test/TestStructuredOperands.kt new file mode 100644 index 000000000..6e5d23d7e --- /dev/null +++ b/intermediate/test/TestStructuredOperands.kt @@ -0,0 +1,199 @@ +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import prog8.intermediate.* + + +class TestStructuredOperands: FunSpec({ + + fun testIndexOperand() = RegisterOperand( + VirtualRegister.IntReg(RegisterNum(3)), + IRDataType.WORD, + OperandRole.INDEX, + OperandDirection.USE, + AllocationHint.PREFER_DATA + ) + + test("integer and float registers are distinct") { + val intReg: VirtualRegister = VirtualRegister.IntReg(RegisterNum(5)) + val floatReg: VirtualRegister = VirtualRegister.FloatReg(RegisterNum(5)) + intReg shouldNotBe floatReg + intReg.number shouldBe floatReg.number + intReg shouldBe VirtualRegister.int(5) + floatReg shouldBe VirtualRegister.float(5) + } + + test("float registers require the float type") { + shouldThrow { + RegisterOperand(VirtualRegister.float(1), IRDataType.WORD, OperandRole.VALUE, OperandDirection.USE) + } + shouldThrow { + RegisterOperand(VirtualRegister.int(1), IRDataType.FLOAT, OperandRole.VALUE, OperandDirection.USE) + } + } + + test("indexed memory keeps base index scale and displacement") { + val index = RegisterOperand( + VirtualRegister.IntReg(RegisterNum(3)), + IRDataType.WORD, + OperandRole.INDEX, + OperandDirection.USE, + AllocationHint.PREFER_DATA + ) + val ref = MemoryReference.Indexed( + AddressBase.Symbol("main.items"), + index, + scale = 6, + displacement = 4 + ) + ref.scale shouldBe 6 + ref.displacement shouldBe 4 + ref.symbolName shouldBe "main.items" + ref.registers shouldBe listOf(index) + } + + test("indexed scale must be in range") { + shouldThrow { + MemoryReference.Indexed( + AddressBase.Symbol("main.items"), + testIndexOperand(), + scale = 0 + ) + } + shouldThrow { + MemoryReference.Indexed( + AddressBase.Symbol("main.items"), + testIndexOperand(), + scale = 65536 + ) + } + } + + test("embedded register operands have the required semantic role") { + shouldThrow { + MemoryReference.Indexed( + AddressBase.Symbol("main.items"), + IRMemory.pointerOperand(3), + scale = 2 + ) + } + shouldThrow { + MemoryReference.Indirect( + IRMemory.indexOperand(3, IRDataType.WORD) + ) + } + shouldThrow { + CodeReference.Indirect(IRMemory.pointerOperand(3)) + } + } + + test("indirect displacement is limited to the loadi/storei range") { + MemoryReference.Indirect(IRMemory.pointerOperand(2), 65535).displacement shouldBe 65535 + shouldThrow { + MemoryReference.Indirect(IRMemory.pointerOperand(2), -1) + } + shouldThrow { + MemoryReference.Indirect(IRMemory.pointerOperand(2), 65536) + } + } + + test("symbol names are validated") { + shouldThrow { AddressBase.Symbol("_underscore") } + shouldThrow { AddressBase.Symbol("\$d020") } + shouldThrow { CodeReference.Label("with space") } + shouldThrow { ImmediateOperand.SymbolAddress("_bad") } + AddressBase.Symbol("main.items").name shouldBe "main.items" + } + + test("memory references can be mapped to other registers") { + val ref = IRMemory.indexed("main.items", 3, IRDataType.WORD, scale = 2, displacement = 4) + val mapped = ref.mapRegisters { VirtualRegister.int(7) } + (mapped as MemoryReference.Indexed).index.registerNumber shouldBe 7 + mapped.scale shouldBe 2 + mapped.displacement shouldBe 4 + } + + test("structured views of a register instruction") { + val add = IRInstructions.binary(Opcode.ADDR, IRDataType.WORD, destination = 1, source = 2) + add.dest shouldBe RegisterOperand( + VirtualRegister.IntReg(RegisterNum(1)), + IRDataType.WORD, + OperandRole.RESULT, + OperandDirection.USE_DEF + ) + add.srcA shouldBe RegisterOperand( + VirtualRegister.IntReg(RegisterNum(2)), + IRDataType.WORD, + OperandRole.RIGHT, + OperandDirection.USE + ) + add.uses shouldBe setOf( + VirtualRegister.IntReg(RegisterNum(1)), + VirtualRegister.IntReg(RegisterNum(2)) + ) + add.definitions shouldBe setOf(VirtualRegister.IntReg(RegisterNum(1))) + } + + test("structured views of a float instruction") { + val sin = IRInstructions.binary(Opcode.FSIN, IRDataType.FLOAT, destination = 1, source = 2) + sin.requireFloatDest().register shouldBe VirtualRegister.float(1) + sin.requireFloatSourceA().register shouldBe VirtualRegister.float(2) + sin.definitions shouldBe setOf(VirtualRegister.FloatReg(RegisterNum(1))) + sin.uses shouldBe setOf(VirtualRegister.FloatReg(RegisterNum(2))) + } + + test("structured views of an indexed memory instruction") { + val loadx = IRInstructions.loadMemory( + Opcode.LOADX, IRDataType.BYTE, 1, + IRMemory.indexed("main.items", 2, IRDataType.WORD, scale = 3, displacement = 4) + ) + val memory = loadx.requireMemory() as MemoryReference.Indexed + memory.symbolName shouldBe "main.items" + memory.scale shouldBe 3 + memory.displacement shouldBe 4 + loadx.uses shouldBe setOf(VirtualRegister.int(2)) + loadx.definitions shouldBe setOf(VirtualRegister.int(1)) + loadx.memoryEffect shouldBe MemoryEffect.READ + } + + test("structured views of a hardware slot instruction") { + val loadhr = IRInstructions.hardwareLoad(IRDataType.WORD, 5, CallingConventionSlot(10)) + loadhr.requireHardwareSlot() shouldBe HardwareSlotOperand(CallingConventionSlot(10), IRDataType.WORD) + loadhr.definitions shouldBe setOf(VirtualRegister.int(5)) + } + + test("structured views of a branch instruction") { + val branch = IRInstructions.branchImmediate(Opcode.BGT, IRDataType.BYTE, 3, 42, codeLabel("main.label")) + branch.requireTarget() shouldBe CodeReference.Label("main.label") + branch.requireImmediateInt() shouldBe 42 + branch.uses shouldBe setOf(VirtualRegister.int(3)) + branch.definitions shouldBe emptySet() + branch.controlFlow shouldBe ControlFlowEffect.BRANCH + } + + test("register mapping only changes the selected register") { + val instr = IRInstructions.loadMemory( + Opcode.LOADX, IRDataType.BYTE, 1, + IRMemory.indexed("main.items", 2, IRDataType.WORD, scale = 3, displacement = 4) + ) + val mapped = instr.mapRegisters { if (it == VirtualRegister.int(2)) VirtualRegister.int(9) else it } + mapped.requireDest().registerNumber shouldBe 1 + (mapped.requireMemory() as MemoryReference.Indexed).index.registerNumber shouldBe 9 + (mapped.requireMemory() as MemoryReference.Indexed).scale shouldBe 3 + } + + test("destination register may also be a source register, the IR is not SSA") { + // registers are freely reused: addr r1,r1 doubles r1 in place + val add = IRInstructions.binary(Opcode.ADDR, IRDataType.BYTE, destination = 1, source = 1) + add.definitions shouldBe setOf(VirtualRegister.int(1)) + add.uses shouldBe setOf(VirtualRegister.int(1)) + // LOADI may also reuse the pointer register + IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, 1, IRMemory.indirect(1, 8)).uses shouldBe + setOf(VirtualRegister.int(1)) + // but an instruction with two destinations must keep them distinct + shouldThrow { + IRInstructions.divmodRegister(Opcode.DIVMODR, IRDataType.BYTE, quotient = 1, remainder = 1) + } + } +}) diff --git a/m68k-reg-problems.md b/m68k-reg-problems.md new file mode 100644 index 000000000..0bdd5bd41 --- /dev/null +++ b/m68k-reg-problems.md @@ -0,0 +1,191 @@ +# Problems in the M68k Register-Allocation Design + +This report records the correctness problems and internal contradictions found +in `ideas/m68k-register-allocation.md`. It focuses on issues that must be +resolved before Stage 2 can be implemented safely. + +## 1. Translator scratch registers cannot simultaneously hold allocated values + +The design assigns two incompatible roles to the same physical registers: + +- Section 2.3 allows call-free virtual registers to be allocated to + D0, D1, A0, A1, FP0, and FP1. +- The existing translators use D0, D1, A0, A1, FP0, and FP1 unconditionally as + scratch registers while expanding individual IR instructions. +- Stage 1 later says that D0, D1, A0, and A1 never hold allocated virtual + registers and proposes an assertion enforcing that rule. FP0 and FP1 are + omitted from that assertion even though the backend explicitly defines them + as its FPU scratch registers. + +CALL liveness does not make this safe. A call-free virtual register allocated +to D0 can remain live while a later, unrelated IR instruction uses D0 as an +accumulator and destroys it. The same failure applies to the other integer, +address, and FPU scratch registers. A scratch temporary being confined to one +instruction expansion does not prevent it from overlapping a virtual +register's live range across that instruction. + +The design must choose one of these models: + +1. Reserve all six translator scratch registers permanently. The initial + allocatable pools then become D2-D6, A2-A4, and FP2-FP7. +2. Make scratch use allocator-aware. Every instruction expansion must declare + its physical uses and clobbers, acquire non-conflicting scratch registers, + and preserve or relocate live allocated values where necessary. + +The first model is substantially simpler and matches the stated Stage 1 +approach. The pool sizes, pressure estimates, success metrics, and tests must +then stop counting D0, D1, A0, A1, FP0, and FP1 as allocatable registers. + +## 2. D2 has contradictory calling-convention roles + +The convention correctly classifies D2-D6 as callee-saved, but it also says +that the commonly used D0-D2 asmsub argument and return slots are all +caller-saved. D2 cannot be both. + +Using D2 as an argument is possible only if a live allocator value currently +held in D2 is preserved or relocated before argument marshalling. Using D2 as +a return register or declaring `clobbers(D2)` defines or destroys its old +contents, so the caller must likewise preserve any unrelated live value. +These operations are call-site-specific and are not represented by the +document's rule that every CALL has the same effects. + +The problem extends beyond D2. Existing Prog8 and Amiga APIs use fixed +registers throughout the nominally callee-saved and reserved sets. Examples +include: + +- Standard-library asmsubs that declare D2-D6, A2, or A6 in `clobbers`. +- Amiga library calls with arguments in D2-D7 and A2-A5. +- `Supervisor(pointer userFunction @A5)`. +- `Alert(long alertNum @D7)`. +- Multi-value returns using D0-D3. + +Rejecting callee-saved or reserved registers in `clobbers` would therefore +break existing standard-library code. External routines also cannot be changed +to preserve Prog8's internal allocator convention. + +Fixed-register calls need call-site metadata containing: + +- Physical registers read as arguments. +- Physical registers defined as results. +- Additional physical registers destroyed by the call. + +The allocator must create interference or preservation moves for those +registers. Wrapper stubs are another option, especially when an external ABI +uses A5 or D7. The uniform CALL rule can remain valid for ordinary Prog8 +subroutines, but it cannot describe arbitrary asmsub and extsub boundaries. + +## 3. Reserved registers can be destroyed by argument marshalling + +The clobber policy only discusses registers modified inside a called routine. +Loading an argument into a fixed hardware slot already overwrites that +register before the call. + +This is critical for A5 and D7: + +- Once A5 is the frame pointer, marshalling an `@A5` argument destroys access + to the caller's frame. +- D7 is the active physical `repeat` counter, so marshalling an `@D7` argument + can destroy the surrounding loop state. + +Rejecting `clobbers(A5)` or `clobbers(D7)` does not address either case. +Calls using reserved argument or result slots need generated save, marshal, +call, and restore sequences, or dedicated ABI wrappers. The saved value must +be restored only after all return values and flags that depend on the call +have been captured. + +## 4. Push/pop around a live range is not a general spill strategy + +The preferred spill target is described as pushing and popping around a live +range. General live ranges are not necessarily nested in LIFO order. They can +cross branches, loops, calls, and multiple uses. Naive push/pop insertion can +therefore produce different stack depths at CFG joins or pop a different +value from the one expected. + +Sound stack spilling requires one of these mechanisms: + +1. Reserve a fixed-size spill area once in the subroutine prologue, keep SP + stable throughout the body, and address each spill through a fixed SP + displacement. +2. Implement the A5 frame model first and assign fixed frame slots. +3. Perform substantially more complex CFG-aware stack-slot scheduling with + proven stack-depth consistency. + +The first or second mechanism is appropriate for the initial allocator. BSS +spill slots remain a temporary non-reentrant fallback and must be globally +unique across callers, callees, and interruptible code. + +## 5. The recursion claims exceed what the current memory model supports + +Stack-based register saves and spills are per invocation, but ordinary Prog8 +parameters and locals remain statically allocated. A recursive call overwrites +the caller's parameter and local storage. Register allocation alone therefore +does not make normal subroutines recursive or reentrant. + +The design currently claims that recursion is handled by the uniform CALL rule +and includes a recursion test justified by stack-frame saves. That only proves +that allocator-owned register state survives; it does not make the program's +variables recursion-safe. + +Until `m68k-stack-memory-model.md` is implemented, the document should: + +- Keep normal Prog8 recursion explicitly unsupported. +- Limit allocator recursion tests to synthetic routines without static + per-invocation state, or describe them strictly as register-preservation + tests. +- Avoid presenting stack spills as sufficient for full recursion soundness. + +## 6. The caller-saved spill test conflicts with whole-live-range allocation + +Section 2.3 says a virtual register may use a caller-saved physical register +only when its entire live range contains no CALL. Under that rule, a value in a +caller-saved register can never require spilling around a call. + +The test plan nevertheless requires a caller-saved register to be spilled +around a call. Supporting that test requires live-range splitting: one segment +may use a caller-saved register, then move to a callee-saved register or spill +slot across the call, and optionally move back afterward. Whole-vreg greedy +colouring does not provide this. + +The design must explicitly choose between: + +- No live-range splitting initially: call-crossing values are assigned only to + callee-saved registers or permanent spill slots, and the caller-spill test is + removed. +- Live-range splitting: add interval segmentation, boundary moves, and the + corresponding CFG correctness requirements to Stage 2. + +## 7. Address-register pressure is underestimated + +The pressure analysis says short-lived pointer virtual registers flow through +A0 or A1 and therefore do not consume A2-A4. That is incompatible with making +A0 and A1 pure scratch. + +A pointer produced by one IR instruction and consumed by a later instruction +must persist between those instructions. It must occupy an allocated address +register, a spill slot, or a deliberately precoloured and protected scratch +live range. Merely copying it through A0 during its eventual use does not store +the value between its definition and use. + +Address-register demand must therefore be measured from actual IR liveness, +including pointer temporaries. Def-use folding could reduce this pressure +later, but it cannot be assumed by the initial allocator design. + +## Required design decisions + +Before Stage 2 is delegable, the design should explicitly settle these points: + +1. Whether translator scratch registers are permanently reserved or managed by + the allocator. +2. How fixed-register argument, return, and clobber constraints are represented + per call site. +3. How A5 and D7 ABI slots are wrapped without destroying frame or loop state. +4. Which fixed-offset stack or frame mechanism stores spills. +5. Whether live-range splitting is in scope. +6. That recursion remains outside the allocator's guarantees until parameters + and locals are frame-resident. +7. Pool-pressure measurements based on real IR live ranges rather than source + usage assumptions. + +Without these decisions, the proposed allocator can silently corrupt live +values even when its virtual-register liveness and interference graph are +otherwise correct. diff --git a/virtualmachine/src/prog8/vm/SysCalls.kt b/virtualmachine/src/prog8/vm/SysCalls.kt index 8802d0bf1..aad47e731 100644 --- a/virtualmachine/src/prog8/vm/SysCalls.kt +++ b/virtualmachine/src/prog8/vm/SysCalls.kt @@ -1,6 +1,8 @@ package prog8.vm -import prog8.intermediate.FunctionCallArgs +import prog8.intermediate.CallArgument +import prog8.intermediate.CallResult +import prog8.intermediate.CallSite import prog8.intermediate.IRDataType import java.io.File import kotlin.io.path.* @@ -163,19 +165,19 @@ enum class Syscall { } object SysCalls { - private fun getArgValues(argspec: List, vm: VirtualMachine): List> { + private fun getArgValues(argspec: List, vm: VirtualMachine): List> { return argspec.map { - when(it.reg.dt) { - IRDataType.BYTE -> vm.registers.getUB(it.reg.registerNum) - IRDataType.WORD -> vm.registers.getUW(it.reg.registerNum) - IRDataType.POINTER -> vm.registers.getUL(it.reg.registerNum) - IRDataType.LONG -> vm.registers.getSL(it.reg.registerNum) - IRDataType.FLOAT -> vm.registers.getFloat(it.reg.registerNum) + when(it.source.type) { + IRDataType.BYTE -> vm.registers.getUB(it.source.registerNumber) + IRDataType.WORD -> vm.registers.getUW(it.source.registerNumber) + IRDataType.POINTER -> vm.registers.getUL(it.source.registerNumber) + IRDataType.LONG -> vm.registers.getSL(it.source.registerNumber) + IRDataType.FLOAT -> vm.registers.getFloat(it.source.registerNumber) } } } - private fun returnValue(returns: FunctionCallArgs.RegSpec, value: Comparable, vm: VirtualMachine) { + private fun returnValue(returns: CallResult, value: Comparable, vm: VirtualMachine) { // NOTE: currently syscalls can only return A SINGLE VALUE using this routine! // HACKY WORKAROUND: you can use a single UWORD return value to encode two bytes (LSB + MSB), or a single LONG to encode two words (MSW + LSW) and then split those in the calling IR code. It is NOT POSSIBLE to return two longs like this! val vv: Double = when(value) { @@ -190,16 +192,17 @@ object SysCalls { is Boolean -> if(value) 1.0 else 0.0 else -> (value as Number).toDouble() } - when(returns.dt) { - IRDataType.BYTE -> vm.registers.setUB(returns.registerNum, vv.toInt().toUByte()) - IRDataType.WORD -> vm.registers.setUW(returns.registerNum, vv.toInt().toUShort()) - IRDataType.POINTER -> vm.registers.setUL(returns.registerNum, vv.toUInt()) - IRDataType.LONG -> vm.registers.setSL(returns.registerNum, vv.toInt()) - IRDataType.FLOAT -> vm.registers.setFloat(returns.registerNum, vv) + val dest = requireNotNull(returns.destination) { "syscall result needs a destination register" } + when(dest.type) { + IRDataType.BYTE -> vm.registers.setUB(dest.registerNumber, vv.toInt().toUByte()) + IRDataType.WORD -> vm.registers.setUW(dest.registerNumber, vv.toInt().toUShort()) + IRDataType.POINTER -> vm.registers.setUL(dest.registerNumber, vv.toUInt()) + IRDataType.LONG -> vm.registers.setSL(dest.registerNumber, vv.toInt()) + IRDataType.FLOAT -> vm.registers.setFloat(dest.registerNumber, vv) } } - fun call(call: Syscall, callspec: FunctionCallArgs, vm: VirtualMachine) { + fun call(call: Syscall, callspec: CallSite, vm: VirtualMachine) { when(call) { Syscall.RESET -> { @@ -242,7 +245,7 @@ object SysCalls { if(maxlenvalue>0) input = input.take(min(input.length, maxlenvalue)) vm.memory.setString((address as Int).toUInt(), input, true) - returnValue(callspec.returns.single(), input.length, vm) + returnValue(callspec.results.single(), input.length, vm) } Syscall.SLEEP -> { val duration = getArgValues(callspec.arguments, vm).single() as UShort @@ -263,7 +266,7 @@ object SysCalls { Syscall.GFX_GETPIXEL -> { val (x,y) = getArgValues(callspec.arguments, vm) val color = vm.gfx_getpixel(x as UShort, y as UShort) - returnValue(callspec.returns.single(), color, vm) + returnValue(callspec.results.single(), color, vm) } Syscall.WAIT -> { val time = getArgValues(callspec.arguments, vm).single() as UShort @@ -282,29 +285,29 @@ object SysCalls { } catch(_: NumberFormatException) { 0u } - returnValue(callspec.returns.single(), value, vm) + returnValue(callspec.results.single(), value, vm) } Syscall.STR_TO_WORD -> { val stringAddr = getArgValues(callspec.arguments, vm).single() as Int val memstring = vm.memory.getString(stringAddr.toUInt()) - val match = Regex("^[+-]?\\d+").find(memstring) ?: return returnValue(callspec.returns.single(), 0, vm) + val match = Regex("^[+-]?\\d+").find(memstring) ?: return returnValue(callspec.results.single(), 0, vm) val value = try { match.value.toShort() } catch(_: NumberFormatException) { 0 } - return returnValue(callspec.returns.single(), value, vm) + return returnValue(callspec.results.single(), value, vm) } Syscall.STR_TO_LONG -> { val stringAddr = (getArgValues(callspec.arguments, vm).single() as Int).toUInt() val memstring = vm.memory.getString(stringAddr) - val match = Regex("^[+-]?\\d+").find(memstring) ?: return returnValue(callspec.returns.single(), 0, vm) + val match = Regex("^[+-]?\\d+").find(memstring) ?: return returnValue(callspec.results.single(), 0, vm) val value = try { match.value.toInt() } catch(_: NumberFormatException) { 0 } - return returnValue(callspec.returns.single(), value, vm) + return returnValue(callspec.results.single(), value, vm) } Syscall.STR_TO_FLOAT -> { val stringAddr = getArgValues(callspec.arguments, vm).single() as Int @@ -319,7 +322,7 @@ object SysCalls { 0.0 } } - returnValue(callspec.returns.single(), result, vm) + returnValue(callspec.results.single(), result, vm) } Syscall.COMPARE_STRINGS -> { val (firstV, secondV) = getArgValues(callspec.arguments, vm) @@ -329,11 +332,11 @@ object SysCalls { val second = vm.memory.getString(secondAddr.toUInt()) val comparison = first.compareTo(second) if(comparison==0) - returnValue(callspec.returns.single(), 0, vm) + returnValue(callspec.results.single(), 0, vm) else if(comparison<0) - returnValue(callspec.returns.single(), -1, vm) + returnValue(callspec.results.single(), -1, vm) else - returnValue(callspec.returns.single(), 1, vm) + returnValue(callspec.results.single(), 1, vm) } Syscall.COMPARE_STRINGS_NOCASE -> { val (firstV, secondV) = getArgValues(callspec.arguments, vm) @@ -343,11 +346,11 @@ object SysCalls { val second = vm.memory.getString(secondAddr.toUInt()) val comparison = first.compareTo(second, ignoreCase = true) if(comparison==0) - returnValue(callspec.returns.single(), 0, vm) + returnValue(callspec.results.single(), 0, vm) else if(comparison<0) - returnValue(callspec.returns.single(), -1, vm) + returnValue(callspec.results.single(), -1, vm) else - returnValue(callspec.returns.single(), 1, vm) + returnValue(callspec.results.single(), 1, vm) } Syscall.MEMCMP -> { val (firstV, secondV, sizeV) = getArgValues(callspec.arguments, vm) @@ -357,14 +360,14 @@ object SysCalls { while(size>0) { val comparison = vm.memory.getUB(firstAddr.toUInt()).compareTo(vm.memory.getUB(secondAddr.toUInt())) if(comparison<0) - return returnValue(callspec.returns.single(), -1, vm) + return returnValue(callspec.results.single(), -1, vm) else if(comparison>0) - return returnValue(callspec.returns.single(), 1, vm) + return returnValue(callspec.results.single(), 1, vm) firstAddr++ secondAddr++ size-- } - return returnValue(callspec.returns.single(), 0, vm) + return returnValue(callspec.results.single(), 0, vm) } Syscall.RNDFSEED -> { val seed = getArgValues(callspec.arguments, vm).single() as Double @@ -378,20 +381,20 @@ object SysCalls { vm.randomSeed(seed1 as UShort, seed2 as UShort) } Syscall.RND -> { - returnValue(callspec.returns.single(), (vm.nextRandWord().toInt() and 255).toUByte(), vm) + returnValue(callspec.results.single(), (vm.nextRandWord().toInt() and 255).toUByte(), vm) } Syscall.RNDW -> { - returnValue(callspec.returns.single(), vm.nextRandWord(), vm) + returnValue(callspec.results.single(), vm.nextRandWord(), vm) } Syscall.RNDF -> { - returnValue(callspec.returns.single(), vm.randomGeneratorFloats.nextFloat(), vm) + returnValue(callspec.results.single(), vm.randomGeneratorFloats.nextFloat(), vm) } Syscall.STRING_CONTAINS -> { val (charV, addr) = getArgValues(callspec.arguments, vm) val stringAddr = (addr as Int).toUInt() val char = (charV as UByte).toInt().toChar() val string = vm.memory.getString(stringAddr) - returnValue(callspec.returns.single(), if(char in string) 1u else 0u, vm) + returnValue(callspec.results.single(), if(char in string) 1u else 0u, vm) } Syscall.BYTEARRAY_CONTAINS -> { val (value, arrayV, lengthV) = getArgValues(callspec.arguments, vm) @@ -399,11 +402,11 @@ object SysCalls { var array = (arrayV as Int).toUInt() while(length>0u) { if(vm.memory.getUB(array)==value) - return returnValue(callspec.returns.single(), 1u, vm) + return returnValue(callspec.results.single(), 1u, vm) array++ length-- } - returnValue(callspec.returns.single(), 0u, vm) + returnValue(callspec.results.single(), 0u, vm) } Syscall.WORDARRAY_CONTAINS -> { val (value, arrayV, lengthV) = getArgValues(callspec.arguments, vm) @@ -411,11 +414,11 @@ object SysCalls { var array = (arrayV as Int).toUInt() while(length>0u) { if(vm.memory.getUW(array)==value) - return returnValue(callspec.returns.single(), 1u, vm) + return returnValue(callspec.results.single(), 1u, vm) array += 2u length-- } - returnValue(callspec.returns.single(), 0u, vm) + returnValue(callspec.results.single(), 0u, vm) } Syscall.SPLIT_WORDARRAY_CONTAINS -> { val (value, arrayV, lengthV) = getArgValues(callspec.arguments, vm) @@ -428,9 +431,9 @@ object SysCalls { val hi = vm.memory.getUB(array + msbOffset + length) val word = (hi.toUInt() shl 8 or lo.toUInt()).toUShort() if(word == value) - return returnValue(callspec.returns.single(), 1u, vm) + return returnValue(callspec.results.single(), 1u, vm) } - returnValue(callspec.returns.single(), 0u, vm) + returnValue(callspec.results.single(), 0u, vm) } Syscall.LONGARRAY_CONTAINS -> { val (value, arrayV, lengthV) = getArgValues(callspec.arguments, vm) @@ -440,9 +443,9 @@ object SysCalls { length-- val el = vm.memory.getSL(array + length.toUInt() * 4u) if(el == value) - return returnValue(callspec.returns.single(), 1u, vm) + return returnValue(callspec.results.single(), 1u, vm) } - returnValue(callspec.returns.single(), 0u, vm) + returnValue(callspec.results.single(), 0u, vm) } Syscall.FLOATARRAY_CONTAINS -> { val (value, arrayV, lengthV) = getArgValues(callspec.arguments, vm) @@ -450,11 +453,11 @@ object SysCalls { var array = (arrayV as Int).toUInt() while(length>0u) { if(vm.memory.getFloat(array)==value) - return returnValue(callspec.returns.single(), 1u, vm) + return returnValue(callspec.results.single(), 1u, vm) array += vm.machine.FLOAT_MEM_SIZE length-- } - returnValue(callspec.returns.single(), 0u, vm) + returnValue(callspec.results.single(), 0u, vm) } Syscall.CLAMP_BYTE -> { val (valueU, minimumU, maximumU) = getArgValues(callspec.arguments, vm) @@ -462,7 +465,7 @@ object SysCalls { val minimum = (minimumU as UByte).toByte().toInt() val maximum = (maximumU as UByte).toByte().toInt() val result = min(max(value, minimum), maximum) - returnValue(callspec.returns.single(), result, vm) + returnValue(callspec.results.single(), result, vm) } Syscall.CLAMP_UBYTE -> { val (valueU, minimumU, maximumU) = getArgValues(callspec.arguments, vm) @@ -470,7 +473,7 @@ object SysCalls { val minimum = (minimumU as UByte).toInt() val maximum = (maximumU as UByte).toInt() val result = min(max(value, minimum), maximum) - returnValue(callspec.returns.single(), result, vm) + returnValue(callspec.results.single(), result, vm) } Syscall.CLAMP_WORD -> { val (valueU, minimumU, maximumU) = getArgValues(callspec.arguments, vm) @@ -478,7 +481,7 @@ object SysCalls { val minimum = (minimumU as UShort).toShort().toInt() val maximum = (maximumU as UShort).toShort().toInt() val result = min(max(value, minimum), maximum) - returnValue(callspec.returns.single(), result, vm) + returnValue(callspec.results.single(), result, vm) } Syscall.CLAMP_UWORD -> { val (valueU, minimumU, maximumU) = getArgValues(callspec.arguments, vm) @@ -486,7 +489,7 @@ object SysCalls { val minimum = (minimumU as UShort).toInt() val maximum = (maximumU as UShort).toInt() val result = min(max(value, minimum), maximum) - returnValue(callspec.returns.single(), result, vm) + returnValue(callspec.results.single(), result, vm) } Syscall.CLAMP_LONG -> { val (valueU, minimumU, maximumU) = getArgValues(callspec.arguments, vm) @@ -494,7 +497,7 @@ object SysCalls { val minimum = minimumU as Int val maximum = maximumU as Int val result = min(max(value, minimum), maximum) - returnValue(callspec.returns.single(), result, vm) + returnValue(callspec.results.single(), result, vm) } Syscall.UNUSED1 -> throw NotImplementedError("unused1 syscall") Syscall.ATAN -> { @@ -507,10 +510,10 @@ object SysCalls { if(radians<0) radians+=2*PI val result = floor(radians/2.0/PI*256.0) - returnValue(callspec.returns.single(), result, vm) + returnValue(callspec.results.single(), result, vm) } Syscall.MUL16_LAST_UPPER -> { - returnValue(callspec.returns.single(), vm.mul16LastUpper, vm) + returnValue(callspec.results.single(), vm.mul16LastUpper, vm) } Syscall.FLOAT_TO_STR -> { val (buffer, number) = getArgValues(callspec.arguments, vm) @@ -552,7 +555,7 @@ object SysCalls { val target = targetA as Int val string = vm.memory.getString(source.toUInt()).take((maxlength as UByte).toInt()) vm.memory.setString(target.toUInt(), string, true) - returnValue(callspec.returns.single(), string.length, vm) + returnValue(callspec.results.single(), string.length, vm) } Syscall.LOAD -> { val (filenameA, addrA) = getArgValues(callspec.arguments, vm) @@ -563,9 +566,9 @@ object SysCalls { for (i in 0.. { @@ -577,9 +580,9 @@ object SysCalls { for (i in data.indices) { vm.memory.setUB(addr + i.toUInt(), data[i].toUByte()) } - returnValue(callspec.returns.single(), addr + data.size.toUInt(), vm) + returnValue(callspec.results.single(), addr + data.size.toUInt(), vm) } else { - returnValue(callspec.returns.single(), 0u, vm) + returnValue(callspec.results.single(), 0u, vm) } } Syscall.SAVE -> { @@ -605,10 +608,10 @@ object SysCalls { } val filename = vm.memory.getString((filenamePtr as Int).toUInt()) if (File(filename).exists()) - returnValue(callspec.returns.single(), 0u, vm) + returnValue(callspec.results.single(), 0u, vm) else { File(filename).writeBytes(data) - returnValue(callspec.returns.single(), 1u, vm) + returnValue(callspec.results.single(), 1u, vm) } } Syscall.DELETE -> { @@ -629,12 +632,12 @@ object SysCalls { directory.listDirectoryEntries().sorted().forEach { println("${it.toFile().length()}\t${it.normalize()}") } - returnValue(callspec.returns.single(), 1u, vm) + returnValue(callspec.results.single(), 1u, vm) } Syscall.GETGONSOLESIZE -> { // no arguments, returns size encoded IN ONE WORD (MSB=lines, LSB=columns) if(System.console()==null) { - return returnValue(callspec.returns.single(), 30*256 + 80, vm) // just return some defaults in this case 80*30 + return returnValue(callspec.results.single(), 30*256 + 80, vm) // just return some defaults in this case 80*30 } val linesS = System.getenv("LINES") @@ -642,7 +645,7 @@ object SysCalls { if(linesS!=null && columnsS!=null) { val lines = linesS.toInt() val columns = columnsS.toInt() - return returnValue(callspec.returns.single(), lines*256 + columns, vm) + return returnValue(callspec.results.single(), lines*256 + columns, vm) } try { @@ -652,18 +655,18 @@ object SysCalls { val response = process.inputStream.bufferedReader().lineSequence().iterator() val width = response.next().toInt() val height = response.next().toInt() - return returnValue(callspec.returns.single(), height*256 + width, vm) + return returnValue(callspec.results.single(), height*256 + width, vm) } } catch (_: Exception) { // don't know what happened... } - return returnValue(callspec.returns.single(), 30*256 + 80, vm) // just return some defaults in this case 80*30 + return returnValue(callspec.results.single(), 30*256 + 80, vm) // just return some defaults in this case 80*30 } Syscall.CURDIR -> { val curdir = Path("").absolute().toString() vm.memory.setString(0xfe00u, curdir, true) - return returnValue(callspec.returns.single(), 0xfe00u, vm) + return returnValue(callspec.results.single(), 0xfe00u, vm) } Syscall.MKDIR -> { val namePtr = getArgValues(callspec.arguments, vm).single() as Int @@ -680,42 +683,42 @@ object SysCalls { val namePtr = getArgValues(callspec.arguments, vm).single() as Int val name = vm.memory.getString(namePtr.toUInt()) val success = vm.open_file_read(name) - return returnValue(callspec.returns.single(), success, vm) + return returnValue(callspec.results.single(), success, vm) } Syscall.OPEN_FILE_WRITE -> { val namePtr = getArgValues(callspec.arguments, vm).single() as Int val name = vm.memory.getString(namePtr.toUInt()) val success = vm.open_file_write(name) - return returnValue(callspec.returns.single(), success, vm) + return returnValue(callspec.results.single(), success, vm) } Syscall.READ_FILE_BYTE -> { val (success, byte) = vm.read_file_byte() return if(success) - returnValue(callspec.returns.single(), 0x0100 or byte.toInt(), vm) + returnValue(callspec.results.single(), 0x0100 or byte.toInt(), vm) else - returnValue(callspec.returns.single(), 0x0000, vm) + returnValue(callspec.results.single(), 0x0000, vm) } Syscall.WRITE_FILE_BYTE -> { val byte = getArgValues(callspec.arguments, vm).single() as UByte return if(vm.write_file_byte(byte)) - returnValue(callspec.returns.single(), 1, vm) + returnValue(callspec.results.single(), 1, vm) else - returnValue(callspec.returns.single(), 0, vm) + returnValue(callspec.results.single(), 0, vm) } Syscall.CLOSE_FILE -> vm.close_file_read() Syscall.CLOSE_FILE_WRITE -> vm.close_file_write() Syscall.SEEK_FILE -> { val position = getArgValues(callspec.arguments, vm).single() as Int val success = vm.seek_file(position) - returnValue(callspec.returns.single(), success, vm) + returnValue(callspec.results.single(), success, vm) } Syscall.TELL_FILE_POS -> { val position = vm.tell_file_pos() - returnValue(callspec.returns.single(), position, vm) + returnValue(callspec.results.single(), position, vm) } Syscall.TELL_FILE_SIZE -> { val size = vm.tell_file_size() - returnValue(callspec.returns.single(), size, vm) + returnValue(callspec.results.single(), size, vm) } Syscall.GFX_TEXT -> { val (xV, yV, textAddrV, colorV) = getArgValues(callspec.arguments, vm) diff --git a/virtualmachine/src/prog8/vm/VirtualMachine.kt b/virtualmachine/src/prog8/vm/VirtualMachine.kt index 6dcc1c74c..9e3d7621e 100644 --- a/virtualmachine/src/prog8/vm/VirtualMachine.kt +++ b/virtualmachine/src/prog8/vm/VirtualMachine.kt @@ -43,7 +43,7 @@ class BreakpointException(val pcChunk: IRCodeChunk, val pcIndex: Int): Exception @Suppress("FunctionName") -class VirtualMachine(irProgram: IRProgram) { +class VirtualMachine(private val irProgram: IRProgram) { init { // The VM implements the STRICT status-bits contract: only CMP, CMPI, SEC, CLC, @@ -61,7 +61,7 @@ class VirtualMachine(irProgram: IRProgram) { } } - class CallSiteContext(val returnChunk: IRCodeChunk, val returnIndex: Int, val fcallSpec: FunctionCallArgs) + class CallSiteContext(val returnChunk: IRCodeChunk, val returnIndex: Int, val callSite: CallSite) // Constants for performance and maintainability private companion object { @@ -236,30 +236,33 @@ class VirtualMachine(irProgram: IRProgram) { } private fun branchTo(i: IRInstruction) { - when (val target = i.branchTarget) { - is IRCodeChunk -> { - pcChunk = target - pcIndex = 0 + // Branch/jump/call targets are resolved via the program's label linkage (filled in by + // IRProgram.linkChunks(), which VmProgramLoader.load() calls after loop expansion), rather + // than through a mutable per-instruction chunk reference (that field no longer exists). + when (val target = i.codeTarget) { + is CodeReference.Label -> { + when (val chunk = irProgram.resolveCodeTarget(target)) { + is IRCodeChunk -> { + pcChunk = chunk + pcIndex = 0 + } + is IRLoopChunk -> { + // Loops should have been expanded by VmProgramLoader; if still present, enter its first body chunk + val firstBody = chunk.body.firstOrNull() as? IRCodeChunk + ?: throw IllegalArgumentException("vm cannot branch to empty loop $chunk") + pcChunk = firstBody + pcIndex = 0 + } + is IRInlineAsmChunk -> TODO("branch to inline asm chunk") + is IRInlineBinaryChunk -> throw IllegalArgumentException("can't branch to inline binary chunk") + null -> throw IllegalArgumentException("label '${target.name}' not found for $i") + } } - is IRLoopChunk -> { - // Loops should have been expanded by VmProgramLoader; if still present, enter its first body chunk - val firstBody = target.body.firstOrNull() as? IRCodeChunk - ?: throw IllegalArgumentException("vm cannot branch to empty loop $target") - pcChunk = firstBody - pcIndex = 0 - } - null -> { - if(i.address!=null) - throw IllegalArgumentException("vm program can't jump to system memory address (${i.opcode} ${i.address!!.value.toInt().toHex()})") - else if(i.labelSymbol!=null) - throw IllegalArgumentException("vm program can't jump to system memory address (${i.opcode} ${i.labelSymbol})") - else if(i.reg1!=null) - throw IllegalArgumentException("vm program can't jump to system memory address (${i} = ${registers.getUW(i.reg1!!)})") - else - throw IllegalArgumentException("no branchtarget in $i") - } - is IRInlineAsmChunk -> TODO("branch to inline asm chunk") - is IRInlineBinaryChunk -> throw IllegalArgumentException("can't branch to inline binary chunk") + is CodeReference.Absolute -> + throw IllegalArgumentException("vm program can't jump to system memory address (${i.opcode} ${target.address.value.toInt().toHex()})") + is CodeReference.Indirect -> + throw IllegalArgumentException("vm program can't jump to system memory address (${i} = ${registers.getUW(target.pointer.registerNumber)})") + null -> throw IllegalArgumentException("no branchtarget in $i") } } @@ -466,29 +469,48 @@ class VirtualMachine(irProgram: IRProgram) { } } + // Helpers to compute effective addresses from the structured memory operands. + // VmProgramLoader resolves symbolic bases to absolute addresses before the VM runs, + // so base+displacement (+index*scale) is all that is left to compute here. + private fun MemoryReference.requireBaseAddress(): UInt = + absoluteAddress?.value ?: throw IllegalArgumentException("vm requires a resolved absolute memory address: $this") + + private fun MemoryReference.Direct.resolvedAddress(): UInt = requireBaseAddress() + displacement.toUInt() + + private fun MemoryReference.Indexed.resolvedBaseAddress(): UInt = requireBaseAddress() + displacement.toUInt() + + // element index register value, sized according to the target's index register width + private fun RegisterOperand.readIndexValue(): UInt = + if(wordArrayIndex) registers.getUW(registerNumber).toUInt() else registers.getUB(registerNumber).toUInt() + + // pointer register value, sized according to the target's pointer width + private fun RegisterOperand.readPointerValue(): UInt = + if(wordArrayIndex) registers.getSL(registerNumber).toUInt() else registers.getUW(registerNumber).toUInt() + private fun InsPUSH(i: IRInstruction) { if(valueStack.size >= VALUE_STACK_MAX) throw StackOverflowError("valuestack limit $VALUE_STACK_MAX exceeded") + val src = i.requireSrcA().registerNumber when(i.type!!) { IRDataType.BYTE -> { - val value = registers.getUB(i.reg1!!) + val value = registers.getUB(src) valueStack.add(value) } IRDataType.WORD -> { - val value = registers.getUW(i.reg1!!) + val value = registers.getUW(src) valueStack.pushw(value) } IRDataType.POINTER -> { - val value = registers.getSL(i.reg1!!) + val value = registers.getSL(src) valueStack.pushl(value) } IRDataType.LONG -> { - val value = registers.getSL(i.reg1!!) + val value = registers.getSL(src) valueStack.pushl(value) } IRDataType.FLOAT -> { - val value = registers.getFloat(i.fpReg1!!) + val value = registers.getFloat(src) valueStack.pushf(value) } } @@ -496,12 +518,13 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsPOP(i: IRInstruction) { + val dest = i.requireDest().registerNumber when(i.type!!) { - IRDataType.BYTE -> setResultReg(i.reg1!!, valueStack.removeLast().toInt(), i.type!!) - IRDataType.WORD -> setResultReg(i.reg1!!, valueStack.popw().toInt(), i.type!!) - IRDataType.POINTER -> setResultReg(i.reg1!!, valueStack.popl(), i.type!!) - IRDataType.LONG -> setResultReg(i.reg1!!, valueStack.popl(), i.type!!) - IRDataType.FLOAT -> registers.setFloat(i.fpReg1!!, valueStack.popf()) + IRDataType.BYTE -> setResultReg(dest, valueStack.removeLast().toInt(), i.type!!) + IRDataType.WORD -> setResultReg(dest, valueStack.popw().toInt(), i.type!!) + IRDataType.POINTER -> setResultReg(dest, valueStack.popl(), i.type!!) + IRDataType.LONG -> setResultReg(dest, valueStack.popl(), i.type!!) + IRDataType.FLOAT -> registers.setFloat(dest, valueStack.popf()) } nextPc() } @@ -543,8 +566,11 @@ class VirtualMachine(irProgram: IRProgram) { } value.dt=null } - val call = Syscall.fromInt(i.immediate!!) - SysCalls.call(call, i.fcallArgs!!, this) // note: any result value(s) are pushed back on the value stack + val site = i.requireCallSite() + val syscallNumber = (site.target as? CallTarget.SystemCall)?.number + ?: throw IllegalArgumentException("syscall must have a SystemCall target: $i") + val call = Syscall.fromInt(syscallNumber) + SysCalls.call(call, site, this) // note: any result value(s) are pushed back on the value stack nextPc() } @@ -557,82 +583,78 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsLOAD(i: IRInstruction) { + val dest = i.requireDest().registerNumber + val imm = i.requireImmediate() if(i.type==IRDataType.FLOAT) { - if (i.immediateFp != null) - registers.setFloat(i.fpReg1!!, i.immediateFp!!) - else { - if (i.labelSymbol == null) - throw IllegalArgumentException("expected LOAD of immediate or labelsymbol") - registers.setFloat(i.fpReg1!!, i.address!!.value.toDouble()) + when(imm) { + is ImmediateOperand.FloatValue -> registers.setFloat(dest, imm.value) + is ImmediateOperand.Integer -> registers.setFloat(dest, imm.value.toDouble()) + is ImmediateOperand.SymbolAddress -> throw IllegalArgumentException("expected LOAD of a resolved immediate or address, got unresolved symbol '${imm.symbol}'") } } else { - if(i.immediate!=null) { - setResultReg(i.reg1!!, i.immediate!!, i.type!!) - } - else { - if(i.labelSymbol==null) - throw IllegalArgumentException("expected LOAD of address of labelsymbol") - setResultReg(i.reg1!!, i.address!!.value.toInt(), i.type!!) + when(imm) { + is ImmediateOperand.Integer -> setResultReg(dest, imm.value, i.type!!) + is ImmediateOperand.SymbolAddress -> throw IllegalArgumentException("expected LOAD of a resolved address, got unresolved symbol '${imm.symbol}'") + is ImmediateOperand.FloatValue -> throw IllegalArgumentException("unexpected float immediate for non-float LOAD") } } nextPc() } private fun InsLOADM(i: IRInstruction) { + val dest = i.requireDest().registerNumber + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() when(i.type!!) { IRDataType.BYTE -> { - val value = memory.getUB(i.address!!.value) - registers.setUB(i.reg1!!, value) + val value = memory.getUB(address) + registers.setUB(dest, value) } IRDataType.WORD -> { - val value = memory.getUW(i.address!!.value) - registers.setUW(i.reg1!!, value) + val value = memory.getUW(address) + registers.setUW(dest, value) } IRDataType.POINTER -> { - val value = memory.getSL(i.address!!.value) - registers.setSL(i.reg1!!, value) + val value = memory.getSL(address) + registers.setSL(dest, value) } IRDataType.LONG -> { - val value = memory.getSL(i.address!!.value) - registers.setSL(i.reg1!!, value) + val value = memory.getSL(address) + registers.setSL(dest, value) } - IRDataType.FLOAT -> registers.setFloat(i.fpReg1!!, memory.getFloat(i.address!!.value)) + IRDataType.FLOAT -> registers.setFloat(dest, memory.getFloat(address)) } nextPc() } private fun InsLOADI(i: IRInstruction) { - val offset = i.immediate!! + val dest = i.requireDest().registerNumber + val mem = i.requireMemory() as MemoryReference.Indirect + val offset = mem.displacement require(offset in 0..65535) + val baseAddr = mem.pointer.readPointerValue() when(i.type!!) { IRDataType.FLOAT -> { - val baseAddr = if(wordArrayIndex) registers.getSL(i.reg1!!).toUInt() else registers.getUW(i.reg1!!).toUInt() val value = memory.getFloat(baseAddr + offset.toUInt()) - registers.setFloat(i.fpReg1!!, value) - } - else -> { - val baseAddr = if(wordArrayIndex) registers.getSL(i.reg2!!).toUInt() else registers.getUW(i.reg2!!).toUInt() - when(i.type!!) { - IRDataType.BYTE -> registers.setUB(i.reg1!!, memory.getUB(baseAddr + offset.toUInt())) - IRDataType.WORD -> registers.setUW(i.reg1!!, memory.getUW(baseAddr + offset.toUInt())) - IRDataType.POINTER -> registers.setUL(i.reg1!!, memory.getUL(baseAddr + offset.toUInt())) - IRDataType.LONG -> registers.setSL(i.reg1!!, memory.getSL(baseAddr + offset.toUInt())) - IRDataType.FLOAT -> throw IllegalStateException("unreachable") - } + registers.setFloat(dest, value) } + IRDataType.BYTE -> registers.setUB(dest, memory.getUB(baseAddr + offset.toUInt())) + IRDataType.WORD -> registers.setUW(dest, memory.getUW(baseAddr + offset.toUInt())) + IRDataType.POINTER -> registers.setUL(dest, memory.getUL(baseAddr + offset.toUInt())) + IRDataType.LONG -> registers.setSL(dest, memory.getSL(baseAddr + offset.toUInt())) } nextPc() } private fun InsLOADP_INC(i: IRInstruction) { - val ptrVarAddr = i.address!!.value + val dest = i.requireDest().registerNumber + val ptrVarAddr = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() val ptr = memory.getSL(ptrVarAddr).toUInt() when(i.type!!) { - IRDataType.BYTE -> registers.setUB(i.reg1!!, memory.getUB(ptr)) - IRDataType.WORD -> registers.setUW(i.reg1!!, memory.getUW(ptr)) - IRDataType.POINTER -> registers.setUL(i.reg1!!, memory.getUL(ptr)) - IRDataType.LONG -> registers.setSL(i.reg1!!, memory.getSL(ptr)) + IRDataType.BYTE -> registers.setUB(dest, memory.getUB(ptr)) + IRDataType.WORD -> registers.setUW(dest, memory.getUW(ptr)) + IRDataType.POINTER -> registers.setUL(dest, memory.getUL(ptr)) + IRDataType.LONG -> registers.setSL(dest, memory.getSL(ptr)) IRDataType.FLOAT -> throw IllegalStateException("LOADP_INC float not supported") } val inc = when(i.type!!) { @@ -647,13 +669,14 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsSTOREP_INC(i: IRInstruction) { - val ptrVarAddr = i.address!!.value + val src = i.requireSrcA().registerNumber + val ptrVarAddr = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() val ptr = memory.getSL(ptrVarAddr).toUInt() when(i.type!!) { - IRDataType.BYTE -> memory.setUB(ptr, registers.getUB(i.reg1!!)) - IRDataType.WORD -> memory.setUW(ptr, registers.getUW(i.reg1!!)) - IRDataType.POINTER -> memory.setUL(ptr, registers.getUL(i.reg1!!)) - IRDataType.LONG -> memory.setSL(ptr, registers.getSL(i.reg1!!)) + IRDataType.BYTE -> memory.setUB(ptr, registers.getUB(src)) + IRDataType.WORD -> memory.setUW(ptr, registers.getUW(src)) + IRDataType.POINTER -> memory.setUL(ptr, registers.getUL(src)) + IRDataType.LONG -> memory.setSL(ptr, registers.getSL(src)) IRDataType.FLOAT -> throw IllegalStateException("STOREP_INC float not supported") } val inc = when(i.type!!) { @@ -667,130 +690,123 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsLOADX(i: IRInstruction) { - val scale = i.scale.toUInt() + val dest = i.requireDest().registerNumber + val mem = i.requireMemory() as MemoryReference.Indexed + val scale = mem.scale.toUInt() + val base = mem.resolvedBaseAddress() + val index = mem.index.readIndexValue() + val address = base + index * scale when (i.type!!) { - IRDataType.FLOAT -> { - val index = if(wordArrayIndex) registers.getUW(i.reg1!!).toUInt() else registers.getUB(i.reg1!!).toUInt() - registers.setFloat(i.fpReg1!!, memory.getFloat(i.address!!.value + index * scale)) - } - else -> { - val index = if(wordArrayIndex) registers.getUW(i.reg2!!).toUInt() else registers.getUB(i.reg2!!).toUInt() - when (i.type!!) { - IRDataType.BYTE -> registers.setUB(i.reg1!!, memory.getUB(i.address!!.value + index * scale)) - IRDataType.WORD -> registers.setUW(i.reg1!!, memory.getUW(i.address!!.value + index * scale)) - IRDataType.POINTER -> registers.setUL(i.reg1!!, memory.getUL(i.address!!.value + index * scale)) - IRDataType.LONG -> registers.setSL(i.reg1!!, memory.getSL(i.address!!.value + index * scale)) - IRDataType.FLOAT -> throw IllegalStateException("unreachable") - } - } + IRDataType.BYTE -> registers.setUB(dest, memory.getUB(address)) + IRDataType.WORD -> registers.setUW(dest, memory.getUW(address)) + IRDataType.POINTER -> registers.setUL(dest, memory.getUL(address)) + IRDataType.LONG -> registers.setSL(dest, memory.getSL(address)) + IRDataType.FLOAT -> registers.setFloat(dest, memory.getFloat(address)) } nextPc() } private fun InsLOADR(i: IRInstruction) { + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber when(i.type!!) { IRDataType.BYTE -> { - val value = registers.getUB(i.reg2!!) - registers.setUB(i.reg1!!, value) + val value = registers.getUB(src) + registers.setUB(dest, value) } IRDataType.WORD -> { - val value = registers.getUW(i.reg2!!) - registers.setUW(i.reg1!!, value) + val value = registers.getUW(src) + registers.setUW(dest, value) } IRDataType.POINTER -> { - val value = registers.getSL(i.reg2!!) - registers.setSL(i.reg1!!, value) + val value = registers.getSL(src) + registers.setSL(dest, value) } IRDataType.LONG -> { - val value = registers.getSL(i.reg2!!) - registers.setSL(i.reg1!!, value) + val value = registers.getSL(src) + registers.setSL(dest, value) } - IRDataType.FLOAT -> registers.setFloat(i.fpReg1!!, registers.getFloat(i.fpReg2!!)) + IRDataType.FLOAT -> registers.setFloat(dest, registers.getFloat(src)) } nextPc() } private fun InsSTOREM(i: IRInstruction) { + val src = i.requireSrcA().registerNumber + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() when(i.type!!) { - IRDataType.BYTE -> memory.setUB(i.address!!.value, registers.getUB(i.reg1!!)) - IRDataType.WORD -> memory.setUW(i.address!!.value, registers.getUW(i.reg1!!)) - IRDataType.POINTER -> memory.setSL(i.address!!.value, registers.getSL(i.reg1!!)) - IRDataType.LONG -> memory.setSL(i.address!!.value, registers.getSL(i.reg1!!)) - IRDataType.FLOAT -> memory.setFloat(i.address!!.value, registers.getFloat(i.fpReg1!!)) + IRDataType.BYTE -> memory.setUB(address, registers.getUB(src)) + IRDataType.WORD -> memory.setUW(address, registers.getUW(src)) + IRDataType.POINTER -> memory.setSL(address, registers.getSL(src)) + IRDataType.LONG -> memory.setSL(address, registers.getSL(src)) + IRDataType.FLOAT -> memory.setFloat(address, registers.getFloat(src)) } nextPc() } private fun InsSTOREI(i: IRInstruction) { - val offset = i.immediate!! + val src = i.requireSrcA().registerNumber + val mem = i.requireMemory() as MemoryReference.Indirect + val offset = mem.displacement require(offset in 0..65535) + val baseAddr = mem.pointer.readPointerValue() when (i.type!!) { - IRDataType.FLOAT -> { - val baseAddr = if(wordArrayIndex) registers.getSL(i.reg1!!).toUInt() else registers.getUW(i.reg1!!).toUInt() - memory.setFloat(baseAddr + offset.toUInt(), registers.getFloat(i.fpReg1!!)) - } - else -> { - val baseAddr = if(wordArrayIndex) registers.getSL(i.reg2!!).toUInt() else registers.getUW(i.reg2!!).toUInt() - when (i.type!!) { - IRDataType.BYTE -> memory.setUB(baseAddr + offset.toUInt(), registers.getUB(i.reg1!!)) - IRDataType.WORD -> memory.setUW(baseAddr + offset.toUInt(), registers.getUW(i.reg1!!)) - IRDataType.POINTER -> memory.setUL(baseAddr + offset.toUInt(), registers.getUL(i.reg1!!)) - IRDataType.LONG -> memory.setSL(baseAddr + offset.toUInt(), registers.getSL(i.reg1!!)) - IRDataType.FLOAT -> throw IllegalStateException("unreachable") - } - } + IRDataType.FLOAT -> memory.setFloat(baseAddr + offset.toUInt(), registers.getFloat(src)) + IRDataType.BYTE -> memory.setUB(baseAddr + offset.toUInt(), registers.getUB(src)) + IRDataType.WORD -> memory.setUW(baseAddr + offset.toUInt(), registers.getUW(src)) + IRDataType.POINTER -> memory.setUL(baseAddr + offset.toUInt(), registers.getUL(src)) + IRDataType.LONG -> memory.setSL(baseAddr + offset.toUInt(), registers.getSL(src)) } nextPc() } private fun InsSTOREX(i: IRInstruction) { - val scale = i.scale.toUInt() + val src = i.requireSrcA().registerNumber + val mem = i.requireMemory() as MemoryReference.Indexed + val scale = mem.scale.toUInt() + val base = mem.resolvedBaseAddress() + val index = mem.index.readIndexValue() + val address = base + index * scale when (i.type!!) { - IRDataType.FLOAT -> { - val index = if(wordArrayIndex) registers.getUW(i.reg1!!).toUInt() else registers.getUB(i.reg1!!).toUInt() - memory.setFloat(i.address!!.value + index * scale, registers.getFloat(i.fpReg1!!)) - } - else -> { - val index = if(wordArrayIndex) registers.getUW(i.reg2!!).toUInt() else registers.getUB(i.reg2!!).toUInt() - when (i.type!!) { - IRDataType.BYTE -> memory.setUB(i.address!!.value + index * scale, registers.getUB(i.reg1!!)) - IRDataType.WORD -> memory.setUW(i.address!!.value + index * scale, registers.getUW(i.reg1!!)) - IRDataType.POINTER -> memory.setUL(i.address!!.value + index * scale, registers.getUL(i.reg1!!)) - IRDataType.LONG -> memory.setSL(i.address!!.value + index * scale, registers.getSL(i.reg1!!)) - IRDataType.FLOAT -> throw IllegalStateException("unreachable") - } - } + IRDataType.BYTE -> memory.setUB(address, registers.getUB(src)) + IRDataType.WORD -> memory.setUW(address, registers.getUW(src)) + IRDataType.POINTER -> memory.setUL(address, registers.getUL(src)) + IRDataType.LONG -> memory.setSL(address, registers.getSL(src)) + IRDataType.FLOAT -> memory.setFloat(address, registers.getFloat(src)) } nextPc() } private fun InsSTOREZM(i: IRInstruction) { + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() when(i.type!!) { - IRDataType.BYTE -> memory.setUB(i.address!!.value, 0u) - IRDataType.WORD -> memory.setUW(i.address!!.value, 0u) - IRDataType.POINTER -> memory.setSL(i.address!!.value, 0) - IRDataType.LONG -> memory.setSL(i.address!!.value, 0) - IRDataType.FLOAT -> memory.setFloat(i.address!!.value, 0.0) + IRDataType.BYTE -> memory.setUB(address, 0u) + IRDataType.WORD -> memory.setUW(address, 0u) + IRDataType.POINTER -> memory.setSL(address, 0) + IRDataType.LONG -> memory.setSL(address, 0) + IRDataType.FLOAT -> memory.setFloat(address, 0.0) } nextPc() } private fun InsSTOREIM(i: IRInstruction) { - val addr = i.address!!.value + val addr = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() + val imm = i.requireImmediate() when(i.type!!) { - IRDataType.BYTE -> memory.setUB(addr, (i.immediate!! and 0xff).toUByte()) - IRDataType.WORD -> memory.setUW(addr, (i.immediate!! and 0xffff).toUShort()) - IRDataType.POINTER -> memory.setSL(addr, i.immediate!!) - IRDataType.LONG -> memory.setSL(addr, i.immediate!!) - IRDataType.FLOAT -> memory.setFloat(addr, i.immediateFp!!) + IRDataType.BYTE -> memory.setUB(addr, (i.requireImmediateInt() and 0xff).toUByte()) + IRDataType.WORD -> memory.setUW(addr, (i.requireImmediateInt() and 0xffff).toUShort()) + IRDataType.POINTER -> memory.setSL(addr, i.requireImmediateInt()) + IRDataType.LONG -> memory.setSL(addr, i.requireImmediateInt()) + IRDataType.FLOAT -> memory.setFloat(addr, (imm as ImmediateOperand.FloatValue).value) } nextPc() } private fun InsSTOREZI(i: IRInstruction) { - val offset = i.immediate!! + val mem = i.requireMemory() as MemoryReference.Indirect + val offset = mem.displacement require(offset in 0..65535) - val baseAddr = if(wordArrayIndex) registers.getSL(i.reg1!!).toUInt() else registers.getUW(i.reg1!!).toUInt() + val baseAddr = mem.pointer.readPointerValue() when (i.type!!) { IRDataType.BYTE -> memory.setUB(baseAddr + offset.toUInt(), 0u) IRDataType.WORD -> memory.setUW(baseAddr + offset.toUInt(), 0u) @@ -802,14 +818,17 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsSTOREZX(i: IRInstruction) { - val index = if(wordArrayIndex) registers.getUW(i.reg1!!).toUInt() else registers.getUB(i.reg1!!).toUInt() - val scale = i.scale.toUInt() + val mem = i.requireMemory() as MemoryReference.Indexed + val index = mem.index.readIndexValue() + val scale = mem.scale.toUInt() + val base = mem.resolvedBaseAddress() + val address = base + index * scale when (i.type!!) { - IRDataType.BYTE -> memory.setUB(i.address!!.value + index * scale, 0u) - IRDataType.WORD -> memory.setUW(i.address!!.value + index * scale, 0u) - IRDataType.POINTER -> memory.setUL(i.address!!.value + index * scale, 0u) - IRDataType.LONG -> memory.setSL(i.address!!.value + index * scale, 0) - IRDataType.FLOAT -> memory.setFloat(i.address!!.value + index * scale, 0.0) + IRDataType.BYTE -> memory.setUB(address, 0u) + IRDataType.WORD -> memory.setUW(address, 0u) + IRDataType.POINTER -> memory.setUL(address, 0u) + IRDataType.LONG -> memory.setSL(address, 0) + IRDataType.FLOAT -> memory.setFloat(address, 0.0) } nextPc() } @@ -819,7 +838,8 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsJUMPI(i: IRInstruction) { - val artificialAddress: UInt = registers.getUL(i.reg1!!) + val pointer = (i.requireTarget() as CodeReference.Indirect).pointer + val artificialAddress: UInt = registers.getUL(pointer.registerNumber) if(!artificialLabelAddresses.contains(artificialAddress)) throw IllegalArgumentException("vm program can't jump to system memory address (${i.opcode} ${artificialAddress.toHex()})") pcChunk = artificialLabelAddresses.getValue(artificialAddress) @@ -830,18 +850,21 @@ class VirtualMachine(irProgram: IRProgram) { private val syscallParams = Array(100) { SyscallParamValue(null, null) } private fun InsCALL(i: IRInstruction) { - i.fcallArgs!!.arguments.forEach { arg -> - requireNotNull(arg.address) {"argument variable should have been given its memory address as well"} - when(arg.reg.dt) { - IRDataType.BYTE -> memory.setUB(arg.address!!, registers.getUB(arg.reg.registerNum)) - IRDataType.WORD -> memory.setUW(arg.address!!, registers.getUW(arg.reg.registerNum)) - IRDataType.POINTER -> memory.setSL(arg.address!!, registers.getSL(arg.reg.registerNum)) - IRDataType.LONG -> memory.setSL(arg.address!!, registers.getSL(arg.reg.registerNum)) - IRDataType.FLOAT -> memory.setFloat(arg.address!!, registers.getFloat(arg.reg.registerNum)) + val site = i.requireCallSite() + site.arguments.forEach { arg -> + val location = arg.location as? CallLocation.ParameterMemory + ?: throw IllegalArgumentException("argument variable should have been given its memory address as well") + val address = requireNotNull(location.address) { "argument variable should have been given its memory address as well" } + when(arg.source.type) { + IRDataType.BYTE -> memory.setUB(address.value, registers.getUB(arg.source.registerNumber)) + IRDataType.WORD -> memory.setUW(address.value, registers.getUW(arg.source.registerNumber)) + IRDataType.POINTER -> memory.setSL(address.value, registers.getSL(arg.source.registerNumber)) + IRDataType.LONG -> memory.setSL(address.value, registers.getSL(arg.source.registerNumber)) + IRDataType.FLOAT -> memory.setFloat(address.value, registers.getFloat(arg.source.registerNumber)) } } // store the call site and jump - callStack.add(CallSiteContext(pcChunk, pcIndex+1, i.fcallArgs!!)) + callStack.add(CallSiteContext(pcChunk, pcIndex+1, site)) branchTo(i) } @@ -861,12 +884,12 @@ class VirtualMachine(irProgram: IRProgram) { exit(0) else { val context = callStack.removeLast() - val returns = context.fcallSpec.returns + val returns = context.callSite.results when (i.type!!) { IRDataType.BYTE -> { if(returns.isNotEmpty()) { - val value = i.immediate!!.toUByte() - registers.setUB(returns.single().registerNum, value) + val value = i.requireImmediateInt().toUByte() + registers.setUB(returns.single().destination!!.registerNumber, value) } else { val callInstr = context.returnChunk.instructions[context.returnIndex-1] if(callInstr.opcode!=Opcode.CALL) @@ -875,8 +898,8 @@ class VirtualMachine(irProgram: IRProgram) { } IRDataType.WORD -> { if(returns.isNotEmpty()) { - val value = i.immediate!!.toUShort() - registers.setUW(returns.single().registerNum, value) + val value = i.requireImmediateInt().toUShort() + registers.setUW(returns.single().destination!!.registerNumber, value) } else { val callInstr = context.returnChunk.instructions[context.returnIndex-1] if(callInstr.opcode!=Opcode.CALL) @@ -885,8 +908,8 @@ class VirtualMachine(irProgram: IRProgram) { } IRDataType.POINTER -> { if(returns.isNotEmpty()) { - val value = i.immediate!! - registers.setSL(returns.single().registerNum, value) + val value = i.requireImmediateInt() + registers.setSL(returns.single().destination!!.registerNumber, value) } else { val callInstr = context.returnChunk.instructions[context.returnIndex-1] if(callInstr.opcode!=Opcode.CALL) @@ -895,8 +918,8 @@ class VirtualMachine(irProgram: IRProgram) { } IRDataType.LONG -> { if(returns.isNotEmpty()) { - val value = i.immediate!! - registers.setSL(returns.single().registerNum, value) + val value = i.requireImmediateInt() + registers.setSL(returns.single().destination!!.registerNumber, value) } else { val callInstr = context.returnChunk.instructions[context.returnIndex-1] if(callInstr.opcode!=Opcode.CALL) @@ -905,7 +928,7 @@ class VirtualMachine(irProgram: IRProgram) { } IRDataType.FLOAT -> { if(returns.isNotEmpty()) - registers.setFloat(returns.single().registerNum, i.immediateFp!!) + registers.setFloat(returns.single().destination!!.registerNumber, i.requireImmediateFloat()) else { val callInstr = context.returnChunk.instructions[context.returnIndex-1] if(callInstr.opcode!=Opcode.CALL) @@ -923,12 +946,13 @@ class VirtualMachine(irProgram: IRProgram) { exit(0) else { val context = callStack.removeLast() - val returns = context.fcallSpec.returns + val returns = context.callSite.results + val src = i.requireSrcA().registerNumber when (i.type!!) { IRDataType.BYTE -> { if(returns.isNotEmpty()) { - val value = registers.getUB(i.reg1!!) - registers.setUB(returns.single().registerNum, value) + val value = registers.getUB(src) + registers.setUB(returns.single().destination!!.registerNumber, value) } else { val callInstr = context.returnChunk.instructions[context.returnIndex-1] if(callInstr.opcode!=Opcode.CALL) @@ -937,8 +961,8 @@ class VirtualMachine(irProgram: IRProgram) { } IRDataType.WORD -> { if(returns.isNotEmpty()) { - val value = registers.getUW(i.reg1!!) - registers.setUW(returns.single().registerNum, value) + val value = registers.getUW(src) + registers.setUW(returns.single().destination!!.registerNumber, value) } else { val callInstr = context.returnChunk.instructions[context.returnIndex-1] if(callInstr.opcode!=Opcode.CALL) @@ -947,8 +971,8 @@ class VirtualMachine(irProgram: IRProgram) { } IRDataType.POINTER -> { if(returns.isNotEmpty()) { - val value = registers.getSL(i.reg1!!) - registers.setSL(returns.single().registerNum, value) + val value = registers.getSL(src) + registers.setSL(returns.single().destination!!.registerNumber, value) } else { val callInstr = context.returnChunk.instructions[context.returnIndex-1] if(callInstr.opcode!=Opcode.CALL) @@ -957,8 +981,8 @@ class VirtualMachine(irProgram: IRProgram) { } IRDataType.LONG -> { if(returns.isNotEmpty()) { - val value = registers.getSL(i.reg1!!) - registers.setSL(returns.single().registerNum, value) + val value = registers.getSL(src) + registers.setSL(returns.single().destination!!.registerNumber, value) } else { val callInstr = context.returnChunk.instructions[context.returnIndex-1] if(callInstr.opcode!=Opcode.CALL) @@ -967,7 +991,7 @@ class VirtualMachine(irProgram: IRProgram) { } IRDataType.FLOAT -> { if(returns.isNotEmpty()) - registers.setFloat(returns.single().registerNum, registers.getFloat(i.fpReg1!!)) + registers.setFloat(returns.single().destination!!.registerNumber, registers.getFloat(src)) else { val callInstr = context.returnChunk.instructions[context.returnIndex-1] if(callInstr.opcode!=Opcode.CALL) @@ -1133,30 +1157,31 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsINC(i: IRInstruction) { + val dest = i.requireDest().registerNumber when(i.type!!) { IRDataType.BYTE -> { - val value = (registers.getUB(i.reg1!!)+1u).toUByte() - registers.setUB(i.reg1!!, value) + val value = (registers.getUB(dest)+1u).toUByte() + registers.setUB(dest, value) } IRDataType.WORD -> { - val value = (registers.getUW(i.reg1!!)+1u).toUShort() - registers.setUW(i.reg1!!, value) + val value = (registers.getUW(dest)+1u).toUShort() + registers.setUW(dest, value) } IRDataType.POINTER -> { - val value = registers.getSL(i.reg1!!)+1 - registers.setSL(i.reg1!!, value) + val value = registers.getSL(dest)+1 + registers.setSL(dest, value) } IRDataType.LONG -> { - val value = registers.getSL(i.reg1!!)+1 - registers.setSL(i.reg1!!, value) + val value = registers.getSL(dest)+1 + registers.setSL(dest, value) } - IRDataType.FLOAT -> registers.setFloat(i.fpReg1!!, registers.getFloat(i.fpReg1!!)+1f) + IRDataType.FLOAT -> registers.setFloat(dest, registers.getFloat(dest)+1f) } nextPc() } private fun InsINCM(i: IRInstruction) { - val address = i.address!!.value + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() when(i.type!!) { IRDataType.BYTE -> { val value = (memory.getUB(address)+1u).toUByte() @@ -1180,76 +1205,79 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsDEC(i: IRInstruction) { + val dest = i.requireDest().registerNumber when(i.type!!) { IRDataType.BYTE -> { - val value = (registers.getUB(i.reg1!!)-1u).toUByte() - registers.setUB(i.reg1!!, value) + val value = (registers.getUB(dest)-1u).toUByte() + registers.setUB(dest, value) } IRDataType.WORD -> { - val value = (registers.getUW(i.reg1!!)-1u).toUShort() - registers.setUW(i.reg1!!, value) + val value = (registers.getUW(dest)-1u).toUShort() + registers.setUW(dest, value) } IRDataType.POINTER -> { - val value = registers.getSL(i.reg1!!)-1 - registers.setSL(i.reg1!!, value) + val value = registers.getSL(dest)-1 + registers.setSL(dest, value) } IRDataType.LONG -> { - val value = registers.getSL(i.reg1!!)-1 - registers.setSL(i.reg1!!, value) + val value = registers.getSL(dest)-1 + registers.setSL(dest, value) } - IRDataType.FLOAT -> registers.setFloat(i.fpReg1!!, registers.getFloat(i.fpReg1!!)-1f) + IRDataType.FLOAT -> registers.setFloat(dest, registers.getFloat(dest)-1f) } nextPc() } private fun InsDECM(i: IRInstruction) { + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() when(i.type!!) { IRDataType.BYTE -> { - val value = (memory.getUB(i.address!!.value)-1u).toUByte() - memory.setUB(i.address!!.value, value) + val value = (memory.getUB(address)-1u).toUByte() + memory.setUB(address, value) } IRDataType.WORD -> { - val value = (memory.getUW(i.address!!.value)-1u).toUShort() - memory.setUW(i.address!!.value, value) + val value = (memory.getUW(address)-1u).toUShort() + memory.setUW(address, value) } IRDataType.POINTER -> { - val value = memory.getSL(i.address!!.value)-1 - memory.setSL(i.address!!.value, value) + val value = memory.getSL(address)-1 + memory.setSL(address, value) } IRDataType.LONG -> { - val value = memory.getSL(i.address!!.value)-1 - memory.setSL(i.address!!.value, value) + val value = memory.getSL(address)-1 + memory.setSL(address, value) } - IRDataType.FLOAT -> memory.setFloat(i.address!!.value, memory.getFloat(i.address!!.value)-1f) + IRDataType.FLOAT -> memory.setFloat(address, memory.getFloat(address)-1f) } nextPc() } private fun InsNEG(i: IRInstruction) { + val dest = i.requireDest().registerNumber when(i.type!!) { IRDataType.BYTE -> { - val value = -registers.getUB(i.reg1!!).toInt() - registers.setUB(i.reg1!!, value.toUByte()) + val value = -registers.getUB(dest).toInt() + registers.setUB(dest, value.toUByte()) } IRDataType.WORD -> { - val value = -registers.getUW(i.reg1!!).toInt() - registers.setUW(i.reg1!!, value.toUShort()) + val value = -registers.getUW(dest).toInt() + registers.setUW(dest, value.toUShort()) } IRDataType.POINTER -> { - val value = -registers.getSL(i.reg1!!) - registers.setSL(i.reg1!!, value) + val value = -registers.getSL(dest) + registers.setSL(dest, value) } IRDataType.LONG -> { - val value = -registers.getSL(i.reg1!!) - registers.setSL(i.reg1!!, value) + val value = -registers.getSL(dest) + registers.setSL(dest, value) } - IRDataType.FLOAT -> registers.setFloat(i.fpReg1!!, -registers.getFloat(i.fpReg1!!)) + IRDataType.FLOAT -> registers.setFloat(dest, -registers.getFloat(dest)) } nextPc() } private fun InsNEGM(i: IRInstruction) { - val address = i.address!!.value + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() when(i.type!!) { IRDataType.BYTE -> { val value = -memory.getUB(address).toInt() @@ -1273,46 +1301,50 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsADDR(i: IRInstruction) { + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber when(i.type!!) { - IRDataType.BYTE -> plusMinusMultAnyByte("+", i.reg1!!, i.reg2!!) - IRDataType.WORD -> plusMinusMultAnyWord("+", i.reg1!!, i.reg2!!) - IRDataType.POINTER -> plusMinusMultAnyLong("+", i.reg1!!, i.reg2!!) - IRDataType.LONG -> plusMinusMultAnyLong("+", i.reg1!!, i.reg2!!) + IRDataType.BYTE -> plusMinusMultAnyByte("+", dest, src) + IRDataType.WORD -> plusMinusMultAnyWord("+", dest, src) + IRDataType.POINTER -> plusMinusMultAnyLong("+", dest, src) + IRDataType.LONG -> plusMinusMultAnyLong("+", dest, src) IRDataType.FLOAT -> { - val left = registers.getFloat(i.fpReg1!!) - val right = registers.getFloat(i.fpReg2!!) + val left = registers.getFloat(dest) + val right = registers.getFloat(src) val result = arithFloat(left, "+", right) - registers.setFloat(i.fpReg1!!, result) + registers.setFloat(dest, result) } } nextPc() } private fun InsADD(i: IRInstruction) { + val dest = i.requireDest().registerNumber when(i.type!!) { - IRDataType.BYTE -> plusMinusMultConstByte("+", i.reg1!!, i.immediate!!.toUByte()) - IRDataType.WORD -> plusMinusMultConstWord("+", i.reg1!!, i.immediate!!.toUShort()) - IRDataType.POINTER -> plusMinusMultConstLong("+", i.reg1!!, i.immediate!!) - IRDataType.LONG -> plusMinusMultConstLong("+", i.reg1!!, i.immediate!!) + IRDataType.BYTE -> plusMinusMultConstByte("+", dest, i.requireImmediateInt().toUByte()) + IRDataType.WORD -> plusMinusMultConstWord("+", dest, i.requireImmediateInt().toUShort()) + IRDataType.POINTER -> plusMinusMultConstLong("+", dest, i.requireImmediateInt()) + IRDataType.LONG -> plusMinusMultConstLong("+", dest, i.requireImmediateInt()) IRDataType.FLOAT -> { - val left = registers.getFloat(i.fpReg1!!) - val result = arithFloat(left, "+", i.immediateFp!!) - registers.setFloat(i.fpReg1!!, result) + val left = registers.getFloat(dest) + val result = arithFloat(left, "+", i.requireImmediateFloat()) + registers.setFloat(dest, result) } } nextPc() } private fun InsADDM(i: IRInstruction) { - val address = i.address!!.value + val src = i.requireSrcA().registerNumber + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() when(i.type!!) { - IRDataType.BYTE -> plusMinusMultAnyByteInplace("+", i.reg1!!, address) - IRDataType.WORD -> plusMinusMultAnyWordInplace("+", i.reg1!!, address) - IRDataType.POINTER -> plusMinusMultAnyLongInplace("+", i.reg1!!, address) - IRDataType.LONG -> plusMinusMultAnyLongInplace("+", i.reg1!!, address) + IRDataType.BYTE -> plusMinusMultAnyByteInplace("+", src, address) + IRDataType.WORD -> plusMinusMultAnyWordInplace("+", src, address) + IRDataType.POINTER -> plusMinusMultAnyLongInplace("+", src, address) + IRDataType.LONG -> plusMinusMultAnyLongInplace("+", src, address) IRDataType.FLOAT -> { val left = memory.getFloat(address) - val right = registers.getFloat(i.fpReg1!!) + val right = registers.getFloat(src) val result = arithFloat(left, "+", right) memory.setFloat(address, result) } @@ -1321,15 +1353,15 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsADDIM(i: IRInstruction) { - val address = i.address!!.value + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() when(i.type!!) { - IRDataType.BYTE -> plusMinusMultConstByteInplace("+", i.immediate!!.toUByte(), address) - IRDataType.WORD -> plusMinusMultConstWordInplace("+", i.immediate!!.toUShort(), address) - IRDataType.POINTER -> plusMinusMultConstLongInplace("+", i.immediate!!, address) - IRDataType.LONG -> plusMinusMultConstLongInplace("+", i.immediate!!, address) + IRDataType.BYTE -> plusMinusMultConstByteInplace("+", i.requireImmediateInt().toUByte(), address) + IRDataType.WORD -> plusMinusMultConstWordInplace("+", i.requireImmediateInt().toUShort(), address) + IRDataType.POINTER -> plusMinusMultConstLongInplace("+", i.requireImmediateInt(), address) + IRDataType.LONG -> plusMinusMultConstLongInplace("+", i.requireImmediateInt(), address) IRDataType.FLOAT -> { val left = memory.getFloat(address) - val result = arithFloat(left, "+", i.immediateFp!!) + val result = arithFloat(left, "+", i.requireImmediateFloat()) memory.setFloat(address, result) } } @@ -1337,46 +1369,50 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsSUBR(i: IRInstruction) { + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber when(i.type!!) { - IRDataType.BYTE -> plusMinusMultAnyByte("-", i.reg1!!, i.reg2!!) - IRDataType.WORD -> plusMinusMultAnyWord("-", i.reg1!!, i.reg2!!) - IRDataType.POINTER -> plusMinusMultAnyLong("-", i.reg1!!, i.reg2!!) - IRDataType.LONG -> plusMinusMultAnyLong("-", i.reg1!!, i.reg2!!) + IRDataType.BYTE -> plusMinusMultAnyByte("-", dest, src) + IRDataType.WORD -> plusMinusMultAnyWord("-", dest, src) + IRDataType.POINTER -> plusMinusMultAnyLong("-", dest, src) + IRDataType.LONG -> plusMinusMultAnyLong("-", dest, src) IRDataType.FLOAT -> { - val left = registers.getFloat(i.fpReg1!!) - val right = registers.getFloat(i.fpReg2!!) + val left = registers.getFloat(dest) + val right = registers.getFloat(src) val result = arithFloat(left, "-", right) - registers.setFloat(i.fpReg1!!, result) + registers.setFloat(dest, result) } } nextPc() } private fun InsSUB(i: IRInstruction) { + val dest = i.requireDest().registerNumber when(i.type!!) { - IRDataType.BYTE -> plusMinusMultConstByte("-", i.reg1!!, i.immediate!!.toUByte()) - IRDataType.WORD -> plusMinusMultConstWord("-", i.reg1!!, i.immediate!!.toUShort()) - IRDataType.POINTER -> plusMinusMultConstLong("-", i.reg1!!, i.immediate!!) - IRDataType.LONG -> plusMinusMultConstLong("-", i.reg1!!, i.immediate!!) + IRDataType.BYTE -> plusMinusMultConstByte("-", dest, i.requireImmediateInt().toUByte()) + IRDataType.WORD -> plusMinusMultConstWord("-", dest, i.requireImmediateInt().toUShort()) + IRDataType.POINTER -> plusMinusMultConstLong("-", dest, i.requireImmediateInt()) + IRDataType.LONG -> plusMinusMultConstLong("-", dest, i.requireImmediateInt()) IRDataType.FLOAT -> { - val left = registers.getFloat(i.fpReg1!!) - val result = arithFloat(left, "-", i.immediateFp!!) - registers.setFloat(i.fpReg1!!, result) + val left = registers.getFloat(dest) + val result = arithFloat(left, "-", i.requireImmediateFloat()) + registers.setFloat(dest, result) } } nextPc() } private fun InsSUBM(i: IRInstruction) { - val address = i.address!!.value + val src = i.requireSrcA().registerNumber + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() when(i.type!!) { - IRDataType.BYTE -> plusMinusMultAnyByteInplace("-", i.reg1!!, address) - IRDataType.WORD -> plusMinusMultAnyWordInplace("-", i.reg1!!, address) - IRDataType.POINTER -> plusMinusMultAnyLongInplace("-", i.reg1!!, address) - IRDataType.LONG -> plusMinusMultAnyLongInplace("-", i.reg1!!, address) + IRDataType.BYTE -> plusMinusMultAnyByteInplace("-", src, address) + IRDataType.WORD -> plusMinusMultAnyWordInplace("-", src, address) + IRDataType.POINTER -> plusMinusMultAnyLongInplace("-", src, address) + IRDataType.LONG -> plusMinusMultAnyLongInplace("-", src, address) IRDataType.FLOAT -> { val left = memory.getFloat(address) - val right = registers.getFloat(i.fpReg1!!) + val right = registers.getFloat(src) val result = arithFloat(left, "-", right) memory.setFloat(address, result) } @@ -1385,15 +1421,15 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsSUBIM(i: IRInstruction) { - val address = i.address!!.value + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() when(i.type!!) { - IRDataType.BYTE -> plusMinusMultConstByteInplace("-", i.immediate!!.toUByte(), address) - IRDataType.WORD -> plusMinusMultConstWordInplace("-", i.immediate!!.toUShort(), address) - IRDataType.POINTER -> plusMinusMultConstLongInplace("-", i.immediate!!, address) - IRDataType.LONG -> plusMinusMultConstLongInplace("-", i.immediate!!, address) + IRDataType.BYTE -> plusMinusMultConstByteInplace("-", i.requireImmediateInt().toUByte(), address) + IRDataType.WORD -> plusMinusMultConstWordInplace("-", i.requireImmediateInt().toUShort(), address) + IRDataType.POINTER -> plusMinusMultConstLongInplace("-", i.requireImmediateInt(), address) + IRDataType.LONG -> plusMinusMultConstLongInplace("-", i.requireImmediateInt(), address) IRDataType.FLOAT -> { val left = memory.getFloat(address) - val result = arithFloat(left, "-", i.immediateFp!!) + val result = arithFloat(left, "-", i.requireImmediateFloat()) memory.setFloat(address, result) } } @@ -1401,46 +1437,50 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsMULR(i: IRInstruction) { + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber when(i.type!!) { - IRDataType.BYTE -> plusMinusMultAnyByte("*", i.reg1!!, i.reg2!!) - IRDataType.WORD -> plusMinusMultAnyWord("*", i.reg1!!, i.reg2!!) - IRDataType.POINTER -> plusMinusMultAnyLong("*", i.reg1!!, i.reg2!!) + IRDataType.BYTE -> plusMinusMultAnyByte("*", dest, src) + IRDataType.WORD -> plusMinusMultAnyWord("*", dest, src) + IRDataType.POINTER -> plusMinusMultAnyLong("*", dest, src) IRDataType.LONG -> throw IllegalArgumentException("mulr unsigned long not supported") IRDataType.FLOAT -> { - val left = registers.getFloat(i.fpReg1!!) - val right = registers.getFloat(i.fpReg2!!) + val left = registers.getFloat(dest) + val right = registers.getFloat(src) val result = arithFloat(left, "*", right) - registers.setFloat(i.fpReg1!!, result) + registers.setFloat(dest, result) } } nextPc() } private fun InsMUL(i: IRInstruction) { + val dest = i.requireDest().registerNumber when(i.type!!) { - IRDataType.BYTE -> plusMinusMultConstByte("*", i.reg1!!, i.immediate!!.toUByte()) - IRDataType.WORD -> plusMinusMultConstWord("*", i.reg1!!, i.immediate!!.toUShort()) - IRDataType.POINTER -> plusMinusMultConstLong("*", i.reg1!!, i.immediate!!) + IRDataType.BYTE -> plusMinusMultConstByte("*", dest, i.requireImmediateInt().toUByte()) + IRDataType.WORD -> plusMinusMultConstWord("*", dest, i.requireImmediateInt().toUShort()) + IRDataType.POINTER -> plusMinusMultConstLong("*", dest, i.requireImmediateInt()) IRDataType.LONG -> throw IllegalArgumentException("mul unsigned long not supported") IRDataType.FLOAT -> { - val left = registers.getFloat(i.fpReg1!!) - val result = arithFloat(left, "*", i.immediateFp!!) - registers.setFloat(i.fpReg1!!, result) + val left = registers.getFloat(dest) + val result = arithFloat(left, "*", i.requireImmediateFloat()) + registers.setFloat(dest, result) } } nextPc() } private fun InsMULM(i: IRInstruction) { - val address = i.address!!.value + val src = i.requireSrcA().registerNumber + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() when(i.type!!) { - IRDataType.BYTE -> plusMinusMultAnyByteInplace("*", i.reg1!!, address) - IRDataType.WORD -> plusMinusMultAnyWordInplace("*", i.reg1!!, address) - IRDataType.POINTER -> plusMinusMultAnyLongInplace("*", i.reg1!!, address) + IRDataType.BYTE -> plusMinusMultAnyByteInplace("*", src, address) + IRDataType.WORD -> plusMinusMultAnyWordInplace("*", src, address) + IRDataType.POINTER -> plusMinusMultAnyLongInplace("*", src, address) IRDataType.LONG -> throw IllegalArgumentException("mulm unsigned long not supported") IRDataType.FLOAT -> { val left = memory.getFloat(address) - val right = registers.getFloat(i.fpReg1!!) + val right = registers.getFloat(src) val result = arithFloat(left, "*", right) memory.setFloat(address, result) } @@ -1449,46 +1489,50 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsMULSR(i: IRInstruction) { + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber when(i.type!!) { - IRDataType.BYTE -> multiplyAnyByteSigned(i.reg1!!, i.reg2!!) - IRDataType.WORD -> multiplyAnyWordSigned(i.reg1!!, i.reg2!!) - IRDataType.POINTER -> multiplyAnyLongSigned(i.reg1!!, i.reg2!!) - IRDataType.LONG -> multiplyAnyLongSigned(i.reg1!!, i.reg2!!) + IRDataType.BYTE -> multiplyAnyByteSigned(dest, src) + IRDataType.WORD -> multiplyAnyWordSigned(dest, src) + IRDataType.POINTER -> multiplyAnyLongSigned(dest, src) + IRDataType.LONG -> multiplyAnyLongSigned(dest, src) IRDataType.FLOAT -> { - val left = registers.getFloat(i.fpReg1!!) - val right = registers.getFloat(i.fpReg2!!) + val left = registers.getFloat(dest) + val right = registers.getFloat(src) val result = arithFloat(left, "*", right) - registers.setFloat(i.fpReg1!!, result) + registers.setFloat(dest, result) } } nextPc() } private fun InsMULS(i: IRInstruction) { + val dest = i.requireDest().registerNumber when(i.type!!) { - IRDataType.BYTE -> multiplyConstByteSigned(i.reg1!!, i.immediate!!.toByte()) - IRDataType.WORD -> multiplyConstWordSigned(i.reg1!!, i.immediate!!.toShort()) - IRDataType.POINTER -> multiplyConstLongSigned(i.reg1!!, i.immediate!!) - IRDataType.LONG -> multiplyConstLongSigned(i.reg1!!, i.immediate!!) + IRDataType.BYTE -> multiplyConstByteSigned(dest, i.requireImmediateInt().toByte()) + IRDataType.WORD -> multiplyConstWordSigned(dest, i.requireImmediateInt().toShort()) + IRDataType.POINTER -> multiplyConstLongSigned(dest, i.requireImmediateInt()) + IRDataType.LONG -> multiplyConstLongSigned(dest, i.requireImmediateInt()) IRDataType.FLOAT -> { - val left = registers.getFloat(i.fpReg1!!) - val result = arithFloat(left, "*", i.immediateFp!!) - registers.setFloat(i.fpReg1!!, result) + val left = registers.getFloat(dest) + val result = arithFloat(left, "*", i.requireImmediateFloat()) + registers.setFloat(dest, result) } } nextPc() } private fun InsMULSM(i: IRInstruction) { - val address = i.address!!.value + val src = i.requireSrcA().registerNumber + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() when(i.type!!) { - IRDataType.BYTE -> multiplyAnyByteSignedInplace(i.reg1!!, address) - IRDataType.WORD -> multiplyAnyWordSignedInplace(i.reg1!!, address) - IRDataType.POINTER -> multiplyAnyLongSignedInplace(i.reg1!!, address) - IRDataType.LONG -> multiplyAnyLongSignedInplace(i.reg1!!, address) + IRDataType.BYTE -> multiplyAnyByteSignedInplace(src, address) + IRDataType.WORD -> multiplyAnyWordSignedInplace(src, address) + IRDataType.POINTER -> multiplyAnyLongSignedInplace(src, address) + IRDataType.LONG -> multiplyAnyLongSignedInplace(src, address) IRDataType.FLOAT -> { val left = memory.getFloat(address) - val right = registers.getFloat(i.fpReg1!!) + val right = registers.getFloat(src) val result = arithFloat(left, "*", right) memory.setFloat(address, result) } @@ -1497,9 +1541,11 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsDIVR(i: IRInstruction) { + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber when(i.type!!) { - IRDataType.BYTE -> divOrModByteUnsigned("/", i.reg1!!, i.reg2!!) - IRDataType.WORD -> divOrModWordUnsigned("/", i.reg1!!, i.reg2!!) + IRDataType.BYTE -> divOrModByteUnsigned("/", dest, src) + IRDataType.WORD -> divOrModWordUnsigned("/", dest, src) IRDataType.POINTER -> throw IllegalArgumentException("divr unsigned pointer not supported") IRDataType.LONG -> throw IllegalArgumentException("divr unsigned long not supported") IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") @@ -1508,9 +1554,10 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsDIV(i: IRInstruction) { + val dest = i.requireDest().registerNumber when(i.type!!) { - IRDataType.BYTE -> divOrModConstByteUnsigned("/", i.reg1!!, i.immediate!!.toUByte()) - IRDataType.WORD -> divOrModConstWordUnsigned("/", i.reg1!!, i.immediate!!.toUShort()) + IRDataType.BYTE -> divOrModConstByteUnsigned("/", dest, i.requireImmediateInt().toUByte()) + IRDataType.WORD -> divOrModConstWordUnsigned("/", dest, i.requireImmediateInt().toUShort()) IRDataType.POINTER -> throw IllegalArgumentException("div unsigned pointer not supported") IRDataType.LONG -> throw IllegalArgumentException("div unsigned long not supported") IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") @@ -1519,10 +1566,11 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsDIVM(i: IRInstruction) { - val address = i.address!!.value + val src = i.requireSrcA().registerNumber + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() when(i.type!!) { - IRDataType.BYTE -> divModByteUnsignedInplace("/", i.reg1!!, address) - IRDataType.WORD -> divModWordUnsignedInplace("/", i.reg1!!, address) + IRDataType.BYTE -> divModByteUnsignedInplace("/", src, address) + IRDataType.WORD -> divModWordUnsignedInplace("/", src, address) IRDataType.POINTER -> throw IllegalArgumentException("divm unsigned pointer not supported") IRDataType.LONG -> throw IllegalArgumentException("divm unsigned long not supported") IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") @@ -1531,46 +1579,50 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsDIVSR(i: IRInstruction) { + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber when(i.type!!) { - IRDataType.BYTE -> divModByteSigned("/", i.reg1!!, i.reg2!!) - IRDataType.WORD -> divModWordSigned("/", i.reg1!!, i.reg2!!) - IRDataType.POINTER -> divModLongSigned("/", i.reg1!!, i.reg2!!) - IRDataType.LONG -> divModLongSigned("/", i.reg1!!, i.reg2!!) + IRDataType.BYTE -> divModByteSigned("/", dest, src) + IRDataType.WORD -> divModWordSigned("/", dest, src) + IRDataType.POINTER -> divModLongSigned("/", dest, src) + IRDataType.LONG -> divModLongSigned("/", dest, src) IRDataType.FLOAT -> { - val left = registers.getFloat(i.fpReg1!!) - val right = registers.getFloat(i.fpReg2!!) + val left = registers.getFloat(dest) + val right = registers.getFloat(src) val result = arithFloat(left, "/", right) - registers.setFloat(i.fpReg1!!, result) + registers.setFloat(dest, result) } } nextPc() } private fun InsDIVS(i: IRInstruction) { + val dest = i.requireDest().registerNumber when(i.type!!) { - IRDataType.BYTE -> divModConstByteSigned("/", i.reg1!!, i.immediate!!.toByte()) - IRDataType.WORD -> divModConstWordSigned("/", i.reg1!!, i.immediate!!.toShort()) - IRDataType.POINTER -> divModConstLongSigned("/", i.reg1!!, i.immediate!!) - IRDataType.LONG -> divModConstLongSigned("/", i.reg1!!, i.immediate!!) + IRDataType.BYTE -> divModConstByteSigned("/", dest, i.requireImmediateInt().toByte()) + IRDataType.WORD -> divModConstWordSigned("/", dest, i.requireImmediateInt().toShort()) + IRDataType.POINTER -> divModConstLongSigned("/", dest, i.requireImmediateInt()) + IRDataType.LONG -> divModConstLongSigned("/", dest, i.requireImmediateInt()) IRDataType.FLOAT -> { - val left = registers.getFloat(i.fpReg1!!) - val result = arithFloat(left, "/", i.immediateFp!!) - registers.setFloat(i.fpReg1!!, result) + val left = registers.getFloat(dest) + val result = arithFloat(left, "/", i.requireImmediateFloat()) + registers.setFloat(dest, result) } } nextPc() } private fun InsDIVSM(i: IRInstruction) { - val address = i.address!!.value + val src = i.requireSrcA().registerNumber + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() when(i.type!!) { - IRDataType.BYTE -> divModByteSignedInplace("/", i.reg1!!, address) - IRDataType.WORD -> divModWordSignedInplace("/", i.reg1!!, address) - IRDataType.POINTER -> divModLongSignedInplace("/", i.reg1!!, address) - IRDataType.LONG -> divModLongSignedInplace("/", i.reg1!!, address) + IRDataType.BYTE -> divModByteSignedInplace("/", src, address) + IRDataType.WORD -> divModWordSignedInplace("/", src, address) + IRDataType.POINTER -> divModLongSignedInplace("/", src, address) + IRDataType.LONG -> divModLongSignedInplace("/", src, address) IRDataType.FLOAT -> { val left = memory.getFloat(address) - val right = registers.getFloat(i.fpReg1!!) + val right = registers.getFloat(src) val result = arithFloat(left, "/", right) memory.setFloat(address, result) } @@ -1579,9 +1631,11 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsMODR(i: IRInstruction) { + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber when(i.type!!) { - IRDataType.BYTE -> divOrModByteUnsigned("%", i.reg1!!, i.reg2!!) - IRDataType.WORD -> divOrModWordUnsigned("%", i.reg1!!, i.reg2!!) + IRDataType.BYTE -> divOrModByteUnsigned("%", dest, src) + IRDataType.WORD -> divOrModWordUnsigned("%", dest, src) IRDataType.POINTER -> throw IllegalArgumentException("modr unsigned pointer not supported") IRDataType.LONG -> throw IllegalArgumentException("modr unsigned long not supported") IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") @@ -1590,9 +1644,10 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsMOD(i: IRInstruction) { + val dest = i.requireDest().registerNumber when(i.type!!) { - IRDataType.BYTE -> divOrModConstByteUnsigned("%", i.reg1!!, i.immediate!!.toUByte()) - IRDataType.WORD -> divOrModConstWordUnsigned("%", i.reg1!!, i.immediate!!.toUShort()) + IRDataType.BYTE -> divOrModConstByteUnsigned("%", dest, i.requireImmediateInt().toUByte()) + IRDataType.WORD -> divOrModConstWordUnsigned("%", dest, i.requireImmediateInt().toUShort()) IRDataType.POINTER -> throw IllegalArgumentException("mod unsigned pointer not supported") IRDataType.LONG -> throw IllegalArgumentException("mod unsigned long not supported") IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") @@ -1601,31 +1656,36 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsMODSR(i: IRInstruction) { + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber when(i.type!!) { - IRDataType.BYTE -> divModByteSigned("%", i.reg1!!, i.reg2!!) - IRDataType.WORD -> divModWordSigned("%", i.reg1!!, i.reg2!!) - IRDataType.POINTER -> divModLongSigned("%", i.reg1!!, i.reg2!!) - IRDataType.LONG -> divModLongSigned("%", i.reg1!!, i.reg2!!) + IRDataType.BYTE -> divModByteSigned("%", dest, src) + IRDataType.WORD -> divModWordSigned("%", dest, src) + IRDataType.POINTER -> divModLongSigned("%", dest, src) + IRDataType.LONG -> divModLongSigned("%", dest, src) IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } nextPc() } private fun InsMODS(i: IRInstruction) { + val dest = i.requireDest().registerNumber when(i.type!!) { - IRDataType.BYTE -> divModConstByteSigned("%", i.reg1!!, i.immediate!!.toByte()) - IRDataType.WORD -> divModConstWordSigned("%", i.reg1!!, i.immediate!!.toShort()) - IRDataType.POINTER -> divModConstLongSigned("%", i.reg1!!, i.immediate!!) - IRDataType.LONG -> divModConstLongSigned("%", i.reg1!!, i.immediate!!) + IRDataType.BYTE -> divModConstByteSigned("%", dest, i.requireImmediateInt().toByte()) + IRDataType.WORD -> divModConstWordSigned("%", dest, i.requireImmediateInt().toShort()) + IRDataType.POINTER -> divModConstLongSigned("%", dest, i.requireImmediateInt()) + IRDataType.LONG -> divModConstLongSigned("%", dest, i.requireImmediateInt()) IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } nextPc() } private fun InsDIVMODR(i: IRInstruction) { + val quotient = i.requireDest().registerNumber + val remainder = i.requireDestB().registerNumber when(i.type!!) { - IRDataType.BYTE -> divAndModUByte(i.reg1!!, i.reg2!!) - IRDataType.WORD -> divAndModUWord(i.reg1!!, i.reg2!!) + IRDataType.BYTE -> divAndModUByte(quotient, remainder) + IRDataType.WORD -> divAndModUWord(quotient, remainder) IRDataType.POINTER -> throw IllegalArgumentException("divmodr unsigned pointer not supported") IRDataType.LONG -> throw IllegalArgumentException("divmodr unsigned long not supported") IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") @@ -1634,9 +1694,11 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsDIVMOD(i: IRInstruction) { + val quotient = i.requireDest().registerNumber + val remainder = i.requireDestB().registerNumber when(i.type!!) { - IRDataType.BYTE -> divAndModConstUByte(i.reg1!!, i.reg2!!, i.immediate!!.toUByte()) - IRDataType.WORD -> divAndModConstUWord(i.reg1!!, i.reg2!!, i.immediate!!.toUShort()) + IRDataType.BYTE -> divAndModConstUByte(quotient, remainder, i.requireImmediateInt().toUByte()) + IRDataType.WORD -> divAndModConstUWord(quotient, remainder, i.requireImmediateInt().toUShort()) IRDataType.POINTER -> throw IllegalArgumentException("divmod unsigned pointer not supported") IRDataType.LONG -> throw IllegalArgumentException("divmod unsigned long not supported") IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") @@ -1645,9 +1707,11 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsSDIVMODR(i: IRInstruction) { + val quotient = i.requireDest().registerNumber + val remainder = i.requireDestB().registerNumber when(i.type!!) { - IRDataType.BYTE -> divAndModSByte(i.reg1!!, i.reg2!!) - IRDataType.WORD -> divAndModSWord(i.reg1!!, i.reg2!!) + IRDataType.BYTE -> divAndModSByte(quotient, remainder) + IRDataType.WORD -> divAndModSWord(quotient, remainder) IRDataType.POINTER -> throw IllegalArgumentException("divmodr signed pointer not supported") IRDataType.LONG -> throw IllegalArgumentException("divmodr signed long not supported") IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") @@ -1656,9 +1720,11 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsSDIVMOD(i: IRInstruction) { + val quotient = i.requireDest().registerNumber + val remainder = i.requireDestB().registerNumber when(i.type!!) { - IRDataType.BYTE -> divAndModConstSByte(i.reg1!!, i.reg2!!, i.immediate!!.toByte()) - IRDataType.WORD -> divAndModConstSWord(i.reg1!!, i.reg2!!, i.immediate!!.toShort()) + IRDataType.BYTE -> divAndModConstSByte(quotient, remainder, i.requireImmediateInt().toByte()) + IRDataType.WORD -> divAndModConstSWord(quotient, remainder, i.requireImmediateInt().toShort()) IRDataType.POINTER -> throw IllegalArgumentException("divmod signed pointer not supported") IRDataType.LONG -> throw IllegalArgumentException("divmod signed long not supported") IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") @@ -1667,58 +1733,64 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsSGN(i: IRInstruction) { + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber val sign: Int = when (i.type!!) { - IRDataType.BYTE -> registers.getSB(i.reg2!!).toInt().sign - IRDataType.WORD -> registers.getSW(i.reg2!!).toInt().sign - IRDataType.POINTER -> registers.getSL(i.reg2!!).sign - IRDataType.LONG -> registers.getSL(i.reg2!!).sign - IRDataType.FLOAT -> registers.getFloat(i.fpReg1!!).sign.toInt() + IRDataType.BYTE -> registers.getSB(src).toInt().sign + IRDataType.WORD -> registers.getSW(src).toInt().sign + IRDataType.POINTER -> registers.getSL(src).sign + IRDataType.LONG -> registers.getSL(src).sign + IRDataType.FLOAT -> registers.getFloat(src).sign.toInt() } - registers.setSB(i.reg1!!, sign.toByte()) + registers.setSB(dest, sign.toByte()) statusbitsComparisonWithOverflow(sign, 0, IRDataType.BYTE) nextPc() } private fun InsSQRT(i: IRInstruction) { + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber when(i.type!!) { - IRDataType.BYTE -> registers.setUB(i.reg1!!, sqrt(registers.getUB(i.reg2!!).toDouble()).toInt().toUByte()) - IRDataType.WORD -> registers.setUB(i.reg1!!, sqrt(registers.getUW(i.reg2!!).toDouble()).toInt().toUByte()) + IRDataType.BYTE -> registers.setUB(dest, sqrt(registers.getUB(src).toDouble()).toInt().toUByte()) + IRDataType.WORD -> registers.setUB(dest, sqrt(registers.getUW(src).toDouble()).toInt().toUByte()) IRDataType.POINTER -> { - val value = registers.getUL(i.reg2!!) - registers.setUL(i.reg1!!, sqrt(value.toDouble()).toUInt()) + val value = registers.getUL(src) + registers.setUL(dest, sqrt(value.toDouble()).toUInt()) } IRDataType.LONG -> { - val value = registers.getSL(i.reg2!!) + val value = registers.getSL(src) if(value<0) - throw IllegalArgumentException("sqrt of negative long $value reg=${i.reg2}") - registers.setSL(i.reg1!!, sqrt(value.toDouble()).toInt()) + throw IllegalArgumentException("sqrt of negative long $value reg=$src") + registers.setSL(dest, sqrt(value.toDouble()).toInt()) } - IRDataType.FLOAT -> registers.setFloat(i.fpReg1!!, sqrt(registers.getFloat(i.fpReg2!!))) + IRDataType.FLOAT -> registers.setFloat(dest, sqrt(registers.getFloat(src))) } nextPc() } private fun InsSQUARE(i: IRInstruction) { + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber when(i.type!!) { IRDataType.BYTE -> { - val value = registers.getUB(i.reg2!!).toInt() - registers.setUB(i.reg1!!, (value*value).toUByte()) + val value = registers.getUB(src).toInt() + registers.setUB(dest, (value*value).toUByte()) } IRDataType.WORD -> { - val value = registers.getUW(i.reg2!!).toInt() - registers.setUW(i.reg1!!, (value*value).toUShort()) + val value = registers.getUW(src).toInt() + registers.setUW(dest, (value*value).toUShort()) } IRDataType.POINTER -> { - val value = registers.getSL(i.reg2!!) - registers.setSL(i.reg1!!, value*value) + val value = registers.getSL(src) + registers.setSL(dest, value*value) } IRDataType.LONG -> { - val value = registers.getSL(i.reg2!!) - registers.setSL(i.reg1!!, value*value) + val value = registers.getSL(src) + registers.setSL(dest, value*value) } IRDataType.FLOAT -> { - val value = registers.getFloat(i.fpReg2!!) - registers.setFloat(i.fpReg1!!, value*value) + val value = registers.getFloat(src) + registers.setFloat(dest, value*value) } } nextPc() @@ -1726,18 +1798,20 @@ class VirtualMachine(irProgram: IRProgram) { private fun InsCMP(i: IRInstruction) { val type = i.type!! + val leftReg = i.requireSrcA().registerNumber + val rightReg = i.requireSrcB().registerNumber val left = when(type) { - IRDataType.BYTE -> registers.getUB(i.reg1!!).toInt() - IRDataType.WORD -> registers.getUW(i.reg1!!).toInt() - IRDataType.POINTER -> registers.getSL(i.reg1!!) - IRDataType.LONG -> registers.getSL(i.reg1!!) + IRDataType.BYTE -> registers.getUB(leftReg).toInt() + IRDataType.WORD -> registers.getUW(leftReg).toInt() + IRDataType.POINTER -> registers.getSL(leftReg) + IRDataType.LONG -> registers.getSL(leftReg) IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } val right = when(type) { - IRDataType.BYTE -> registers.getUB(i.reg2!!).toInt() - IRDataType.WORD -> registers.getUW(i.reg2!!).toInt() - IRDataType.POINTER -> registers.getSL(i.reg2!!) - IRDataType.LONG -> registers.getSL(i.reg2!!) + IRDataType.BYTE -> registers.getUB(rightReg).toInt() + IRDataType.WORD -> registers.getUW(rightReg).toInt() + IRDataType.POINTER -> registers.getSL(rightReg) + IRDataType.LONG -> registers.getSL(rightReg) IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } statusbitsComparisonWithOverflow(left, right, type) @@ -1746,53 +1820,54 @@ class VirtualMachine(irProgram: IRProgram) { private fun InsCMPI(i: IRInstruction) { val type = i.type!! + val leftReg = i.requireSrcA().registerNumber val left = when(type) { - IRDataType.BYTE -> registers.getUB(i.reg1!!).toInt() - IRDataType.WORD -> registers.getUW(i.reg1!!).toInt() - IRDataType.POINTER -> registers.getSL(i.reg1!!) - IRDataType.LONG -> registers.getSL(i.reg1!!) + IRDataType.BYTE -> registers.getUB(leftReg).toInt() + IRDataType.WORD -> registers.getUW(leftReg).toInt() + IRDataType.POINTER -> registers.getSL(leftReg) + IRDataType.LONG -> registers.getSL(leftReg) IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } val right = when(type) { - IRDataType.BYTE -> i.immediate!! and 0xff - IRDataType.WORD -> i.immediate!! and 0xffff - IRDataType.POINTER -> i.immediate!! - IRDataType.LONG -> i.immediate!! + IRDataType.BYTE -> i.requireImmediateInt() and 0xff + IRDataType.WORD -> i.requireImmediateInt() and 0xffff + IRDataType.POINTER -> i.requireImmediateInt() + IRDataType.LONG -> i.requireImmediateInt() IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } statusbitsComparisonWithOverflow(left, right, type) nextPc() } - private fun InsLOADHFACZERO(ins: IRInstruction) { - registers.setFloat(ins.fpReg1!!, hardwareRegisterFAC0) + private fun InsLOADHFACZERO(i: IRInstruction) { + registers.setFloat(i.requireFloatDest().registerNumber, hardwareRegisterFAC0) nextPc() } - private fun InsLOADHFACONE(ins: IRInstruction) { - registers.setFloat(ins.fpReg1!!, hardwareRegisterFAC1) + private fun InsLOADHFACONE(i: IRInstruction) { + registers.setFloat(i.requireFloatDest().registerNumber, hardwareRegisterFAC1) nextPc() } - private fun InsSTOREHFACZERO(ins: IRInstruction) { - hardwareRegisterFAC0 = registers.getFloat(ins.fpReg1!!) + private fun InsSTOREHFACZERO(i: IRInstruction) { + hardwareRegisterFAC0 = registers.getFloat(i.requireFloatSourceA().registerNumber) nextPc() } - private fun InsSTOREHFACONE(ins: IRInstruction) { - hardwareRegisterFAC1 = registers.getFloat(ins.fpReg1!!) + private fun InsSTOREHFACONE(i: IRInstruction) { + hardwareRegisterFAC1 = registers.getFloat(i.requireFloatSourceA().registerNumber) nextPc() } private fun InsBITTST(i: IRInstruction) { - if (i.reg1 == null) throw IllegalArgumentException("bittst needs a register") - val mask = 1 shl i.immediate!! + val src = i.requireSrcA().registerNumber + val mask = 1 shl i.requireImmediateInt() val value: Int = when(i.type!!) { - IRDataType.BYTE -> registers.getUB(i.reg1!!).toInt() - IRDataType.WORD -> registers.getUW(i.reg1!!).toInt() - IRDataType.POINTER -> registers.getSL(i.reg1!!) - IRDataType.LONG -> registers.getSL(i.reg1!!) + IRDataType.BYTE -> registers.getUB(src).toInt() + IRDataType.WORD -> registers.getUW(src).toInt() + IRDataType.POINTER -> registers.getSL(src) + IRDataType.LONG -> registers.getSL(src) IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } statusZero = value and mask == 0 @@ -1800,23 +1875,24 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsBITSET(i: IRInstruction) { - val mask = 1 shl i.immediate!! + val dest = i.requireDest().registerNumber + val mask = 1 shl i.requireImmediateInt() when(i.type!!) { IRDataType.BYTE -> { - val value = registers.getUB(i.reg1!!).toInt() or mask - registers.setUB(i.reg1!!, value.toUByte()) + val value = registers.getUB(dest).toInt() or mask + registers.setUB(dest, value.toUByte()) } IRDataType.WORD -> { - val value = registers.getUW(i.reg1!!).toInt() or mask - registers.setUW(i.reg1!!, value.toUShort()) + val value = registers.getUW(dest).toInt() or mask + registers.setUW(dest, value.toUShort()) } IRDataType.POINTER -> { - val value = registers.getSL(i.reg1!!) or mask - registers.setSL(i.reg1!!, value) + val value = registers.getSL(dest) or mask + registers.setSL(dest, value) } IRDataType.LONG -> { - val value = registers.getSL(i.reg1!!) or mask - registers.setSL(i.reg1!!, value) + val value = registers.getSL(dest) or mask + registers.setSL(dest, value) } IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } @@ -1824,23 +1900,24 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsBITCLR(i: IRInstruction) { - val mask = 1 shl i.immediate!! + val dest = i.requireDest().registerNumber + val mask = 1 shl i.requireImmediateInt() when(i.type!!) { IRDataType.BYTE -> { - val value = registers.getUB(i.reg1!!).toInt() and mask.inv() - registers.setUB(i.reg1!!, value.toUByte()) + val value = registers.getUB(dest).toInt() and mask.inv() + registers.setUB(dest, value.toUByte()) } IRDataType.WORD -> { - val value = registers.getUW(i.reg1!!).toInt() and mask.inv() - registers.setUW(i.reg1!!, value.toUShort()) + val value = registers.getUW(dest).toInt() and mask.inv() + registers.setUW(dest, value.toUShort()) } IRDataType.POINTER -> { - val value = registers.getSL(i.reg1!!) and mask.inv() - registers.setSL(i.reg1!!, value) + val value = registers.getSL(dest) and mask.inv() + registers.setSL(dest, value) } IRDataType.LONG -> { - val value = registers.getSL(i.reg1!!) and mask.inv() - registers.setSL(i.reg1!!, value) + val value = registers.getSL(dest) and mask.inv() + registers.setSL(dest, value) } IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } @@ -1848,23 +1925,24 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsBITTOG(i: IRInstruction) { - val mask = 1 shl i.immediate!! + val dest = i.requireDest().registerNumber + val mask = 1 shl i.requireImmediateInt() when(i.type!!) { IRDataType.BYTE -> { - val value = registers.getUB(i.reg1!!).toInt() xor mask - registers.setUB(i.reg1!!, value.toUByte()) + val value = registers.getUB(dest).toInt() xor mask + registers.setUB(dest, value.toUByte()) } IRDataType.WORD -> { - val value = registers.getUW(i.reg1!!).toInt() xor mask - registers.setUW(i.reg1!!, value.toUShort()) + val value = registers.getUW(dest).toInt() xor mask + registers.setUW(dest, value.toUShort()) } IRDataType.POINTER -> { - val value = registers.getSL(i.reg1!!) xor mask - registers.setSL(i.reg1!!, value) + val value = registers.getSL(dest) xor mask + registers.setSL(dest, value) } IRDataType.LONG -> { - val value = registers.getSL(i.reg1!!) xor mask - registers.setSL(i.reg1!!, value) + val value = registers.getSL(dest) xor mask + registers.setSL(dest, value) } IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } @@ -1872,9 +1950,11 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsEXT(i: IRInstruction) { + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber when(i.type!!){ - IRDataType.BYTE -> registers.setUW(i.reg1!!, registers.getUB(i.reg2!!).toUShort()) - IRDataType.WORD -> registers.setSL(i.reg1!!, registers.getUW(i.reg2!!).toInt()) + IRDataType.BYTE -> registers.setUW(dest, registers.getUB(src).toUShort()) + IRDataType.WORD -> registers.setSL(dest, registers.getUW(src).toInt()) IRDataType.POINTER -> throw IllegalArgumentException("ext.p makes no sense, 32 bits is already the widest you can get") IRDataType.LONG -> throw IllegalArgumentException("ext.l makes no sense, 32 bits is already the widest you can get") IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") @@ -1883,9 +1963,11 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsEXTS(i: IRInstruction) { + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber when(i.type!!){ - IRDataType.BYTE -> registers.setSW(i.reg1!!, registers.getSB(i.reg2!!).toShort()) - IRDataType.WORD -> registers.setSL(i.reg1!!, registers.getSW(i.reg2!!).toInt()) + IRDataType.BYTE -> registers.setSW(dest, registers.getSB(src).toShort()) + IRDataType.WORD -> registers.setSL(dest, registers.getSW(src).toInt()) IRDataType.POINTER -> throw IllegalArgumentException("exts.p makes no sense, 32 bits is already the widest you can get") IRDataType.LONG -> throw IllegalArgumentException("exts.l makes no sense, 32 bits is already the widest you can get") IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") @@ -1894,46 +1976,52 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsEXTL(i: IRInstruction) { - registers.setSL(i.reg1!!, registers.getUB(i.reg2!!).toInt()) + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber + registers.setSL(dest, registers.getUB(src).toInt()) nextPc() } private fun InsEXTLS(i: IRInstruction) { - registers.setSL(i.reg1!!, registers.getSB(i.reg2!!).toInt()) + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber + registers.setSL(dest, registers.getSB(src).toInt()) nextPc() } private fun InsANDR(i: IRInstruction) { + val dest = i.requireDest().registerNumber val (left: UInt, right: UInt) = getLogicalOperandsU(i) val value = (left and right).toInt() when(i.type!!) { - IRDataType.BYTE -> registers.setUB(i.reg1!!, value.toUByte()) - IRDataType.WORD -> registers.setUW(i.reg1!!, value.toUShort()) - IRDataType.POINTER -> registers.setSL(i.reg1!!, value) - IRDataType.LONG -> registers.setSL(i.reg1!!, value) + IRDataType.BYTE -> registers.setUB(dest, value.toUByte()) + IRDataType.WORD -> registers.setUW(dest, value.toUShort()) + IRDataType.POINTER -> registers.setSL(dest, value) + IRDataType.LONG -> registers.setSL(dest, value) IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } nextPc() } private fun InsAND(i: IRInstruction) { + val dest = i.requireDest().registerNumber val value: Int when(i.type!!) { IRDataType.BYTE -> { - value = registers.getUB(i.reg1!!).toInt() and i.immediate!! - registers.setUB(i.reg1!!, value.toUByte()) + value = registers.getUB(dest).toInt() and i.requireImmediateInt() + registers.setUB(dest, value.toUByte()) } IRDataType.WORD -> { - value = registers.getUW(i.reg1!!).toInt() and i.immediate!! - registers.setUW(i.reg1!!, value.toUShort()) + value = registers.getUW(dest).toInt() and i.requireImmediateInt() + registers.setUW(dest, value.toUShort()) } IRDataType.POINTER -> { - value = registers.getSL(i.reg1!!) and i.immediate!! - registers.setSL(i.reg1!!, value) + value = registers.getSL(dest) and i.requireImmediateInt() + registers.setSL(dest, value) } IRDataType.LONG -> { - value = registers.getSL(i.reg1!!) and i.immediate!! - registers.setSL(i.reg1!!, value) + value = registers.getSL(dest) and i.requireImmediateInt() + registers.setSL(dest, value) } IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } @@ -1941,30 +2029,31 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsANDM(i: IRInstruction) { + val src = i.requireSrcA().registerNumber + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() val value: Int - val address = i.address!!.value when(i.type!!) { IRDataType.BYTE -> { val left = memory.getUB(address) - val right = registers.getUB(i.reg1!!) + val right = registers.getUB(src) value = left.toInt() and right.toInt() memory.setUB(address, value.toUByte()) } IRDataType.WORD -> { val left = memory.getUW(address) - val right = registers.getUW(i.reg1!!) + val right = registers.getUW(src) value = left.toInt() and right.toInt() memory.setUW(address, value.toUShort()) } IRDataType.POINTER -> { val left = memory.getSL(address) - val right = registers.getSL(i.reg1!!) + val right = registers.getSL(src) value = left and right memory.setSL(address, value) } IRDataType.LONG -> { val left = memory.getSL(address) - val right = registers.getSL(i.reg1!!) + val right = registers.getSL(src) value = left and right memory.setSL(address, value) } @@ -1974,36 +2063,38 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsORR(i: IRInstruction) { + val dest = i.requireDest().registerNumber val (left: UInt, right: UInt) = getLogicalOperandsU(i) val value = (left or right).toInt() when(i.type!!) { - IRDataType.BYTE -> registers.setUB(i.reg1!!, value.toUByte()) - IRDataType.WORD -> registers.setUW(i.reg1!!, value.toUShort()) - IRDataType.POINTER -> registers.setSL(i.reg1!!, value) - IRDataType.LONG -> registers.setSL(i.reg1!!, value) + IRDataType.BYTE -> registers.setUB(dest, value.toUByte()) + IRDataType.WORD -> registers.setUW(dest, value.toUShort()) + IRDataType.POINTER -> registers.setSL(dest, value) + IRDataType.LONG -> registers.setSL(dest, value) IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } nextPc() } private fun InsOR(i: IRInstruction) { + val dest = i.requireDest().registerNumber val value: Int when(i.type!!) { IRDataType.BYTE -> { - value = registers.getUB(i.reg1!!).toInt() or i.immediate!! - registers.setUB(i.reg1!!, value.toUByte()) + value = registers.getUB(dest).toInt() or i.requireImmediateInt() + registers.setUB(dest, value.toUByte()) } IRDataType.WORD -> { - value = registers.getUW(i.reg1!!).toInt() or i.immediate!! - registers.setUW(i.reg1!!, value.toUShort()) + value = registers.getUW(dest).toInt() or i.requireImmediateInt() + registers.setUW(dest, value.toUShort()) } IRDataType.POINTER -> { - value = registers.getSL(i.reg1!!) or i.immediate!! - registers.setSL(i.reg1!!, value) + value = registers.getSL(dest) or i.requireImmediateInt() + registers.setSL(dest, value) } IRDataType.LONG -> { - value = registers.getSL(i.reg1!!) or i.immediate!! - registers.setSL(i.reg1!!, value) + value = registers.getSL(dest) or i.requireImmediateInt() + registers.setSL(dest, value) } IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } @@ -2011,30 +2102,31 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsORM(i: IRInstruction) { + val src = i.requireSrcA().registerNumber + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() val value: Int - val address = i.address!!.value when(i.type!!) { IRDataType.BYTE -> { val left = memory.getUB(address) - val right = registers.getUB(i.reg1!!) + val right = registers.getUB(src) value = left.toInt() or right.toInt() memory.setUB(address, value.toUByte()) } IRDataType.WORD -> { val left = memory.getUW(address) - val right = registers.getUW(i.reg1!!) + val right = registers.getUW(src) value = left.toInt() or right.toInt() memory.setUW(address, value.toUShort()) } IRDataType.POINTER -> { val left = memory.getSL(address) - val right = registers.getSL(i.reg1!!) + val right = registers.getSL(src) value = left or right memory.setSL(address, value) } IRDataType.LONG -> { val left = memory.getSL(address) - val right = registers.getSL(i.reg1!!) + val right = registers.getSL(src) value = left or right memory.setSL(address, value) } @@ -2044,36 +2136,38 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsXORR(i: IRInstruction) { + val dest = i.requireDest().registerNumber val (left: UInt, right: UInt) = getLogicalOperandsU(i) val value = (left xor right).toInt() when(i.type!!) { - IRDataType.BYTE -> registers.setUB(i.reg1!!, value.toUByte()) - IRDataType.WORD -> registers.setUW(i.reg1!!, value.toUShort()) - IRDataType.POINTER -> registers.setSL(i.reg1!!, value) - IRDataType.LONG -> registers.setSL(i.reg1!!, value) + IRDataType.BYTE -> registers.setUB(dest, value.toUByte()) + IRDataType.WORD -> registers.setUW(dest, value.toUShort()) + IRDataType.POINTER -> registers.setSL(dest, value) + IRDataType.LONG -> registers.setSL(dest, value) IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } nextPc() } private fun InsXOR(i: IRInstruction) { + val dest = i.requireDest().registerNumber val value: Int when(i.type!!) { IRDataType.BYTE -> { - value = registers.getUB(i.reg1!!).toInt() xor i.immediate!! - registers.setUB(i.reg1!!, value.toUByte()) + value = registers.getUB(dest).toInt() xor i.requireImmediateInt() + registers.setUB(dest, value.toUByte()) } IRDataType.WORD -> { - value = registers.getUW(i.reg1!!).toInt() xor i.immediate!! - registers.setUW(i.reg1!!, value.toUShort()) + value = registers.getUW(dest).toInt() xor i.requireImmediateInt() + registers.setUW(dest, value.toUShort()) } IRDataType.POINTER -> { - value = registers.getSL(i.reg1!!) xor i.immediate!! - registers.setSL(i.reg1!!, value) + value = registers.getSL(dest) xor i.requireImmediateInt() + registers.setSL(dest, value) } IRDataType.LONG -> { - value = registers.getSL(i.reg1!!) xor i.immediate!! - registers.setSL(i.reg1!!, value) + value = registers.getSL(dest) xor i.requireImmediateInt() + registers.setSL(dest, value) } IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } @@ -2081,30 +2175,31 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsXORM(i: IRInstruction) { + val src = i.requireSrcA().registerNumber + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() val value: Int - val address = i.address!!.value when(i.type!!) { IRDataType.BYTE -> { val left = memory.getUB(address) - val right = registers.getUB(i.reg1!!) + val right = registers.getUB(src) value = left.toInt() xor right.toInt() memory.setUB(address, value.toUByte()) } IRDataType.WORD -> { val left = memory.getUW(address) - val right = registers.getUW(i.reg1!!) + val right = registers.getUW(src) value = left.toInt() xor right.toInt() memory.setUW(address, value.toUShort()) } IRDataType.POINTER -> { val left = memory.getSL(address) - val right = registers.getSL(i.reg1!!) + val right = registers.getSL(src) value = left xor right memory.setSL(address, value) } IRDataType.LONG -> { val left = memory.getSL(address) - val right = registers.getSL(i.reg1!!) + val right = registers.getSL(src) value = left xor right memory.setSL(address, value) } @@ -2114,18 +2209,19 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsINV(i: IRInstruction) { + val dest = i.requireDest().registerNumber when(i.type!!) { - IRDataType.BYTE -> registers.setUB(i.reg1!!, registers.getUB(i.reg1!!).inv()) - IRDataType.WORD -> registers.setUW(i.reg1!!, registers.getUW(i.reg1!!).inv()) - IRDataType.POINTER -> registers.setSL(i.reg1!!, registers.getSL(i.reg1!!).inv()) - IRDataType.LONG -> registers.setSL(i.reg1!!, registers.getSL(i.reg1!!).inv()) + IRDataType.BYTE -> registers.setUB(dest, registers.getUB(dest).inv()) + IRDataType.WORD -> registers.setUW(dest, registers.getUW(dest).inv()) + IRDataType.POINTER -> registers.setSL(dest, registers.getSL(dest).inv()) + IRDataType.LONG -> registers.setSL(dest, registers.getSL(dest).inv()) IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } nextPc() } private fun InsINVM(i: IRInstruction) { - val address = i.address!!.value + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() when(i.type!!) { IRDataType.BYTE -> memory.setUB(address, memory.getUB(address).inv()) IRDataType.WORD -> memory.setUW(address, memory.getUW(address).inv()) @@ -2157,33 +2253,35 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsASRN(i: IRInstruction) { + val dest = i.requireDest().registerNumber val (left: Int, right: Int) = getLogicalOperandsS(i) when(i.type!!) { - IRDataType.BYTE -> registers.setSB(i.reg1!!, shiftRightArithmetic(left, right, 8).toByte()) - IRDataType.WORD -> registers.setSW(i.reg1!!, shiftRightArithmetic(left, right, 16).toShort()) - IRDataType.POINTER -> registers.setSL(i.reg1!!, shiftRightArithmetic(left, right, 32)) - IRDataType.LONG -> registers.setSL(i.reg1!!, shiftRightArithmetic(left, right, 32)) + IRDataType.BYTE -> registers.setSB(dest, shiftRightArithmetic(left, right, 8).toByte()) + IRDataType.WORD -> registers.setSW(dest, shiftRightArithmetic(left, right, 16).toShort()) + IRDataType.POINTER -> registers.setSL(dest, shiftRightArithmetic(left, right, 32)) + IRDataType.LONG -> registers.setSL(dest, shiftRightArithmetic(left, right, 32)) IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } nextPc() } private fun InsASRI(i: IRInstruction) { + val dest = i.requireDest().registerNumber val left = getLogicalOperandS(i) - val right = i.immediate!! + val right = i.requireImmediateInt() when(i.type!!) { - IRDataType.BYTE -> registers.setSB(i.reg1!!, shiftRightArithmetic(left, right, 8).toByte()) - IRDataType.WORD -> registers.setSW(i.reg1!!, shiftRightArithmetic(left, right, 16).toShort()) - IRDataType.POINTER -> registers.setSL(i.reg1!!, shiftRightArithmetic(left, right, 32)) - IRDataType.LONG -> registers.setSL(i.reg1!!, shiftRightArithmetic(left, right, 32)) + IRDataType.BYTE -> registers.setSB(dest, shiftRightArithmetic(left, right, 8).toByte()) + IRDataType.WORD -> registers.setSW(dest, shiftRightArithmetic(left, right, 16).toShort()) + IRDataType.POINTER -> registers.setSL(dest, shiftRightArithmetic(left, right, 32)) + IRDataType.LONG -> registers.setSL(dest, shiftRightArithmetic(left, right, 32)) IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } nextPc() } private fun InsASRNM(i: IRInstruction) { - val address = i.address!!.value - val operand = registers.getUB(i.reg1!!).toInt() + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() + val operand = registers.getUB(i.requireSrcA().registerNumber).toInt() when(i.type!!) { IRDataType.BYTE -> { val memvalue = memory.getSB(address).toInt() @@ -2207,26 +2305,27 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsASR(i: IRInstruction) { + val dest = i.requireDest().registerNumber when(i.type!!) { IRDataType.BYTE -> { - val value = registers.getSB(i.reg1!!).toInt() + val value = registers.getSB(dest).toInt() statusCarry = (value and 1)!=0 - registers.setSB(i.reg1!!, (value shr 1).toByte()) + registers.setSB(dest, (value shr 1).toByte()) } IRDataType.WORD -> { - val value = registers.getSW(i.reg1!!).toInt() + val value = registers.getSW(dest).toInt() statusCarry = (value and 1)!=0 - registers.setSW(i.reg1!!, (value shr 1).toShort()) + registers.setSW(dest, (value shr 1).toShort()) } IRDataType.POINTER -> { - val value = registers.getSL(i.reg1!!) + val value = registers.getSL(dest) statusCarry = (value and 1)!=0 - registers.setSL(i.reg1!!, value shr 1) + registers.setSL(dest, value shr 1) } IRDataType.LONG -> { - val value = registers.getSL(i.reg1!!) + val value = registers.getSL(dest) statusCarry = (value and 1)!=0 - registers.setSL(i.reg1!!, value shr 1) + registers.setSL(dest, value shr 1) } IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } @@ -2234,7 +2333,7 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsASRM(i: IRInstruction) { - val address = i.address!!.value + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() when(i.type!!) { IRDataType.BYTE -> { val value = memory.getSB(address).toInt() @@ -2262,33 +2361,35 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsLSRN(i: IRInstruction) { + val dest = i.requireDest().registerNumber val (left: UInt, right: UInt) = getLogicalOperandsU(i) when(i.type!!) { - IRDataType.BYTE -> registers.setUB(i.reg1!!, shiftRightLogical(left, right.toInt(), 8).toUByte()) - IRDataType.WORD -> registers.setUW(i.reg1!!, shiftRightLogical(left, right.toInt(), 16).toUShort()) - IRDataType.POINTER -> registers.setSL(i.reg1!!, shiftRightLogical(left, right.toInt(), 32).toInt()) - IRDataType.LONG -> registers.setSL(i.reg1!!, shiftRightLogical(left, right.toInt(), 32).toInt()) + IRDataType.BYTE -> registers.setUB(dest, shiftRightLogical(left, right.toInt(), 8).toUByte()) + IRDataType.WORD -> registers.setUW(dest, shiftRightLogical(left, right.toInt(), 16).toUShort()) + IRDataType.POINTER -> registers.setSL(dest, shiftRightLogical(left, right.toInt(), 32).toInt()) + IRDataType.LONG -> registers.setSL(dest, shiftRightLogical(left, right.toInt(), 32).toInt()) IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } nextPc() } private fun InsLSRI(i: IRInstruction) { + val dest = i.requireDest().registerNumber val left = getLogicalOperandU(i) - val right = i.immediate!! + val right = i.requireImmediateInt() when(i.type!!) { - IRDataType.BYTE -> registers.setUB(i.reg1!!, shiftRightLogical(left, right, 8).toUByte()) - IRDataType.WORD -> registers.setUW(i.reg1!!, shiftRightLogical(left, right, 16).toUShort()) - IRDataType.POINTER -> registers.setSL(i.reg1!!, shiftRightLogical(left, right, 32).toInt()) - IRDataType.LONG -> registers.setSL(i.reg1!!, shiftRightLogical(left, right, 32).toInt()) + IRDataType.BYTE -> registers.setUB(dest, shiftRightLogical(left, right, 8).toUByte()) + IRDataType.WORD -> registers.setUW(dest, shiftRightLogical(left, right, 16).toUShort()) + IRDataType.POINTER -> registers.setSL(dest, shiftRightLogical(left, right, 32).toInt()) + IRDataType.LONG -> registers.setSL(dest, shiftRightLogical(left, right, 32).toInt()) IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } nextPc() } private fun InsLSRNM(i: IRInstruction) { - val address = i.address!!.value - val operand = registers.getUB(i.reg1!!).toInt() + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() + val operand = registers.getUB(i.requireSrcA().registerNumber).toInt() when(i.type!!) { IRDataType.BYTE -> { val memvalue = memory.getUB(address).toInt() @@ -2312,26 +2413,27 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsLSR(i: IRInstruction) { + val dest = i.requireDest().registerNumber when(i.type!!) { IRDataType.BYTE -> { - val value = registers.getUB(i.reg1!!).toInt() + val value = registers.getUB(dest).toInt() statusCarry = (value and 1)!=0 - registers.setUB(i.reg1!!, (value shr 1).toUByte()) + registers.setUB(dest, (value shr 1).toUByte()) } IRDataType.WORD -> { - val value = registers.getUW(i.reg1!!).toInt() + val value = registers.getUW(dest).toInt() statusCarry = (value and 1)!=0 - registers.setUW(i.reg1!!, (value shr 1).toUShort()) + registers.setUW(dest, (value shr 1).toUShort()) } IRDataType.POINTER -> { - val value = registers.getSL(i.reg1!!) + val value = registers.getSL(dest) statusCarry = (value and 1)!=0 - registers.setSL(i.reg1!!, value ushr 1) + registers.setSL(dest, value ushr 1) } IRDataType.LONG -> { - val value = registers.getSL(i.reg1!!) + val value = registers.getSL(dest) statusCarry = (value and 1)!=0 - registers.setSL(i.reg1!!, value ushr 1) + registers.setSL(dest, value ushr 1) } IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } @@ -2339,7 +2441,7 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsLSRM(i: IRInstruction) { - val address = i.address!!.value + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() when(i.type!!) { IRDataType.BYTE -> { val value = memory.getUB(address).toInt() @@ -2367,19 +2469,20 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsLSLN(i: IRInstruction) { + val dest = i.requireDest().registerNumber val (left: UInt, right: UInt) = getLogicalOperandsU(i) when(i.type!!) { IRDataType.BYTE -> { - registers.setUB(i.reg1!!, shiftLeft(left, right.toInt(), 8).toUByte()) + registers.setUB(dest, shiftLeft(left, right.toInt(), 8).toUByte()) } IRDataType.WORD -> { - registers.setUW(i.reg1!!, shiftLeft(left, right.toInt(), 16).toUShort()) + registers.setUW(dest, shiftLeft(left, right.toInt(), 16).toUShort()) } IRDataType.POINTER -> { - registers.setSL(i.reg1!!, shiftLeft(left, right.toInt(), 32).toInt()) + registers.setSL(dest, shiftLeft(left, right.toInt(), 32).toInt()) } IRDataType.LONG -> { - registers.setSL(i.reg1!!, shiftLeft(left, right.toInt(), 32).toInt()) + registers.setSL(dest, shiftLeft(left, right.toInt(), 32).toInt()) } IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } @@ -2387,21 +2490,22 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsLSLI(i: IRInstruction) { + val dest = i.requireDest().registerNumber val left = getLogicalOperandU(i) - val right = i.immediate!! + val right = i.requireImmediateInt() when(i.type!!) { - IRDataType.BYTE -> registers.setUB(i.reg1!!, shiftLeft(left, right, 8).toUByte()) - IRDataType.WORD -> registers.setUW(i.reg1!!, shiftLeft(left, right, 16).toUShort()) - IRDataType.POINTER -> registers.setSL(i.reg1!!, shiftLeft(left, right, 32).toInt()) - IRDataType.LONG -> registers.setSL(i.reg1!!, shiftLeft(left, right, 32).toInt()) + IRDataType.BYTE -> registers.setUB(dest, shiftLeft(left, right, 8).toUByte()) + IRDataType.WORD -> registers.setUW(dest, shiftLeft(left, right, 16).toUShort()) + IRDataType.POINTER -> registers.setSL(dest, shiftLeft(left, right, 32).toInt()) + IRDataType.LONG -> registers.setSL(dest, shiftLeft(left, right, 32).toInt()) IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } nextPc() } private fun InsLSLNM(i: IRInstruction) { - val address = i.address!!.value - val operand = registers.getUB(i.reg1!!).toInt() + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() + val operand = registers.getUB(i.requireSrcA().registerNumber).toInt() when(i.type!!) { IRDataType.BYTE -> { val memvalue = memory.getUB(address).toInt() @@ -2425,26 +2529,27 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsLSL(i: IRInstruction) { + val dest = i.requireDest().registerNumber when(i.type!!) { IRDataType.BYTE -> { - val value = registers.getUB(i.reg1!!).toInt() + val value = registers.getUB(dest).toInt() statusCarry = (value and 0x80)!=0 - registers.setUB(i.reg1!!, (value shl 1).toUByte()) + registers.setUB(dest, (value shl 1).toUByte()) } IRDataType.WORD -> { - val value = registers.getUW(i.reg1!!).toInt() + val value = registers.getUW(dest).toInt() statusCarry = (value and 0x8000)!=0 - registers.setUW(i.reg1!!, (value shl 1).toUShort()) + registers.setUW(dest, (value shl 1).toUShort()) } IRDataType.POINTER -> { - val value = registers.getSL(i.reg1!!) + val value = registers.getSL(dest) statusCarry = value<0 - registers.setSL(i.reg1!!, value shl 1) + registers.setSL(dest, value shl 1) } IRDataType.LONG -> { - val value = registers.getSL(i.reg1!!) + val value = registers.getSL(dest) statusCarry = value<0 - registers.setSL(i.reg1!!, value shl 1) + registers.setSL(dest, value shl 1) } IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } @@ -2452,7 +2557,7 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsLSLM(i: IRInstruction) { - val address = i.address!!.value + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() when(i.type!!) { IRDataType.BYTE -> { val value = memory.getUB(address).toInt() @@ -2480,47 +2585,48 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsROR(i: IRInstruction, useCarry: Boolean) { + val dest = i.requireDest().registerNumber val newStatusCarry: Boolean when (i.type!!) { IRDataType.BYTE -> { - val orig = registers.getUB(i.reg1!!) + val orig = registers.getUB(dest) newStatusCarry = (orig.toInt() and 1) != 0 val rotated: UByte = if (useCarry) { val carry = if (statusCarry) 0x80u else 0x00u (orig.toUInt().rotateRight(1) or carry).toUByte() } else orig.rotateRight(1) - registers.setUB(i.reg1!!, rotated) + registers.setUB(dest, rotated) } IRDataType.WORD -> { - val orig = registers.getUW(i.reg1!!) + val orig = registers.getUW(dest) newStatusCarry = (orig.toInt() and 1) != 0 val rotated: UShort = if (useCarry) { val carry = if (statusCarry) 0x8000u else 0x0000u (orig.toUInt().rotateRight(1) or carry).toUShort() } else orig.rotateRight(1) - registers.setUW(i.reg1!!, rotated) + registers.setUW(dest, rotated) } IRDataType.POINTER -> { - val orig = registers.getUL(i.reg1!!) + val orig = registers.getUL(dest) newStatusCarry = (orig and 1u) != 0u val rotated: UInt = if (useCarry) { val carry = if (statusCarry) 0x80000000u else 0u (orig.rotateRight(1) or carry) } else orig.rotateRight(1) - registers.setUL(i.reg1!!, rotated) + registers.setUL(dest, rotated) } IRDataType.LONG -> { - val orig = registers.getSL(i.reg1!!).toUInt() + val orig = registers.getSL(dest).toUInt() newStatusCarry = (orig and 1u) != 0u val rotated: UInt = if (useCarry) { val carry = if (statusCarry) 0x80000000u else 0u (orig.rotateRight(1) or carry) } else orig.rotateRight(1) - registers.setSL(i.reg1!!, rotated.toInt()) + registers.setSL(dest, rotated.toInt()) } IRDataType.FLOAT -> { throw IllegalArgumentException("can't ROR a float") @@ -2531,8 +2637,8 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsRORM(i: IRInstruction, useCarry: Boolean) { + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() val newStatusCarry: Boolean - val address = i.address!!.value when (i.type!!) { IRDataType.BYTE -> { val orig = memory.getUB(address) @@ -2583,47 +2689,48 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsROL(i: IRInstruction, useCarry: Boolean) { + val dest = i.requireDest().registerNumber val newStatusCarry: Boolean when (i.type!!) { IRDataType.BYTE -> { - val orig = registers.getUB(i.reg1!!) + val orig = registers.getUB(dest) newStatusCarry = (orig.toInt() and 0x80) != 0 val rotated: UByte = if (useCarry) { val carry = if (statusCarry) 1u else 0u (orig.toUInt().rotateLeft(1) or carry).toUByte() } else orig.rotateLeft(1) - registers.setUB(i.reg1!!, rotated) + registers.setUB(dest, rotated) } IRDataType.WORD -> { - val orig = registers.getUW(i.reg1!!) + val orig = registers.getUW(dest) newStatusCarry = (orig.toInt() and 0x8000) != 0 val rotated: UShort = if (useCarry) { val carry = if (statusCarry) 1u else 0u (orig.toUInt().rotateLeft(1) or carry).toUShort() } else orig.rotateLeft(1) - registers.setUW(i.reg1!!, rotated) + registers.setUW(dest, rotated) } IRDataType.POINTER -> { - val orig = registers.getUL(i.reg1!!) + val orig = registers.getUL(dest) newStatusCarry = (orig and 0x80000000u) != 0u val rotated: UInt = if (useCarry) { val carry = if (statusCarry) 1u else 0u (orig.rotateLeft(1) or carry) } else orig.rotateLeft(1) - registers.setUL(i.reg1!!, rotated) + registers.setUL(dest, rotated) } IRDataType.LONG -> { - val orig = registers.getSL(i.reg1!!).toUInt() + val orig = registers.getSL(dest).toUInt() newStatusCarry = (orig and 0x80000000u) != 0u val rotated: UInt = (if (useCarry) { val carry = if (statusCarry) 1u else 0u (orig.rotateLeft(1) or carry) } else orig.rotateLeft(1)) - registers.setSL(i.reg1!!, rotated.toInt()) + registers.setSL(dest, rotated.toInt()) } IRDataType.FLOAT -> { throw IllegalArgumentException("can't ROL a float") @@ -2634,7 +2741,7 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsROLM(i: IRInstruction, useCarry: Boolean) { - val address = i.address!!.value + val address = (i.requireMemory() as MemoryReference.Direct).resolvedAddress() val newStatusCarry: Boolean when (i.type!!) { IRDataType.BYTE -> { @@ -2686,21 +2793,23 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsLSIGB(i: IRInstruction) { + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber when(i.type!!) { IRDataType.WORD -> { - val value = registers.getUW(i.reg2!!) + val value = registers.getUW(src) val byte = value.toUByte() - registers.setUB(i.reg1!!, byte) + registers.setUB(dest, byte) } IRDataType.POINTER -> { - val value = registers.getSL(i.reg2!!) + val value = registers.getSL(src) val byte = value.toUByte() - registers.setUB(i.reg1!!, byte) + registers.setUB(dest, byte) } IRDataType.LONG -> { - val value = registers.getSL(i.reg2!!) + val value = registers.getSL(src) val byte = value.toUByte() - registers.setUB(i.reg1!!, byte) + registers.setUB(dest, byte) } else -> throw IllegalArgumentException("invalid float type for this instruction $i") } @@ -2708,31 +2817,35 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsLSIGW(i: IRInstruction) { + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber if (i.type!! == IRDataType.LONG || i.type!! == IRDataType.POINTER) { - val value = registers.getSL(i.reg2!!) + val value = registers.getSL(src) val word = value.toUShort() - registers.setUW(i.reg1!!, word) + registers.setUW(dest, word) } else throw IllegalArgumentException("invalid float type for this instruction $i") nextPc() } private fun InsMSIGB(i: IRInstruction) { + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber when(i.type!!) { IRDataType.WORD -> { - val value = registers.getUW(i.reg2!!) + val value = registers.getUW(src) val newValue = value.toInt() ushr 8 - registers.setUB(i.reg1!!, newValue.toUByte()) + registers.setUB(dest, newValue.toUByte()) } IRDataType.POINTER -> { - val value = registers.getSL(i.reg2!!) + val value = registers.getSL(src) val newValue = value ushr 24 - registers.setUB(i.reg1!!, newValue.toUByte()) + registers.setUB(dest, newValue.toUByte()) } IRDataType.LONG -> { - val value = registers.getSL(i.reg2!!) + val value = registers.getSL(src) val newValue = value ushr 24 - registers.setUB(i.reg1!!, newValue.toUByte()) + registers.setUB(dest, newValue.toUByte()) } else -> throw IllegalArgumentException("invalid float type for this instruction $i") } @@ -2740,41 +2853,50 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsBSIGB(i: IRInstruction) { - val value = registers.getSL(i.reg2!!) + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber + val value = registers.getSL(src) val newValue = value ushr 16 and 255 - registers.setUB(i.reg1!!, newValue.toUByte()) + registers.setUB(dest, newValue.toUByte()) nextPc() } private fun InsMIDB(i: IRInstruction) { - val value = registers.getSL(i.reg2!!) + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber + val value = registers.getSL(src) val newValue = value ushr 8 and 255 - registers.setUB(i.reg1!!, newValue.toUByte()) + registers.setUB(dest, newValue.toUByte()) nextPc() } private fun InsMSIGW(i: IRInstruction) { + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber if (i.type!! == IRDataType.LONG || i.type!! == IRDataType.POINTER) { - val value = registers.getSL(i.reg2!!) + val value = registers.getSL(src) val newValue = value ushr 16 - registers.setUW(i.reg1!!, newValue.toUShort()) + registers.setUW(dest, newValue.toUShort()) } else throw IllegalArgumentException("invalid float type for this instruction $i") nextPc() } private fun InsCONCAT(i: IRInstruction) { + val dest = i.requireDest().registerNumber + val msbReg = i.requireSrcA().registerNumber + val lsbReg = i.requireSrcB().registerNumber when(i.type!!) { IRDataType.BYTE -> { - val msb = registers.getUB(i.reg2!!) - val lsb = registers.getUB(i.reg3!!) + val msb = registers.getUB(msbReg) + val lsb = registers.getUB(lsbReg) val value = ((msb.toInt() shl 8) or lsb.toInt()) - registers.setUW(i.reg1!!, value.toUShort()) + registers.setUW(dest, value.toUShort()) } IRDataType.WORD -> { - val msw = registers.getUW(i.reg2!!) - val lsw = registers.getUW(i.reg3!!) - registers.setSL(i.reg1!!, ((msw.toInt() shl 16) or lsw.toInt())) + val msw = registers.getUW(msbReg) + val lsw = registers.getUW(lsbReg) + registers.setSL(dest, ((msw.toInt() shl 16) or lsw.toInt())) } IRDataType.POINTER -> throw IllegalArgumentException("concat.p makes no sense, 32 bits is already the widest") IRDataType.LONG -> throw IllegalArgumentException("concat.l makes no sense, 32 bits is already the widest") @@ -2784,125 +2906,148 @@ class VirtualMachine(irProgram: IRProgram) { } private fun InsFFROMUB(i: IRInstruction) { - registers.setFloat(i.fpReg1!!, registers.getUB(i.reg1!!).toDouble()) + registers.setFloat(i.requireFloatDest().registerNumber, registers.getUB(i.requireIntSourceA().registerNumber).toDouble()) nextPc() } private fun InsFFROMSB(i: IRInstruction) { - registers.setFloat(i.fpReg1!!, registers.getSB(i.reg1!!).toDouble()) + registers.setFloat(i.requireFloatDest().registerNumber, registers.getSB(i.requireIntSourceA().registerNumber).toDouble()) nextPc() } private fun InsFFROMUW(i: IRInstruction) { - registers.setFloat(i.fpReg1!!, registers.getUW(i.reg1!!).toDouble()) + registers.setFloat(i.requireFloatDest().registerNumber, registers.getUW(i.requireIntSourceA().registerNumber).toDouble()) nextPc() } private fun InsFFROMSW(i: IRInstruction) { - registers.setFloat(i.fpReg1!!, registers.getSW(i.reg1!!).toDouble()) + registers.setFloat(i.requireFloatDest().registerNumber, registers.getSW(i.requireIntSourceA().registerNumber).toDouble()) nextPc() } private fun InsFFROMSL(i: IRInstruction) { - registers.setFloat(i.fpReg1!!, registers.getSL(i.reg1!!).toDouble()) + registers.setFloat(i.requireFloatDest().registerNumber, registers.getSL(i.requireIntSourceA().registerNumber).toDouble()) nextPc() } private fun InsFTOUB(i: IRInstruction) { - registers.setUB(i.reg1!!, registers.getFloat(i.fpReg1!!).toInt().toUByte()) + registers.setUB(i.requireIntDest().registerNumber, registers.getFloat(i.requireFloatSourceA().registerNumber).toInt().toUByte()) nextPc() } private fun InsFTOUW(i: IRInstruction) { - registers.setUW(i.reg1!!, registers.getFloat(i.fpReg1!!).toInt().toUShort()) + registers.setUW(i.requireIntDest().registerNumber, registers.getFloat(i.requireFloatSourceA().registerNumber).toInt().toUShort()) nextPc() } private fun InsFTOSB(i: IRInstruction) { - registers.setSB(i.reg1!!, registers.getFloat(i.fpReg1!!).toInt().toByte()) + registers.setSB(i.requireIntDest().registerNumber, registers.getFloat(i.requireFloatSourceA().registerNumber).toInt().toByte()) nextPc() } private fun InsFTOSW(i: IRInstruction) { - registers.setSW(i.reg1!!, registers.getFloat(i.fpReg1!!).toInt().toShort()) + registers.setSW(i.requireIntDest().registerNumber, registers.getFloat(i.requireFloatSourceA().registerNumber).toInt().toShort()) nextPc() } private fun InsFTOSL(i: IRInstruction) { - registers.setSL(i.reg1!!, registers.getFloat(i.fpReg1!!).toInt()) + registers.setSL(i.requireIntDest().registerNumber, registers.getFloat(i.requireFloatSourceA().registerNumber).toInt()) nextPc() } private fun InsFPOW(i: IRInstruction) { - val value = registers.getFloat(i.fpReg1!!) - val exponent = registers.getFloat(i.fpReg2!!) - registers.setFloat(i.fpReg1!!, value.pow(exponent)) + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber + val value = registers.getFloat(dest) + val exponent = registers.getFloat(src) + registers.setFloat(dest, value.pow(exponent)) nextPc() } private fun InsFSIN(i: IRInstruction) { - val angle = registers.getFloat(i.fpReg2!!) - registers.setFloat(i.fpReg1!!, sin(angle)) + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber + val angle = registers.getFloat(src) + registers.setFloat(dest, sin(angle)) nextPc() } private fun InsFCOS(i: IRInstruction) { - val angle = registers.getFloat(i.fpReg2!!) - registers.setFloat(i.fpReg1!!, cos(angle)) + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber + val angle = registers.getFloat(src) + registers.setFloat(dest, cos(angle)) nextPc() } private fun InsFTAN(i: IRInstruction) { - val angle = registers.getFloat(i.fpReg2!!) - registers.setFloat(i.fpReg1!!, tan(angle)) + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber + val angle = registers.getFloat(src) + registers.setFloat(dest, tan(angle)) nextPc() } private fun InsFATAN(i: IRInstruction) { - val angle = registers.getFloat(i.fpReg2!!) - registers.setFloat(i.fpReg1!!, atan(angle)) + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber + val angle = registers.getFloat(src) + registers.setFloat(dest, atan(angle)) nextPc() } private fun InsFABS(i: IRInstruction) { - val value = registers.getFloat(i.fpReg2!!) - registers.setFloat(i.fpReg1!!, abs(value)) + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber + val value = registers.getFloat(src) + registers.setFloat(dest, abs(value)) nextPc() } private fun InsFLN(i: IRInstruction) { - val value = registers.getFloat(i.fpReg2!!) - registers.setFloat(i.fpReg1!!, ln(value)) + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber + val value = registers.getFloat(src) + registers.setFloat(dest, ln(value)) nextPc() } private fun InsFLOG(i: IRInstruction) { - val value = registers.getFloat(i.fpReg2!!) - registers.setFloat(i.fpReg1!!, log2(value)) + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber + val value = registers.getFloat(src) + registers.setFloat(dest, log2(value)) nextPc() } private fun InsFROUND(i: IRInstruction) { - val value = registers.getFloat(i.fpReg2!!) - registers.setFloat(i.fpReg1!!, round(value)) + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber + val value = registers.getFloat(src) + registers.setFloat(dest, round(value)) nextPc() } private fun InsFFLOOR(i: IRInstruction) { - val value = registers.getFloat(i.fpReg2!!) - registers.setFloat(i.fpReg1!!, floor(value)) + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber + val value = registers.getFloat(src) + registers.setFloat(dest, floor(value)) nextPc() } private fun InsFCEIL(i: IRInstruction) { - val value = registers.getFloat(i.fpReg2!!) - registers.setFloat(i.fpReg1!!, ceil(value)) + val dest = i.requireDest().registerNumber + val src = i.requireSrcA().registerNumber + val value = registers.getFloat(src) + registers.setFloat(dest, ceil(value)) nextPc() } private fun InsFCOMP(i: IRInstruction) { - val left = registers.getFloat(i.fpReg1!!) - val right = registers.getFloat(i.fpReg2!!) + val dest = i.requireIntDest().registerNumber + val left = registers.getFloat(i.requireFloatSourceA().registerNumber) + val right = registers.getFloat(i.requireSrcB().registerNumber) val result = if(left registers.setUB(i.reg1!!, hardwareRegisterA) - 1 -> registers.setUB(i.reg1!!, hardwareRegisterX) - 2 -> registers.setUB(i.reg1!!, hardwareRegisterY) - 3 -> registers.setUW(i.reg1!!, ((hardwareRegisterX.toUInt() shl 8) + hardwareRegisterA).toUShort()) - 4 -> registers.setUW(i.reg1!!, ((hardwareRegisterY.toUInt() shl 8) + hardwareRegisterA).toUShort()) - 5 -> registers.setUW(i.reg1!!, ((hardwareRegisterY.toUInt() shl 8) + hardwareRegisterX).toUShort()) - 6 -> registers.setFloat(i.fpReg1!!, hardwareRegisterFAC0) - 7 -> registers.setFloat(i.fpReg1!!, hardwareRegisterFAC1) + val dest = i.requireDest().registerNumber + when(val slot = i.requireHardwareSlot().slot.value) { + 0 -> registers.setUB(dest, hardwareRegisterA) + 1 -> registers.setUB(dest, hardwareRegisterX) + 2 -> registers.setUB(dest, hardwareRegisterY) + 3 -> registers.setUW(dest, ((hardwareRegisterX.toUInt() shl 8) + hardwareRegisterA).toUShort()) + 4 -> registers.setUW(dest, ((hardwareRegisterY.toUInt() shl 8) + hardwareRegisterA).toUShort()) + 5 -> registers.setUW(dest, ((hardwareRegisterY.toUInt() shl 8) + hardwareRegisterX).toUShort()) + 6 -> registers.setFloat(dest, hardwareRegisterFAC0) + 7 -> registers.setFloat(dest, hardwareRegisterFAC1) in 10..17 -> { - val reg = i.immediate!! - 10 + val reg = slot - 10 when(i.type) { - IRDataType.BYTE -> registers.setUB(i.reg1!!, hardwareRegisterD[reg].toUByte()) - IRDataType.WORD -> registers.setUW(i.reg1!!, hardwareRegisterD[reg].toUShort()) - IRDataType.LONG, IRDataType.POINTER -> registers.setUL(i.reg1!!, hardwareRegisterD[reg]) + IRDataType.BYTE -> registers.setUB(dest, hardwareRegisterD[reg].toUByte()) + IRDataType.WORD -> registers.setUW(dest, hardwareRegisterD[reg].toUShort()) + IRDataType.LONG, IRDataType.POINTER -> registers.setUL(dest, hardwareRegisterD[reg]) else -> throw IllegalArgumentException("invalid type for D register LOADHR: ${i.type}") } } in 18..24 -> { - val reg = i.immediate!! - 18 + val reg = slot - 18 when(i.type) { - IRDataType.WORD -> registers.setUW(i.reg1!!, hardwareRegisterAddr[reg].toUShort()) - IRDataType.LONG, IRDataType.POINTER -> registers.setUL(i.reg1!!, hardwareRegisterAddr[reg]) + IRDataType.WORD -> registers.setUW(dest, hardwareRegisterAddr[reg].toUShort()) + IRDataType.LONG, IRDataType.POINTER -> registers.setUL(dest, hardwareRegisterAddr[reg]) else -> throw IllegalArgumentException("invalid type for A register LOADHR: ${i.type}") } } in 25..32 -> { - val reg = i.immediate!! - 25 + val reg = slot - 25 if(i.type==IRDataType.FLOAT) - registers.setFloat(i.fpReg1!!, hardwareRegisterFP[reg]) + registers.setFloat(dest, hardwareRegisterFP[reg]) else throw IllegalArgumentException("invalid type for FP register LOADHR: ${i.type}") } - else -> throw IllegalArgumentException("unknown hardware register slot: ${i.immediate}") + else -> throw IllegalArgumentException("unknown hardware register slot: $slot") } nextPc() } private fun InsSTOREHR(i: IRInstruction) { - when(i.immediate) { - 0 -> hardwareRegisterA = registers.getUB(i.reg1!!) - 1 -> hardwareRegisterX = registers.getUB(i.reg1!!) - 2 -> hardwareRegisterY = registers.getUB(i.reg1!!) + val src = i.requireSrcA().registerNumber + when(val slot = i.requireHardwareSlot().slot.value) { + 0 -> hardwareRegisterA = registers.getUB(src) + 1 -> hardwareRegisterX = registers.getUB(src) + 2 -> hardwareRegisterY = registers.getUB(src) 3 -> { - val word = registers.getUW(i.reg1!!).toUInt() + val word = registers.getUW(src).toUInt() hardwareRegisterA = (word and 255u).toUByte() hardwareRegisterX = (word shr 8).toUByte() } 4 -> { - val word = registers.getUW(i.reg1!!).toUInt() + val word = registers.getUW(src).toUInt() hardwareRegisterA = (word and 255u).toUByte() hardwareRegisterY = (word shr 8).toUByte() } 5 -> { - val word = registers.getUW(i.reg1!!).toUInt() + val word = registers.getUW(src).toUInt() hardwareRegisterX = (word and 255u).toUByte() hardwareRegisterY = (word shr 8).toUByte() } - 6 -> hardwareRegisterFAC0 = registers.getFloat(i.fpReg1!!) - 7 -> hardwareRegisterFAC1 = registers.getFloat(i.fpReg1!!) + 6 -> hardwareRegisterFAC0 = registers.getFloat(src) + 7 -> hardwareRegisterFAC1 = registers.getFloat(src) in 10..17 -> { - val reg = i.immediate!! - 10 + val reg = slot - 10 hardwareRegisterD[reg] = when(i.type) { - IRDataType.BYTE -> registers.getUB(i.reg1!!).toUInt() - IRDataType.WORD -> registers.getUW(i.reg1!!).toUInt() - IRDataType.LONG, IRDataType.POINTER -> registers.getUL(i.reg1!!) + IRDataType.BYTE -> registers.getUB(src).toUInt() + IRDataType.WORD -> registers.getUW(src).toUInt() + IRDataType.LONG, IRDataType.POINTER -> registers.getUL(src) else -> throw IllegalArgumentException("invalid type for D register STOREHR: ${i.type}") } } in 18..24 -> { - val reg = i.immediate!! - 18 + val reg = slot - 18 hardwareRegisterAddr[reg] = when(i.type) { - IRDataType.WORD -> registers.getUW(i.reg1!!).toUInt() - IRDataType.LONG, IRDataType.POINTER -> registers.getUL(i.reg1!!) + IRDataType.WORD -> registers.getUW(src).toUInt() + IRDataType.LONG, IRDataType.POINTER -> registers.getUL(src) else -> throw IllegalArgumentException("invalid type for A register STOREHR: ${i.type}") } } in 25..32 -> { - val reg = i.immediate!! - 25 + val reg = slot - 25 if(i.type==IRDataType.FLOAT) - hardwareRegisterFP[reg] = registers.getFloat(i.fpReg1!!) + hardwareRegisterFP[reg] = registers.getFloat(src) else throw IllegalArgumentException("invalid type for FP register STOREHR: ${i.type}") } - else -> throw IllegalArgumentException("unknown hardware register slot: ${i.immediate}") + else -> throw IllegalArgumentException("unknown hardware register slot: $slot") } nextPc() } diff --git a/virtualmachine/src/prog8/vm/VmArithmetic.kt b/virtualmachine/src/prog8/vm/VmArithmetic.kt index 2149a16c8..6c52c9c2f 100644 --- a/virtualmachine/src/prog8/vm/VmArithmetic.kt +++ b/virtualmachine/src/prog8/vm/VmArithmetic.kt @@ -2,6 +2,10 @@ package prog8.vm import prog8.intermediate.IRDataType import prog8.intermediate.IRInstruction +import prog8.intermediate.requireDest +import prog8.intermediate.requireImmediateInt +import prog8.intermediate.requireSrcA +import prog8.intermediate.requireSrcB // Note: statusbitsNZ() was removed as part of the strict status-bits contract cleanup. // The VM no longer sets Z/N on arithmetic or load operations; only CMP, CMPI, SGN, @@ -503,10 +507,12 @@ internal fun arithFloat(left: Double, operator: String, right: Double): Double = } internal fun VirtualMachine.getBranchOperands(i: IRInstruction): Pair { + val left = i.requireSrcA().registerNumber + val right = i.requireSrcB().registerNumber return when(i.type) { - IRDataType.BYTE -> Pair(registers.getSB(i.reg1!!).toInt(), registers.getSB(i.reg2!!).toInt()) - IRDataType.WORD -> Pair(registers.getSW(i.reg1!!).toInt(), registers.getSW(i.reg2!!).toInt()) - IRDataType.LONG, IRDataType.POINTER -> Pair(registers.getSL(i.reg1!!), registers.getSL(i.reg2!!)) + IRDataType.BYTE -> Pair(registers.getSB(left).toInt(), registers.getSB(right).toInt()) + IRDataType.WORD -> Pair(registers.getSW(left).toInt(), registers.getSW(right).toInt()) + IRDataType.LONG, IRDataType.POINTER -> Pair(registers.getSL(left), registers.getSL(right)) IRDataType.FLOAT -> { throw IllegalArgumentException("can't use float here") } @@ -515,10 +521,12 @@ internal fun VirtualMachine.getBranchOperands(i: IRInstruction): Pair } internal fun VirtualMachine.getBranchOperandsImm(i: IRInstruction): Pair { + val left = i.requireSrcA().registerNumber + val right = i.requireImmediateInt() return when(i.type) { - IRDataType.BYTE -> Pair(registers.getSB(i.reg1!!).toInt(), i.immediate!!) - IRDataType.WORD -> Pair(registers.getSW(i.reg1!!).toInt(), i.immediate!!) - IRDataType.LONG, IRDataType.POINTER -> Pair(registers.getSL(i.reg1!!), i.immediate!!) + IRDataType.BYTE -> Pair(registers.getSB(left).toInt(), right) + IRDataType.WORD -> Pair(registers.getSW(left).toInt(), right) + IRDataType.LONG, IRDataType.POINTER -> Pair(registers.getSL(left), right) IRDataType.FLOAT -> { throw IllegalArgumentException("can't use float here") } @@ -527,10 +535,12 @@ internal fun VirtualMachine.getBranchOperandsImm(i: IRInstruction): Pair { + val left = i.requireSrcA().registerNumber + val right = i.requireSrcB().registerNumber return when(i.type) { - IRDataType.BYTE -> Pair(registers.getUB(i.reg1!!).toUInt(), registers.getUB(i.reg2!!).toUInt()) - IRDataType.WORD -> Pair(registers.getUW(i.reg1!!).toUInt(), registers.getUW(i.reg2!!).toUInt()) - IRDataType.LONG, IRDataType.POINTER -> Pair(registers.getSL(i.reg1!!).toUInt(), registers.getSL(i.reg2!!).toUInt()) + IRDataType.BYTE -> Pair(registers.getUB(left).toUInt(), registers.getUB(right).toUInt()) + IRDataType.WORD -> Pair(registers.getUW(left).toUInt(), registers.getUW(right).toUInt()) + IRDataType.LONG, IRDataType.POINTER -> Pair(registers.getSL(left).toUInt(), registers.getSL(right).toUInt()) IRDataType.FLOAT -> { throw IllegalArgumentException("can't use float here") } @@ -539,10 +549,12 @@ internal fun VirtualMachine.getBranchOperandsU(i: IRInstruction): Pair { + val left = i.requireSrcA().registerNumber + val right = i.requireImmediateInt().toUInt() return when(i.type) { - IRDataType.BYTE -> Pair(registers.getUB(i.reg1!!).toUInt(), i.immediate!!.toUInt()) - IRDataType.WORD -> Pair(registers.getUW(i.reg1!!).toUInt(), i.immediate!!.toUInt()) - IRDataType.LONG, IRDataType.POINTER -> Pair(registers.getSL(i.reg1!!).toUInt(), i.immediate!!.toUInt()) + IRDataType.BYTE -> Pair(registers.getUB(left).toUInt(), right) + IRDataType.WORD -> Pair(registers.getUW(left).toUInt(), right) + IRDataType.LONG, IRDataType.POINTER -> Pair(registers.getSL(left).toUInt(), right) IRDataType.FLOAT -> { throw IllegalArgumentException("can't use float here") } @@ -551,10 +563,12 @@ internal fun VirtualMachine.getBranchOperandsImmU(i: IRInstruction): Pair { + val left = i.requireDest().registerNumber + val right = i.requireSrcA().registerNumber return when(i.type) { - IRDataType.BYTE -> Pair(registers.getUB(i.reg1!!).toUInt(), registers.getUB(i.reg2!!).toUInt()) - IRDataType.WORD -> Pair(registers.getUW(i.reg1!!).toUInt(), registers.getUW(i.reg2!!).toUInt()) - IRDataType.LONG, IRDataType.POINTER -> Pair(registers.getSL(i.reg1!!).toUInt(), registers.getSL(i.reg2!!).toUInt()) + IRDataType.BYTE -> Pair(registers.getUB(left).toUInt(), registers.getUB(right).toUInt()) + IRDataType.WORD -> Pair(registers.getUW(left).toUInt(), registers.getUW(right).toUInt()) + IRDataType.LONG, IRDataType.POINTER -> Pair(registers.getSL(left).toUInt(), registers.getSL(right).toUInt()) IRDataType.FLOAT -> { throw IllegalArgumentException("can't use float here") } @@ -563,20 +577,23 @@ internal fun VirtualMachine.getLogicalOperandsU(i: IRInstruction): Pair registers.getUB(i.reg1!!).toUInt() - IRDataType.WORD -> registers.getUW(i.reg1!!).toUInt() - IRDataType.LONG, IRDataType.POINTER -> registers.getSL(i.reg1!!).toUInt() + IRDataType.BYTE -> registers.getUB(reg).toUInt() + IRDataType.WORD -> registers.getUW(reg).toUInt() + IRDataType.LONG, IRDataType.POINTER -> registers.getSL(reg).toUInt() IRDataType.FLOAT -> throw IllegalArgumentException("can't use float here") null -> throw IllegalArgumentException("need type for logical instruction") } } internal fun VirtualMachine.getLogicalOperandsS(i: IRInstruction): Pair { + val left = i.requireDest().registerNumber + val right = i.requireSrcA().registerNumber return when(i.type) { - IRDataType.BYTE -> Pair(registers.getSB(i.reg1!!).toInt(), registers.getSB(i.reg2!!).toInt()) - IRDataType.WORD -> Pair(registers.getSW(i.reg1!!).toInt(), registers.getSW(i.reg2!!).toInt()) - IRDataType.LONG, IRDataType.POINTER -> Pair(registers.getSL(i.reg1!!), registers.getSL(i.reg2!!)) + IRDataType.BYTE -> Pair(registers.getSB(left).toInt(), registers.getSB(right).toInt()) + IRDataType.WORD -> Pair(registers.getSW(left).toInt(), registers.getSW(right).toInt()) + IRDataType.LONG, IRDataType.POINTER -> Pair(registers.getSL(left), registers.getSL(right)) IRDataType.FLOAT -> { throw IllegalArgumentException("can't use float here") } @@ -585,10 +602,11 @@ internal fun VirtualMachine.getLogicalOperandsS(i: IRInstruction): Pair registers.getSB(i.reg1!!).toInt() - IRDataType.WORD -> registers.getSW(i.reg1!!).toInt() - IRDataType.LONG, IRDataType.POINTER -> registers.getSL(i.reg1!!) + IRDataType.BYTE -> registers.getSB(reg).toInt() + IRDataType.WORD -> registers.getSW(reg).toInt() + IRDataType.LONG, IRDataType.POINTER -> registers.getSL(reg) IRDataType.FLOAT -> throw IllegalArgumentException("can't use float here") null -> throw IllegalArgumentException("need type for logical instruction") } diff --git a/virtualmachine/src/prog8/vm/VmProgramLoader.kt b/virtualmachine/src/prog8/vm/VmProgramLoader.kt index 92ec73733..5afee279b 100644 --- a/virtualmachine/src/prog8/vm/VmProgramLoader.kt +++ b/virtualmachine/src/prog8/vm/VmProgramLoader.kt @@ -47,21 +47,21 @@ class VmProgramLoader { while(idx < chunks.size) { val chunk = chunks[idx] if(chunk is IRLoopChunk) { - val used = chunk.usedRegisters(irProgram.options.compTarget.indexRegType) - val usedNums = (used.readRegs.keys + used.writeRegs.keys).map { it.value }.toSet() + val used = chunk.usedRegisters() + val usedNums = (used.intRegsRead.keys + used.intRegsWritten.keys).map { it.value }.toSet() var loopReg = nextLoopReg while(loopReg in usedNums || loopReg in 99000..99300) loopReg++ nextLoopReg = loopReg + 1 val initVal = if(chunk.trip==65536) 0 else chunk.trip val header = IRCodeChunk(null, null) - header += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=loopReg, immediate=initVal) + header += IRInstructions.load(IRDataType.WORD, loopReg, initVal) // recursively expand nested loops inside body first expandLoopsInList(chunk.body) val loopStart = IRCodeChunk(chunk.label, null) val tail = IRCodeChunk(null, null) - tail += IRInstruction(Opcode.DEC, IRDataType.WORD, reg1=loopReg) - tail += IRInstruction(Opcode.CMPI, IRDataType.WORD, reg1=loopReg, immediate=0) - tail += IRInstruction(Opcode.BSTNE, labelSymbol=chunk.label!!) + tail += IRInstructions.unary(Opcode.DEC, IRDataType.WORD, loopReg) + tail += IRInstructions.compareImmediate(IRDataType.WORD, loopReg, 0) + tail += IRInstructions.branch(Opcode.BSTNE, codeLabel(chunk.label!!)) // replace loop chunk with header + loopStart + body + tail chunks.removeAt(idx) val replacement = mutableListOf() @@ -89,19 +89,19 @@ class VmProgramLoader { val el = mutable[i] if(el is IRLoopChunk) { expandLoopsInList(el.body) - val used = el.usedRegisters(irProgram.options.compTarget.indexRegType) - val usedNums = (used.readRegs.keys + used.writeRegs.keys).map { it.value }.toSet() + val used = el.usedRegisters() + val usedNums = (used.intRegsRead.keys + used.intRegsWritten.keys).map { it.value }.toSet() var loopReg=nextLoopReg while(loopReg in usedNums || loopReg in 99000..99300) loopReg++ nextLoopReg = loopReg + 1 val initVal = if(el.trip==65536) 0 else el.trip val header = IRCodeChunk(null, null) - header += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=loopReg, immediate=initVal) + header += IRInstructions.load(IRDataType.WORD, loopReg, initVal) val loopStart = IRCodeChunk(el.label, null) val tail = IRCodeChunk(null, null) - tail += IRInstruction(Opcode.DEC, IRDataType.WORD, reg1=loopReg) - tail += IRInstruction(Opcode.CMPI, IRDataType.WORD, reg1=loopReg, immediate=0) - tail += IRInstruction(Opcode.BSTNE, labelSymbol=el.label!!) + tail += IRInstructions.unary(Opcode.DEC, IRDataType.WORD, loopReg) + tail += IRInstructions.compareImmediate(IRDataType.WORD, loopReg, 0) + tail += IRInstructions.branch(Opcode.BSTNE, codeLabel(el.label!!)) mutable.removeAt(i) mutable.add(i, tail) el.body.reversed().forEach { mutable.add(i, it) } @@ -129,8 +129,8 @@ class VmProgramLoader { val previous = programChunks.lastOrNull() val chunk = IRCodeChunk(null, previous) placeholders[Pair(chunk, 0)] = "main.start" - chunk += IRInstruction(Opcode.JUMP, labelSymbol = "main.start") - // note: labelSymbol "main.start" doesn't need stripping since it's not prefixed + chunk += IRInstructions.jump(codeLabel("main.start")) + // note: the "main.start" label doesn't need stripping since it's not prefixed previous?.let { p -> p.next = chunk } programChunks += chunk } @@ -139,15 +139,15 @@ class VmProgramLoader { // helper to expand IRLoopChunk into dec/bne sequence for VM execution (fallback for any remaining loops during flat list building) fun expandLoop(loop: IRLoopChunk, out: MutableList) { // allocate a fresh WORD register not used by the body - val used = loop.usedRegisters(irProgram.options.compTarget.indexRegType) - val usedNums = (used.readRegs.keys + used.writeRegs.keys).map { it.value }.toSet() + val used = loop.usedRegisters() + val usedNums = (used.intRegsRead.keys + used.intRegsWritten.keys).map { it.value }.toSet() var loopReg = nextLoopReg while(loopReg in usedNums || loopReg in 99000..99300) loopReg++ nextLoopReg = loopReg + 1 // handle trip up to 65536: WORD register, 65536 wraps to 0 val initVal = if(loop.trip==65536) 0 else loop.trip val header = IRCodeChunk(null, null) - header += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=loopReg, immediate=initVal) + header += IRInstructions.load(IRDataType.WORD, loopReg, initVal) out += header val loopStart = IRCodeChunk(loop.label, null) out += loopStart @@ -159,8 +159,8 @@ class VmProgramLoader { is IRInlineAsmChunk -> { val asmText = chunk.assembly.trim() when (asmText) { - "clc" -> out.lastOrNull()?.instructions?.add(IRInstruction(Opcode.CLC)) - "sec" -> out.lastOrNull()?.instructions?.add(IRInstruction(Opcode.SEC)) + "clc" -> out.lastOrNull()?.instructions?.add(IRInstructions.simple(Opcode.CLC)) + "sec" -> out.lastOrNull()?.instructions?.add(IRInstructions.simple(Opcode.SEC)) else -> throw IRParseException("encountered unconverted inline assembly chunk in loop") } } @@ -169,9 +169,9 @@ class VmProgramLoader { } loop.body.forEach { addBodyChunk(it) } val tail = IRCodeChunk(null, null) - tail += IRInstruction(Opcode.DEC, IRDataType.WORD, reg1=loopReg) - tail += IRInstruction(Opcode.CMPI, IRDataType.WORD, reg1=loopReg, immediate=0) - tail += IRInstruction(Opcode.BSTNE, labelSymbol=loop.label!!) + tail += IRInstructions.unary(Opcode.DEC, IRDataType.WORD, loopReg) + tail += IRInstructions.compareImmediate(IRDataType.WORD, loopReg, 0) + tail += IRInstructions.branch(Opcode.BSTNE, codeLabel(loop.label!!)) out += tail } @@ -189,8 +189,8 @@ class VmProgramLoader { is IRInlineAsmChunk -> { val asmText = child.assembly.trim() when (asmText) { - "clc" -> programChunks.lastOrNull()?.instructions?.add(IRInstruction(Opcode.CLC)) - "sec" -> programChunks.lastOrNull()?.instructions?.add(IRInstruction(Opcode.SEC)) + "clc" -> programChunks.lastOrNull()?.instructions?.add(IRInstructions.simple(Opcode.CLC)) + "sec" -> programChunks.lastOrNull()?.instructions?.add(IRInstructions.simple(Opcode.SEC)) else -> throw IRParseException("encountered unconverted inline assembly chunk") } } @@ -202,8 +202,8 @@ class VmProgramLoader { is IRInlineAsmChunk -> { val asmText = subChunk.assembly.trim() when (asmText) { - "clc" -> programChunks.lastOrNull()?.instructions?.add(IRInstruction(Opcode.CLC)) - "sec" -> programChunks.lastOrNull()?.instructions?.add(IRInstruction(Opcode.SEC)) + "clc" -> programChunks.lastOrNull()?.instructions?.add(IRInstructions.simple(Opcode.CLC)) + "sec" -> programChunks.lastOrNull()?.instructions?.add(IRInstructions.simple(Opcode.SEC)) else -> throw IRParseException("encountered unconverted inline assembly chunk") } } @@ -223,14 +223,54 @@ class VmProgramLoader { (programChunks + irProgram.globalInits).forEach { it.instructions.forEach { ins -> - if (ins.labelSymbol != null && ins.opcode !in OpcodesThatBranch) - requireNotNull(ins.address) { "instruction with labelSymbol for a var should have value set to the memory address" } + if (ins.opcode !in OpcodesThatBranch) { + val stillUnresolved = ins.memory?.symbolName != null || ins.immediate is ImmediateOperand.SymbolAddress + require(!stillUnresolved) { "instruction still references symbol '${placeholderSymbol(ins)}' that should have been resolved to a memory address: $ins" } + } } } return programChunks to artificialLabelAddresses } + /** the symbol name that an instruction still references unresolved (memory base, or address-of immediate), if any */ + private fun placeholderSymbol(instr: IRInstruction): String? = when { + instr.memory?.symbolName != null -> instr.memory!!.symbolName + instr.immediate is ImmediateOperand.SymbolAddress -> (instr.immediate as ImmediateOperand.SymbolAddress).symbol + else -> null + } + + /** the offset to add to that symbol's address (array indexing, "symbol+42") */ + private fun placeholderOffset(instr: IRInstruction): Int = when { + instr.memory != null -> when(val m = instr.memory!!) { + is MemoryReference.Direct -> m.displacement + is MemoryReference.Indexed -> m.displacement + is MemoryReference.Indirect -> 0 + } + instr.immediate is ImmediateOperand.SymbolAddress -> (instr.immediate as ImmediateOperand.SymbolAddress).offset + else -> 0 + } + + /** patch the resolved, final (base+offset already folded in) address into the instruction's memory or immediate operand */ + private fun resolvedSymbolInstruction(instr: IRInstruction, fullAddress: UInt): IRInstruction = + if (instr.memory != null) + instr.mapMemoryReferences { mem -> + when (mem) { + is MemoryReference.Direct -> MemoryReference.Direct(AddressBase.Absolute(fullAddress.toAddress())) + is MemoryReference.Indexed -> MemoryReference.Indexed(AddressBase.Absolute(fullAddress.toAddress()), mem.index, mem.scale) + is MemoryReference.Indirect -> mem + } + } + else { + // the resolved address must adopt the data type that the instruction's schema prescribes, + // not the (default POINTER) type the unresolved symbol reference happened to carry. + val immediate = if (instr.type == IRDataType.FLOAT) + ImmediateOperand.FloatValue(fullAddress.toDouble()) + else + ImmediateOperand.Integer(fullAddress.toInt(), instr.type ?: (instr.immediate as ImmediateOperand.SymbolAddress).type) + instr.copy(immediate = immediate) + } + private fun phase2relinkReplacedChunks( replacements: List>, programChunks: List @@ -240,11 +280,10 @@ class VmProgramLoader { if(chunk.next === old) { chunk.next = new } - chunk.instructions.forEach { ins -> - if(ins.branchTarget === old) { - ins.branchTarget = new - } else if(ins.branchTarget==null && ins.labelSymbol==new.label) { - ins.branchTarget = new + chunk.instructions.forEachIndexed { index, ins -> + val target = ins.codeTarget + if(target is CodeReference.Label && (target.name == old.label || target.name == new.label)) { + chunk.instructions[index] = ins.withTarget(codeLabel(new.label ?: old.label ?: target.name)) } } } @@ -256,7 +295,9 @@ class VmProgramLoader { chunk.instructions.withIndex().forEach { (index, ins) -> if(ins.opcode == Opcode.SYSCALL) { // convert IR Syscall to VM Syscall - val vmSyscall = when(ins.immediate!!) { + val callSite = ins.requireCallSite() + val syscallNumber = (callSite.target as CallTarget.SystemCall).number + val vmSyscall = when(syscallNumber) { IMSyscall.COMPARE_STRINGS.number -> Syscall.COMPARE_STRINGS IMSyscall.STRING_CONTAINS.number -> Syscall.STRING_CONTAINS IMSyscall.BYTEARRAY_CONTAINS.number -> Syscall.BYTEARRAY_CONTAINS @@ -276,10 +317,10 @@ class VmProgramLoader { } if(vmSyscall!=null) - chunk.instructions[index] = ins.copy(immediate = vmSyscall.ordinal) + chunk.instructions[index] = ins.copy(callSite = callSite.copy(target = CallTarget.SystemCall(vmSyscall.ordinal))) } - val label = ins.labelSymbol + val label = placeholderSymbol(ins) if (label != null && (ins.opcode !in OpcodesThatBranch)) { placeholders[Pair(chunk, index)] = SymbolNames.stripPrefixes(label) } @@ -296,48 +337,48 @@ class VmProgramLoader { val (chunk, line) = ref val replacement = variableAddresses[label] val instr = chunk.instructions[line] - val offset = (instr.labelSymbolOffset ?: 0).toUInt() + val offset = placeholderOffset(instr).toUInt() if(replacement==null) { // it could be an address + index: symbol+42 if(offset>0u) { val address = variableAddresses.getValue(label) + offset - chunk.instructions[line] = instr.copy(address = address.toAddress()) + chunk.instructions[line] = resolvedSymbolInstruction(instr, address) } else { // placeholder is not a variable, so it must be a label of a code chunk instead val target: IRCodeChunk? = chunks.firstOrNull { SymbolNames.stripPrefixes(it.label ?: "")==label } if(target==null) throw IRParseException("label '$label' not found in variables nor labels. VM cannot reference other things such as blocks, and constants should have been replaced by their value") - else if(instr.opcode in OpcodesThatBranch) - chunk.instructions[line] = instr.copy(branchTarget = target, address = null) - else { + else if(instr.opcode in OpcodesThatBranch) { + // the instruction's code target already correctly refers to this label; nothing more to patch. + } else { var address: UInt? = artificialLabelAddresses[label] if(address==null) { // generate an artificial address address = 0x1000000u + artificialLabelAddresses.size.toUInt() artificialLabelAddresses[label] = address } - chunk.instructions[line] = instr.copy(address=address.toAddress(), branchTarget = target) + chunk.instructions[line] = resolvedSymbolInstruction(instr, address) } } } else { - chunk.instructions[line] = instr.copy(address = (replacement + offset).toAddress()) + chunk.instructions[line] = resolvedSymbolInstruction(instr, replacement + offset) } } subroutines.forEach { it.value.chunks.forEach { chunk -> - chunk.instructions.withIndex().forEach { (_, ins) -> + chunk.instructions.withIndex().forEach { (index, ins) -> if(ins.opcode==Opcode.CALL) { - val fcallspec = ins.fcallArgs!! - val argsWithAddresses = fcallspec.arguments.map { arg -> - if(arg.address!=null) - arg - else { - val address = ins.address?.value ?: variableAddresses.getValue(SymbolNames.stripPrefixes(ins.labelSymbol!!) + "." + SymbolNames.stripPrefixes(arg.name)) - FunctionCallArgs.ArgumentSpec(arg.name, address, arg.reg) - } + val callSite = ins.requireCallSite() + val fixedAddress = (ins.codeTarget as? CodeReference.Absolute)?.address?.value + val argsWithAddresses = callSite.arguments.map { arg -> + val loc = arg.location + if(loc is CallLocation.ParameterMemory && loc.address==null) { + val address = fixedAddress ?: variableAddresses.getValue(SymbolNames.stripPrefixes(ins.labelTarget!!) + "." + SymbolNames.stripPrefixes(loc.name)) + arg.copy(location = loc.copy(address = address.toAddress())) + } else arg } - fcallspec.arguments = argsWithAddresses + chunk.instructions[index] = ins.copy(callSite = callSite.copy(arguments = argsWithAddresses)) } } } diff --git a/virtualmachine/test/AddimSubimTest.kt b/virtualmachine/test/AddimSubimTest.kt index 550cd60ad..fa0ac9e4e 100644 --- a/virtualmachine/test/AddimSubimTest.kt +++ b/virtualmachine/test/AddimSubimTest.kt @@ -9,7 +9,7 @@ class AddimSubimTest : FunSpec({ fun runProgram(): VirtualMachine { val source = $$""" - + compTarget=virtual output=PRG @@ -54,31 +54,31 @@ outputDir=/tmp -load.b r1, #100 -storem.b r1, $2000 -addim.b #20, $2000 -subim.b #30, $2000 -load.b r2, #200 -storem.b r2, $2001 -addim.b #100, $2001 -load.w r3, #1000 -storem.w r3, $2002 -addim.w #200, $2002 -subim.w #300, $2002 -load.w r4, #100 -storem.w r4, $2004 -subim.w #200, $2004 -load.l r5, #100000 -storem.l r5, $2006 -addim.l #20000, $2006 -subim.l #30000, $2006 -load.l r6, #100 -storem.l r6, $200A -subim.l #200, $200A -load.f fr7, #1.5 -storem.f fr7, $200E -addim.f #2.0, $200E -subim.f #0.5, $200E +load.b r1.b,#100.b +storem.b r1.b,[$2000] +addim.b #20.b,[$2000] +subim.b #30.b,[$2000] +load.b r2.b,#200.b +storem.b r2.b,[$2001] +addim.b #100.b,[$2001] +load.w r3.w,#1000.w +storem.w r3.w,[$2002] +addim.w #200.w,[$2002] +subim.w #300.w,[$2002] +load.w r4.w,#100.w +storem.w r4.w,[$2004] +subim.w #200.w,[$2004] +load.l r5.l,#100000.l +storem.l r5.l,[$2006] +addim.l #20000.l,[$2006] +subim.l #30000.l,[$2006] +load.l r6.l,#100.l +storem.l r6.l,[$200A] +subim.l #200.l,[$200A] +load.f fr7.f,#1.5.f +storem.f fr7.f,[$200E] +addim.f #2.0.f,[$200E] +subim.f #0.5.f,[$200E] return diff --git a/virtualmachine/test/TestVm.kt b/virtualmachine/test/TestVm.kt index 0a2e8e8da..74a3f0782 100644 --- a/virtualmachine/test/TestVm.kt +++ b/virtualmachine/test/TestVm.kt @@ -47,9 +47,9 @@ class TestVm: FunSpec( { val startSub = IRSubroutine("testmain.testsub", emptyList(), emptyList(), Position.DUMMY) val code = IRCodeChunk(startSub.label, null) code += IRInstruction(Opcode.NOP) - code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=1, immediate=12345) - code += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=1, address=1000u.toAddress()) - code += IRInstruction(Opcode.RETURN) + code += IRInstructions.load(IRDataType.WORD, 1, 12345) + code += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.WORD, 1, IRMemory.direct(1000u)) + code += IRInstructions.returnVoid() startSub += code block += startSub program.addBlock(block) @@ -92,7 +92,7 @@ class TestVm: FunSpec( { test("vmrunner") { val runner = VmRunner() val irSource=""" - + @@ -150,10 +150,10 @@ class TestVm: FunSpec( { val startSub = IRSubroutine("main.test", emptyList(), emptyList(), Position.DUMMY) val code = IRCodeChunk(startSub.label, null) - code += IRInstruction(Opcode.LOADR, IRDataType.LONG, reg1=1, reg2=3) - code += IRInstruction(Opcode.LOADR, IRDataType.LONG, reg1=2, reg2=4) - code += IRInstruction(Opcode.DIVSR, IRDataType.LONG, reg1=1, reg2=2) - code += IRInstruction(Opcode.RETURN) + code += IRInstructions.move(IRDataType.LONG, 1, 3) + code += IRInstructions.move(IRDataType.LONG, 2, 4) + code += IRInstructions.binary(Opcode.DIVSR, IRDataType.LONG, 1, 2) + code += IRInstructions.returnVoid() startSub += code block += startSub @@ -172,9 +172,9 @@ class TestVm: FunSpec( { val startSub = IRSubroutine("main.test", emptyList(), emptyList(), Position.DUMMY) val code = IRCodeChunk(startSub.label, null) - code += IRInstruction(Opcode.LOADR, IRDataType.LONG, reg1=1, reg2=2) - code += IRInstruction(Opcode.MODS, IRDataType.LONG, reg1=1, immediate=7) - code += IRInstruction(Opcode.RETURN) + code += IRInstructions.move(IRDataType.LONG, 1, 2) + code += IRInstructions.binaryImmediate(Opcode.MODS, IRDataType.LONG, 1, 7) + code += IRInstructions.returnVoid() startSub += code block += startSub @@ -192,9 +192,9 @@ class TestVm: FunSpec( { val startSub = IRSubroutine("main.test", emptyList(), emptyList(), Position.DUMMY) val code = IRCodeChunk(startSub.label, null) - code += IRInstruction(Opcode.LOADR, IRDataType.LONG, reg1=1, reg2=2) - code += IRInstruction(Opcode.DIVS, IRDataType.LONG, reg1=1, immediate=8) - code += IRInstruction(Opcode.RETURN) + code += IRInstructions.move(IRDataType.LONG, 1, 2) + code += IRInstructions.binaryImmediate(Opcode.DIVS, IRDataType.LONG, 1, 8) + code += IRInstructions.returnVoid() startSub += code block += startSub @@ -212,10 +212,10 @@ class TestVm: FunSpec( { val startSub = IRSubroutine("main.test", emptyList(), emptyList(), Position.DUMMY) val code = IRCodeChunk(startSub.label, null) - code += IRInstruction(Opcode.LOADR, IRDataType.LONG, reg1=1, reg2=3) - code += IRInstruction(Opcode.LOADR, IRDataType.LONG, reg1=2, reg2=4) - code += IRInstruction(Opcode.DIVSR, IRDataType.LONG, reg1=1, reg2=2) - code += IRInstruction(Opcode.RETURN) + code += IRInstructions.move(IRDataType.LONG, 1, 3) + code += IRInstructions.move(IRDataType.LONG, 2, 4) + code += IRInstructions.binary(Opcode.DIVSR, IRDataType.LONG, 1, 2) + code += IRInstructions.returnVoid() startSub += code block += startSub @@ -234,11 +234,11 @@ class TestVm: FunSpec( { val startSub = IRSubroutine("main.test", emptyList(), emptyList(), Position.DUMMY) val code = IRCodeChunk(startSub.label, null) - // BWL: memory[registers[reg2] + offset] -> registers[reg1] - code += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1=1, reg2=2, immediate=10) - // FLOAT: memory[registers[reg1] + offset] -> registers[fpReg1] - code += IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1=RegisterNum(3), reg1=2, immediate=20) - code += IRInstruction(Opcode.RETURN) + // BWL: memory[pointer register + displacement] -> destination register + code += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.WORD, 1, IRMemory.indirect(2, 10)) + // FLOAT: memory[pointer register + displacement] -> float destination register + code += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.FLOAT, 3, IRMemory.indirect(2, 20)) + code += IRInstructions.returnVoid() startSub += code block += startSub @@ -260,11 +260,11 @@ class TestVm: FunSpec( { val startSub = IRSubroutine("main.test", emptyList(), emptyList(), Position.DUMMY) val code = IRCodeChunk(startSub.label, null) - // BWL: memory[address + registers[reg2]] -> registers[reg1] - code += IRInstruction(Opcode.LOADX, IRDataType.WORD, reg1=1, reg2=2, address=1000u.toAddress()) - // FLOAT: memory[address + registers[reg1]] -> registers[fpReg1] - code += IRInstruction(Opcode.LOADX, IRDataType.FLOAT, fpReg1=RegisterNum(3), reg1=2, address=2000u.toAddress()) - code += IRInstruction(Opcode.RETURN) + // BWL: memory[base + index register * scale] -> destination register + code += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.WORD, 1, IRMemory.indexed(1000u, 2, IRDataType.WORD)) + // FLOAT: memory[base + index register * scale] -> float destination register + code += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.FLOAT, 3, IRMemory.indexed(2000u, 2, IRDataType.WORD)) + code += IRInstructions.returnVoid() startSub += code block += startSub @@ -286,11 +286,11 @@ class TestVm: FunSpec( { val startSub = IRSubroutine("main.test", emptyList(), emptyList(), Position.DUMMY) val code = IRCodeChunk(startSub.label, null) - // BWL: registers[reg1] -> memory[registers[reg2] + offset] - code += IRInstruction(Opcode.STOREI, IRDataType.WORD, reg1=1, reg2=2, immediate=10) - // FLOAT: registers[fpReg1] -> memory[registers[reg1] + offset] - code += IRInstruction(Opcode.STOREI, IRDataType.FLOAT, fpReg1=RegisterNum(3), reg1=2, immediate=20) - code += IRInstruction(Opcode.RETURN) + // BWL: source register -> memory[pointer register + displacement] + code += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.WORD, 1, IRMemory.indirect(2, 10)) + // FLOAT: float source register -> memory[pointer register + displacement] + code += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.FLOAT, 3, IRMemory.indirect(2, 20)) + code += IRInstructions.returnVoid() startSub += code block += startSub @@ -312,11 +312,11 @@ class TestVm: FunSpec( { val startSub = IRSubroutine("main.test", emptyList(), emptyList(), Position.DUMMY) val code = IRCodeChunk(startSub.label, null) - // BWL: registers[reg1] -> memory[address + registers[reg2]] - code += IRInstruction(Opcode.STOREX, IRDataType.WORD, reg1=1, reg2=2, address=1000u.toAddress()) - // FLOAT: registers[fpReg1] -> memory[address + registers[reg1]] - code += IRInstruction(Opcode.STOREX, IRDataType.FLOAT, fpReg1=RegisterNum(3), reg1=2, address=2000u.toAddress()) - code += IRInstruction(Opcode.RETURN) + // BWL: source register -> memory[base + index register * scale] + code += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.WORD, 1, IRMemory.indexed(1000u, 2, IRDataType.WORD)) + // FLOAT: float source register -> memory[base + index register * scale] + code += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.FLOAT, 3, IRMemory.indexed(2000u, 2, IRDataType.WORD)) + code += IRInstructions.returnVoid() startSub += code block += startSub @@ -338,8 +338,8 @@ class TestVm: FunSpec( { val startSub = IRSubroutine("main.test", emptyList(), emptyList(), Position.DUMMY) val code = IRCodeChunk(startSub.label, null) - code += IRInstruction(Opcode.LSR, IRDataType.LONG, reg1=1) - code += IRInstruction(Opcode.RETURN) + code += IRInstructions.unary(Opcode.LSR, IRDataType.LONG, 1) + code += IRInstructions.returnVoid() startSub += code block += startSub diff --git a/virtualmachine/test/TestVmOperandSchema.kt b/virtualmachine/test/TestVmOperandSchema.kt new file mode 100644 index 000000000..df87914f5 --- /dev/null +++ b/virtualmachine/test/TestVmOperandSchema.kt @@ -0,0 +1,189 @@ +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import prog8.code.core.CompilationOptions +import prog8.code.core.OutputType +import prog8.code.core.Position +import prog8.code.core.ZeropageType +import prog8.code.target.VMTarget +import prog8.intermediate.* +import prog8.vm.ProgramExitException +import prog8.vm.Syscall +import prog8.vm.VirtualMachine + +/** + * Every VM instruction handler must read exactly the operands that the OpcodeSchema declares for + * that (opcode, type) combination. This test builds a canonical instruction for every combination + * straight from the schema and executes it, so a handler that reaches for an operand slot the + * instruction doesn't have (or casts a memory reference to the wrong kind) fails here. + */ +class TestVmOperandSchema: FunSpec({ + + fun getTestOptions(): CompilationOptions { + val target = VMTarget() + return CompilationOptions.builder(target) + .output(OutputType.RAW) + .zeropage(ZeropageType.DONTUSE) + .floats(true) + .compilerVersion("99.99") + .loadAddress(target.PROGRAM_LOAD_ADDRESS) + .memtopAddress(0xffffu) + .build() + } + + val targetLabel = "main.test" + val dataAddress = 0x2000u + + // registers used for the generic operands; they are all pre-loaded with harmless values + val destReg = 1 + val destBReg = 2 + val srcAReg = 3 + val srcBReg = 4 + val indexReg = 5 + val pointerReg = 6 + val indirectTargetReg = 7 + val bankReg = 8 + val argReg = 9 + val resultReg = 10 + + fun registerOperand(schema: OpcodeSchema, slotSchema: RegisterSlotSchema, number: Int): RegisterOperand { + val type = schema.typeFor(slotSchema.typeRule) ?: IRDataType.WORD + val register = if (slotSchema.registerFile == RegisterFile.FLOAT) + VirtualRegister.float(number) else VirtualRegister.int(number) + return RegisterOperand(register, type, slotSchema.role, slotSchema.direction) + } + + // a calling convention slot that the VM understands for the given data type + fun hardwareSlotFor(type: IRDataType?): CallingConventionSlot = when (type) { + IRDataType.BYTE -> CallingConventionSlot(0) // A + IRDataType.WORD -> CallingConventionSlot(3) // AX + IRDataType.LONG, IRDataType.POINTER -> CallingConventionSlot(10) // D0 + IRDataType.FLOAT -> CallingConventionSlot(6) // FAC0 + null -> CallingConventionSlot(0) + } + + fun callSiteFor(kind: CallKind): CallSite = when (kind) { + CallKind.NORMAL -> CallSite( + CallTarget.Direct(codeLabel(targetLabel)), + arguments = listOf( + Calls.argument(argReg, IRDataType.WORD, CallLocation.ParameterMemory("main.test.arg", dataAddress.toAddress())) + ), + results = listOf(Calls.result(resultReg, IRDataType.WORD)) + ) + CallKind.INDIRECT -> CallSite(CallTarget.Direct(codeIndirect(indirectTargetReg))) + CallKind.FAR -> CallSite(CallTarget.Banked(1, codeAddress(dataAddress))) + CallKind.FAR_VARBANK -> CallSite( + CallTarget.BankedVariable( + RegisterOperand(VirtualRegister.int(bankReg), IRDataType.BYTE, OperandRole.VALUE, OperandDirection.USE), + codeAddress(dataAddress) + ) + ) + CallKind.SYSCALL -> CallSite( + CallTarget.SystemCall(Syscall.RNDSEED.ordinal), + arguments = listOf( + Calls.argument(argReg, IRDataType.WORD), + Calls.argument(argReg, IRDataType.WORD) + ) + ) + } + + fun buildInstruction(opcode: Opcode, type: IRDataType?): IRInstruction { + val schema = OpcodeSchemas.get(opcode, type) + var dest: RegisterOperand? = null + var destB: RegisterOperand? = null + var srcA: RegisterOperand? = null + var srcB: RegisterOperand? = null + var immediate: ImmediateOperand? = null + var hardwareSlot: HardwareSlotOperand? = null + var memory: MemoryReference? = null + var target: CodeReference? = null + var callSite: CallSite? = null + + for (slotSchema in schema.slots) { + when (slotSchema) { + is RegisterSlotSchema -> when (slotSchema.slot) { + InstructionSlot.DEST -> dest = registerOperand(schema, slotSchema, destReg) + InstructionSlot.DEST_B -> destB = registerOperand(schema, slotSchema, destBReg) + InstructionSlot.SRC_A -> srcA = registerOperand(schema, slotSchema, srcAReg) + InstructionSlot.SRC_B -> srcB = registerOperand(schema, slotSchema, srcBReg) + else -> throw IllegalArgumentException("unexpected register slot ${slotSchema.slot}") + } + is ImmediateSlotSchema -> { + val immType = schema.typeFor(slotSchema.typeRule) ?: IRDataType.WORD + immediate = if (immType == IRDataType.FLOAT) + ImmediateOperand.FloatValue(2.0) else ImmediateOperand.Integer(1, immType) + } + is MemorySlotSchema -> memory = when (slotSchema.kind) { + MemoryKind.DIRECT -> IRMemory.direct(dataAddress) + MemoryKind.INDEXED -> IRMemory.indexed(dataAddress, indexReg, IRDataType.WORD) + MemoryKind.INDIRECT -> IRMemory.indirect(pointerReg) + } + is HardwareSlotSchema -> hardwareSlot = + HardwareSlotOperand(hardwareSlotFor(type), schema.typeFor(slotSchema.typeRule) ?: IRDataType.WORD) + is TargetSlotSchema -> target = when (slotSchema.kind) { + TargetKind.STATIC -> codeLabel(targetLabel) + TargetKind.INDIRECT -> codeIndirect(indirectTargetReg) + } + is CallSiteSlotSchema -> callSite = callSiteFor(slotSchema.kind) + } + } + + return IRInstruction(opcode, type, dest, destB, srcA, srcB, immediate, hardwareSlot, memory, target, callSite) + } + + fun runSingle(instruction: IRInstruction) { + val program = IRProgram("test", IRSymbolTable(), getTestOptions(), VMTarget()) + val block = IRBlock("main", false, IRBlock.Options(), Position.DUMMY) + val sub = IRSubroutine(targetLabel, emptyList(), emptyList(), Position.DUMMY) + val code = IRCodeChunk(sub.label, null) + code += instruction + code += IRInstructions.returnVoid() + sub += code + block += sub + program.addBlock(block) + + val vm = VirtualMachine(program) + vm.breakpointHandler = { _, _ -> } + vm.reset(true) // memory starts out random; zero it so pointer variables hold valid addresses + // give all operand registers harmless but valid values + vm.registers.setUL(pointerReg, dataAddress) + vm.registers.setUL(indirectTargetReg, dataAddress) + vm.registers.setUB(indexReg, 0u) + vm.registers.setUB(bankReg, 0u) + vm.registers.setUW(srcAReg, 100u) + vm.registers.setUW(srcBReg, 7u) + vm.registers.setFloat(srcAReg, 100.0) + vm.registers.setFloat(srcBReg, 7.0) + // the pointer variable that LOADP_INC/STOREP_INC dereference + vm.memory.setSL(dataAddress, 0x3000) + // enough bytes on the value stack for POP of any width + repeat(16) { vm.valueStack.add(0u) } + vm.step() + } + + test("every opcode handler matches its operand schema") { + val operandProblems = mutableListOf() + for (opcode in Opcode.entries) { + for (type in OpcodeSchemas.typesFor(opcode)) { + val instruction = buildInstruction(opcode, type) + try { + runSingle(instruction) + } catch (ex: ProgramExitException) { + // fine: return/exit style instruction ran to completion + } catch (ex: NullPointerException) { + operandProblems += "$instruction -> NPE (operand assumed present): ${ex.message}" + } catch (ex: ClassCastException) { + operandProblems += "$instruction -> wrong operand kind: ${ex.message}" + } catch (ex: IllegalArgumentException) { + val message = ex.message ?: "" + // "missing ... operand" comes from the require*() operand accessors, so it means + // the handler reads a slot that this instruction doesn't have. + if ("missing" in message && "operand" in message) + operandProblems += "$instruction -> $message" + } catch (ex: Exception) { + operandProblems += "$instruction -> unexpected ${ex::class.simpleName}: ${ex.message}" + } + } + } + operandProblems shouldBe emptyList() + } +}) diff --git a/virtualmachine/test/TestVmStructuredOperands.kt b/virtualmachine/test/TestVmStructuredOperands.kt new file mode 100644 index 000000000..77856e4e7 --- /dev/null +++ b/virtualmachine/test/TestVmStructuredOperands.kt @@ -0,0 +1,144 @@ +package prog8.vm + +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import prog8.intermediate.IRFileReader + +/** + * End-to-end check of the structured IR operands as the VM consumes them: + * indexed and indirect memory references, a call with an argument and a captured result, + * hardware register slots, an explicit status-flag comparison plus branch, and a syscall. + */ +class TestVmStructuredOperands : FunSpec({ + + fun runProgram(): VirtualMachine { + val source = $$""" + + + +compTarget=virtual +output=PRG +launcher=BASIC +zeropage=BASICSAFE +zpAllowed=0,255 +loadAddress=0 +memtop=$ffff +optimize=false +romable=false +noSysInit=false + + + + + + + + + + +uword[4] main.arr=11,22,33,44 zp=DONTCARE align=0 +uword main.result=0 zp=DONTCARE align=0 +uword main.flag=0 zp=DONTCARE align=0 +ubyte main.hwval=0 zp=DONTCARE align=0 +uword main.cmp=0 zp=DONTCARE align=0 +uword main.doubled.value=0 zp=DONTCARE align=0 + + + + + + + + + + + + + + + + + + + + + + +load.w r10.w,#2.w +loadx.w r1.w,[main.arr+r10.w*2] +call main.doubled(value=r1.w):r2.w +storem.w r2.w,[main.result] +load.p r3.p,#main.arr +load.w r4.w,#99.w +storei.w r4.w,[r3.p+4] +load.b r6.b,#$5a.b +storehr.b r6.b,s0.b +loadhr.b r7.b,s0.b +storem.b r7.b,[main.hwval] +cmpi.w r2.w,#66.w +bsteq main.ok + + +load.w r8.w,#0.w +jump main.end + + +load.w r8.w,#1.w + + +storem.w r8.w,[main.flag] +load.l r11.l,#main.arr +load.l r12.l,#main.arr +load.w r13.w,#8.w +syscall $2f(r11.l,r12.l,r13.w):r14.w +storem.w r14.w,[main.cmp] +load.f fr15.f,#main.arr +return + + + + +uword main.doubled.value + + +loadm.w r20.w,[main.doubled.value] +mul.w r20.w,#2.w +returnr.w r20.w + + + + + +""".trimIndent() + val irProgram = IRFileReader().read(source) + val vm = VirtualMachine(irProgram) + vm.run(true) + return vm + } + + test("structured operands execute correctly") { + val vm = runProgram() + val arrAddress = vm.registers.getUL(3) + + // indexed memory read arr[2]==33, passed to a subroutine that doubles it and returns it + vm.registers.getUW(1) shouldBe 33u + vm.registers.getUW(2) shouldBe 66u + vm.memory.getUW(arrAddress + 4u) shouldBe 99u // indirect store through pointer register + displacement + vm.memory.getUW(arrAddress) shouldBe 11u // untouched array element + + // hardware register slot roundtrip (slot 0 == cpu register A) + vm.registers.getUB(7) shouldBe 0x5au + vm.hardwareRegisterA shouldBe 0x5au + + // the CMPI status flags drove the branch to main.ok + vm.registers.getUW(8) shouldBe 1u + vm.statusZero shouldBe true + + // syscall MEMCMP of the array against itself returns 0 + vm.registers.getUW(14) shouldBe 0u + + // an address-of a symbol keeps the data type the instruction prescribes + vm.registers.getSL(11) shouldBe arrAddress.toInt() + vm.registers.getFloat(15) shouldBe arrAddress.toDouble() + } +})