diff --git a/.agents/skills/m68k-coder/SKILL.md b/.agents/skills/m68k-coder/SKILL.md index 88ce632f9..52cebc599 100644 --- a/.agents/skills/m68k-coder/SKILL.md +++ b/.agents/skills/m68k-coder/SKILL.md @@ -214,6 +214,23 @@ All Prog8 symbols are prefixed when accessed from assembly: - `movea.l` does NOT set condition codes (address register variant) - Use `move.l` when you need to test the result +### MOVE.b / MOVE.w do NOT zero-fill the upper bits (data register destination) +- **FALSE assumption**: "`move.w , d0` zero-fills the upper 16 bits of d0". +- **Actual behavior**: `move.b , dn` and `move.w , dn` (data register as destination) modify ONLY the low 8 or 16 bits of the destination register. The upper bits keep their PREVIOUS value and are NOT cleared. +- This is unlike x86 `movzx` and unlike `move.w ,An` (movea.w) to an ADDRESS register, which DOES sign-extend the word to 32 bits. +- Consequence: if the destination register holds a stale non-zero upper part, any later 32-bit operation (`move.l`, `add.l`, `sub.l` for pointer arithmetic, etc.) uses those garbage upper bits, silently corrupting addresses and arithmetic. +- To zero-extend a loaded byte/word into a data register, clear first or mask explicitly: + ```asm + moveq #0,d0 ; clear upper bits + move.b p8v_byte,d0 ; then load (upper bits stay 0) + ``` + or + ```asm + move.w p8v_word,d0 + and.l #$ffff,d0 ; zero-extend after the load + ``` +- Pitfall for code generators with a memory-based register file: reading a virtual register with a `.l` suffix when its slot is only 1 or 2 bytes over-reads into the NEXT register's slot (and `move.w`/`move.b` from a register slot never widens it in the destination). Always widen explicitly with `and.l #$ff/#$ffff`, `moveq #0`, or `ext`/`extb.l`. + ### Stack Alignment - Stack pointer (A7) should be word-aligned - Pushing odd-sized data can misalign the stack diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt index 654dc9bfa..c660d83ba 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt @@ -743,7 +743,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } result += chunk } else { - val (code, indexReg) = codeGen.loadIndexReg(targetArray.index, eltSize, false, targetArray.splitWords) + val (code, indexReg) = codeGen.loadIndexReg(targetArray.index, eltSize, codeGen.wordArrayIndex, targetArray.splitWords) result += code result += IRCodeChunk(null, null).also { if(targetArray.splitWords) { @@ -763,7 +763,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } result += chunk } else { - val (code, indexReg) = codeGen.loadIndexReg(targetArray.index, eltSize, false, targetArray.splitWords) + 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) @@ -784,7 +784,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } result += chunk } else { - val (code, indexReg) = codeGen.loadIndexReg(targetArray.index, eltSize, false, targetArray.splitWords) + val (code, indexReg) = codeGen.loadIndexReg(targetArray.index, eltSize, codeGen.wordArrayIndex, targetArray.splitWords) result += code result += IRCodeChunk(null, null).also { if(targetArray.splitWords) { diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt index a34df79f0..514a09477 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt @@ -5,7 +5,6 @@ import prog8.code.SymbolTable import prog8.code.ast.* import prog8.code.core.AssemblyError import prog8.code.core.BaseDataType -import prog8.code.core.DataType import prog8.intermediate.* @@ -926,18 +925,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = targetVariable.name) } } else { - val indexTr = exprGen.translateExpression(target.index) - addToResult(result, indexTr, indexTr.resultReg, -1) + val (code, indexReg) = codeGen.loadIndexReg(target.index, eltSize, codeGen.wordArrayIndex, false) + result += code + val indexDt = if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE result += IRCodeChunk(null, null).also { - if(eltSize>1) - it += codeGen.multiplyByConst(DataType.UBYTE, indexTr.resultReg, eltSize) if(msb) { it += if(target.type.isLong) - IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1 = indexTr.resultReg, immediate = 3) + IRInstruction(Opcode.ADD, indexDt, reg1 = indexReg, immediate = 3) else - IRInstruction(Opcode.INC, IRDataType.BYTE, reg1 = indexTr.resultReg) + IRInstruction(Opcode.INC, indexDt, reg1 = indexReg) } - it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = targetVariable.name) + it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexReg, labelSymbol = targetVariable.name) } } } else { @@ -951,18 +949,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = targetVariable.name) } } else { - val indexTr = exprGen.translateExpression(target.index) - addToResult(result, indexTr, indexTr.resultReg, -1) + val (code, indexReg) = codeGen.loadIndexReg(target.index, eltSize, codeGen.wordArrayIndex, false) + result += code + val indexDt = if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE result += IRCodeChunk(null, null).also { - if(eltSize>1) - it += codeGen.multiplyByConst(DataType.UBYTE, indexTr.resultReg, eltSize) if(msb) { it += if(target.type.isLong) - IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1 = indexTr.resultReg, immediate = 3) + IRInstruction(Opcode.ADD, indexDt, reg1 = indexReg, immediate = 3) else - IRInstruction(Opcode.INC, IRDataType.BYTE, reg1 = indexTr.resultReg) + IRInstruction(Opcode.INC, indexDt, reg1 = indexReg) } - it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = targetVariable.name) + it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexReg, labelSymbol = targetVariable.name) } } } diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt index 94e0d18ea..8afd7a7f6 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt @@ -643,15 +643,15 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { } fun indexByExpression() { - val (code, indexByteReg) = codeGen.loadIndexReg(arrayIx.index, eltSize, false, arrayIx.splitWords) + val (code, indexReg) = codeGen.loadIndexReg(arrayIx.index, eltSize, codeGen.wordArrayIndex, arrayIx.splitWords) result += code if(vmDt==IRDataType.FLOAT) { resultFpRegister = codeGen.registers.next(IRDataType.FLOAT) - addInstr(result, IRInstruction(Opcode.LOADX, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpRegister), reg1=indexByteReg, labelSymbol = arrayVarSymbol), null) + addInstr(result, IRInstruction(Opcode.LOADX, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpRegister), reg1=indexReg, labelSymbol = arrayVarSymbol), null) } else { resultRegister = codeGen.registers.next(vmDt) - addInstr(result, IRInstruction(Opcode.LOADX, vmDt, reg1=resultRegister, reg2=indexByteReg, labelSymbol = arrayVarSymbol), null) + addInstr(result, IRInstruction(Opcode.LOADX, vmDt, reg1=resultRegister, reg2=indexReg, labelSymbol = arrayVarSymbol), null) } } diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt index 03d7d845d..1cb1b6081 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt @@ -30,6 +30,9 @@ class IRCodeGen( var wasPackingApplied: Boolean = false private set + // on 32-bit targets, LOADX/STOREX/STOREZX use a word index register (0-32767) instead of a byte (0-255) + internal val wordArrayIndex: Boolean = options.compTarget.POINTER_MEM_SIZE > 2u + fun generate(): IRProgram { // The pure "virtual" (VM) target doesn't need symbol prefixing because the VM // doesn't emit assembly and has no risk of symbol clashes. Any other target diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt index 0e2832d8a..e3c4aa792 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt @@ -80,6 +80,7 @@ class IRUnusedCodeRemover( } // remove stray loads + val indexRegType = if(irprog.options.compTarget.POINTER_MEM_SIZE > 2u) IRDataType.WORD else IRDataType.BYTE val readRegs = mutableSetOf() val readFpRegs = mutableSetOf() instructions.forEach { ins -> @@ -88,7 +89,7 @@ class IRUnusedCodeRemover( val writeRegsCounts = mutableMapOf() val writeFpRegsCounts = mutableMapOf() val regsTypes = mutableMapOf() - ins.addUsedRegistersCounts(readRegsCounts, writeRegsCounts, readFpRegsCounts, writeFpRegsCounts, regsTypes, null) + ins.addUsedRegistersCounts(readRegsCounts, writeRegsCounts, readFpRegsCounts, writeFpRegsCounts, regsTypes, null, indexRegType) readRegs.addAll(readRegsCounts.keys.map { it.value }) readFpRegs.addAll(readFpRegsCounts.keys.map { it.value }) } diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/RegisterPacker.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/RegisterPacker.kt index 116216668..2afb560ba 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/RegisterPacker.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/RegisterPacker.kt @@ -71,12 +71,13 @@ object RegisterPacker { data class Interval(val register: Int, val start: Int, val end: Int, val type: IRDataType) fun pack(irProg: IRProgram) { + val indexRegType = if(irProg.options.compTarget.POINTER_MEM_SIZE > 2u) IRDataType.WORD else IRDataType.BYTE val allRegTypes = mutableMapOf() irProg.foreachCodeChunk { chunk -> for (instr in chunk.instructions) { val (written, read) = getRegisterAccess(instr) for (r in written + read) { - val dt = getRegisterType(instr, r) + val dt = getRegisterType(instr, r, indexRegType) allRegTypes.putIfAbsent(r, dt) } } @@ -88,7 +89,7 @@ object RegisterPacker { val maxReg = allRegTypes.keys.maxOrNull() ?: 0 val startSlot = maxReg + 1 val globalSlotTypes = mutableMapOf() - irProg.foreachSub { sub -> packSubroutine(sub, allRegTypes, globalSlotTypes, startSlot) } + irProg.foreachSub { sub -> packSubroutine(sub, allRegTypes, globalSlotTypes, startSlot, indexRegType) } val afterTypes = rebuildTypeMap(irProg) val afterCount = afterTypes.size @@ -100,12 +101,13 @@ object RegisterPacker { // 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 = if(irProg.options.compTarget.POINTER_MEM_SIZE > 2u) IRDataType.WORD else IRDataType.BYTE val newTypes = mutableMapOf() irProg.foreachCodeChunk { chunk -> 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) - newTypes.putIfAbsent(RegisterNum(reg), determineIntRegType(instr, regNum)) + 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) } @@ -120,7 +122,7 @@ object RegisterPacker { return newTypes } - private fun packSubroutine(sub: IRSubroutine, allRegTypes: Map, globalSlotTypes: MutableMap, startSlot: Int) { + private fun packSubroutine(sub: IRSubroutine, allRegTypes: Map, globalSlotTypes: MutableMap, startSlot: Int, indexRegType: IRDataType) { if (sub.chunks.isEmpty()) return @@ -160,7 +162,7 @@ object RegisterPacker { // Process READ first so types are set before WRITE processing for (r in read) { - val dt = getRegisterType(instr, r) + val dt = getRegisterType(instr, r, indexRegType) registerTypes.putIfAbsent(r, dt) if (r !in liveSet) { lastUse[r] = globalI @@ -172,7 +174,7 @@ object RegisterPacker { for (r in written) { if (r in liveSet) { val end = lastUse.getOrElse(r) { globalI } - val actualType = getRegisterType(instr, r) + val actualType = getRegisterType(instr, r, indexRegType) registerIntervals.getOrPut(r) { mutableListOf() } .add(Interval(r, globalI, end, actualType)) liveSet.remove(r) @@ -216,21 +218,21 @@ object RegisterPacker { mergedIntervals.removeAll { it.register in skipRegs } } - val conflictGraph = buildConflictGraph(sub) + val conflictGraph = buildConflictGraph(sub, indexRegType) val packing = greedyColor(mergedIntervals, conflictGraph, allRegTypes, globalSlotTypes, startSlot) if (packing.isNotEmpty()) rewrite(sub, packing) } - private fun buildConflictGraph(sub: IRSubroutine): Map> { + private fun buildConflictGraph(sub: IRSubroutine, indexRegType: IRDataType): Map> { val conflicts = mutableMapOf>() for (chunk in sub.chunks.filterIsInstance()) { 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) + val dt = determineIntRegType(instr, regNum, indexRegType) typeRegs.getOrPut(dt) { mutableSetOf() }.add(reg) } } @@ -427,14 +429,14 @@ object RegisterPacker { return written to read } - private fun getRegisterType(instr: IRInstruction, register: Int): IRDataType { + 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) - instr.reg2 == register -> determineIntRegType(instr, 2) - instr.reg3 == register -> determineIntRegType(instr, 3) + 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 @@ -451,7 +453,7 @@ object RegisterPacker { } } - private fun determineIntRegType(instr: IRInstruction, regNum: Int): IRDataType { + private fun determineIntRegType(instr: IRInstruction, regNum: Int, indexRegType: IRDataType): IRDataType { val opcode = instr.opcode val type = instr.type @@ -459,7 +461,7 @@ object RegisterPacker { return when (opcode) { Opcode.FFROMUB, Opcode.FFROMSB, Opcode.FTOUB, Opcode.FTOSB, Opcode.FCOMP, Opcode.SGN -> IRDataType.BYTE - Opcode.LOADX, Opcode.STOREX, Opcode.STOREZX -> IRDataType.BYTE + Opcode.LOADX, Opcode.STOREX, Opcode.STOREZX -> indexRegType Opcode.FFROMSL, Opcode.FTOSL -> IRDataType.LONG Opcode.LOADI, Opcode.STOREI -> IRDataType.POINTER else -> IRDataType.WORD @@ -468,7 +470,8 @@ object RegisterPacker { if (type == IRDataType.WORD && regNum == 1) { return when (opcode) { - Opcode.SGN, Opcode.STOREZX, Opcode.SQRT -> IRDataType.BYTE + Opcode.SGN, Opcode.SQRT -> IRDataType.BYTE + Opcode.STOREZX -> indexRegType Opcode.EXT, Opcode.EXTS, Opcode.CONCAT -> IRDataType.LONG else -> type } @@ -478,13 +481,14 @@ object RegisterPacker { 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 -> IRDataType.BYTE + Opcode.LOADX, Opcode.STOREX -> indexRegType Opcode.LOADI, Opcode.STOREI -> IRDataType.POINTER Opcode.ASRN, Opcode.LSRN, Opcode.LSLN -> IRDataType.BYTE else -> type ?: IRDataType.BYTE diff --git a/codeGenM68k/src/prog8/codegen/m68k/AsmGen.kt b/codeGenM68k/src/prog8/codegen/m68k/AsmGen.kt index 773c4d6e0..50b51904c 100644 --- a/codeGenM68k/src/prog8/codegen/m68k/AsmGen.kt +++ b/codeGenM68k/src/prog8/codegen/m68k/AsmGen.kt @@ -161,13 +161,25 @@ internal class AsmGen(val program: IRProgram, internal val target: ICompilationT fun loadIndexToD0(idx: Int) { // load an index register into d0, zero-extending to 32 bits - val idxType = program.registersUsed().regsTypes[RegisterNum(idx)] - emitLine("moveq.l #0,d0") // clear everything for any caller that uses (a0,d0.l) addressing - when (idxType) { - IRDataType.BYTE -> emitLine("move.b ${regAddr(idx)}, d0") - IRDataType.POINTER, IRDataType.LONG -> emitLine("move.l ${regAddr(idx)}, d0") - else -> emitLine("move.w ${regAddr(idx)}, d0") - } + loadRegToD0(idx) + } + + // Load a virtual register into d0 using the register's ACTUAL slot size, + // zero-extending to 32 bits. m68k move.b/move.w into a data register do NOT + // clear the upper bits, and reading a slot with a larger suffix than the + // slot's size would bleed into the adjacent register slot. Always clear + // first and use the register's own type for the load. + fun loadRegToD0(reg: Int) { + val regType = program.registersUsed().regsTypes[RegisterNum(reg)] ?: IRDataType.BYTE + emitLine("moveq #0, d0") + emitLine("move${dtSuffix(regType)} ${regAddr(reg)}, d0") + } + + // Same as loadRegToD0, but into d1. + fun loadRegToD1(reg: Int) { + val regType = program.registersUsed().regsTypes[RegisterNum(reg)] ?: IRDataType.BYTE + emitLine("moveq #0, d1") + emitLine("move${dtSuffix(regType)} ${regAddr(reg)}, d1") } // === label/symbol helpers === diff --git a/codeGenM68k/src/prog8/codegen/m68k/InstrArithmetic.kt b/codeGenM68k/src/prog8/codegen/m68k/InstrArithmetic.kt index 4a3a0ce81..cd1b259e8 100644 --- a/codeGenM68k/src/prog8/codegen/m68k/InstrArithmetic.kt +++ b/codeGenM68k/src/prog8/codegen/m68k/InstrArithmetic.kt @@ -51,7 +51,7 @@ internal fun AsmGen.translateArithmetic(insn: IRInstruction) { Opcode.ADDR -> { val dstReg = r1 ?: error("ADDR needs reg1") val srcReg = r2 ?: error("ADDR needs reg2") - emitLine("move${dtSuffix(type)} ${regAddr(srcReg)}, d0") + loadRegToD0(srcReg) emitLine("add${dtSuffix(type)} d0, ${regAddr(dstReg)}") } @@ -100,7 +100,7 @@ internal fun AsmGen.translateArithmetic(insn: IRInstruction) { Opcode.SUBR -> { val dstReg = r1 ?: error("SUBR needs reg1") val srcReg = r2 ?: error("SUBR needs reg2") - emitLine("move${dtSuffix(type)} ${regAddr(srcReg)}, d0") + loadRegToD0(srcReg) emitLine("sub${dtSuffix(type)} d0, ${regAddr(dstReg)}") } @@ -322,8 +322,9 @@ internal fun AsmGen.translateArithmetic(insn: IRInstruction) { Opcode.CMP -> { val leftReg = insn.reg1 ?: error("CMP needs reg1") val rightReg = insn.reg2 ?: error("CMP needs reg2") - emitLine("move${dtSuffix(type)} ${regAddr(leftReg)}, d0") - emitLine("cmp${dtSuffix(type)} ${regAddr(rightReg)}, d0") + loadRegToD0(leftReg) + loadRegToD1(rightReg) + emitLine("cmp${dtSuffix(type)} d1, d0") } Opcode.CMPI -> { diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 6335bebce..909981e9f 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,6 +3,7 @@ TODO - need a bunch of type casting/conversion checks that test the handling of the 4-byte/long pointer datatype on the qemu68k target. - amiga library structs: use more typed pointers if it knows the struct type , rather than using `pointer`. Consider both the extsubs but also the struct fields in the amigaDOS structs in the generated library modules. +- BUG (m68k targets): the ``setlsb``/``setmsb`` builtins on array elements produce byte-swapped results on big-endian targets: ``funcSetLsbMsb`` in BuiltinFuncGen.kt assumes little-endian word layout (it adds +1 to the index for msb), but m68k stores words big-endian in memory so lsb/msb hit the wrong byte. E.g. ``uword[100] arr; arr[i]=$1234; setlsb(arr[i], $56)`` yields ``$5634`` instead of ``$1256`` on qemu68k. Probably needs a target-endianness dependent offset (and check other lsb/msb/lsb-msb-concat word byte-extraction code paths for the same assumption). Future Things and Ideas @@ -82,6 +83,7 @@ Optimizations ^^^^^^^^^^^^^ - Port more benchmarks from https://thred.github.io/c-bench-64/ to prog8 and see how it stacks up. (see benchmark-c/ directory) - Compilation speed: try to join multiple modifications in 1 result in the AST processors instead of returning it straight away every time +- Compilation speed: cache ``IRProgram.registersUsed()`` - it rescans every instruction in the whole program on each call, and the m68k AsmGen helpers (``loadRegToD0``/``loadRegToD1``, used by ADDR/SUBR/CMP and indexed addressing) call it per instruction, making codegen O(program size x arith instruction count). Cache once in ``AsmGen`` (``private val regsUsed by lazy { program.registersUsed() }``) or memoize in ``IRProgram`` itself. - various optimizers skip stuff if compTarget.name==VMTarget.NAME. Once new 6502 codegen is done from IR code, those 6502 only optimizations should probably be removed - **Register packer** (`RegisterPacker.kt`) look in register-packing.md for all details diff --git a/examples/test.p8 b/examples/test.p8 index e85ec5957..65be41961 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -3,22 +3,48 @@ main { sub start() { - uword @shared ptr1 - long @shared ptr2 - pointer @shared ptr3 - ^^bool @shared ptr4 - ^^word @shared ptr5 + struct Foo { + bool field + pointer[5] Planes + } - ubyte x = ptr1[10] ; should fail on 32 bits target - ubyte y = ptr2[10] - ubyte z = ptr3[10] - bool q = ptr4[10] - word r = ptr5[10] + ^^Foo bm = [ + true, + [ $1111,$2222,$3333,$4444,$5555] + ] - ptr1[10]++ ; should fail on 32 bits target - ptr2[10]++ - ptr3[10]++ - ptr4[10] = true - ptr5[10] = -32768 + txt.print("bitplane0: ") + txt.print_ulhex(bm.Planes[0], true) + txt.print("\nbitplane1: ") + txt.print_ulhex(bm.Planes[1], true) + txt.print("\nbitplane2: ") + txt.print_ulhex(bm.Planes[2], true) + txt.print("\nbitplane3: ") + txt.print_ulhex(bm.Planes[3], true) + txt.print("\nbitplane4: ") + txt.print_ulhex(bm.Planes[4], true) + txt.nl() + txt.nl() + + ubyte plane = 0 + txt.print("bitplane0: ") + txt.print_ulhex(bm.Planes[plane], true) + txt.print("\nbitplane1: ") + txt.print_ulhex(bm.Planes[plane+1], true) + txt.print("\nbitplane2: ") + txt.print_ulhex(bm.Planes[plane+2], true) + txt.print("\nbitplane3: ") + txt.print_ulhex(bm.Planes[plane+3], true) + txt.print("\nbitplane4: ") + txt.print_ulhex(bm.Planes[plane+4], true) + txt.nl() + txt.nl() + + for plane in 0 to 4 { + ^^ubyte target = bm.Planes[plane] ; + bytesPerRow*y + txt.print_ulhex(target as long, true) + txt.spc() + } + txt.nl() } } diff --git a/intermediate/src/prog8/intermediate/IRFileWriter.kt b/intermediate/src/prog8/intermediate/IRFileWriter.kt index b0f3b1587..68c0b43bb 100644 --- a/intermediate/src/prog8/intermediate/IRFileWriter.kt +++ b/intermediate/src/prog8/intermediate/IRFileWriter.kt @@ -169,7 +169,8 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) { } private fun writeCodeChunk(chunk: IRCodeChunk) { - val usedRegs = chunk.usedRegisters() + val indexRegType = if(irProgram.options.compTarget.POINTER_MEM_SIZE > 2u) IRDataType.WORD else IRDataType.BYTE + val usedRegs = chunk.usedRegisters(indexRegType) val regs = StringBuilder() if(usedRegs.readRegs.any() || usedRegs.writeRegs.any()) { regs.append("\nINT REGS:\n") diff --git a/intermediate/src/prog8/intermediate/IRInstructions.kt b/intermediate/src/prog8/intermediate/IRInstructions.kt index 137988c1e..eca34e89a 100644 --- a/intermediate/src/prog8/intermediate/IRInstructions.kt +++ b/intermediate/src/prog8/intermediate/IRInstructions.kt @@ -1026,7 +1026,8 @@ data class IRInstruction( readFpRegsCounts: MutableMap, writeFpRegsCounts: MutableMap, regsTypes: MutableMap, - chunk: IRCodeChunk? + 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) @@ -1048,28 +1049,28 @@ data class IRInstruction( OperandDirection.UNUSED -> {} OperandDirection.READ -> { incReadReg(RegisterNum(this.reg1!!)) - determineReg1Type()?.let { setRegType(RegisterNum(this.reg1), it) } + determineReg1Type(indexRegType)?.let { setRegType(RegisterNum(this.reg1), it) } } OperandDirection.WRITE -> { incWriteReg(RegisterNum(this.reg1!!)) - determineReg1Type()?.let { setRegType(RegisterNum(this.reg1), it) } + determineReg1Type(indexRegType)?.let { setRegType(RegisterNum(this.reg1), it) } } OperandDirection.READWRITE -> { incReadReg(RegisterNum(this.reg1!!)) incWriteReg(RegisterNum(this.reg1)) - determineReg1Type()?.let { setRegType(RegisterNum(this.reg1), it) } + determineReg1Type(indexRegType)?.let { setRegType(RegisterNum(this.reg1), it) } } } when (this.reg2direction) { OperandDirection.UNUSED -> {} OperandDirection.READ -> { incReadReg(RegisterNum(this.reg2!!)) - determineReg2Type()?.let { setRegType(RegisterNum(this.reg2), it) } + determineReg2Type(indexRegType)?.let { setRegType(RegisterNum(this.reg2), it) } } OperandDirection.READWRITE -> { incReadReg(RegisterNum(this.reg2!!)) incWriteReg(RegisterNum(this.reg2)) - determineReg2Type()?.let { setRegType(RegisterNum(this.reg2), it) } + determineReg2Type(indexRegType)?.let { setRegType(RegisterNum(this.reg2), it) } } else -> throw IllegalArgumentException("reg2 can only be read or readwrite") } @@ -1114,7 +1115,7 @@ data class IRInstruction( } } - private fun determineReg1Type(): IRDataType? { + 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) { @@ -1123,10 +1124,10 @@ data class IRInstruction( Opcode.FTOUB, Opcode.FTOSB, Opcode.FCOMP, + Opcode.SGN -> IRDataType.BYTE Opcode.LOADX, Opcode.STOREX, - Opcode.STOREZX, - Opcode.SGN -> IRDataType.BYTE + 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 @@ -1136,7 +1137,8 @@ data class IRInstruction( if(type==IRDataType.WORD) { // some word instructions have byte reg1 when (opcode) { - Opcode.SGN, Opcode.STOREZX, Opcode.SQRT -> return IRDataType.BYTE + Opcode.SGN, Opcode.SQRT -> return IRDataType.BYTE + Opcode.STOREZX -> return indexRegType Opcode.EXT, Opcode.EXTS, Opcode.CONCAT -> return IRDataType.LONG else -> {} } @@ -1147,7 +1149,7 @@ data class IRInstruction( if(opcode==Opcode.SQRT) return IRDataType.WORD if(opcode==Opcode.STOREZX) - return IRDataType.BYTE + return indexRegType } if(opcode in setOf(Opcode.JUMPI, Opcode.CALLI, Opcode.STOREZI)) return IRDataType.POINTER @@ -1162,9 +1164,9 @@ data class IRInstruction( return this.type } - private fun determineReg2Type(): IRDataType? { + private fun determineReg2Type(indexRegType: IRDataType): IRDataType? { if(opcode==Opcode.LOADX || opcode==Opcode.STOREX) - return IRDataType.BYTE + return indexRegType if(opcode==Opcode.LOADI || opcode==Opcode.STOREI) return IRDataType.POINTER if(opcode==Opcode.ASRN || opcode==Opcode.LSRN || opcode==Opcode.LSLN) diff --git a/intermediate/src/prog8/intermediate/IRProgram.kt b/intermediate/src/prog8/intermediate/IRProgram.kt index 6cf0ef42f..b8a9edd1d 100644 --- a/intermediate/src/prog8/intermediate/IRProgram.kt +++ b/intermediate/src/prog8/intermediate/IRProgram.kt @@ -312,6 +312,9 @@ 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 = if(options.compTarget.POINTER_MEM_SIZE > 2u) IRDataType.WORD else IRDataType.BYTE + globalInits.instructions.forEach { it.addUsedRegistersCounts( readRegsCounts, @@ -319,7 +322,8 @@ class IRProgram(val name: String, readFpRegsCounts, writeFpRegsCounts, regsTypes, - globalInits + globalInits, + indexRegType ) } @@ -327,10 +331,10 @@ class IRProgram(val name: String, block.children.forEach { child -> when(child) { is IRAsmSubroutine -> addUsed(child.usedRegisters(), child) - is IRCodeChunk -> addUsed(child.usedRegisters(), child) - is IRInlineAsmChunk -> addUsed(child.usedRegisters(), child) - is IRInlineBinaryChunk -> addUsed(child.usedRegisters(), child) - is IRSubroutine -> child.chunks.forEach { chunk -> addUsed(chunk.usedRegisters(), child) } + is IRCodeChunk -> addUsed(child.usedRegisters(indexRegType), child) + is IRInlineAsmChunk -> addUsed(child.usedRegisters(indexRegType), child) + is IRInlineBinaryChunk -> addUsed(child.usedRegisters(indexRegType), child) + is IRSubroutine -> child.chunks.forEach { chunk -> addUsed(chunk.usedRegisters(indexRegType), child) } } } } @@ -472,16 +476,17 @@ class IRProgram(val name: String, * which this strict check would reject. Use RegisterPacker.rebuildTypeMap() instead. */ fun verifyRegisterTypes(registerTypes: Map) { + val indexRegType = if(options.compTarget.POINTER_MEM_SIZE > 2u) IRDataType.WORD else IRDataType.BYTE for(block in blocks) { for(bc in block.children) { when(bc) { is IRAsmSubroutine -> bc.usedRegisters().validate(registerTypes, null) - is IRCodeChunk -> bc.usedRegisters().validate(registerTypes, bc) - is IRInlineAsmChunk -> bc.usedRegisters().validate(registerTypes, bc) - is IRInlineBinaryChunk -> bc.usedRegisters().validate(registerTypes, bc) + is IRCodeChunk -> bc.usedRegisters(indexRegType).validate(registerTypes, bc) + is IRInlineAsmChunk -> bc.usedRegisters(indexRegType).validate(registerTypes, bc) + is IRInlineBinaryChunk -> bc.usedRegisters(indexRegType).validate(registerTypes, bc) is IRSubroutine -> { for(sc in bc.chunks) { - sc.usedRegisters().validate(registerTypes, sc) + sc.usedRegisters(indexRegType).validate(registerTypes, sc) } } } @@ -586,20 +591,20 @@ sealed class IRCodeChunkBase(override val label: String?, var next: IRCodeChunkB abstract override fun isEmpty(): Boolean abstract override fun isNotEmpty(): Boolean - abstract fun usedRegisters(): RegistersUsed + abstract fun usedRegisters(indexRegType: IRDataType = IRDataType.BYTE): RegistersUsed } class IRCodeChunk(label: String?, next: IRCodeChunkBase?): IRCodeChunkBase(label, next) { override fun isEmpty() = instructions.isEmpty() override fun isNotEmpty() = instructions.isNotEmpty() - override fun usedRegisters(): RegistersUsed { + override fun usedRegisters(indexRegType: IRDataType): RegistersUsed { val readRegsCounts = mutableMapOf().withDefault { 0 } val regsTypes = mutableMapOf() val readFpRegsCounts = mutableMapOf().withDefault { 0 } val writeRegsCounts = mutableMapOf().withDefault { 0 } val writeFpRegsCounts = mutableMapOf().withDefault { 0 } - instructions.forEach { it.addUsedRegistersCounts(readRegsCounts, writeRegsCounts, readFpRegsCounts, writeFpRegsCounts, regsTypes, this) } + instructions.forEach { it.addUsedRegistersCounts(readRegsCounts, writeRegsCounts, readFpRegsCounts, writeFpRegsCounts, regsTypes, this, indexRegType) } return RegistersUsed(readRegsCounts, writeRegsCounts, readFpRegsCounts, writeFpRegsCounts, regsTypes) } @@ -639,7 +644,7 @@ class IRInlineAsmChunk(label: String?, require(!assembly.endsWith('\n') && !assembly.endsWith('\r')) { "inline assembly should be trimmed" } } - override fun usedRegisters() = registersUsed + override fun usedRegisters(indexRegType: IRDataType) = registersUsed } class IRInlineBinaryChunk(label: String?, @@ -648,7 +653,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() = RegistersUsed(emptyMap(), emptyMap(), emptyMap(), emptyMap(), emptyMap()) + override fun usedRegisters(indexRegType: IRDataType) = RegistersUsed(emptyMap(), emptyMap(), emptyMap(), emptyMap(), emptyMap()) } typealias IRCodeChunks = List