ir: more optimized inplace array assignments

This commit is contained in:
Irmen de Jong committed 2026-08-28 23:38:54 +02:00
1 parent a14a8f4e62
commit a7b14c2931
3 files changed
+490 -15

No files matched your search

@@ -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<IRCodeChunkBase>()
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<IRCodeChunkBase>()
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<IRCodeChunkBase>()
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<IRCodeChunkBase>()
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<IRCodeChunkBase>()
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<IRCodeChunkBase>()
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<IRCodeChunkBase>()
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<IRCodeChunkBase>()
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<IRCodeChunkBase>()
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<IRCodeChunkBase>()
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<IRCodeChunkBase>()
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<IRCodeChunkBase>()
val resultReg = codeGen.registers.next(vmDt)
+228
View File
@@ -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"
}
})
-1
View File
@@ -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."