From 250ddda11bfe267facaf0860a41166b26ea1f4db Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Thu, 27 Aug 2026 02:48:25 +0200 Subject: [PATCH] ir,m68k: add LOADP_INC/STOREP_INC for pointer post-inc (a0)+ --- .../codegen/intermediate/AssignmentGen.kt | 12 +- .../prog8/codegen/intermediate/IRCodeGen.kt | 2 - .../intermediate/IRPeepholeOptimizer.kt | 112 ++++++++++++++++- codeGenM68k/src/prog8/codegen/m68k/AsmGen.kt | 1 + .../src/prog8/codegen/m68k/InstrLoadStore.kt | 18 +++ .../src/prog8/codegen/new6502/AsmGen.kt | 1 + .../prog8/codegen/new6502/InstrLoadStore.kt | 114 ++++++++++++++++++ compiler/test/TestCompilerOnExamples.kt | 12 +- compiler/test/codegeneration/TestM68k.kt | 38 ++++++ .../src/prog8/intermediate/IRInstructions.kt | 6 + intermediate/src/prog8/intermediate/Utils.kt | 2 +- virtualmachine/src/prog8/vm/VirtualMachine.kt | 45 ++++++- 12 files changed, 341 insertions(+), 22 deletions(-) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt index 1d024fdd7..2a066c847 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt @@ -981,7 +981,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen // Non-constant index - use LOADX for each byte array val indexTr = exprGen.translateExpression(array.index) addToResult(result, indexTr, indexTr.resultReg, -1) - val (indexReg, indexDt) = normalizeArrayIndex(result, indexTr) + val (indexReg, _) = normalizeArrayIndex(result, indexTr) val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) if(valueTr.resultReg < 0) return null @@ -1110,7 +1110,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen // Non-constant index val indexTr = exprGen.translateExpression(array.index) addToResult(result, indexTr, indexTr.resultReg, -1) - val (indexReg, indexDt) = normalizeArrayIndex(result, indexTr) + val (indexReg, _) = normalizeArrayIndex(result, indexTr) if(constValue!=null) { val loadReg = codeGen.registers.next(vmDt) val constReg = codeGen.registers.next(vmDt) @@ -1239,7 +1239,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } val indexTr = exprGen.translateExpression(array.index) addToResult(result, indexTr, indexTr.resultReg, -1) - val (indexReg, indexDt) = normalizeArrayIndex(result, indexTr) + val (indexReg, _) = normalizeArrayIndex(result, indexTr) val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) if(valueTr.resultReg < 0) return null @@ -1371,7 +1371,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen // Non-constant index val indexTr = exprGen.translateExpression(array.index) addToResult(result, indexTr, indexTr.resultReg, -1) - val (indexReg, indexDt) = normalizeArrayIndex(result, indexTr) + val (indexReg, _) = normalizeArrayIndex(result, indexTr) val arrayVarName2 = array.variable!!.name if(constValue!=null) { val loadReg = codeGen.registers.next(vmDt) @@ -2452,7 +2452,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } val indexTr = exprGen.translateExpression(array.index) addToResult(result, indexTr, indexTr.resultReg, -1) - val (indexReg, indexDt) = normalizeArrayIndex(result, indexTr) + val (indexReg, _) = normalizeArrayIndex(result, indexTr) val valueTr = exprGen.translateExpression(operand) addToResult(result, valueTr, valueTr.resultReg, -1) if(valueTr.resultReg < 0) return null @@ -2483,7 +2483,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen // Non-constant index - use LOADX/XORR/STOREX val indexTr = exprGen.translateExpression(array.index) addToResult(result, indexTr, indexTr.resultReg, -1) - val (indexReg, indexDt) = normalizeArrayIndex(result, indexTr) + val (indexReg, _) = normalizeArrayIndex(result, indexTr) val arrayVarName2 = array.variable!!.name if(constValue!=null) { val loadReg = codeGen.registers.next(vmDt) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt index e541d8513..f76dc269c 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt @@ -2249,7 +2249,6 @@ class IRCodeGen( else -> throw IllegalArgumentException("unexpected index dt $indexDt for wordIndex") } indexReg = newReg - indexDt = IRDataType.WORD } result += multiplyByConst(DataType.UWORD, indexReg, itemsize) return Pair(result, indexReg) @@ -2277,7 +2276,6 @@ class IRCodeGen( else -> throw IllegalArgumentException("unexpected index conversion $indexDt -> $indexRegType") } indexReg = newReg - indexDt = indexRegType } if(itemsize==1 || arrayIsSplitWords) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt index f8bcedec4..26d51b130 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt @@ -65,6 +65,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram, private val retainSSA: || removeSelfIdentityOps(chunk1, indexedInstructions) || simplifyShiftByZero(chunk1, indexedInstructions) || cancelAdjacentOps(chunk1, indexedInstructions) + || fusePointerPostInc(chunk1, indexedInstructions) || removeNops(chunk1, indexedInstructions) // last time, in case one of the optimizers replaced something with a nop } while (changed) } @@ -795,6 +796,106 @@ jump p8_label_gen_2 return changed } + private fun fusePointerPostInc(chunk: IRCodeChunk, indexedInstructions: List>): Boolean { + // Fuse loadm + loadi/storei + incm on same pointer variable into LOADP_INC/STOREP_INC + // Pattern: loadm.l rX, P ; loadi.b rY,rX,#0 ; ... ; incm.l P => loadp_inc.b rY,P + // 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() + 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 + } + // 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() + 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!! + loadmByAddr.getOrPut(addr) { mutableListOf() }.add(idx) + loadmRegByIdx[idx] = ins.reg1!! + loadmAddrByIdx[idx] = addr + } + } + // 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!! + 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()) { + if(loadmIdx >= incIdx) continue + if(loadmIdx in toRemove) continue + val baseReg = loadmRegByIdx[loadmIdx] ?: continue + // Check baseReg is used exactly once (in loadi/storei) and not otherwise + if((readCounts[baseReg] ?: 0) != 1) continue + // Find loadi/storei that uses baseReg between loadm and incm + var foundIdx: Int? = null + var foundIns: IRInstruction? = null + for((midIdx, midIns) in indexedInstructions) { + 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) { + // LOADI must have offset 0 to be fusible to post-inc (otherwise need add) + if(midIns.immediate != 0) continue + foundIdx = midIdx + foundIns = midIns + break + } + if(midIns.opcode == Opcode.STOREI && midIns.reg2 == baseReg) { + if(midIns.immediate != 0) continue + foundIdx = midIdx + foundIns = midIns + break + } + } + if(foundIdx == null || foundIns == null) continue + // 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) + toReplace[foundIdx] = newIns + toRemove.add(loadmIdx) + toRemove.add(incIdx) + changed = true + break // one incm fuses one loadm + } + } + if(!changed) return false + // Apply replacements and removals in reverse order to keep indices stable + val allIndices = (toRemove + toReplace.keys).sortedDescending() + for(idx in allIndices) { + if(idx in toRemove) { + chunk.instructions[idx] = IRInstruction(Opcode.NOP) + } + } + for((idx, newIns) in toReplace) { + chunk.instructions[idx] = newIns + } + return true + } + private fun removeNops(chunk: IRCodeChunk, indexedInstructions: List>): Boolean { var changed = false indexedInstructions.reversed().forEach { (idx, ins) -> @@ -1195,18 +1296,17 @@ jump p8_label_gen_2 // find inc chunk: contains INCM loopvar and JUMP loopLabel (possibly same chunk or inc then jump) var incIdx = -1 - var incIsCombined = false 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 } if (!hasInc) continue val hasJumpInSame = c.instructions.any { it.opcode == Opcode.JUMP && it.labelSymbol == loopLabel } - if (hasJumpInSame) { incIdx = k; incIsCombined = true; break } + 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) { - incIdx = k; incIsCombined = false; break + incIdx = k; break } } } @@ -1298,9 +1398,9 @@ jump p8_label_gen_2 } if (changed) { // Need to re-link chunks after structural changes (labels still valid) - sub.chunks.forEachIndexed { i, ch -> - // ensure next pointers will be recomputed via linkChunks later; for now keep as is - } +// sub.chunks.forEachIndexed { i, ch -> +// // ensure next pointers will be recomputed via linkChunks later; for now keep as is +// } } return changed } diff --git a/codeGenM68k/src/prog8/codegen/m68k/AsmGen.kt b/codeGenM68k/src/prog8/codegen/m68k/AsmGen.kt index 63e447fad..dc4cc0c2c 100644 --- a/codeGenM68k/src/prog8/codegen/m68k/AsmGen.kt +++ b/codeGenM68k/src/prog8/codegen/m68k/AsmGen.kt @@ -661,6 +661,7 @@ internal class AsmGen(val program: IRProgram, internal val target: ICompilationT Opcode.BREAKPOINT -> emitLine("illegal") Opcode.LOAD, Opcode.LOADM, Opcode.LOADR, Opcode.LOADX, Opcode.LOADHR, Opcode.LOADI, + Opcode.LOADP_INC, Opcode.STOREP_INC, Opcode.STOREM, Opcode.STOREX, Opcode.STOREZM, Opcode.STOREZI, Opcode.STOREIM, Opcode.STOREZX, Opcode.STOREHR, Opcode.STOREI, Opcode.LOADHFACZERO, Opcode.LOADHFACONE, Opcode.STOREHFACZERO, Opcode.STOREHFACONE -> diff --git a/codeGenM68k/src/prog8/codegen/m68k/InstrLoadStore.kt b/codeGenM68k/src/prog8/codegen/m68k/InstrLoadStore.kt index d5ed6990e..a49cbd34b 100644 --- a/codeGenM68k/src/prog8/codegen/m68k/InstrLoadStore.kt +++ b/codeGenM68k/src/prog8/codegen/m68k/InstrLoadStore.kt @@ -186,6 +186,24 @@ internal fun AsmGen.translateLoadStore(insn: IRInstruction, suppressRegfileStore } } + 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 + emitLine("move.l $target, a0") + emitLine("move$s (a0)+, d0") + emitLine("move.l a0, $target") + emitLine("move$s d0, ${regAddr(dst)}") + } + + Opcode.STOREP_INC -> { + val value = r1 ?: error("STOREP_INC needs reg1") + // a : *a = r1; a += sizeof(type) + emitLine("move.l $target, a0") + emitLine("move$s ${regAddr(value)}, (a0)+") + emitLine("move.l a0, $target") + } + else -> error("Unknown load/store opcode: ${insn.opcode}") } } diff --git a/codeGenNew6502/src/prog8/codegen/new6502/AsmGen.kt b/codeGenNew6502/src/prog8/codegen/new6502/AsmGen.kt index 999e4efcf..4da47875c 100644 --- a/codeGenNew6502/src/prog8/codegen/new6502/AsmGen.kt +++ b/codeGenNew6502/src/prog8/codegen/new6502/AsmGen.kt @@ -762,6 +762,7 @@ internal class AsmGen(val program: IRProgram, private val target: ICompilationTa Opcode.LOAD, Opcode.LOADM, Opcode.LOADR, Opcode.LOADX, Opcode.LOADHR, Opcode.LOADI, Opcode.STOREM, Opcode.STOREX, Opcode.STOREZM, Opcode.STOREZI, Opcode.STOREIM, Opcode.STOREZX, Opcode.STOREHR, Opcode.STOREI, + Opcode.LOADP_INC, Opcode.STOREP_INC, Opcode.LOADHFACZERO, Opcode.LOADHFACONE, Opcode.STOREHFACZERO, Opcode.STOREHFACONE -> translateLoadStore(insn) diff --git a/codeGenNew6502/src/prog8/codegen/new6502/InstrLoadStore.kt b/codeGenNew6502/src/prog8/codegen/new6502/InstrLoadStore.kt index 3a7e9cfde..d29c58544 100644 --- a/codeGenNew6502/src/prog8/codegen/new6502/InstrLoadStore.kt +++ b/codeGenNew6502/src/prog8/codegen/new6502/InstrLoadStore.kt @@ -126,6 +126,120 @@ internal fun AsmGen.translateLoadStore(insn: IRInstruction) { indirectStore(r1 ?: error("STOREI needs reg1"), baseReg, offset, type) } + Opcode.LOADP_INC -> { + val dst = r1 ?: error("LOADP_INC needs reg1") + val ptrVar = resolveAddress(addr, label, offset) + // r1 = *ptrVar; ptrVar += sizeof(type) (post-inc) + // Use $22/$23 as temp pointer + emitLine("lda $ptrVar") + emitLine("sta $$22") + emitLine("lda ${ptrVar}+1") + emitLine("sta $$23") + emitLine("ldy #0") + when (type) { + IRDataType.BYTE -> { + emitLine("lda ($$22),y") + emitLine("sta ${regAddr(dst)}") + emitLine("inc $ptrVar") + emitLine("bne +") + emitLine("inc ${ptrVar}+1") + emitLine("+") + } + IRDataType.WORD, IRDataType.POINTER -> { + emitLine("lda ($$22),y") + emitLine("sta ${regAddr(dst)}") + emitLine("iny") + emitLine("lda ($$22),y") + emitLine("sta ${regAddr(dst)}+1") + emitLine("lda $ptrVar") + emitLine("clc") + emitLine("adc #2") + emitLine("sta $ptrVar") + emitLine("bcc +") + emitLine("inc ${ptrVar}+1") + emitLine("+") + } + IRDataType.LONG -> { + // long not used on 6502 for pointer post-inc, fallback to 4-byte inc + emitLine("lda ($$22),y") + emitLine("sta ${regAddr(dst)}") + emitLine("iny") + emitLine("lda ($$22),y") + emitLine("sta ${regAddr(dst)}+1") + emitLine("iny") + emitLine("lda ($$22),y") + emitLine("sta ${regAddr(dst)}+2") + emitLine("iny") + emitLine("lda ($$22),y") + emitLine("sta ${regAddr(dst)}+3") + emitLine("lda $ptrVar") + emitLine("clc") + emitLine("adc #4") + emitLine("sta $ptrVar") + emitLine("bcc +") + emitLine("inc ${ptrVar}+1") + emitLine("+") + } + else -> TODO("LOADP_INC for $type") + } + } + + Opcode.STOREP_INC -> { + val src = r1 ?: error("STOREP_INC needs reg1") + val ptrVar = resolveAddress(addr, label, offset) + // *ptrVar = r1; ptrVar += sizeof(type) + emitLine("lda $ptrVar") + emitLine("sta $$22") + emitLine("lda ${ptrVar}+1") + emitLine("sta $$23") + emitLine("ldy #0") + when (type) { + IRDataType.BYTE -> { + emitLine("lda ${regAddr(src)}") + emitLine("sta ($$22),y") + emitLine("inc $ptrVar") + emitLine("bne +") + emitLine("inc ${ptrVar}+1") + emitLine("+") + } + IRDataType.WORD, IRDataType.POINTER -> { + emitLine("lda ${regAddr(src)}") + emitLine("sta ($$22),y") + emitLine("iny") + emitLine("lda ${regAddr(src)}+1") + emitLine("sta ($$22),y") + emitLine("lda $ptrVar") + emitLine("clc") + emitLine("adc #2") + emitLine("sta $ptrVar") + emitLine("bcc +") + emitLine("inc ${ptrVar}+1") + emitLine("+") + } + IRDataType.LONG -> { + emitLine("lda ${regAddr(src)}") + emitLine("sta ($$22),y") + emitLine("iny") + emitLine("lda ${regAddr(src)}+1") + emitLine("sta ($$22),y") + emitLine("iny") + emitLine("lda ${regAddr(src)}+2") + emitLine("sta ($$22),y") + emitLine("iny") + emitLine("lda ${regAddr(src)}+3") + emitLine("sta ($$22),y") + emitLine("lda $ptrVar") + emitLine("clc") + emitLine("adc #4") + emitLine("sta $ptrVar") + emitLine("bcc +") + emitLine("inc ${ptrVar}+1") + emitLine("+") + } + else -> TODO("STOREP_INC for $type") + } + } + Opcode.LOADHFACZERO -> TODO("LOADHFACZERO (float)") Opcode.LOADHFACONE -> TODO("LOADHFACONE (float)") Opcode.STOREHFACZERO -> TODO("STOREHFACZERO (float)") diff --git a/compiler/test/TestCompilerOnExamples.kt b/compiler/test/TestCompilerOnExamples.kt index 33185f877..26e78b838 100644 --- a/compiler/test/TestCompilerOnExamples.kt +++ b/compiler/test/TestCompilerOnExamples.kt @@ -332,16 +332,16 @@ class TestCompilerOnExamplesVirtual: FunSpec({ val onlyVirtual = cartesianProduct( listOf( ExampleSizes("bouncegfx", virtualInstrCountOptimized=227, virtualInstrCountUnoptimized=756, virtualRegCountOptimized=90, virtualRegCountUnoptimized=325), - ExampleSizes("bsieve", virtualInstrCountOptimized=273, virtualInstrCountUnoptimized=1900, virtualRegCountOptimized=74, virtualRegCountUnoptimized=800), + ExampleSizes("bsieve", virtualInstrCountOptimized=262, virtualInstrCountUnoptimized=1900, virtualRegCountOptimized=74, virtualRegCountUnoptimized=800), ExampleSizes("fountain", virtualInstrCountOptimized=207, virtualInstrCountUnoptimized=738, virtualRegCountOptimized=80, virtualRegCountUnoptimized=318), ExampleSizes("pixelshader", virtualInstrCountOptimized=93, virtualInstrCountUnoptimized=218, virtualRegCountOptimized=17, virtualRegCountUnoptimized=60), ExampleSizes("sincos", virtualInstrCountOptimized=334, virtualInstrCountUnoptimized=834, virtualRegCountOptimized=148, virtualRegCountUnoptimized=357), - ExampleSizes("pointers/animalgame", virtualInstrCountOptimized=428, virtualInstrCountUnoptimized=2053, virtualRegCountOptimized=155, virtualRegCountUnoptimized=875), - ExampleSizes("pointers/binarytree", virtualInstrCountOptimized=658, virtualInstrCountUnoptimized=2223, virtualRegCountOptimized=260, virtualRegCountUnoptimized=961), - ExampleSizes("pointers/hashtable", virtualInstrCountOptimized=682, virtualInstrCountUnoptimized=2224, virtualRegCountOptimized=291, virtualRegCountUnoptimized=980), - ExampleSizes("pointers/sortedlist", virtualInstrCountOptimized=404, virtualInstrCountUnoptimized=2386, virtualRegCountOptimized=130, virtualRegCountUnoptimized=1027), + ExampleSizes("pointers/animalgame", virtualInstrCountOptimized=417, virtualInstrCountUnoptimized=2053, virtualRegCountOptimized=147, virtualRegCountUnoptimized=875), + ExampleSizes("pointers/binarytree", virtualInstrCountOptimized=637, virtualInstrCountUnoptimized=2223, virtualRegCountOptimized=253, virtualRegCountUnoptimized=961), + ExampleSizes("pointers/hashtable", virtualInstrCountOptimized=663, virtualInstrCountUnoptimized=2224, virtualRegCountOptimized=279, virtualRegCountUnoptimized=980), + ExampleSizes("pointers/sortedlist", virtualInstrCountOptimized=386, virtualInstrCountUnoptimized=2386, virtualRegCountOptimized=122, virtualRegCountUnoptimized=1027), ExampleSizes("pointers/fountain-virtual", virtualInstrCountOptimized=203, virtualInstrCountUnoptimized=733, virtualRegCountOptimized=75, virtualRegCountUnoptimized=311), - ExampleSizes("pointers/sorting", virtualInstrCountOptimized=513, virtualInstrCountUnoptimized=2211, virtualRegCountOptimized=169, virtualRegCountUnoptimized=937) + ExampleSizes("pointers/sorting", virtualInstrCountOptimized=497, virtualInstrCountUnoptimized=2211, virtualRegCountOptimized=159, virtualRegCountUnoptimized=937) ), listOf(false, true) ) diff --git a/compiler/test/codegeneration/TestM68k.kt b/compiler/test/codegeneration/TestM68k.kt index 487fc1639..41cd7d72c 100644 --- a/compiler/test/codegeneration/TestM68k.kt +++ b/compiler/test/codegeneration/TestM68k.kt @@ -265,4 +265,42 @@ main { }""" compileText(Qemu68kTarget(), false, src, outputDir, writeAssembly = false) shouldNotBe null } + + test("pointer post-increment fuses to (a0)+ on qemu68k") { + // #3.6: loadm+loadi+incm and loadm+storei+incm on same pointer variable + // should fuse to loadp_inc/storep_inc and lower to m68k (a0)+ + val src = """ + main { + sub start() { + ubyte[4] @shared src = [1,2,3,4] + ubyte[4] @shared dst = [0,0,0,0] + pointer pSrc = &src + pointer pDst = &dst + ubyte i + for i in 0 to 3 { + @(pDst) = @(pSrc) + pSrc++ + pDst++ + } + ubyte @shared dummy = dst[0] + } + }""" + // without optimization the peephole is disabled, so no post-inc fusion + val resNoOpt = compileText(Qemu68kTarget(), optimize = false, src, outputDir, writeAssembly = true, assemble = false) + resNoOpt shouldNotBe null + // with optimization the IR should contain the new post-inc ops + val resOpt = compileText(Qemu68kTarget(), optimize = true, src, outputDir, writeAssembly = true, assemble = false) + resOpt shouldNotBe null + val p8ir = outputDir.toFile().walkTopDown().firstOrNull { it.extension == "p8ir" }?.readText() ?: "" + // check that the optimized IR uses the fused ops (single type specifier, no second type field) + (p8ir.contains("loadp_inc.b") || p8ir.contains("loadp_inc")) shouldBe true + (p8ir.contains("storep_inc.b") || p8ir.contains("storep_inc")) shouldBe true + // and that the m68k assembly uses post-increment addressing (a0)+ + val asm = outputDir.toFile().walkTopDown().filter { it.extension == "asm" }.map { it.readText() }.joinToString("\n") + val lines = asm.lines().map { it.trim() } + lines.any { it.contains("(a0)+") } shouldBe true + // cx16 must still compile (fallback lowers loadp_inc to $22 indirect + inc) + val resCx16 = compileText(prog8.code.target.Cx16Target(), optimize = true, src, outputDir, writeAssembly = true, assemble = false) + resCx16 shouldNotBe null + } }) diff --git a/intermediate/src/prog8/intermediate/IRInstructions.kt b/intermediate/src/prog8/intermediate/IRInstructions.kt index d36929e42..78f30d0b9 100644 --- a/intermediate/src/prog8/intermediate/IRInstructions.kt +++ b/intermediate/src/prog8/intermediate/IRInstructions.kt @@ -112,6 +112,8 @@ loadr reg1, reg2 - load reg1 with value in register reg2, loadhr reg1, slot - load cpu hardware register from calling convention slot (s0=A, s1=X, s2=Y, s3=AX, s4=AY, s5=XY, s6=FAC1, s7=FAC2) into reg1 loadhfaczero fpreg1 - load "cpu hardware register" fac0 into freg1.f loadhfacone fpreg1 - load "cpu hardware register" fac1 into freg1.f +loadp_inc reg1, address - load reg1 with value in memory indirect via pointer variable at address, then post-increment the pointer variable by sizeof(type) (r1 = *a; a += sizeof(type), uses (a)+ on m68k) +storep_inc reg1, address - store reg1 to memory indirect via pointer variable at address, then post-increment the pointer variable by sizeof(type) (*a = r1; a += sizeof(type), uses (a)+ on m68k) storem reg1, address - store reg1 at memory address storei reg1, reg2, value - store reg1 in memory indirect, pointed to by reg2 + offsetvalue 0-65535 (often used to write a field from a pointer to a struct, or with offset=0 just straight to the pointer) storeim value, address - store an immediate value (constant) at memory address (the constant goes in the value field, NOT via a register). @@ -356,6 +358,8 @@ enum class Opcode { STOREI, STOREHFACZERO, STOREHFACONE, + LOADP_INC, + STOREP_INC, JUMP, JUMPI, @@ -759,6 +763,8 @@ val instructionFormats = mutableMapOf( Opcode.STOREI to InstructionFormat.from("BWL,r1,<>a"), + Opcode.STOREP_INC to InstructionFormat.from("BWL,a"), Opcode.JUMP to InstructionFormat.from("N, 255) 1 else 0 + var carry: Int a = sum and 0xFF rnd_b1 = a.toUByte() carry = a and 1 // lsr carry @@ -264,6 +264,8 @@ class VirtualMachine(irProgram: IRProgram) { Opcode.LOADHFACZERO -> InsLOADHFACZERO(ins) Opcode.LOADHFACONE -> InsLOADHFACONE(ins) Opcode.LOADI -> InsLOADI(ins) + Opcode.LOADP_INC -> InsLOADP_INC(ins) + Opcode.STOREP_INC -> InsSTOREP_INC(ins) Opcode.STOREM -> InsSTOREM(ins) Opcode.STOREX -> InsSTOREX(ins) Opcode.STOREI-> InsSTOREI(ins) @@ -609,6 +611,47 @@ class VirtualMachine(irProgram: IRProgram) { nextPc() } + private fun InsLOADP_INC(i: IRInstruction) { + val ptrVarAddr = i.address!!.value + 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.FLOAT -> throw IllegalStateException("LOADP_INC float not supported") + } + val inc = when(i.type!!) { + IRDataType.BYTE -> 1 + IRDataType.WORD, IRDataType.POINTER -> if(i.type==IRDataType.POINTER && wordArrayIndex) 4 else 2 + IRDataType.LONG -> 4 + else -> 0 + } + // pointer variable is LONG (32-bit) + memory.setSL(ptrVarAddr, (ptr + inc.toUInt()).toInt()) + nextPc() + } + + private fun InsSTOREP_INC(i: IRInstruction) { + val ptrVarAddr = i.address!!.value + 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.FLOAT -> throw IllegalStateException("STOREP_INC float not supported") + } + val inc = when(i.type!!) { + IRDataType.BYTE -> 1 + IRDataType.WORD, IRDataType.POINTER -> if(i.type==IRDataType.POINTER && wordArrayIndex) 4 else 2 + IRDataType.LONG -> 4 + else -> 0 + } + memory.setSL(ptrVarAddr, (ptr + inc.toUInt()).toInt()) + nextPc() + } + private fun InsLOADX(i: IRInstruction) { when (i.type!!) { IRDataType.FLOAT -> {