diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt index 015eb19af..87be7bc45 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt @@ -1453,7 +1453,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen return result } - private fun operatorDivideInplace(symbol: String?, array: PtArrayIndexer?, constAddress: UInt?, memory: PtMemoryByte?, vmDt: IRDataType, operand: PtExpression, signed: Boolean): IRCodeChunks? { + private fun operatorDivideInplace(symbol: String?, array: PtArrayIndexer?, constAddress: UInt?, memory: PtMemoryByte?, vmDt: IRDataType, operand: PtExpression, signed: Boolean): IRCodeChunks { if(array!=null) { if(array.splitWords) return operatorDivideInplaceSplitArray(array, operand, signed) @@ -1551,8 +1551,46 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } return result } - if(constAddress==null && memory!=null) - return null // TODO("optimized memory in-place /"") + if(constAddress==null && memory!=null) { + val resultVar = mutableListOf() + val addrTr = exprGen.translateExpression(memory.address) + addToResult(resultVar, addrTr, addrTr.resultReg, -1) + val addressReg = addrTr.resultReg + if(vmDt==IRDataType.FLOAT) { + val tr = exprGen.translateExpression(operand) + 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) + } + } else { + val loadReg = codeGen.registers.next(vmDt) + resultVar += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADI, vmDt, reg1=loadReg, reg2=addressReg, immediate=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) + 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) + } + } 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) + } + } + } + return resultVar + } val result = mutableListOf() val constFactorRight = operand as? PtNumber @@ -1665,8 +1703,46 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } return result } - if(constAddress==null && memory!=null) - return null // TODO("optimized memory in-place *"") + if(constAddress==null && memory!=null) { + val resultVar = mutableListOf() + val addrTr = exprGen.translateExpression(memory.address) + addToResult(resultVar, addrTr, addrTr.resultReg, -1) + val addressReg = addrTr.resultReg + if(vmDt==IRDataType.FLOAT) { + val tr = exprGen.translateExpression(operand) + 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) + } + } else { + val loadReg = codeGen.registers.next(vmDt) + resultVar += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADI, vmDt, reg1=loadReg, reg2=addressReg, immediate=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) + 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) + } + } 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) + } + } + } + return resultVar + } val result = mutableListOf() val constFactorRight = operand as? PtNumber @@ -2320,10 +2396,87 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } return result } - return null // TODO("optimized >> in array") + // optimized handling for variable index or variable shift count (non-split) + if(!array.splitWords) { + val eltSize = codeGen.program.memsizer.memorySize(array.type, null) + val resultVar = mutableListOf() + val indexTr = exprGen.translateExpression(array.index) + addToResult(resultVar, indexTr, indexTr.resultReg, -1) + val (indexReg, indexDt) = normalizeArrayIndex(resultVar, indexTr) + if(eltSize > 1) + 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) + } + 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) + } + } 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) + 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) + } + } + } else { + val shiftTr = exprGen.translateExpression(operand) + 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) + } + } + return resultVar + } + return null + } + if(constAddress==null && memory!=null) { + // optimized memory in-place >> with variable address + val resultVar = mutableListOf() + val addrTr = exprGen.translateExpression(memory.address) + addToResult(resultVar, addrTr, addrTr.resultReg, -1) + val addressReg = addrTr.resultReg + 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) + } + 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) + } + } 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) + val opc = if(signed) Opcode.ASRN else Opcode.LSRN + it += IRInstruction(opc, vmDt, reg1=loadReg, reg2=shiftReg) + } + } + } else { + val shiftTr = exprGen.translateExpression(operand) + 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) + } + } + resultVar += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.STOREI, vmDt, reg1=loadReg, reg2=addressReg, immediate=0) + } + return resultVar } - if(constAddress==null && memory!=null) - return null // TODO("optimized memory in-place >>"") val result = mutableListOf() if(codeGen.isOne(operand)) { @@ -2379,10 +2532,79 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } return result } - return null // TODO("optimized << in array") + if(!array.splitWords) { + val eltSize = codeGen.program.memsizer.memorySize(array.type, null) + val resultVar = mutableListOf() + val indexTr = exprGen.translateExpression(array.index) + addToResult(resultVar, indexTr, indexTr.resultReg, -1) + val (indexReg, indexDt) = normalizeArrayIndex(resultVar, indexTr) + if(eltSize > 1) + 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) + } + 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) + } + } 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) + } + } + } 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) + } + } + return resultVar + } + return null + } + if(constAddress==null && memory!=null) { + val resultVar = mutableListOf() + val addrTr = exprGen.translateExpression(memory.address) + addToResult(resultVar, addrTr, addrTr.resultReg, -1) + val addressReg = addrTr.resultReg + 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) + } + if(constShift!=null) { + if(constShift==1) { + resultVar += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LSL, vmDt, reg1=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) + } + } + } 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) + } + } + resultVar += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.STOREI, vmDt, reg1=loadReg, reg2=addressReg, immediate=0) + } + return resultVar } - if(constAddress==null && memory!=null) - return null // TODO("optimized memory in-place <<"") val result = mutableListOf() if(codeGen.isOne(operand)){ @@ -2603,7 +2825,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen return result } - private fun operatorModuloInplace(symbol: String?, array: PtArrayIndexer?, constAddress: UInt?, memory: PtMemoryByte?, vmDt: IRDataType, operand: PtExpression, signed: Boolean): IRCodeChunks? { + private fun operatorModuloInplace(symbol: String?, array: PtArrayIndexer?, constAddress: UInt?, memory: PtMemoryByte?, vmDt: IRDataType, operand: PtExpression, signed: Boolean): IRCodeChunks { if(array!=null) { if(array.splitWords) return operatorModuloInplaceSplitArray(array, operand, signed) @@ -2665,8 +2887,34 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } return result } - if(constAddress==null && memory!=null) - return null // TODO("optimized memory in-place %"") + if(constAddress==null && memory!=null) { + val resultVar = mutableListOf() + val addrTr = exprGen.translateExpression(memory.address) + addToResult(resultVar, addrTr, addrTr.resultReg, -1) + 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) + } + 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) + } + } 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) + } + } + return resultVar + } val result = mutableListOf() val resultReg = codeGen.registers.next(vmDt) diff --git a/compiler/test/TestInplaceOptimization.kt b/compiler/test/TestInplaceOptimization.kt new file mode 100644 index 000000000..8271403d7 --- /dev/null +++ b/compiler/test/TestInplaceOptimization.kt @@ -0,0 +1,228 @@ +package prog8tests.compiler + +import io.kotest.core.spec.style.FunSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.string.shouldContain +import prog8.code.target.VMTarget +import prog8tests.helpers.compileText +import kotlin.io.path.readText + +class TestInplaceOptimization: FunSpec({ + val outputDir = tempdir().toPath() + + test("array shift right in-place with variable index") { + val src = """ + %zeropage basicsafe + %option no_sysinit + main { + ubyte[] arr = [8, 16, 32, 64] + ubyte @shared result + sub start() { + ubyte @shared idx = 2 + ubyte @shared sh = 1 + arr[idx] >>= sh + result = arr[idx] + } + } + """.trimIndent() + val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = true)!! + val ir = outputDir.resolve(result.compilerAst.name + ".p8ir").readText() + ir shouldContain "loadx.b" + ir shouldContain "lsrn.b" + ir shouldContain "storex.b" + } + + test("array shift left in-place with variable index") { + val src = """ + %zeropage basicsafe + %option no_sysinit + main { + ubyte[] arr = [1, 2, 4, 8] + ubyte @shared result + sub start() { + ubyte @shared idx = 1 + arr[idx] <<= 2 + result = arr[idx] + } + } + """.trimIndent() + val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = true)!! + val ir = outputDir.resolve(result.compilerAst.name + ".p8ir").readText() + ir shouldContain "loadx.b" + ir shouldContain "lsln.b" + ir shouldContain "storex.b" + } + + test("array multiply in-place with variable index") { + val src = """ + %zeropage basicsafe + %option no_sysinit + main { + ubyte[] arr = [1, 2, 4, 8] + ubyte @shared result + sub start() { + ubyte @shared idx = 2 + arr[idx] *= 3 + result = arr[idx] + } + } + """.trimIndent() + val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = true)!! + val ir = outputDir.resolve(result.compilerAst.name + ".p8ir").readText() + ir shouldContain "loadx.b" + ir shouldContain "mul.b" + ir shouldContain "storex.b" + } + + test("array divide in-place with variable index") { + val src = """ + %zeropage basicsafe + %option no_sysinit + main { + ubyte[] arr = [8, 16, 32, 64] + ubyte @shared result + sub start() { + ubyte @shared idx = 2 + arr[idx] /= 3 + result = arr[idx] + } + } + """.trimIndent() + val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = true)!! + val ir = outputDir.resolve(result.compilerAst.name + ".p8ir").readText() + ir shouldContain "loadx.b" + ir shouldContain "div.b" + ir shouldContain "storex.b" + } + + test("array modulo in-place with variable index") { + val src = """ + %zeropage basicsafe + %option no_sysinit + main { + ubyte[] arr = [8, 16, 32, 64] + ubyte @shared result + sub start() { + ubyte @shared idx = 2 + arr[idx] %= 6 + result = arr[idx] + } + } + """.trimIndent() + val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = true)!! + val ir = outputDir.resolve(result.compilerAst.name + ".p8ir").readText() + ir shouldContain "loadx.b" + ir shouldContain "mod.b" + ir shouldContain "storex.b" + } + + test("memory multiply in-place with variable address") { + val src = """ + %zeropage basicsafe + %option no_sysinit + main { + uword @shared ptr + ubyte @shared result + sub start() { + ptr = ${'$'}c000 + @(ptr) = 5 + @(ptr) *= 3 + result = @(ptr) + } + } + """.trimIndent() + val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = true)!! + val ir = outputDir.resolve(result.compilerAst.name + ".p8ir").readText() + ir shouldContain "loadi.b" + ir shouldContain "mul.b" + ir shouldContain "storei.b" + } + + test("memory divide in-place with variable address") { + val src = """ + %zeropage basicsafe + %option no_sysinit + main { + uword @shared ptr + ubyte @shared result + sub start() { + ptr = ${'$'}c000 + @(ptr) = 20 + @(ptr) /= 3 + result = @(ptr) + } + } + """.trimIndent() + val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = true)!! + val ir = outputDir.resolve(result.compilerAst.name + ".p8ir").readText() + ir shouldContain "loadi.b" + ir shouldContain "div.b" + ir shouldContain "storei.b" + } + + test("memory modulo in-place with variable address") { + val src = """ + %zeropage basicsafe + %option no_sysinit + main { + uword @shared ptr + ubyte @shared result + sub start() { + ptr = ${'$'}c000 + @(ptr) = 20 + @(ptr) %= 6 + result = @(ptr) + } + } + """.trimIndent() + val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = true)!! + val ir = outputDir.resolve(result.compilerAst.name + ".p8ir").readText() + ir shouldContain "loadi.b" + ir shouldContain "mod.b" + ir shouldContain "storei.b" + } + + test("memory shift right in-place with variable address") { + val src = """ + %zeropage basicsafe + %option no_sysinit + main { + uword @shared ptr + ubyte @shared result + sub start() { + ptr = ${'$'}c000 + @(ptr) = 16 + @(ptr) >>= 1 + result = @(ptr) + } + } + """.trimIndent() + val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = true)!! + val ir = outputDir.resolve(result.compilerAst.name + ".p8ir").readText() + ir shouldContain "loadi.b" + ir shouldContain "lsr.b" + ir shouldContain "storei.b" + } + + test("memory shift left in-place with variable address") { + val src = """ + %zeropage basicsafe + %option no_sysinit + main { + uword @shared ptr + ubyte @shared result + sub start() { + ptr = ${'$'}c000 + @(ptr) = 4 + @(ptr) <<= 1 + result = @(ptr) + } + } + """.trimIndent() + val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = true)!! + val ir = outputDir.resolve(result.compilerAst.name + ".p8ir").readText() + ir shouldContain "loadi.b" + ir shouldContain "lsl.b" + ir shouldContain "storei.b" + } +}) diff --git a/docs/source/todo.rst b/docs/source/todo.rst index a93678297..6d0c8a0f2 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -44,7 +44,6 @@ Romable (%option romable) IR/VM ^^^^^ - encode indexed scaling into IR (so that m68k codegen can use scale factor addressing) see ideas/scaled-indexing-IR.md -- implement more TODOs in AssignmentGen? - add even more optimizations in IRPeepholeOptimizer? - **Multi-Level IR Design**: Consider introducing a High-Level IR (HLIR) layer before the current low-level IR to preserve semantics like loop bounds, array indexing, and structure field access. The current IR is effectively "assembly with infinite registers."