structured IR

This commit is contained in:
Irmen de Jong committed 2026-09-11 20:20:40 +02:00
1 parent 20374f4059
commit 468850bc47
81 files changed
+10779 -7073

No files matched your search

File diff suppressed because it is too large. Load diff
@@ -81,7 +81,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=tr.resultReg), null)
addInstr(result, IRInstructions.push(IRDataType.BYTE, tr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
@@ -89,7 +89,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.WORD, reg1=tr.resultReg), null)
addInstr(result, IRInstructions.push(IRDataType.WORD, tr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
@@ -97,7 +97,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.LONG, reg1=tr.resultReg), null)
addInstr(result, IRInstructions.push(IRDataType.LONG, tr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
@@ -105,35 +105,35 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, -1, tr.resultFpReg)
addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.FLOAT, fpReg1 = RegisterNum(tr.resultFpReg)), null)
addInstr(result, IRInstructions.push(IRDataType.FLOAT, tr.resultFpReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcPop(): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=resultRegister), null)
addInstr(result, IRInstructions.pop(IRDataType.BYTE, resultRegister), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
private fun funcPopW(): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.POP, IRDataType.WORD, reg1=resultRegister), null)
addInstr(result, IRInstructions.pop(IRDataType.WORD, resultRegister), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
private fun funcPopL(): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.next(IRDataType.LONG)
addInstr(result, IRInstruction(Opcode.POP, IRDataType.LONG, reg1=resultRegister), null)
addInstr(result, IRInstructions.pop(IRDataType.LONG, resultRegister), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
private fun funcPopF(): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.POP, IRDataType.FLOAT, fpReg1 = RegisterNum(resultRegister)), null)
addInstr(result, IRInstructions.pop(IRDataType.FLOAT, resultRegister), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, resultRegister)
}
@@ -147,15 +147,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
IRDataType.LONG -> {
val intermediateReg = codeGen.registers.next(IRDataType.LONG)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, type, reg1=intermediateReg, reg2=trSourceAddr.resultReg, immediate = 0)
it += IRInstruction(Opcode.STOREI, type, reg1=intermediateReg, reg2=trTargetAddr.resultReg, immediate = 0)
it += IRInstructions.loadMemory(Opcode.LOADI, type, intermediateReg, IRMemory.indirect(trSourceAddr.resultReg))
it += IRInstructions.storeMemory(Opcode.STOREI, type, intermediateReg, IRMemory.indirect(trTargetAddr.resultReg))
}
}
IRDataType.FLOAT -> {
val intermediateReg = codeGen.registers.next(IRDataType.FLOAT)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, type, fpReg1=RegisterNum(intermediateReg), reg1=trSourceAddr.resultReg, immediate = 0)
it += IRInstruction(Opcode.STOREI, type, fpReg1=RegisterNum(intermediateReg), reg1=trTargetAddr.resultReg, immediate = 0)
it += IRInstructions.loadMemory(Opcode.LOADI, type, intermediateReg, IRMemory.indirect(trSourceAddr.resultReg))
it += IRInstructions.storeMemory(Opcode.STOREI, type, intermediateReg, IRMemory.indirect(trTargetAddr.resultReg))
}
}
else -> throw AssemblyError("invalid type $type")
@@ -169,12 +169,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
return if(resultType==IRDataType.FLOAT) {
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.SQUARE, resultType, fpReg1 = RegisterNum(resultFpReg), fpReg2 = RegisterNum(valueTr.resultFpReg)), null)
addInstr(result, IRInstructions.binary(Opcode.SQUARE, resultType, resultFpReg, valueTr.resultFpReg), null)
ExpressionCodeResult(result, resultType, -1, resultFpReg)
}
else {
val resultReg = codeGen.registers.next(resultType)
addInstr(result, IRInstruction(Opcode.SQUARE, resultType, reg1 = resultReg, reg2 = valueTr.resultReg), null)
addInstr(result, IRInstructions.binary(Opcode.SQUARE, resultType, resultReg, valueTr.resultReg), null)
ExpressionCodeResult(result, resultType, resultReg, -1)
}
}
@@ -183,13 +183,13 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val result = mutableListOf<IRCodeChunkBase>()
val addressTr = exprGen.translateExpression(call.args[0])
addToResult(result, addressTr, addressTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.CALLI, reg1 = addressTr.resultReg), null)
addInstr(result, IRInstructions.call(Opcode.CALLI, CallSite(CallTarget.Direct(codeIndirect(addressTr.resultReg)))), null)
return if(call.void)
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
else {
val resultvalueReg = codeGen.registers.next(IRDataType.WORD)
val slot = if(codeGen.options.compTarget.cpu.usesM68kConvention) 10 else 4
addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=resultvalueReg, immediate=slot), null)
addInstr(result, IRInstructions.hardwareLoad(IRDataType.WORD, resultvalueReg, CallingConventionSlot(slot)), null)
ExpressionCodeResult(result, IRDataType.WORD, resultvalueReg, -1)
}
}
@@ -202,8 +202,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, bankTr, bankTr.resultReg, -1)
addToResult(result, addressTr, addressTr.resultReg, -1)
addToResult(result, argumentwordTr, argumentwordTr.resultReg, -1)
result += codeGen.makeSyscall(IMSyscall.CALLFAR, listOf(IRDataType.BYTE to bankTr.resultReg, codeGen.addressDt to addressTr.resultReg, IRDataType.WORD to argumentwordTr.resultReg), IRDataType.WORD to argumentwordTr.resultReg)
return ExpressionCodeResult(result, IRDataType.WORD, argumentwordTr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.WORD)
result += codeGen.makeSyscall(IMSyscall.CALLFAR, listOf(IRDataType.BYTE to bankTr.resultReg, codeGen.addressDt to addressTr.resultReg, IRDataType.WORD to argumentwordTr.resultReg), IRDataType.WORD to resultReg)
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
private fun funcCallfar2(call: PtFunctionCall): ExpressionCodeResult {
@@ -220,12 +221,13 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, argumentX, argumentX.resultReg, -1)
addToResult(result, argumentY, argumentY.resultReg, -1)
addToResult(result, argumentCarry, argumentCarry.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.WORD)
result += codeGen.makeSyscall(IMSyscall.CALLFAR2, listOf(IRDataType.BYTE to bankTr.resultReg, codeGen.addressDt to addressTr.resultReg,
IRDataType.BYTE to argumentA.resultReg,
IRDataType.BYTE to argumentX.resultReg,
IRDataType.BYTE to argumentY.resultReg,
IRDataType.BYTE to argumentCarry.resultReg), IRDataType.WORD to addressTr.resultReg)
return ExpressionCodeResult(result, IRDataType.WORD, addressTr.resultReg, -1)
IRDataType.BYTE to argumentCarry.resultReg), IRDataType.WORD to resultReg)
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
private fun funcDivmod(call: PtFunctionCall, type: IRDataType, signed: Boolean): ExpressionCodeResult {
@@ -244,7 +246,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val tr = exprGen.translateExpression(number)
addToResult(result, tr, tr.resultReg, -1)
remainderReg = codeGen.registers.next(type)
addInstr(result, IRInstruction(divmodOpcode, type, reg1 = tr.resultReg, reg2 = remainderReg, immediate = divident.number.toInt()), null)
addInstr(result, IRInstructions.divmodImmediate(divmodOpcode, type, tr.resultReg, remainderReg, divident.number.toInt()), null)
divisionReg = tr.resultReg
} else {
val numTr = exprGen.translateExpression(number)
@@ -252,7 +254,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val dividentTr = exprGen.translateExpression(divident)
addToResult(result, dividentTr, dividentTr.resultReg, -1)
remainderReg = dividentTr.resultReg
addInstr(result, IRInstruction(divmodrOpcode, type, reg1 = numTr.resultReg, reg2 = dividentTr.resultReg), null)
addInstr(result, IRInstructions.divmodRegister(divmodrOpcode, type, numTr.resultReg, dividentTr.resultReg), null)
divisionReg = numTr.resultReg
}
// DIVMOD result convention: quotient in reg1, remainder in reg2.
@@ -261,13 +263,13 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
// m68k: first return value in D0 (slot 10), second in D1 (slot 11)
if(type==IRDataType.BYTE) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1=remainderReg, immediate=11)
it += IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1=divisionReg, immediate=10)
it += IRInstructions.hardwareStore(IRDataType.BYTE, remainderReg, CallingConventionSlot(11))
it += IRInstructions.hardwareStore(IRDataType.BYTE, divisionReg, CallingConventionSlot(10))
}
} else if(type==IRDataType.WORD) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREHR, IRDataType.WORD, reg1=remainderReg, immediate=11)
it += IRInstruction(Opcode.STOREHR, IRDataType.WORD, reg1=divisionReg, immediate=10)
it += IRInstructions.hardwareStore(IRDataType.WORD, remainderReg, CallingConventionSlot(11))
it += IRInstructions.hardwareStore(IRDataType.WORD, divisionReg, CallingConventionSlot(10))
}
} else throw AssemblyError("invalid type for DIVMOD")
} else {
@@ -275,13 +277,13 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
// Note: STOREM must come before STOREHR, otherwise STOREM clobbers A before LOADHR reads it.
if(type==IRDataType.BYTE) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=remainderReg, labelSymbol = "cx16.r15")
it += IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1=divisionReg, immediate=0)
it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, remainderReg, IRMemory.direct("cx16.r15"))
it += IRInstructions.hardwareStore(IRDataType.BYTE, divisionReg, CallingConventionSlot(0))
}
} else if(type==IRDataType.WORD) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=remainderReg, labelSymbol = "cx16.r15")
it += IRInstruction(Opcode.STOREHR, IRDataType.WORD, reg1=divisionReg, immediate=4)
it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.WORD, remainderReg, IRMemory.direct("cx16.r15"))
it += IRInstructions.hardwareStore(IRDataType.WORD, divisionReg, CallingConventionSlot(4))
}
} else throw AssemblyError("invalid type for DIVMOD")
}
@@ -308,7 +310,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, rightTr, rightTr.resultReg, -1)
val dt = codeGen.irType(call.args[0].type)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMP, dt, reg1=leftTr.resultReg, reg2=rightTr.resultReg)
it += IRInstructions.compare(dt, leftTr.resultReg, rightTr.resultReg)
}
return ExpressionCodeResult(result, dt, leftTr.resultReg, -1)
}
@@ -327,11 +329,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val notNegativeLabel = codeGen.createLabelName()
val compareReg = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=compareReg, reg2=tr.resultReg)
it += IRInstructions.move(IRDataType.BYTE, compareReg, tr.resultReg)
if(needsExplicitCmpi)
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=compareReg, immediate = 0)
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=tr.resultReg)
it += IRInstructions.compareImmediate(IRDataType.BYTE, compareReg, 0)
it += IRInstructions.branch(Opcode.BSTPOS, codeLabel(notNegativeLabel))
it += IRInstructions.unary(Opcode.NEG, IRDataType.BYTE, tr.resultReg)
}
result += IRCodeChunk(notNegativeLabel, null)
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
@@ -340,11 +342,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val notNegativeLabel = codeGen.createLabelName()
val compareReg = codeGen.registers.next(IRDataType.WORD)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADR, IRDataType.WORD, reg1=compareReg, reg2=tr.resultReg)
it += IRInstructions.move(IRDataType.WORD, compareReg, tr.resultReg)
if(needsExplicitCmpi)
it += IRInstruction(Opcode.CMPI, IRDataType.WORD, reg1=compareReg, immediate = 0)
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.NEG, IRDataType.WORD, reg1=tr.resultReg)
it += IRInstructions.compareImmediate(IRDataType.WORD, compareReg, 0)
it += IRInstructions.branch(Opcode.BSTPOS, codeLabel(notNegativeLabel))
it += IRInstructions.unary(Opcode.NEG, IRDataType.WORD, tr.resultReg)
}
result += IRCodeChunk(notNegativeLabel, null)
return ExpressionCodeResult(result, IRDataType.WORD, tr.resultReg, -1)
@@ -353,18 +355,18 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val notNegativeLabel = codeGen.createLabelName()
val compareReg = codeGen.registers.next(IRDataType.LONG)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADR, IRDataType.LONG, reg1=compareReg, reg2=tr.resultReg)
it += IRInstructions.move(IRDataType.LONG, compareReg, tr.resultReg)
if(needsExplicitCmpi)
it += IRInstruction(Opcode.CMPI, IRDataType.LONG, reg1=compareReg, immediate = 0)
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.NEG, IRDataType.LONG, reg1=tr.resultReg)
it += IRInstructions.compareImmediate(IRDataType.LONG, compareReg, 0)
it += IRInstructions.branch(Opcode.BSTPOS, codeLabel(notNegativeLabel))
it += IRInstructions.unary(Opcode.NEG, IRDataType.LONG, tr.resultReg)
}
result += IRCodeChunk(notNegativeLabel, null)
return ExpressionCodeResult(result, IRDataType.LONG, tr.resultReg, -1)
}
BaseDataType.FLOAT -> {
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.FABS, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpReg), fpReg2 = RegisterNum(tr.resultFpReg)), null)
addInstr(result, IRInstructions.binary(Opcode.FABS, IRDataType.FLOAT, resultFpReg, tr.resultFpReg), null)
return ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
}
BaseDataType.POINTER -> {
@@ -383,12 +385,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
if(tr.dt==IRDataType.FLOAT) {
addToResult(result, tr, -1, tr.resultFpReg)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SGN, tr.dt, reg1 = resultReg, fpReg1 = RegisterNum(tr.resultFpReg))
it += IRInstructions.binary(Opcode.SGN, tr.dt, resultReg, tr.resultFpReg)
}
} else {
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SGN, tr.dt, reg1 = resultReg, reg2 = tr.resultReg)
it += IRInstructions.binary(Opcode.SGN, tr.dt, resultReg, tr.resultReg)
}
}
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
@@ -403,7 +405,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SQRT, IRDataType.BYTE, reg1=resultReg, reg2=tr.resultReg)
it += IRInstructions.binary(Opcode.SQRT, IRDataType.BYTE, resultReg, tr.resultReg)
}
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
@@ -411,7 +413,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.BYTE) // sqrt of a word still produces just a byte result
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SQRT, IRDataType.WORD, reg1=resultReg, reg2=tr.resultReg)
it += IRInstructions.binary(Opcode.SQRT, IRDataType.WORD, resultReg, tr.resultReg)
}
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
@@ -419,7 +421,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.WORD) // sqrt of a long still produces just a word result
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SQRT, IRDataType.LONG, reg1=resultReg, reg2=tr.resultReg)
it += IRInstructions.binary(Opcode.SQRT, IRDataType.LONG, resultReg, tr.resultReg)
}
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
@@ -427,7 +429,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, tr, -1, tr.resultFpReg)
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SQRT, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpReg), fpReg2 = RegisterNum(tr.resultFpReg))
it += IRInstructions.binary(Opcode.SQRT, IRDataType.FLOAT, resultFpReg, tr.resultFpReg)
}
return ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
}
@@ -442,13 +444,13 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
// msb is 0, use EXT
val lsbTr = exprGen.translateExpression(call.args[1])
addToResult(result, lsbTr, lsbTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=resultReg, reg2 = lsbTr.resultReg), null)
addInstr(result, IRInstructions.binary(Opcode.EXT, IRDataType.BYTE, resultReg, lsbTr.resultReg), null)
} else {
val msbTr = exprGen.translateExpression(call.args[0])
addToResult(result, msbTr, msbTr.resultReg, -1)
val lsbTr = exprGen.translateExpression(call.args[1])
addToResult(result, lsbTr, lsbTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=resultReg, reg2 = msbTr.resultReg, reg3 = lsbTr.resultReg), null)
addInstr(result, IRInstructions.concat(IRDataType.BYTE, resultReg, msbTr.resultReg, lsbTr.resultReg), null)
}
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
@@ -462,13 +464,13 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
// msw is 0, use EXT
val lswTr = exprGen.translateExpression(call.args[1])
addToResult(result, lswTr, lswTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.WORD, reg1=resultReg, reg2 = lswTr.resultReg), null)
addInstr(result, IRInstructions.binary(Opcode.EXT, IRDataType.WORD, resultReg, lswTr.resultReg), null)
} else {
val mswTr = exprGen.translateExpression(call.args[0])
addToResult(result, mswTr, mswTr.resultReg, -1)
val lswTr = exprGen.translateExpression(call.args[1])
addToResult(result, lswTr, lswTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.WORD, reg1=resultReg, reg2 = mswTr.resultReg, reg3 = lswTr.resultReg), null)
addInstr(result, IRInstructions.concat(IRDataType.WORD, resultReg, mswTr.resultReg, lswTr.resultReg), null)
}
} else {
// mklong(msb, b3, b2, lsb)
@@ -476,7 +478,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
// use EXTL.b: zero-extend byte directly to long
val lsbTr = exprGen.translateExpression(call.args[3])
addToResult(result, lsbTr, lsbTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.EXTL, IRDataType.BYTE, reg1=resultReg, reg2 = lsbTr.resultReg), null)
addInstr(result, IRInstructions.binary(Opcode.EXTL, IRDataType.BYTE, resultReg, lsbTr.resultReg), null)
} else {
val msbTr = exprGen.translateExpression(call.args[0])
val b2Tr = exprGen.translateExpression(call.args[1])
@@ -488,9 +490,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, lsbTr, lsbTr.resultReg, -1)
val lswReg = codeGen.registers.next(IRDataType.WORD)
val mswReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=mswReg, reg2 = msbTr.resultReg, reg3 = b2Tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=lswReg, reg2 = b1Tr.resultReg, reg3 = lsbTr.resultReg), null)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.WORD, reg1=resultReg, reg2 = mswReg, reg3 = lswReg), null)
addInstr(result, IRInstructions.concat(IRDataType.BYTE, mswReg, msbTr.resultReg, b2Tr.resultReg), null)
addInstr(result, IRInstructions.concat(IRDataType.BYTE, lswReg, b1Tr.resultReg, lsbTr.resultReg), null)
addInstr(result, IRInstructions.concat(IRDataType.WORD, resultReg, mswReg, lswReg), null)
}
}
@@ -515,13 +517,14 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
BaseDataType.LONG -> IMSyscall.CLAMP_LONG
else -> throw AssemblyError("invalid dt")
}
val resultReg = codeGen.registers.next(type)
result += codeGen.makeSyscall(syscall, listOf(
valueTr.dt to valueTr.resultReg,
minimumTr.dt to minimumTr.resultReg,
maximumTr.dt to maximumTr.resultReg,
), type to valueTr.resultReg
), type to resultReg
)
return ExpressionCodeResult(result, type, valueTr.resultReg, -1)
return ExpressionCodeResult(result, type, resultReg, -1)
}
private fun funcMin(call: PtFunctionCall): ExpressionCodeResult {
@@ -534,10 +537,10 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val comparisonOpcode = if(call.type.isSigned) Opcode.BGTSR else Opcode.BGTR
val after = codeGen.createLabelName()
result += IRCodeChunk(null, null).also {
it += IRInstruction(comparisonOpcode, type, reg1 = rightTr.resultReg, reg2 = leftTr.resultReg, labelSymbol = after)
it += IRInstructions.branchRegister(comparisonOpcode, type, rightTr.resultReg, leftTr.resultReg, codeLabel(after))
// right <= left, take right
it += IRInstruction(Opcode.LOADR, type, reg1=leftTr.resultReg, reg2=rightTr.resultReg)
it += IRInstruction(Opcode.JUMP, labelSymbol = after)
it += IRInstructions.move(type, leftTr.resultReg, rightTr.resultReg)
it += IRInstructions.jump(codeLabel(after))
}
result += IRCodeChunk(after, null)
return ExpressionCodeResult(result, type, leftTr.resultReg, -1)
@@ -553,10 +556,10 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val comparisonOpcode = if(call.type.isSigned) Opcode.BGTSR else Opcode.BGTR
val after = codeGen.createLabelName()
result += IRCodeChunk(null, null).also {
it += IRInstruction(comparisonOpcode, type, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg, labelSymbol = after)
it += IRInstructions.branchRegister(comparisonOpcode, type, leftTr.resultReg, rightTr.resultReg, codeLabel(after))
// right >= left, take right
it += IRInstruction(Opcode.LOADR, type, reg1=leftTr.resultReg, reg2=rightTr.resultReg)
it += IRInstruction(Opcode.JUMP, labelSymbol = after)
it += IRInstructions.move(type, leftTr.resultReg, rightTr.resultReg)
it += IRInstructions.jump(codeLabel(after))
}
result += IRCodeChunk(after, null)
return ExpressionCodeResult(result, type, leftTr.resultReg, -1)
@@ -570,18 +573,18 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toUInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZM, dt, address = address.toAddress())
it += IRInstructions.storeZero(Opcode.STOREZM, dt, IRMemory.direct(address.toAddress()))
}
} else {
val (address, offset) = exprGen.getAddressAndOffset(call.args[0])
if(address!=null) {
val tr = exprGen.translateExpression(address)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.STOREZI, dt, reg1 = tr.resultReg, immediate = offset!!), null)
addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, dt, IRMemory.indirect(tr.resultReg, offset!!)), null)
} else {
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.STOREZI, dt, reg1 = tr.resultReg, immediate = 0), null)
addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, dt, IRMemory.indirect(tr.resultReg)), null)
}
}
} else {
@@ -590,9 +593,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val address = (call.args[0] as PtNumber).number.toUInt()
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
if(dt==IRDataType.FLOAT) {
addInstr(result, IRInstruction(Opcode.STOREM, dt, fpReg1 = RegisterNum(valueTr.resultFpReg), address = address.toAddress()), null)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, dt, valueTr.resultFpReg, IRMemory.direct(address.toAddress())), null)
} else {
addInstr(result, IRInstruction(Opcode.STOREM, dt, reg1 = valueTr.resultReg, address = address.toAddress()), null)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, dt, valueTr.resultReg, IRMemory.direct(address.toAddress())), null)
}
} else {
val (address, offset) = exprGen.getAddressAndOffset(call.args[0])
@@ -601,18 +604,18 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
addToResult(result, addressTr, addressTr.resultReg, -1)
if(dt==IRDataType.FLOAT) {
addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.FLOAT, reg1 = addressTr.resultReg, fpReg1 = RegisterNum(valueTr.resultFpReg), immediate = offset!!), null)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, IRDataType.FLOAT, valueTr.resultFpReg, IRMemory.indirect(addressTr.resultReg, offset!!)), null)
} else {
addInstr(result, IRInstruction(Opcode.STOREI, dt, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg, immediate = offset!!), null)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, dt, valueTr.resultReg, IRMemory.indirect(addressTr.resultReg, offset!!)), null)
}
} else {
val addressTr = exprGen.translateExpression(call.args[0])
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
addToResult(result, addressTr, addressTr.resultReg, -1)
if(dt==IRDataType.FLOAT) {
addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.FLOAT, reg1 = addressTr.resultReg, fpReg1 = RegisterNum(valueTr.resultFpReg), immediate = 0), null)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, IRDataType.FLOAT, valueTr.resultFpReg, IRMemory.indirect(addressTr.resultReg)), null)
} else {
addInstr(result, IRInstruction(Opcode.STOREI, dt, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg, immediate = 0), null)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, dt, valueTr.resultReg, IRMemory.indirect(addressTr.resultReg)), null)
}
}
}
@@ -626,26 +629,26 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
if(call.args[0] is PtNumber) {
val resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
val address = (call.args[0] as PtNumber).number.toUInt()
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpRegister), address = address.toAddress()), null)
addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, IRDataType.FLOAT, resultFpRegister, IRMemory.direct(address.toAddress())), null)
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpRegister)
} else {
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, reg1 = tr.resultReg, fpReg1 = RegisterNum(resultFpReg), immediate = 0), null)
addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, IRDataType.FLOAT, resultFpReg, IRMemory.indirect(tr.resultReg)), null)
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
}
} else {
if (call.args[0] is PtNumber) {
val resultRegister = codeGen.registers.next(dt)
val address = (call.args[0] as PtNumber).number.toUInt()
addInstr(result, IRInstruction(Opcode.LOADM, dt, reg1 = resultRegister, address = address.toAddress()), null)
addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, dt, resultRegister, IRMemory.direct(address.toAddress())), null)
ExpressionCodeResult(result, dt, resultRegister, -1)
} else {
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(dt)
addInstr(result, IRInstruction(Opcode.LOADI, dt, reg1 = resultReg, reg2 = tr.resultReg, immediate = 0), null)
addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, dt, resultReg, IRMemory.indirect(tr.resultReg)), null)
ExpressionCodeResult(result, dt, resultReg, -1)
}
}
@@ -662,11 +665,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val addressNum = address.number.toUInt()
val resultRegister = codeGen.registers.next(IRDataType.BYTE)
if (newValueTr != null) addToResult(result, newValueTr, newValueTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, address = addressNum.toAddress()), null)
addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, resultRegister, IRMemory.direct(addressNum.toAddress())), null)
if (newValueTr == null) {
addInstr(result, IRInstruction(Opcode.STOREZM, IRDataType.BYTE, address = addressNum.toAddress()), null)
addInstr(result, IRInstructions.storeZero(Opcode.STOREZM, IRDataType.BYTE, IRMemory.direct(addressNum.toAddress())), null)
} else {
addInstr(result, IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newValueTr.resultReg, address = addressNum.toAddress()), null)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, newValueTr.resultReg, IRMemory.direct(addressNum.toAddress())), null)
}
ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
} else {
@@ -679,11 +682,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
if (newValueTr != null) addToResult(result, newValueTr, newValueTr.resultReg, -1)
addToResult(result, addressTr, addressTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = addressTr.resultReg, immediate = finalOffset), null)
addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, resultReg, IRMemory.indirect(addressTr.resultReg, finalOffset)), null)
if (newValueTr == null) {
addInstr(result, IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = addressTr.resultReg, immediate = finalOffset), null)
addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, IRDataType.BYTE, IRMemory.indirect(addressTr.resultReg, finalOffset)), null)
} else {
addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = newValueTr.resultReg, reg2 = addressTr.resultReg, immediate = finalOffset), null)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, newValueTr.resultReg, IRMemory.indirect(addressTr.resultReg, finalOffset)), null)
}
ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
@@ -694,7 +697,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val code = IRCodeChunk(null, null)
val resultReg = codeGen.registers.next(IRDataType.POINTER)
val labelname = SymbolTable.labelnameForStructInstance(call)
code += IRInstruction(Opcode.LOAD, IRDataType.POINTER, reg1=resultReg, labelSymbol = "${StStructInstanceBlockName}.$labelname")
code += IRInstructions.loadAddress(IRDataType.POINTER, resultReg, "${StStructInstanceBlockName}.$labelname")
return ExpressionCodeResult(code, IRDataType.POINTER, resultReg, -1)
}
@@ -704,9 +707,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.BYTE)
if(fromLong)
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1 = resultReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, resultReg, tr.resultReg), null)
else
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = resultReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, resultReg, tr.resultReg), null)
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
@@ -720,20 +723,20 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
if(codeGen.options.compTarget.cpu.usesM68kConvention) {
// m68k: 3 return values in D0 (slot 10), D1 (slot 11), D2 (slot 12)
// store high byte first, then mid, then low (to avoid clobbering)
addInstr(result, IRInstruction(Opcode.BSIGB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1 = byteReg, immediate = 12), null)
addInstr(result, IRInstruction(Opcode.MIDB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1 = byteReg, immediate = 11), null)
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1 = byteReg, immediate = 10), null)
addInstr(result, IRInstructions.binary(Opcode.BSIGB, IRDataType.LONG, byteReg, tr.resultReg), null)
addInstr(result, IRInstructions.hardwareStore(IRDataType.BYTE, byteReg, CallingConventionSlot(12)), null)
addInstr(result, IRInstructions.binary(Opcode.MIDB, IRDataType.LONG, byteReg, tr.resultReg), null)
addInstr(result, IRInstructions.hardwareStore(IRDataType.BYTE, byteReg, CallingConventionSlot(11)), null)
addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, byteReg, tr.resultReg), null)
addInstr(result, IRInstructions.hardwareStore(IRDataType.BYTE, byteReg, CallingConventionSlot(10)), null)
} else {
// 6502/cx16: low byte in A (slot 0), mid in cx16.r15, high (bank) in cx16.r14
addInstr(result, IRInstruction(Opcode.BSIGB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = byteReg, labelSymbol = "cx16.r14"), null)
addInstr(result, IRInstruction(Opcode.MIDB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = byteReg, labelSymbol = "cx16.r15"), null)
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1 = byteReg, immediate = 0), null)
addInstr(result, IRInstructions.binary(Opcode.BSIGB, IRDataType.LONG, byteReg, tr.resultReg), null)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, byteReg, IRMemory.direct("cx16.r14")), null)
addInstr(result, IRInstructions.binary(Opcode.MIDB, IRDataType.LONG, byteReg, tr.resultReg), null)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, byteReg, IRMemory.direct("cx16.r15")), null)
addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, byteReg, tr.resultReg), null)
addInstr(result, IRInstructions.hardwareStore(IRDataType.BYTE, byteReg, CallingConventionSlot(0)), null)
}
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
@@ -743,7 +746,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.LSIGW, IRDataType.LONG, reg1 = resultReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstructions.binary(Opcode.LSIGW, IRDataType.LONG, resultReg, tr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
@@ -753,9 +756,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.BYTE)
if(fromLong)
addInstr(result, IRInstruction(Opcode.MSIGB, IRDataType.LONG, reg1 = resultReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstructions.binary(Opcode.MSIGB, IRDataType.LONG, resultReg, tr.resultReg), null)
else
addInstr(result, IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = resultReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, resultReg, tr.resultReg), null)
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
@@ -765,7 +768,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.MSIGW, IRDataType.LONG, reg1 = resultReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstructions.binary(Opcode.MSIGW, IRDataType.LONG, resultReg, tr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
@@ -783,13 +786,13 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val ident = arg as? PtIdentifier
if(ident!=null) {
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, labelSymbol = ident.name), null)
addInstr(result, IRInstructions.memoryOp(opcodeMemAndReg.first, vmDt, IRMemory.direct(ident.name)), null)
return ExpressionCodeResult(result, vmDt, -1, -1)
}
val memAddr: UInt? = (arg as? PtMemoryByte)?.address?.asConstInteger()?.toUInt()
if(memAddr!=null) {
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, address = memAddr.toAddress()), null)
addInstr(result, IRInstructions.memoryOp(opcodeMemAndReg.first, vmDt, IRMemory.direct(memAddr.toAddress())), null)
return ExpressionCodeResult(result, vmDt, -1, -1)
}
@@ -801,19 +804,19 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
result += IRCodeChunk(null, null).also {
when(opcodeMemAndReg.first) {
Opcode.ROXRM, Opcode.RORM -> {
it += IRInstruction(opcodeMemAndReg.first, IRDataType.BYTE, labelSymbol = "${variable}_msb", symbolOffset = index)
it += IRInstruction(opcodeMemAndReg.first, IRDataType.BYTE, labelSymbol = "${variable}_lsb", symbolOffset = index)
it += IRInstructions.memoryOp(opcodeMemAndReg.first, IRDataType.BYTE, IRMemory.direct("${variable}_msb", index))
it += IRInstructions.memoryOp(opcodeMemAndReg.first, IRDataType.BYTE, IRMemory.direct("${variable}_lsb", index))
}
Opcode.ROXLM, Opcode.ROLM -> {
it += IRInstruction(opcodeMemAndReg.first, IRDataType.BYTE, labelSymbol = "${variable}_lsb", symbolOffset = index)
it += IRInstruction(opcodeMemAndReg.first, IRDataType.BYTE, labelSymbol = "${variable}_msb", symbolOffset = index)
it += IRInstructions.memoryOp(opcodeMemAndReg.first, IRDataType.BYTE, IRMemory.direct("${variable}_lsb", index))
it += IRInstructions.memoryOp(opcodeMemAndReg.first, IRDataType.BYTE, IRMemory.direct("${variable}_msb", index))
}
else -> throw AssemblyError("wrong rol/ror opcode")
}
}
} else {
val offset = codeGen.program.memsizer.memorySize(arr.type, index)
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, labelSymbol = variable, symbolOffset = offset), null)
addInstr(result, IRInstructions.memoryOp(opcodeMemAndReg.first, vmDt, IRMemory.direct(variable, offset)), null)
}
return ExpressionCodeResult(result, vmDt, -1, -1)
}
@@ -821,17 +824,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val opcode = opcodeMemAndReg.second
val saveCarry = opcode in OpcodesThatDependOnCarry && !arg.isSimple()
if(saveCarry)
addInstr(result, IRInstruction(Opcode.PUSHST), null) // save Carry
addInstr(result, IRInstructions.simple(Opcode.PUSHST), null) // save Carry
val tr = exprGen.translateExpression(arg)
addToResult(result, tr, tr.resultReg, -1)
if(saveCarry)
addInstr(result, IRInstruction(Opcode.POPST), null)
addInstr(result, IRInstruction(opcode, vmDt, reg1 = tr.resultReg), null)
addInstr(result, IRInstructions.simple(Opcode.POPST), null)
addInstr(result, IRInstructions.unary(opcode, vmDt, tr.resultReg), null)
if(saveCarry)
addInstr(result, IRInstruction(Opcode.PUSHST), null) // save Carry
addInstr(result, IRInstructions.simple(Opcode.PUSHST), null) // save Carry
result += codeGen.assignRegisterTo(arg, tr.resultReg)
if(saveCarry)
addInstr(result, IRInstruction(Opcode.POPST), null)
addInstr(result, IRInstructions.simple(Opcode.POPST), null)
return ExpressionCodeResult(result, vmDt, -1, -1)
}
@@ -847,16 +850,16 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
if(isConstZeroValue) {
result += IRCodeChunk(null, null).also {
val pointerReg = codeGen.registers.next(IRDataType.POINTER)
it += IRInstruction(Opcode.LOAD, IRDataType.POINTER, reg1 = pointerReg, labelSymbol = target.name)
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = pointerReg, immediate = offset)
it += IRInstructions.loadAddress(IRDataType.POINTER, pointerReg, target.name)
it += IRInstructions.storeZero(Opcode.STOREZI, IRDataType.BYTE, IRMemory.indirect(pointerReg, offset))
}
} else {
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
val pointerReg = codeGen.registers.next(IRDataType.POINTER)
it += IRInstruction(Opcode.LOAD, IRDataType.POINTER, reg1 = pointerReg, labelSymbol = target.name)
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = pointerReg, immediate = offset)
it += IRInstructions.loadAddress(IRDataType.POINTER, pointerReg, target.name)
it += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, valueTr.resultReg, IRMemory.indirect(pointerReg, offset))
}
}
}
@@ -871,22 +874,22 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
if(constIndex != null) {
val offset = eltSize * constIndex + elementByteOffset
if(offset > 0)
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.POINTER, reg1 = pointerTr.resultReg, immediate = offset), null)
addInstr(result, IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.POINTER, pointerTr.resultReg, offset), null)
} else {
val (code, indexWordReg) = codeGen.loadIndexReg(target.index, eltSize, true, false)
result += code
if(eltSize!=1)
result += codeGen.multiplyByConst(DataType.UWORD, indexWordReg, eltSize)
addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.POINTER, reg1 = pointerTr.resultReg, reg2 = indexWordReg), null)
addInstr(result, IRInstructions.binary(Opcode.ADDR, IRDataType.POINTER, pointerTr.resultReg, indexWordReg), null)
if(elementByteOffset > 0)
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.POINTER, reg1 = pointerTr.resultReg, immediate = elementByteOffset), null)
addInstr(result, IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.POINTER, pointerTr.resultReg, elementByteOffset), null)
}
if(isConstZeroValue) {
addInstr(result, IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = pointerTr.resultReg, immediate = 0), null)
addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, IRDataType.BYTE, IRMemory.indirect(pointerTr.resultReg)), null)
} else {
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = pointerTr.resultReg, immediate = 0), null)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, valueTr.resultReg, IRMemory.indirect(pointerTr.resultReg)), null)
}
}
else if(target.splitWords) {
@@ -898,15 +901,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val idxType = codeGen.options.compTarget.indexRegType
val offsetReg = codeGen.registers.next(idxType)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, idxType, reg1=offsetReg, immediate = constIndex)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = varName)
it += IRInstructions.load(idxType, offsetReg, constIndex)
it += IRInstructions.storeZero(Opcode.STOREZX, IRDataType.BYTE, IRMemory.indexed(varName, offsetReg, codeGen.options.compTarget.indexRegType))
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
val indexReg = codeGen.canonicalizeIndexReg(result, indexTr)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexReg, labelSymbol = varName)
it += IRInstructions.storeZero(Opcode.STOREZX, IRDataType.BYTE, IRMemory.indexed(varName, indexReg, codeGen.options.compTarget.indexRegType))
}
}
} else {
@@ -916,15 +919,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val idxType = codeGen.options.compTarget.indexRegType
val offsetReg = codeGen.registers.next(idxType)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, idxType, reg1=offsetReg, immediate = constIndex)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = varName)
it += IRInstructions.load(idxType, offsetReg, constIndex)
it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, valueTr.resultReg, IRMemory.indexed(varName, offsetReg, codeGen.options.compTarget.indexRegType))
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
val indexReg = codeGen.canonicalizeIndexReg(result, indexTr)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexReg, labelSymbol = varName)
it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, valueTr.resultReg, IRMemory.indexed(varName, indexReg, codeGen.options.compTarget.indexRegType))
}
}
}
@@ -939,15 +942,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val offsetReg = codeGen.registers.next(if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE)
val offset = eltSize*constIndex + byteOffset(eltSize)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE, reg1=offsetReg, immediate = offset)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = targetVariable.name)
it += IRInstructions.load(if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE, offsetReg, offset)
it += IRInstructions.storeZero(Opcode.STOREZX, IRDataType.BYTE, IRMemory.indexed(targetVariable.name, offsetReg, codeGen.options.compTarget.indexRegType))
}
} else {
val (code, indexReg) = codeGen.loadIndexReg(target.index, eltSize, codeGen.wordArrayIndex, false)
result += code
result += IRCodeChunk(null, null).also {
val offset = byteOffset(eltSize)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexReg, labelSymbol = targetVariable.name, symbolOffset = if(offset!=0) offset else null, scale = eltSize)
it += IRInstructions.storeZero(Opcode.STOREZX, IRDataType.BYTE, IRMemory.indexed(targetVariable.name, indexReg, codeGen.options.compTarget.indexRegType, scale = eltSize, displacement = offset))
}
}
} else {
@@ -957,15 +960,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val offsetReg = codeGen.registers.next(if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE)
val offset = eltSize*constIndex + byteOffset(eltSize)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE, reg1=offsetReg, immediate = offset)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = targetVariable.name)
it += IRInstructions.load(if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE, offsetReg, offset)
it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, valueTr.resultReg, IRMemory.indexed(targetVariable.name, offsetReg, codeGen.options.compTarget.indexRegType))
}
} else {
val (code, indexReg) = codeGen.loadIndexReg(target.index, eltSize, codeGen.wordArrayIndex, false)
result += code
result += IRCodeChunk(null, null).also {
val offset = byteOffset(eltSize)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexReg, labelSymbol = targetVariable.name, symbolOffset = if(offset!=0) offset else null, scale = eltSize)
it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, valueTr.resultReg, IRMemory.indexed(targetVariable.name, indexReg, codeGen.options.compTarget.indexRegType, scale = eltSize, displacement = offset))
}
}
}
@@ -986,10 +989,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
result += idxCode
return if(isZero) {
result += IRCodeChunk(null, null).also {
if(info.fieldOffset==0)
it += IRInstruction(Opcode.STOREZX, dt, reg1=indexReg, labelSymbol=info.arrayName, scale=info.structSize)
else
it += IRInstruction(Opcode.STOREZX, dt, reg1=indexReg, labelSymbol=info.arrayName, symbolOffset=info.fieldOffset, scale=info.structSize)
it += IRInstructions.storeZero(Opcode.STOREZX, dt, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset))
}
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
} else {
@@ -997,15 +997,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
result += IRCodeChunk(null, null).also {
if(dt==IRDataType.FLOAT) {
if(info.fieldOffset==0)
it += IRInstruction(Opcode.STOREX, dt, reg1=indexReg, fpReg1=RegisterNum(valueTr.resultFpReg), labelSymbol=info.arrayName, scale=info.structSize)
else
it += IRInstruction(Opcode.STOREX, dt, reg1=indexReg, fpReg1=RegisterNum(valueTr.resultFpReg), labelSymbol=info.arrayName, symbolOffset=info.fieldOffset, scale=info.structSize)
it += IRInstructions.storeMemory(Opcode.STOREX, dt, valueTr.resultFpReg, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset))
} else {
if(info.fieldOffset==0)
it += IRInstruction(Opcode.STOREX, dt, reg1=valueTr.resultReg, reg2=indexReg, labelSymbol=info.arrayName, scale=info.structSize)
else
it += IRInstruction(Opcode.STOREX, dt, reg1=valueTr.resultReg, reg2=indexReg, labelSymbol=info.arrayName, symbolOffset=info.fieldOffset, scale=info.structSize)
it += IRInstructions.storeMemory(Opcode.STOREX, dt, valueTr.resultReg, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset))
}
}
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,36 @@
package prog8.codegen.intermediate
import prog8.intermediate.*
/*
Construction helpers for the few IR opcodes that have no dedicated IRInstructions factory,
plus a small operand-inspection helper used by the optimizers.
The structured IRInstructions factory covers almost every opcode, but three opcodes have an
operand shape that no named factory produces (a lone source register, or a source register plus
an immediate without a destination). They are built here from schema-derived operands, so this
module never constructs IRInstruction directly.
*/
/** STOREHFACZERO / STOREHFACONE: a single source float register (no dedicated factory exists). */
internal fun storeHwFac(opcode: Opcode, fpRegister: Int): IRInstruction =
IRInstructions.push(IRDataType.FLOAT, fpRegister).copy(opcode = opcode)
/** BITTST: a source register plus a byte immediate bit number, with no destination register. */
internal fun bitTest(type: IRDataType, register: Int, bit: Int): IRInstruction =
IRInstructions.simple(Opcode.NOP).copy(
opcode = Opcode.BITTST,
type = type,
srcA = IRInstructions.operandFor(Opcode.BITTST, type, InstructionSlot.SRC_A, register),
immediate = IRInstructions.immediateFor(Opcode.BITTST, type, bit)
)
/**
* The name of the single symbol this instruction refers to, if any.
* That is either the symbol a memory reference is based on, the symbol whose address is taken
* in an immediate operand, or the label a branch/jump/call transfers control to.
*/
internal fun IRInstruction.referencedSymbol(): String? =
memory?.symbolName
?: (immediate as? ImmediateOperand.SymbolAddress)?.symbol
?: labelTarget
File diff suppressed because it is too large. Load diff
@@ -37,7 +37,7 @@ class IRUnusedCodeRemover(
blockVars.forEach { stVar ->
irprog.allSubs().flatMap { it.chunks }.forEach { chunk ->
chunk.instructions.forEach { ins ->
if(ins.labelSymbol == stVar.name) {
if(ins.referencedSymbol() == stVar.name) {
return // symbol occurs in an instruction
}
}
@@ -74,42 +74,26 @@ class IRUnusedCodeRemover(
private fun removeBlockInits(code: IRProgram, blockLabel: String) {
val instructions = code.globalInits.instructions
instructions.toTypedArray().forEach {ins ->
if(ins.labelSymbol?.startsWith(blockLabel)==true) {
if(ins.referencedSymbol()?.startsWith(blockLabel)==true) {
instructions.remove(ins)
}
}
// remove stray loads
val indexRegType = irprog.options.compTarget.indexRegType
val readRegs = mutableSetOf<Int>()
val readFpRegs = mutableSetOf<Int>()
val readRegs = mutableSetOf<VirtualRegister>()
instructions.forEach { ins ->
val readRegsCounts = mutableMapOf<RegisterNum, Int>()
val readFpRegsCounts = mutableMapOf<RegisterNum, Int>()
val writeRegsCounts = mutableMapOf<RegisterNum, Int>()
val writeFpRegsCounts = mutableMapOf<RegisterNum, Int>()
val regsTypes = mutableMapOf<RegisterNum, IRDataType>()
ins.addUsedRegistersCounts(readRegsCounts, writeRegsCounts, readFpRegsCounts, writeFpRegsCounts, regsTypes, null, indexRegType)
readRegs.addAll(readRegsCounts.keys.map { it.value })
readFpRegs.addAll(readFpRegsCounts.keys.map { it.value })
val readRegsCounts = mutableMapOf<VirtualRegister, Int>()
val writeRegsCounts = mutableMapOf<VirtualRegister, Int>()
val regsTypes = mutableMapOf<VirtualRegister, IRDataType>()
ins.addUsedRegistersCounts(readRegsCounts, writeRegsCounts, regsTypes, null)
readRegs.addAll(readRegsCounts.keys)
}
instructions.toTypedArray().forEach { ins ->
if(ins.opcode in arrayOf(Opcode.LOAD, Opcode.LOADR, Opcode.LOADM)) {
val reg1 = ins.reg1
val fpReg1 = ins.fpReg1
if(reg1!=null) {
if(reg1 !in readRegs) {
if(ins.labelSymbol!=null)
code.st.removeIfExists(ins.labelSymbol!!)
instructions.remove(ins)
}
}
else if(fpReg1!=null) {
if(fpReg1.value !in readFpRegs) {
if(ins.labelSymbol!=null)
code.st.removeIfExists(ins.labelSymbol!!)
instructions.remove(ins)
}
val dest = ins.dest
if(dest != null && dest.register !in readRegs) {
ins.referencedSymbol()?.let { code.st.removeIfExists(it) }
instructions.remove(ins)
}
}
}
@@ -192,8 +176,8 @@ class IRUnusedCodeRemover(
// check if asmsub is linked or called from another regular subroutine
irprog.foreachCodeChunk { chunk ->
chunk.instructions.forEach {
it.labelSymbol?.let { label -> allSubs[label]?.let { cc -> linkedAsmSubs += cc } }
// note: branchTarget can't yet point to another IRAsmSubroutine, so do nothing when it's set
it.referencedSymbol()?.let { label -> allSubs[label]?.let { cc -> linkedAsmSubs += cc } }
// note: codeTarget can't yet point to another IRAsmSubroutine, so do nothing when it's set
}
}
@@ -247,16 +231,20 @@ class IRUnusedCodeRemover(
new += it.body
}
it.instructions.forEach { instr ->
if (instr.branchTarget == null)
instr.labelSymbol?.let { label ->
val codeTarget = instr.codeTarget
if (codeTarget == null) {
instr.referencedSymbol()?.let { label ->
val chunk = allLabeledChunks[label] ?: allLabeledChunks[label.substringBeforeLast('.')]
if(chunk!=null)
new+=chunk
else
allLabeledChunks[label]?.let { c -> new += c }
}
else
new += instr.branchTarget!!
}
else {
val chunk = irprog.resolveCodeTarget(codeTarget)
?: codeTarget.labelName?.let { label -> allLabeledChunks[label] }
if(chunk!=null)
new += chunk
}
}
}
reachable += new
@@ -292,10 +280,14 @@ class IRUnusedCodeRemover(
irprog.foreachCodeChunk { chunk ->
chunk.next?.let { next -> linkedChunks += next }
chunk.instructions.forEach {
if(it.branchTarget==null) {
it.labelSymbol?.let { label -> allLabeledChunks[label]?.let { cc -> linkedChunks += cc } }
val codeTarget = it.codeTarget
if(codeTarget==null) {
it.referencedSymbol()?.let { label -> allLabeledChunks[label]?.let { cc -> linkedChunks += cc } }
} else {
linkedChunks += it.branchTarget!!
val target = irprog.resolveCodeTarget(codeTarget)
?: codeTarget.labelName?.let { label -> allLabeledChunks[label] }
if(target!=null)
linkedChunks += target
}
}
if (chunk.label in entrypointNames)
@@ -305,8 +297,8 @@ class IRUnusedCodeRemover(
// make sure that chunks that are only used as a prefix of a label, are also marked as linked
linkedChunks.toList().forEach { chunk ->
chunk.instructions.forEach {
if(it.labelSymbol!=null) {
val chunkName = it.labelSymbol!!.substringBeforeLast('.')
it.referencedSymbol()?.let { symbol ->
val chunkName = symbol.substringBeforeLast('.')
allLabeledChunks[chunkName]?.let { c -> linkedChunks += c }
}
}
@@ -84,26 +84,22 @@ import prog8.intermediate.*
object RegisterPacker {
data class Interval(val register: Int, val start: Int, val end: Int, val type: IRDataType)
data class Interval(val register: VirtualRegister, val start: Int, val end: Int, val type: IRDataType)
fun pack(irProg: IRProgram) {
val indexRegType = irProg.options.compTarget.indexRegType
val allRegTypes = mutableMapOf<Int, IRDataType>()
val allRegTypes = mutableMapOf<VirtualRegister, IRDataType>()
irProg.forEachInstruction { instr ->
val (written, read) = getRegisterAccess(instr)
for (r in written + read) {
val dt = getRegisterType(instr, r, indexRegType)
allRegTypes.putIfAbsent(r, dt)
}
for (access in instr.registerAccesses)
allRegTypes.putIfAbsent(access.register, access.type)
}
val beforeCount = allRegTypes.size
// Start packing slots after the highest original register number to avoid
// collisions between packed slot numbers and original (non-packed) register numbers.
val maxReg = allRegTypes.keys.maxOrNull() ?: 0
val maxReg = allRegTypes.keys.maxOfOrNull { it.num } ?: 0
val startSlot = maxReg + 1
val globalSlotTypes = mutableMapOf<Int, IRDataType>()
irProg.foreachSub { sub -> packSubroutine(sub, allRegTypes, globalSlotTypes, startSlot, indexRegType) }
val globalSlotTypes = mutableMapOf<VirtualRegister, IRDataType>()
irProg.foreachSub { sub -> packSubroutine(irProg, sub, allRegTypes, globalSlotTypes, startSlot) }
val afterTypes = rebuildTypeMap(irProg)
val afterCount = afterTypes.size
@@ -114,31 +110,20 @@ object RegisterPacker {
// Rebuild the register type map after packing, using the same type determination as the packer.
// This avoids the strict per-instruction validation in usedRegisters() that POINTER↔LONG↔WORD
// cross-type packing can trigger.
fun rebuildTypeMap(irProg: IRProgram): Map<RegisterNum, IRDataType> {
val indexRegType = irProg.options.compTarget.indexRegType
val newTypes = mutableMapOf<RegisterNum, IRDataType>()
fun rebuildTypeMap(irProg: IRProgram): Map<VirtualRegister, IRDataType> {
val newTypes = mutableMapOf<VirtualRegister, IRDataType>()
irProg.forEachInstruction { instr ->
for ((reg, regNum) in listOf(instr.reg1 to 1, instr.reg2 to 2, instr.reg3 to 3)) {
if (reg != null)
newTypes.putIfAbsent(RegisterNum(reg), determineIntRegType(instr, regNum, indexRegType))
}
instr.fpReg1?.let { fpr -> newTypes.putIfAbsent(RegisterNum(fpr.value), IRDataType.FLOAT) }
instr.fpReg2?.let { fpr -> newTypes.putIfAbsent(RegisterNum(fpr.value), IRDataType.FLOAT) }
instr.fcallArgs?.let { fc ->
for (a in fc.arguments)
newTypes.putIfAbsent(RegisterNum(a.reg.registerNum.value), a.reg.dt)
for (r in fc.returns)
newTypes.putIfAbsent(RegisterNum(r.registerNum.value), IRDataType.BYTE)
}
for (access in instr.registerAccesses)
newTypes.putIfAbsent(access.register, access.type)
}
return newTypes
}
private fun packSubroutine(sub: IRSubroutine, allRegTypes: Map<Int, IRDataType>, globalSlotTypes: MutableMap<Int, IRDataType>, startSlot: Int, indexRegType: IRDataType) {
private fun packSubroutine(irProg: IRProgram, sub: IRSubroutine, allRegTypes: Map<VirtualRegister, IRDataType>, globalSlotTypes: MutableMap<VirtualRegister, IRDataType>, startSlot: Int) {
if (sub.chunks.isEmpty())
return
val successors = buildCFG(sub)
val successors = buildCFG(sub, irProg)
val (_, liveOut) = computeLiveness(sub, successors)
val codeChunks = sub.chunks.filterIsInstance<IRCodeChunk>()
@@ -154,8 +139,8 @@ object RegisterPacker {
globalIdx += chunk.instructions.size
}
val registerIntervals = mutableMapOf<Int, MutableList<Interval>>()
val registerTypes = mutableMapOf<Int, IRDataType>()
val registerIntervals = mutableMapOf<VirtualRegister, MutableList<Interval>>()
val registerTypes = mutableMapOf<VirtualRegister, IRDataType>()
for (chunk in codeChunks) {
val range = chunkRanges[chunk]!!
@@ -164,33 +149,37 @@ object RegisterPacker {
continue
val liveSet = liveOut[chunk]?.toMutableSet() ?: mutableSetOf()
val lastUse = mutableMapOf<Int, Int>()
val lastUse = mutableMapOf<VirtualRegister, Int>()
// Scan backward through instructions
for (i in chunk.instructions.indices.reversed()) {
val instr = chunk.instructions[i]
val globalI = chunkStart + i
val (written, read) = getRegisterAccess(instr)
val accesses = instr.registerAccesses
// Process READ first so types are set before WRITE processing
for (r in read) {
val dt = getRegisterType(instr, r, indexRegType)
registerTypes.putIfAbsent(r, dt)
if (r !in liveSet) {
lastUse[r] = globalI
liveSet.add(r)
// Process READ (USE / USE_DEF) first so types are set before WRITE processing
for (access in accesses) {
if (access.direction != OperandDirection.DEF) {
val r = access.register
registerTypes.putIfAbsent(r, access.type)
if (r !in liveSet) {
lastUse[r] = globalI
liveSet.add(r)
}
}
}
// Process WRITTEN registers: end their live range (start of interval going forward)
for (r in written) {
if (r in liveSet) {
val end = lastUse.getOrElse(r) { globalI }
val actualType = getRegisterType(instr, r, indexRegType)
registerIntervals.getOrPut(r) { mutableListOf() }
.add(Interval(r, globalI, end, actualType))
liveSet.remove(r)
lastUse.remove(r)
// Process WRITTEN (DEF / USE_DEF) registers: end their live range (start of interval going forward)
for (access in accesses) {
if (access.direction != OperandDirection.USE) {
val r = access.register
if (r in liveSet) {
val end = lastUse.getOrElse(r) { globalI }
registerIntervals.getOrPut(r) { mutableListOf() }
.add(Interval(r, globalI, end, access.type))
liveSet.remove(r)
lastUse.remove(r)
}
}
}
}
@@ -212,7 +201,7 @@ object RegisterPacker {
// gap to the next use (otherwise another register packed to the same slot
// would clobber it between intervals).
val mergedIntervals = mutableListOf<Interval>()
val skipRegs = mutableSetOf<Int>()
val skipRegs = mutableSetOf<VirtualRegister>()
for ((reg, intervals) in registerIntervals) {
val sorted = intervals.sortedBy { it.start }
var current = sorted.first()
@@ -230,43 +219,33 @@ object RegisterPacker {
mergedIntervals.removeAll { it.register in skipRegs }
}
val conflictGraph = buildConflictGraph(sub, indexRegType)
val conflictGraph = buildConflictGraph(sub)
val packing = greedyColor(mergedIntervals, conflictGraph, allRegTypes, globalSlotTypes, startSlot)
if (packing.isNotEmpty())
rewrite(sub, packing)
}
private fun buildConflictGraph(sub: IRSubroutine, indexRegType: IRDataType): Map<Int, Set<Int>> {
val conflicts = mutableMapOf<Int, MutableSet<Int>>()
private fun buildConflictGraph(sub: IRSubroutine): Map<VirtualRegister, Set<VirtualRegister>> {
val conflicts = mutableMapOf<VirtualRegister, MutableSet<VirtualRegister>>()
for (chunk in sub.chunks.filterIsInstance<IRCodeChunk>()) {
val typeRegs = mutableMapOf<IRDataType, MutableSet<Int>>()
val typeRegs = mutableMapOf<IRDataType, MutableSet<VirtualRegister>>()
for (instr in chunk.instructions) {
for ((reg, regNum) in listOf(instr.reg1 to 1, instr.reg2 to 2, instr.reg3 to 3)) {
if (reg != null) {
val dt = determineIntRegType(instr, regNum, indexRegType)
typeRegs.getOrPut(dt) { mutableSetOf() }.add(reg)
}
}
instr.fpReg1?.let { fpr -> typeRegs.getOrPut(IRDataType.FLOAT) { mutableSetOf() }.add(fpr.value) }
instr.fpReg2?.let { fpr -> typeRegs.getOrPut(IRDataType.FLOAT) { mutableSetOf() }.add(fpr.value) }
val accesses = instr.registerAccesses
for (access in accesses)
typeRegs.getOrPut(access.type) { mutableSetOf() }.add(access.register)
instr.fcallArgs?.let { fc ->
for (arg in fc.arguments) {
val r = arg.reg.registerNum.value
typeRegs.getOrPut(arg.reg.dt) { mutableSetOf() }.add(r)
// Registers accessed together within a single instruction must not share a slot
// (generalizes the old reg1/reg2-only conflict rule to every register access of
// the instruction, now that they are all enumerable via registerAccesses).
val regsInInstr = accesses.map { it.register }.distinct()
for (i in regsInInstr.indices) {
for (j in i + 1 until regsInInstr.size) {
val r1 = regsInInstr[i]
val r2 = regsInInstr[j]
conflicts.getOrPut(r1) { mutableSetOf() }.add(r2)
conflicts.getOrPut(r2) { mutableSetOf() }.add(r1)
}
for (ret in fc.returns) {
val r = ret.registerNum.value
typeRegs.getOrPut(ret.dt) { mutableSetOf() }.add(r)
}
}
val r1 = instr.reg1
val r2 = instr.reg2
if (r1 != null && r2 != null) {
conflicts.getOrPut(r1) { mutableSetOf() }.add(r2)
conflicts.getOrPut(r2) { mutableSetOf() }.add(r1)
}
}
// Add conflicts between registers with incompatible types in the same chunk
@@ -291,7 +270,7 @@ object RegisterPacker {
return conflicts
}
private fun buildCFG(sub: IRSubroutine): Map<IRCodeChunkBase, List<IRCodeChunkBase>> {
private fun buildCFG(sub: IRSubroutine, irProg: IRProgram): Map<IRCodeChunkBase, List<IRCodeChunkBase>> {
val successors = mutableMapOf<IRCodeChunkBase, MutableList<IRCodeChunkBase>>()
val conditionals = setOf(
@@ -315,13 +294,13 @@ object RegisterPacker {
chunk.next?.let { succ.add(it) }
}
if (endsWithConditional) {
lastInstr.branchTarget?.let { target ->
lastInstr.codeTarget?.let { irProg.resolveCodeTarget(it) }?.let { target ->
if (!succ.contains(target))
succ.add(target)
}
} else if (lastInstr?.opcode == Opcode.JUMP) {
succ.clear()
lastInstr.branchTarget?.let { succ.add(it) }
lastInstr.codeTarget?.let { irProg.resolveCodeTarget(it) }?.let { succ.add(it) }
}
} else {
chunk.next?.let { succ.add(it) }
@@ -336,22 +315,23 @@ object RegisterPacker {
private fun computeLiveness(
sub: IRSubroutine,
successors: Map<IRCodeChunkBase, List<IRCodeChunkBase>>
): Pair<Map<IRCodeChunk, Set<Int>>, Map<IRCodeChunk, Set<Int>>> {
): Pair<Map<IRCodeChunk, Set<VirtualRegister>>, Map<IRCodeChunk, Set<VirtualRegister>>> {
val liveIn = mutableMapOf<IRCodeChunk, MutableSet<Int>>()
val liveOut = mutableMapOf<IRCodeChunk, MutableSet<Int>>()
val gen = mutableMapOf<IRCodeChunk, MutableSet<Int>>()
val kill = mutableMapOf<IRCodeChunk, MutableSet<Int>>()
val liveIn = mutableMapOf<IRCodeChunk, MutableSet<VirtualRegister>>()
val liveOut = mutableMapOf<IRCodeChunk, MutableSet<VirtualRegister>>()
val gen = mutableMapOf<IRCodeChunk, MutableSet<VirtualRegister>>()
val kill = mutableMapOf<IRCodeChunk, MutableSet<VirtualRegister>>()
for (chunk in sub.chunks) {
if (chunk !is IRCodeChunk)
continue
val genSet = mutableSetOf<Int>()
val killSet = mutableSetOf<Int>()
val genSet = mutableSetOf<VirtualRegister>()
val killSet = mutableSetOf<VirtualRegister>()
for (instr in chunk.instructions) {
val (written, read) = getRegisterAccess(instr)
val written = instr.definitions
val read = instr.uses
for (r in read) {
if (r !in killSet)
genSet.add(r)
@@ -376,7 +356,7 @@ object RegisterPacker {
if (chunk !is IRCodeChunk)
continue
val newLiveOut = mutableSetOf<Int>()
val newLiveOut = mutableSetOf<VirtualRegister>()
for (succ in successors[chunk].orEmpty()) {
if (succ is IRCodeChunk)
newLiveOut.addAll(liveIn[succ].orEmpty())
@@ -387,7 +367,7 @@ object RegisterPacker {
changed = true
}
val newLiveIn = mutableSetOf<Int>()
val newLiveIn = mutableSetOf<VirtualRegister>()
newLiveIn.addAll(gen[chunk].orEmpty())
newLiveIn.addAll(liveOut[chunk].orEmpty() - kill[chunk].orEmpty())
@@ -401,153 +381,35 @@ object RegisterPacker {
return liveIn to liveOut
}
private fun getRegisterAccess(instr: IRInstruction): Pair<Set<Int>, Set<Int>> {
val written = mutableSetOf<Int>()
val read = mutableSetOf<Int>()
for ((reg, dir) in listOf(
instr.reg1 to instr.reg1direction,
instr.reg2 to instr.reg2direction,
instr.reg3 to instr.reg3direction
)) {
when (dir) {
OperandDirection.READ -> reg?.let { read.add(it) }
OperandDirection.WRITE -> reg?.let { written.add(it) }
OperandDirection.READWRITE -> { reg?.let { read.add(it); written.add(it) } }
OperandDirection.UNUSED -> {}
}
}
for ((fpReg, dir) in listOf(
instr.fpReg1 to instr.fpReg1direction,
instr.fpReg2 to instr.fpReg2direction
)) {
when (dir) {
OperandDirection.READ -> fpReg?.let { read.add(it.value) }
OperandDirection.WRITE -> fpReg?.let { written.add(it.value) }
OperandDirection.READWRITE -> { fpReg?.let { read.add(it.value); written.add(it.value) } }
OperandDirection.UNUSED -> {}
}
}
val fcallArgs = instr.fcallArgs
if (fcallArgs != null) {
for (arg in fcallArgs.arguments)
read.add(arg.reg.registerNum.value)
for (ret in fcallArgs.returns)
written.add(ret.registerNum.value)
}
return written to read
}
private fun getRegisterType(instr: IRInstruction, register: Int, indexRegType: IRDataType): IRDataType {
if (instr.fpReg1?.value == register || instr.fpReg2?.value == register)
return IRDataType.FLOAT
return when {
instr.reg1 == register -> determineIntRegType(instr, 1, indexRegType)
instr.reg2 == register -> determineIntRegType(instr, 2, indexRegType)
instr.reg3 == register -> determineIntRegType(instr, 3, indexRegType)
else -> {
// fcallArgs registers use their own type from the RegSpec, not instr.type
val fcallArgs = instr.fcallArgs
if (fcallArgs != null) {
for (arg in fcallArgs.arguments)
if (arg.reg.registerNum.value == register)
return arg.reg.dt
for (ret in fcallArgs.returns)
if (ret.registerNum.value == register)
return ret.dt
}
instr.type ?: IRDataType.BYTE
}
}
}
private fun determineIntRegType(instr: IRInstruction, regNum: Int, indexRegType: IRDataType): IRDataType {
val opcode = instr.opcode
val type = instr.type
if (type == IRDataType.FLOAT) {
return when (opcode) {
Opcode.FFROMUB, Opcode.FFROMSB, Opcode.FTOUB, Opcode.FTOSB,
Opcode.FCOMP, Opcode.SGN -> IRDataType.BYTE
Opcode.LOADX, Opcode.STOREX, Opcode.STOREZX -> indexRegType
Opcode.FFROMSL, Opcode.FTOSL -> IRDataType.LONG
Opcode.LOADI, Opcode.STOREI -> IRDataType.POINTER
else -> IRDataType.WORD
}
}
if (type == IRDataType.WORD && regNum == 1) {
return when (opcode) {
Opcode.SGN, Opcode.SQRT -> IRDataType.BYTE
Opcode.STOREZX -> indexRegType
Opcode.EXT, Opcode.EXTS, Opcode.CONCAT -> IRDataType.LONG
else -> type
}
}
if (type == IRDataType.LONG && regNum == 1) {
return when (opcode) {
Opcode.SGN -> IRDataType.BYTE
Opcode.SQRT -> IRDataType.WORD
Opcode.STOREZX -> indexRegType
else -> type
}
}
if (regNum == 2 || regNum == 3) {
return when (opcode) {
Opcode.LOADX, Opcode.STOREX -> indexRegType
Opcode.LOADI, Opcode.STOREI -> IRDataType.POINTER
Opcode.ASRN, Opcode.LSRN, Opcode.LSLN -> IRDataType.BYTE
else -> type ?: IRDataType.BYTE
}
}
if (regNum == 1) {
if (opcode in setOf(Opcode.JUMPI, Opcode.CALLI, Opcode.STOREZI))
return IRDataType.POINTER
if (opcode in setOf(Opcode.LSIGW, Opcode.MSIGW))
return IRDataType.WORD
if (opcode in setOf(Opcode.ASRNM, Opcode.LSRNM, Opcode.LSLNM, Opcode.SQRT, Opcode.LSIGB, Opcode.MSIGB, Opcode.BSIGB, Opcode.MIDB))
return IRDataType.BYTE
if (type == IRDataType.BYTE && opcode in setOf(Opcode.EXT, Opcode.EXTS, Opcode.CONCAT))
return IRDataType.WORD
if (type == IRDataType.BYTE && opcode == Opcode.STOREZX)
return indexRegType
}
return type ?: IRDataType.BYTE
}
private fun greedyColor(
intervals: List<Interval>,
conflictGraph: Map<Int, Set<Int>>,
allRegTypes: Map<Int, IRDataType>,
slotTypes: MutableMap<Int, IRDataType>,
conflictGraph: Map<VirtualRegister, Set<VirtualRegister>>,
allRegTypes: Map<VirtualRegister, IRDataType>,
slotTypes: MutableMap<VirtualRegister, IRDataType>,
startSlot: Int
): Map<Int, Int> {
): Map<VirtualRegister, VirtualRegister> {
if (intervals.isEmpty())
return emptyMap()
val packing = mutableMapOf<Int, Int>()
val packing = mutableMapOf<VirtualRegister, VirtualRegister>()
// Reserve slots for non-packed registers
val packedRegNums = intervals.map { it.register }.toSet()
for ((regNum, type) in allRegTypes) {
if (regNum !in packedRegNums)
slotTypes.putIfAbsent(regNum, type)
val packedRegs = intervals.map { it.register }.toSet()
for ((reg, type) in allRegTypes) {
if (reg !in packedRegs)
slotTypes.putIfAbsent(reg, type)
}
val sorted = intervals.sortedWith(compareBy<Interval> { it.type.ordinal }.thenBy { it.start })
val activeSlots = mutableMapOf<Int, Pair<Int, Int>>()
val activeSlots = mutableMapOf<VirtualRegister, Pair<VirtualRegister, Int>>()
for (interval in sorted) {
activeSlots.entries.removeAll { it.value.second < interval.start }
var slot = startSlot
var slotNum = startSlot
var slot: VirtualRegister
while (true) {
// a packed slot stays in the same register file (int/float) as the register it replaces
slot = if (interval.register is VirtualRegister.FloatReg) VirtualRegister.float(slotNum) else VirtualRegister.int(slotNum)
if (slot !in activeSlots) {
val existingType = slotTypes[slot]
if (existingType == null || existingType == interval.type || typesCompatible(existingType, interval.type)) {
@@ -555,7 +417,7 @@ object RegisterPacker {
break
}
}
slot++
slotNum++
}
packing[interval.register] = slot
@@ -572,12 +434,12 @@ object RegisterPacker {
}
private fun conflictsWithRegister(
slot: Int,
regNum: Int,
conflictGraph: Map<Int, Set<Int>>,
packing: Map<Int, Int>
slot: VirtualRegister,
register: VirtualRegister,
conflictGraph: Map<VirtualRegister, Set<VirtualRegister>>,
packing: Map<VirtualRegister, VirtualRegister>
): Boolean {
val conflictingRegs = conflictGraph[regNum] ?: return false
val conflictingRegs = conflictGraph[register] ?: return false
for (cr in conflictingRegs) {
val crSlot = packing[cr]
if (crSlot != null && crSlot == slot)
@@ -588,70 +450,17 @@ object RegisterPacker {
return false
}
private fun rewrite(sub: IRSubroutine, packing: Map<Int, Int>) {
private fun rewrite(sub: IRSubroutine, packing: Map<VirtualRegister, VirtualRegister>) {
for (chunk in sub.chunks) {
if (chunk !is IRCodeChunk)
continue
for (i in chunk.instructions.indices) {
val instr = chunk.instructions[i]
val newReg1 = if (instr.reg1 != null) (packing[instr.reg1] ?: instr.reg1) else null
val newReg2 = if (instr.reg2 != null) (packing[instr.reg2] ?: instr.reg2) else null
val newReg3 = if (instr.reg3 != null) (packing[instr.reg3] ?: instr.reg3) else null
val oldFpReg1 = instr.fpReg1
val newFpReg1 = if (oldFpReg1 != null && oldFpReg1.value in packing)
RegisterNum(packing[oldFpReg1.value]!!) else oldFpReg1
val oldFpReg2 = instr.fpReg2
val newFpReg2 = if (oldFpReg2 != null && oldFpReg2.value in packing)
RegisterNum(packing[oldFpReg2.value]!!) else oldFpReg2
val oldFcallArgs = instr.fcallArgs
val newFcallArgs = if (oldFcallArgs != null)
remapFcallArgs(oldFcallArgs, packing) else oldFcallArgs
val anyChange = newReg1 != instr.reg1 || newReg2 != instr.reg2 || newReg3 != instr.reg3
|| newFpReg1 != instr.fpReg1 || newFpReg2 != instr.fpReg2
|| newFcallArgs != instr.fcallArgs
if (anyChange) {
chunk.instructions[i] = instr.copy(
reg1 = newReg1,
reg2 = newReg2,
reg3 = newReg3,
fpReg1 = newFpReg1,
fpReg2 = newFpReg2,
fcallArgs = newFcallArgs
)
}
val newInstr = instr.mapRegisters { vr -> packing[vr] ?: vr }
if (newInstr != instr)
chunk.instructions[i] = newInstr
}
}
}
private fun remapFcallArgs(args: FunctionCallArgs, packing: Map<Int, Int>): FunctionCallArgs {
var changed = false
val newArgs = args.arguments.map { arg ->
val newRegNum = packing[arg.reg.registerNum.value]
if (newRegNum != null && newRegNum != arg.reg.registerNum.value) {
changed = true
FunctionCallArgs.ArgumentSpec(
arg.name, arg.address,
FunctionCallArgs.RegSpec(arg.reg.dt, RegisterNum(newRegNum), arg.reg.callingConventionSlot, arg.reg.statusflag)
)
} else arg
}
val newReturns = args.returns.map { ret ->
val newRegNum = packing[ret.registerNum.value]
if (newRegNum != null && newRegNum != ret.registerNum.value) {
changed = true
FunctionCallArgs.RegSpec(ret.dt, RegisterNum(newRegNum), ret.callingConventionSlot, ret.statusflag)
} else ret
}
return if (changed)
FunctionCallArgs(newArgs, newReturns)
else
args
}
}
@@ -7,8 +7,10 @@ import prog8.code.ast.PtProgram
import prog8.code.core.CompilationOptions
import prog8.code.core.IErrorReporter
import prog8.codegen.intermediate.IRCodeGen
import prog8.intermediate.IRDataType
import prog8.intermediate.IRFileWriter
import prog8.intermediate.IRProgram
import prog8.intermediate.VirtualRegister
import prog8.intermediate.dumpVariables
class VmCodeGen(val retainSSA: Boolean,
@@ -23,7 +25,11 @@ class VmCodeGen(val retainSSA: Boolean,
val irCodeGen = IRCodeGen(program, symbolTable, options, errors, retainSSA, preassignedCallSiteIds)
val irProgram = irCodeGen.generate()
irProgram.verifyRegisterTypes(irCodeGen.registerTypes())
val virtualRegisterTypes = irCodeGen.registerTypes().entries.associate { (num, type) ->
val register: VirtualRegister = if (type == IRDataType.FLOAT) VirtualRegister.float(num.value) else VirtualRegister.int(num.value)
register to type
}
irProgram.verifyRegisterTypes(virtualRegisterTypes)
if (options.dumpVariables)
dumpVariables(irProgram)
+263 -227
View File
@@ -10,6 +10,8 @@ import prog8.intermediate.*
import prog8tests.helpers.ErrorReporterForTests
class TestIRPeepholeOpt: FunSpec({
val indexRegType = VMTarget().indexRegType
fun makeIRProgram(chunks: List<IRCodeChunkBase>): IRProgram {
require(chunks.first().label=="p8b_main.p8s_start")
val block = IRBlock("p8b_main", false, IRBlock.Options(), Position.DUMMY)
@@ -40,9 +42,9 @@ class TestIRPeepholeOpt: FunSpec({
test("remove nops") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=42),
IRInstruction(Opcode.NOP),
IRInstruction(Opcode.NOP)
IRInstructions.load(IRDataType.BYTE, 1, 42),
IRInstructions.simple(Opcode.NOP),
IRInstructions.simple(Opcode.NOP)
))
irProg.chunks().single().instructions.size shouldBe 3
val opt = IRPeepholeOptimizer(irProg, false)
@@ -52,13 +54,13 @@ class TestIRPeepholeOpt: FunSpec({
test("remove jmp to label below but keep labels") {
val c1 = IRCodeChunk("p8b_main.p8s_start", null)
c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label")
c1 += IRInstructions.jump(codeLabel("label"))
val c2 = IRCodeChunk("label", null)
c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2")
c2 += IRInstruction(Opcode.NOP) // removed
c2 += IRInstructions.jump(codeLabel("label2"))
c2 += IRInstructions.simple(Opcode.NOP) // removed
val c3 = IRCodeChunk("label2", null)
c3 += IRInstruction(Opcode.JUMP, labelSymbol = "label3")
c3 += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=1)
c3 += IRInstructions.jump(codeLabel("label3"))
c3 += IRInstructions.unary(Opcode.INC, IRDataType.BYTE, 1)
val c4 = IRCodeChunk("label3", null)
val irProg = makeIRProgram(listOf(c1, c2, c3, c4))
@@ -84,18 +86,18 @@ class TestIRPeepholeOpt: FunSpec({
test("remove double sec/clc/sei/cli") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.SEC),
IRInstruction(Opcode.SEC),
IRInstruction(Opcode.SEC),
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.SEI),
IRInstruction(Opcode.SEI),
IRInstruction(Opcode.SEI),
IRInstruction(Opcode.CLI),
IRInstruction(Opcode.CLI),
IRInstruction(Opcode.CLI),
IRInstructions.simple(Opcode.SEC),
IRInstructions.simple(Opcode.SEC),
IRInstructions.simple(Opcode.SEC),
IRInstructions.simple(Opcode.CLC),
IRInstructions.simple(Opcode.CLC),
IRInstructions.simple(Opcode.CLC),
IRInstructions.simple(Opcode.SEI),
IRInstructions.simple(Opcode.SEI),
IRInstructions.simple(Opcode.SEI),
IRInstructions.simple(Opcode.CLI),
IRInstructions.simple(Opcode.CLI),
IRInstructions.simple(Opcode.CLI),
))
irProg.chunks().single().instructions.size shouldBe 12
val opt = IRPeepholeOptimizer(irProg, false)
@@ -108,18 +110,18 @@ class TestIRPeepholeOpt: FunSpec({
test("remove double sec/clc/sei/cli reversed") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.SEC),
IRInstruction(Opcode.SEC),
IRInstruction(Opcode.SEC),
IRInstruction(Opcode.CLI),
IRInstruction(Opcode.CLI),
IRInstruction(Opcode.CLI),
IRInstruction(Opcode.SEI),
IRInstruction(Opcode.SEI),
IRInstruction(Opcode.SEI),
IRInstructions.simple(Opcode.CLC),
IRInstructions.simple(Opcode.CLC),
IRInstructions.simple(Opcode.CLC),
IRInstructions.simple(Opcode.SEC),
IRInstructions.simple(Opcode.SEC),
IRInstructions.simple(Opcode.SEC),
IRInstructions.simple(Opcode.CLI),
IRInstructions.simple(Opcode.CLI),
IRInstructions.simple(Opcode.CLI),
IRInstructions.simple(Opcode.SEI),
IRInstructions.simple(Opcode.SEI),
IRInstructions.simple(Opcode.SEI),
))
irProg.chunks().single().instructions.size shouldBe 12
val opt = IRPeepholeOptimizer(irProg, false)
@@ -132,10 +134,10 @@ class TestIRPeepholeOpt: FunSpec({
test("push followed by pop") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=42),
IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=42),
IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=99),
IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=222)
IRInstructions.push(IRDataType.BYTE, 42),
IRInstructions.pop(IRDataType.BYTE, 42),
IRInstructions.push(IRDataType.BYTE, 99),
IRInstructions.pop(IRDataType.BYTE, 222)
))
irProg.chunks().single().instructions.size shouldBe 4
val opt = IRPeepholeOptimizer(irProg, false)
@@ -143,23 +145,23 @@ class TestIRPeepholeOpt: FunSpec({
val instr = irProg.chunks().single().instructions
instr.size shouldBe 1
instr[0].opcode shouldBe Opcode.LOADR
instr[0].reg1 shouldBe 222
instr[0].reg2 shouldBe 99
instr[0].requireDest().register shouldBe VirtualRegister.int(222)
instr[0].requireSrcA().register shouldBe VirtualRegister.int(99)
}
test("remove useless div/mul, add/sub") {
// Use different registers for each test case to avoid dead store elimination removing them
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=1, immediate = 1),
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=2, immediate = 1),
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=3, immediate = 1),
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=4, immediate = 1),
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=5, immediate = 2),
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=6, immediate = 2),
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=7, immediate = 2),
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=8, immediate = 2),
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=9, immediate = 0),
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=10, immediate = 0)
IRInstructions.binaryImmediate(Opcode.DIV, IRDataType.BYTE, 1, 1),
IRInstructions.binaryImmediate(Opcode.DIVS, IRDataType.BYTE, 2, 1),
IRInstructions.binaryImmediate(Opcode.MUL, IRDataType.BYTE, 3, 1),
IRInstructions.binaryImmediate(Opcode.MOD, IRDataType.BYTE, 4, 1),
IRInstructions.binaryImmediate(Opcode.DIV, IRDataType.BYTE, 5, 2),
IRInstructions.binaryImmediate(Opcode.DIVS, IRDataType.BYTE, 6, 2),
IRInstructions.binaryImmediate(Opcode.MUL, IRDataType.BYTE, 7, 2),
IRInstructions.binaryImmediate(Opcode.MOD, IRDataType.BYTE, 8, 2),
IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.BYTE, 9, 0),
IRInstructions.binaryImmediate(Opcode.SUB, IRDataType.BYTE, 10, 0)
))
irProg.chunks().single().instructions.size shouldBe 10
val opt = IRPeepholeOptimizer(irProg, false)
@@ -170,11 +172,11 @@ class TestIRPeepholeOpt: FunSpec({
test("replace integer multiply by zero with load") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=1, immediate = 0),
IRInstruction(Opcode.MULS, IRDataType.WORD, reg1=2, immediate = 0),
IRInstruction(Opcode.MULS, IRDataType.LONG, reg1=3, immediate = 0),
IRInstruction(Opcode.MUL, IRDataType.FLOAT, fpReg1=RegisterNum(4), immediateFp = 0.0),
IRInstruction(Opcode.RETURN)
IRInstructions.binaryImmediate(Opcode.MUL, IRDataType.BYTE, 1, 0),
IRInstructions.binaryImmediate(Opcode.MULS, IRDataType.WORD, 2, 0),
IRInstructions.binaryImmediate(Opcode.MULS, IRDataType.LONG, 3, 0),
IRInstructions.binaryImmediateFloat(Opcode.MUL, 4, 0.0),
IRInstructions.returnVoid()
))
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
@@ -185,52 +187,49 @@ class TestIRPeepholeOpt: FunSpec({
test("fold adjacent integer immediate multiplications") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=1, immediate = 7),
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=1, immediate = 9),
IRInstruction(Opcode.MULS, IRDataType.WORD, reg1=2, immediate = -3),
IRInstruction(Opcode.MULS, IRDataType.WORD, reg1=2, immediate = 4),
IRInstruction(Opcode.MULS, IRDataType.LONG, reg1=3, immediate = 100000),
IRInstruction(Opcode.MULS, IRDataType.LONG, reg1=3, immediate = 30000),
IRInstruction(Opcode.RETURN)
IRInstructions.binaryImmediate(Opcode.MUL, IRDataType.BYTE, 1, 7),
IRInstructions.binaryImmediate(Opcode.MUL, IRDataType.BYTE, 1, 9),
IRInstructions.binaryImmediate(Opcode.MULS, IRDataType.WORD, 2, -3),
IRInstructions.binaryImmediate(Opcode.MULS, IRDataType.WORD, 2, 4),
IRInstructions.binaryImmediate(Opcode.MULS, IRDataType.LONG, 3, 100000),
IRInstructions.binaryImmediate(Opcode.MULS, IRDataType.LONG, 3, 30000),
IRInstructions.returnVoid()
))
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.size shouldBe 4
instr[0].immediate shouldBe 63
instr[1].immediate shouldBe 65524
instr[2].immediate shouldBe 3000000000L.toInt()
instr[0].requireImmediateInt() shouldBe 63
instr[1].requireImmediateInt() shouldBe 65524
instr[2].requireImmediateInt() shouldBe 3000000000L.toInt()
}
test("collapse adjacent integer and float loadr chains") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=2, reg2=1),
IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=3, reg2=2),
IRInstruction(Opcode.LOADR, IRDataType.FLOAT, fpReg1=RegisterNum(5), fpReg2=RegisterNum(4)),
IRInstruction(Opcode.LOADR, IRDataType.FLOAT, fpReg1=RegisterNum(6), fpReg2=RegisterNum(5)),
IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=3, address=MemoryAddress(100u)),
IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1=RegisterNum(6), address=MemoryAddress(200u))
IRInstructions.move(IRDataType.BYTE, 2, 1),
IRInstructions.move(IRDataType.BYTE, 3, 2),
IRInstructions.move(IRDataType.FLOAT, 5, 4),
IRInstructions.move(IRDataType.FLOAT, 6, 5),
IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 3, IRMemory.direct(MemoryAddress(100u))),
IRInstructions.storeMemory(Opcode.STOREM, IRDataType.FLOAT, 6, IRMemory.direct(MemoryAddress(200u)))
))
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.map { Triple(it.opcode, it.reg1, it.fpReg1) } shouldBe listOf(
Triple(Opcode.LOADR, 3, null),
Triple(Opcode.LOADR, null, RegisterNum(6)),
Triple(Opcode.STOREM, 3, null),
Triple(Opcode.STOREM, null, RegisterNum(6))
)
instr[0].reg1 shouldBe 3
instr[0].reg2 shouldBe 1
instr[1].fpReg1 shouldBe RegisterNum(6)
instr[1].fpReg2 shouldBe RegisterNum(4)
instr.map { it.opcode } shouldBe listOf(Opcode.LOADR, Opcode.LOADR, Opcode.STOREM, Opcode.STOREM)
instr[0].requireDest().register shouldBe VirtualRegister.int(3)
instr[0].requireSrcA().register shouldBe VirtualRegister.int(1)
instr[1].requireDest().register shouldBe VirtualRegister.float(6)
instr[1].requireSrcA().register shouldBe VirtualRegister.float(4)
instr[2].requireSrcA().register shouldBe VirtualRegister.int(3)
instr[3].requireSrcA().register shouldBe VirtualRegister.float(6)
}
test("replace add/sub 1 by inc/dec") {
// Use different registers for each test case to avoid dead store elimination removing them
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=1, immediate = 1),
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=2, immediate = 1)
IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.BYTE, 1, 1),
IRInstructions.binaryImmediate(Opcode.SUB, IRDataType.BYTE, 2, 1)
))
irProg.chunks().single().instructions.size shouldBe 2
val opt = IRPeepholeOptimizer(irProg, false)
@@ -244,23 +243,23 @@ class TestIRPeepholeOpt: FunSpec({
test("remove useless and/or/xor") {
// Use different registers for each test case to avoid dead store elimination removing them
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=1, immediate = 0),
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=2, immediate = 0),
IRInstruction(Opcode.AND, IRDataType.LONG, reg1=3, immediate = 0),
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=4, immediate = 255),
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=5, immediate = 65535),
IRInstruction(Opcode.AND, IRDataType.LONG, reg1=6, immediate = 2147483647),
IRInstruction(Opcode.AND, IRDataType.LONG, reg1=7, immediate = -1),
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=8, immediate = 0),
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=9, immediate = 255),
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=10, immediate = 65535),
IRInstruction(Opcode.OR, IRDataType.LONG, reg1=11, immediate = 2147483647),
IRInstruction(Opcode.OR, IRDataType.LONG, reg1=12, immediate = -1),
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=13, immediate = 0),
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=14, immediate = 200),
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=15, immediate = 60000),
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=16, immediate = 1),
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=17, immediate = 1)
IRInstructions.binaryImmediate(Opcode.AND, IRDataType.BYTE, 1, 0),
IRInstructions.binaryImmediate(Opcode.AND, IRDataType.WORD, 2, 0),
IRInstructions.binaryImmediate(Opcode.AND, IRDataType.LONG, 3, 0),
IRInstructions.binaryImmediate(Opcode.AND, IRDataType.BYTE, 4, 255),
IRInstructions.binaryImmediate(Opcode.AND, IRDataType.WORD, 5, 65535),
IRInstructions.binaryImmediate(Opcode.AND, IRDataType.LONG, 6, 2147483647),
IRInstructions.binaryImmediate(Opcode.AND, IRDataType.LONG, 7, -1),
IRInstructions.binaryImmediate(Opcode.OR, IRDataType.BYTE, 8, 0),
IRInstructions.binaryImmediate(Opcode.OR, IRDataType.BYTE, 9, 255),
IRInstructions.binaryImmediate(Opcode.OR, IRDataType.WORD, 10, 65535),
IRInstructions.binaryImmediate(Opcode.OR, IRDataType.LONG, 11, 2147483647),
IRInstructions.binaryImmediate(Opcode.OR, IRDataType.LONG, 12, -1),
IRInstructions.binaryImmediate(Opcode.XOR, IRDataType.BYTE, 13, 0),
IRInstructions.binaryImmediate(Opcode.AND, IRDataType.BYTE, 14, 200),
IRInstructions.binaryImmediate(Opcode.AND, IRDataType.WORD, 15, 60000),
IRInstructions.binaryImmediate(Opcode.OR, IRDataType.BYTE, 16, 1),
IRInstructions.binaryImmediate(Opcode.XOR, IRDataType.BYTE, 17, 1)
))
irProg.chunks().single().instructions.size shouldBe 17
val opt = IRPeepholeOptimizer(irProg, false)
@@ -281,96 +280,86 @@ class TestIRPeepholeOpt: FunSpec({
test("replace and/or/xor by constant number") {
// Use different registers for each test case to avoid dead store elimination removing them
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=1, immediate = 0),
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=2, immediate = 0),
IRInstruction(Opcode.AND, IRDataType.LONG, reg1=3, immediate = 0),
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=4, immediate = 255),
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=5, immediate = 65535),
IRInstruction(Opcode.OR, IRDataType.LONG, reg1=6, immediate = -1)
IRInstructions.binaryImmediate(Opcode.AND, IRDataType.BYTE, 1, 0),
IRInstructions.binaryImmediate(Opcode.AND, IRDataType.WORD, 2, 0),
IRInstructions.binaryImmediate(Opcode.AND, IRDataType.LONG, 3, 0),
IRInstructions.binaryImmediate(Opcode.OR, IRDataType.BYTE, 4, 255),
IRInstructions.binaryImmediate(Opcode.OR, IRDataType.WORD, 5, 65535),
IRInstructions.binaryImmediate(Opcode.OR, IRDataType.LONG, 6, -1)
))
irProg.chunks().single().instructions.size shouldBe 6
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.size shouldBe 6
instr[0].opcode shouldBe Opcode.LOAD
instr[1].opcode shouldBe Opcode.LOAD
instr[2].opcode shouldBe Opcode.LOAD
instr[3].opcode shouldBe Opcode.LOAD
instr[4].opcode shouldBe Opcode.LOAD
instr[5].opcode shouldBe Opcode.LOAD
instr[0].immediate shouldBe 0
instr[1].immediate shouldBe 0
instr[2].immediate shouldBe 0
instr[3].immediate shouldBe 255
instr[4].immediate shouldBe 65535
instr[5].immediate shouldBe -1
instr.map { it.opcode } shouldBe List(6) { Opcode.LOAD }
instr.map { it.requireImmediateInt() } shouldBe listOf(0, 0, 0, 255, 65535, -1)
}
test("combine adjacent immediate and/or operations") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=1, immediate = 0xf0),
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=1, immediate = 0xcc),
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=2, immediate = 0x0010),
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=2, immediate = 0x0003),
IRInstructions.binaryImmediate(Opcode.AND, IRDataType.BYTE, 1, 0xf0),
IRInstructions.binaryImmediate(Opcode.AND, IRDataType.BYTE, 1, 0xcc),
IRInstructions.binaryImmediate(Opcode.OR, IRDataType.WORD, 2, 0x0010),
IRInstructions.binaryImmediate(Opcode.OR, IRDataType.WORD, 2, 0x0003),
))
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.size shouldBe 2
instr[0].opcode shouldBe Opcode.AND
instr[0].immediate shouldBe 0xc0
instr[0].requireImmediateInt() shouldBe 0xc0
instr[1].opcode shouldBe Opcode.OR
instr[1].immediate shouldBe 0x0013
instr[1].requireImmediateInt() shouldBe 0x0013
}
test("cancel identical adjacent immediate xor operations") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=1, immediate = 3),
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=1, immediate = 3),
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate = 42)
IRInstructions.binaryImmediate(Opcode.XOR, IRDataType.BYTE, 1, 3),
IRInstructions.binaryImmediate(Opcode.XOR, IRDataType.BYTE, 1, 3),
IRInstructions.load(IRDataType.BYTE, 2, 42)
))
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.size shouldBe 1
instr[0].opcode shouldBe Opcode.LOAD
instr[0].immediate shouldBe 42
instr[0].requireImmediateInt() shouldBe 42
}
test("fold adjacent immediate arithmetic with inc/dec") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=1, immediate = 4),
IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=1),
IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=2),
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=2, immediate = 4),
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=3, immediate = 4),
IRInstruction(Opcode.DEC, IRDataType.BYTE, reg1=3),
IRInstruction(Opcode.DEC, IRDataType.BYTE, reg1=4),
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=4, immediate = 4)
IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.BYTE, 1, 4),
IRInstructions.unary(Opcode.INC, IRDataType.BYTE, 1),
IRInstructions.unary(Opcode.INC, IRDataType.BYTE, 2),
IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.BYTE, 2, 4),
IRInstructions.binaryImmediate(Opcode.SUB, IRDataType.BYTE, 3, 4),
IRInstructions.unary(Opcode.DEC, IRDataType.BYTE, 3),
IRInstructions.unary(Opcode.DEC, IRDataType.BYTE, 4),
IRInstructions.binaryImmediate(Opcode.SUB, IRDataType.BYTE, 4, 4)
))
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.size shouldBe 4
instr[0].opcode shouldBe Opcode.ADD
instr[0].immediate shouldBe 5
instr[0].requireImmediateInt() shouldBe 5
instr[1].opcode shouldBe Opcode.ADD
instr[1].immediate shouldBe 5
instr[1].requireImmediateInt() shouldBe 5
instr[2].opcode shouldBe Opcode.SUB
instr[2].immediate shouldBe 5
instr[2].requireImmediateInt() shouldBe 5
instr[3].opcode shouldBe Opcode.SUB
instr[3].immediate shouldBe 5
instr[3].requireImmediateInt() shouldBe 5
}
test("replace register arithmetic by immediate operations") {
// Test the new optimization: LOAD + SUBR/DIVR/MODR → immediate operation
// Pattern: LOAD r1, #const followed by SUBR r2, r1 → SUB r2, #const
// SUBR optimization
val subrProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate = 5),
IRInstruction(Opcode.SUBR, IRDataType.BYTE, reg1=2, reg2=1)
IRInstructions.load(IRDataType.BYTE, 1, 5),
IRInstructions.binary(Opcode.SUBR, IRDataType.BYTE, 2, 1)
))
subrProg.chunks().single().instructions.size shouldBe 2
val opt1 = IRPeepholeOptimizer(subrProg, false)
@@ -378,12 +367,12 @@ class TestIRPeepholeOpt: FunSpec({
val subInstr = subrProg.chunks().single().instructions
subInstr.size shouldBe 1
subInstr[0].opcode shouldBe Opcode.SUB
subInstr[0].immediate shouldBe 5
subInstr[0].requireImmediateInt() shouldBe 5
// DIVR optimization
val divrProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=10, immediate = 100),
IRInstruction(Opcode.DIVR, IRDataType.WORD, reg1=20, reg2=10)
IRInstructions.load(IRDataType.WORD, 10, 100),
IRInstructions.binary(Opcode.DIVR, IRDataType.WORD, 20, 10)
))
divrProg.chunks().single().instructions.size shouldBe 2
val opt2 = IRPeepholeOptimizer(divrProg, false)
@@ -391,12 +380,12 @@ class TestIRPeepholeOpt: FunSpec({
val divInstr = divrProg.chunks().single().instructions
divInstr.size shouldBe 1
divInstr[0].opcode shouldBe Opcode.DIV
divInstr[0].immediate shouldBe 100
divInstr[0].requireImmediateInt() shouldBe 100
// MODR optimization
val modrProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=30, immediate = 7),
IRInstruction(Opcode.MODR, IRDataType.BYTE, reg1=40, reg2=30)
IRInstructions.load(IRDataType.BYTE, 30, 7),
IRInstructions.binary(Opcode.MODR, IRDataType.BYTE, 40, 30)
))
modrProg.chunks().single().instructions.size shouldBe 2
val opt3 = IRPeepholeOptimizer(modrProg, false)
@@ -404,34 +393,42 @@ class TestIRPeepholeOpt: FunSpec({
val modInstr = modrProg.chunks().single().instructions
modInstr.size shouldBe 1
modInstr[0].opcode shouldBe Opcode.MOD
modInstr[0].immediate shouldBe 7
modInstr[0].requireImmediateInt() shouldBe 7
}
test("remove self identity operations") {
// LOADR with same fp register is allowed (constructor only blocks integer reg1==reg2)
// The IR is not SSA: a written register may also be a read-only source register.
// Self-identity LOADR/ANDR/ORR become NOP, self-identity XORR becomes load 0.
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOADR, IRDataType.FLOAT, fpReg1=RegisterNum(10), fpReg2=RegisterNum(10)),
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=99, immediate=42) // keep chunk non-empty
IRInstructions.move(IRDataType.FLOAT, 10, 10),
IRInstructions.move(IRDataType.BYTE, 11, 11),
IRInstructions.binary(Opcode.ANDR, IRDataType.BYTE, 12, 12),
IRInstructions.binary(Opcode.ORR, IRDataType.BYTE, 13, 13),
IRInstructions.binary(Opcode.XORR, IRDataType.BYTE, 14, 14),
IRInstructions.load(IRDataType.BYTE, 99, 42) // keep chunk non-empty
))
irProg.chunks().single().instructions.size shouldBe 2
irProg.chunks().single().instructions.size shouldBe 6
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.size shouldBe 1
val instr = irProg.chunks().single().instructions.filter { it.opcode != Opcode.NOP }
instr.size shouldBe 2
instr[0].opcode shouldBe Opcode.LOAD
instr[0].reg1 shouldBe 99
instr[0].immediate shouldBe 42
instr[0].requireDest().register shouldBe VirtualRegister.int(14)
instr[0].requireImmediateInt() shouldBe 0
instr[1].opcode shouldBe Opcode.LOAD
instr[1].requireDest().register shouldBe VirtualRegister.int(99)
instr[1].requireImmediateInt() shouldBe 42
}
test("remove shift by zero") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=10, immediate=0),
IRInstruction(Opcode.LSLN, IRDataType.BYTE, reg1=1, reg2=10),
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=11, immediate=0),
IRInstruction(Opcode.LSRN, IRDataType.BYTE, reg1=2, reg2=11),
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=12, immediate=0),
IRInstruction(Opcode.ASRN, IRDataType.BYTE, reg1=3, reg2=12),
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=99, immediate=42) // keep chunk non-empty
IRInstructions.load(IRDataType.BYTE, 10, 0),
IRInstructions.binary(Opcode.LSLN, IRDataType.BYTE, 1, 10),
IRInstructions.load(IRDataType.BYTE, 11, 0),
IRInstructions.binary(Opcode.LSRN, IRDataType.BYTE, 2, 11),
IRInstructions.load(IRDataType.BYTE, 12, 0),
IRInstructions.binary(Opcode.ASRN, IRDataType.BYTE, 3, 12),
IRInstructions.load(IRDataType.BYTE, 99, 42) // keep chunk non-empty
))
irProg.chunks().single().instructions.size shouldBe 7
val opt = IRPeepholeOptimizer(irProg, false)
@@ -439,30 +436,34 @@ class TestIRPeepholeOpt: FunSpec({
val instr = irProg.chunks().single().instructions
instr.size shouldBe 1
instr[0].opcode shouldBe Opcode.LOAD
instr[0].reg1 shouldBe 99
instr[0].immediate shouldBe 42
instr[0].requireDest().register shouldBe VirtualRegister.int(99)
instr[0].requireImmediateInt() shouldBe 42
}
test("replace concat with ext after harmless instruction") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=10, immediate=0),
IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=20),
IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=1, reg2=10, reg3=11),
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=99, immediate=42)
IRInstructions.load(IRDataType.BYTE, 10, 0),
IRInstructions.unary(Opcode.INC, IRDataType.BYTE, 20),
IRInstructions.concat(IRDataType.BYTE, 1, 10, 11),
IRInstructions.load(IRDataType.BYTE, 99, 42)
))
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.any { it.opcode == Opcode.EXT && it.reg1 == 1 && it.reg2 == 11 } shouldBe true
instr.any {
it.opcode == Opcode.EXT &&
it.requireDest().register == VirtualRegister.int(1) &&
it.requireSrcA().register == VirtualRegister.int(11)
} shouldBe true
instr.any { it.opcode == Opcode.CONCAT } shouldBe false
}
test("do not replace concat after intervening write") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=10, immediate=0),
IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=10),
IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=1, reg2=10, reg3=11),
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=99, immediate=42)
IRInstructions.load(IRDataType.BYTE, 10, 0),
IRInstructions.unary(Opcode.INC, IRDataType.BYTE, 10),
IRInstructions.concat(IRDataType.BYTE, 1, 10, 11),
IRInstructions.load(IRDataType.BYTE, 99, 42)
))
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
@@ -471,10 +472,10 @@ class TestIRPeepholeOpt: FunSpec({
test("do not replace concat across control flow") {
val c1 = IRCodeChunk("p8b_main.p8s_start", null)
c1 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=10, immediate=0)
c1 += IRInstruction(Opcode.JUMP, labelSymbol="after")
c1 += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=1, reg2=10, reg3=11)
c1 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=99, immediate=42)
c1 += IRInstructions.load(IRDataType.BYTE, 10, 0)
c1 += IRInstructions.jump(codeLabel("after"))
c1 += IRInstructions.concat(IRDataType.BYTE, 1, 10, 11)
c1 += IRInstructions.load(IRDataType.BYTE, 99, 42)
val c2 = IRCodeChunk("after", null)
val irProg = makeIRProgram(listOf(c1, c2))
val opt = IRPeepholeOptimizer(irProg, false)
@@ -484,19 +485,19 @@ class TestIRPeepholeOpt: FunSpec({
test("cancel adjacent ops") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.INV, IRDataType.BYTE, reg1=1),
IRInstruction(Opcode.INV, IRDataType.BYTE, reg1=1),
IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=2),
IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=2),
IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=3, reg2=33),
IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=3, reg2=33),
IRInstruction(Opcode.EXTS, IRDataType.WORD, reg1=4, reg2=44),
IRInstruction(Opcode.EXTS, IRDataType.WORD, reg1=4, reg2=44),
IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=5),
IRInstruction(Opcode.DEC, IRDataType.BYTE, reg1=5),
IRInstruction(Opcode.DEC, IRDataType.BYTE, reg1=6),
IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=6),
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=99, immediate=42) // keep chunk non-empty
IRInstructions.unary(Opcode.INV, IRDataType.BYTE, 1),
IRInstructions.unary(Opcode.INV, IRDataType.BYTE, 1),
IRInstructions.unary(Opcode.NEG, IRDataType.BYTE, 2),
IRInstructions.unary(Opcode.NEG, IRDataType.BYTE, 2),
IRInstructions.binary(Opcode.EXT, IRDataType.BYTE, 3, 33),
IRInstructions.binary(Opcode.EXT, IRDataType.BYTE, 3, 33),
IRInstructions.binary(Opcode.EXTS, IRDataType.WORD, 4, 44),
IRInstructions.binary(Opcode.EXTS, IRDataType.WORD, 4, 44),
IRInstructions.unary(Opcode.INC, IRDataType.BYTE, 5),
IRInstructions.unary(Opcode.DEC, IRDataType.BYTE, 5),
IRInstructions.unary(Opcode.DEC, IRDataType.BYTE, 6),
IRInstructions.unary(Opcode.INC, IRDataType.BYTE, 6),
IRInstructions.load(IRDataType.BYTE, 99, 42) // keep chunk non-empty
))
irProg.chunks().single().instructions.size shouldBe 13
val opt = IRPeepholeOptimizer(irProg, false)
@@ -504,21 +505,21 @@ class TestIRPeepholeOpt: FunSpec({
val instr = irProg.chunks().single().instructions
instr.size shouldBe 3
instr[0].opcode shouldBe Opcode.EXT
instr[0].reg1 shouldBe 3
instr[0].reg2 shouldBe 33
instr[0].requireDest().register shouldBe VirtualRegister.int(3)
instr[0].requireSrcA().register shouldBe VirtualRegister.int(33)
instr[1].opcode shouldBe Opcode.EXTS
instr[1].reg1 shouldBe 4
instr[1].reg2 shouldBe 44
instr[1].requireDest().register shouldBe VirtualRegister.int(4)
instr[1].requireSrcA().register shouldBe VirtualRegister.int(44)
instr[2].opcode shouldBe Opcode.LOAD
instr[2].immediate shouldBe 42
instr[2].requireImmediateInt() shouldBe 42
}
test("coalesce redundant LOADX/STOREX to same index is removed") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=1, reg2=10, labelSymbol="myArray"),
IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=99),
IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=1, reg2=10, labelSymbol="myArray"),
IRInstruction(Opcode.RETURN)
IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, 1, IRMemory.indexed("myArray", 10, indexRegType)),
IRInstructions.unary(Opcode.INC, IRDataType.BYTE, 99),
IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, 1, IRMemory.indexed("myArray", 10, indexRegType)),
IRInstructions.returnVoid()
))
irProg.chunks().single().instructions.count { it.opcode==Opcode.STOREX } shouldBe 1
val opt = IRPeepholeOptimizer(irProg, false)
@@ -531,9 +532,9 @@ class TestIRPeepholeOpt: FunSpec({
test("do not coalesce LOADX/STOREX when index differs") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=1, reg2=10, labelSymbol="myArray"),
IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=1, reg2=11, labelSymbol="myArray"),
IRInstruction(Opcode.RETURN)
IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, 1, IRMemory.indexed("myArray", 10, indexRegType)),
IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, 1, IRMemory.indexed("myArray", 11, indexRegType)),
IRInstructions.returnVoid()
))
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
@@ -544,10 +545,10 @@ class TestIRPeepholeOpt: FunSpec({
test("do not coalesce LOADX/STOREX with intervening read of value") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=1, reg2=10, labelSymbol="myArray"),
IRInstruction(Opcode.ADDR, IRDataType.BYTE, reg1=1, reg2=2),
IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=1, reg2=10, labelSymbol="myArray"),
IRInstruction(Opcode.RETURN)
IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, 1, IRMemory.indexed("myArray", 10, indexRegType)),
IRInstructions.binary(Opcode.ADDR, IRDataType.BYTE, 1, 2),
IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, 1, IRMemory.indexed("myArray", 10, indexRegType)),
IRInstructions.returnVoid()
))
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
@@ -557,10 +558,10 @@ class TestIRPeepholeOpt: FunSpec({
test("do not coalesce LOADX/STOREX across chunks single-chunk") {
val c1 = IRCodeChunk("p8b_main.p8s_start", null)
c1 += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=1, reg2=10, labelSymbol="myArray")
c1 += IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, 1, IRMemory.indexed("myArray", 10, indexRegType))
val c2 = IRCodeChunk("other", null)
c2 += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=1, reg2=10, labelSymbol="myArray")
c2 += IRInstruction(Opcode.RETURN)
c2 += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, 1, IRMemory.indexed("myArray", 10, indexRegType))
c2 += IRInstructions.returnVoid()
val irProg = makeIRProgram(listOf(c1, c2))
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
@@ -571,9 +572,9 @@ class TestIRPeepholeOpt: FunSpec({
test("do not fold storem+loadm same symbol different offsets") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=5, labelSymbol="myVar", symbolOffset=0),
IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=6, labelSymbol="myVar", symbolOffset=1),
IRInstruction(Opcode.RETURN)
IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 5, IRMemory.direct("myVar", 0)),
IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, 6, IRMemory.direct("myVar", 1)),
IRInstructions.returnVoid()
))
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
@@ -585,10 +586,10 @@ class TestIRPeepholeOpt: FunSpec({
test("do not fold storem+loadm across chunks single-chunk") {
val c1 = IRCodeChunk("p8b_main.p8s_start", null)
c1 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=5, labelSymbol="myVar")
c1 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 5, IRMemory.direct("myVar"))
val c2 = IRCodeChunk("other", null)
c2 += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=6, labelSymbol="myVar")
c2 += IRInstruction(Opcode.RETURN)
c2 += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, 6, IRMemory.direct("myVar"))
c2 += IRInstructions.returnVoid()
val irProg = makeIRProgram(listOf(c1, c2))
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
@@ -600,9 +601,9 @@ class TestIRPeepholeOpt: FunSpec({
test("do not fold storem+loadm different types") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=5, labelSymbol="myVar"),
IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1=6, labelSymbol="myVar"),
IRInstruction(Opcode.RETURN)
IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 5, IRMemory.direct("myVar")),
IRInstructions.loadMemory(Opcode.LOADM, IRDataType.WORD, 6, IRMemory.direct("myVar")),
IRInstructions.returnVoid()
))
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
@@ -612,22 +613,57 @@ class TestIRPeepholeOpt: FunSpec({
instr.count { it.opcode==Opcode.LOADR } shouldBe 0
}
test("dead store analysis sees reads via secondary operand slots") {
// r10 is only read as the INDEX register inside the LOADX memory reference. The dead
// store detector must see that read, otherwise the first LOAD r10 is wrongly removed
// and the array would be indexed with an uninitialized register.
val irProg = makeIRProgram(listOf(
IRInstructions.load(indexRegType, 10, 3),
IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, 1, IRMemory.indexed("myArray", 10, indexRegType)),
IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 1, IRMemory.direct("sym")),
IRInstructions.load(indexRegType, 10, 7),
IRInstructions.binaryImmediate(Opcode.ADD, indexRegType, 10, 1),
IRInstructions.storeMemory(Opcode.STOREM, indexRegType, 10, IRMemory.direct("sym2")),
IRInstructions.returnVoid()
))
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.count { it.opcode == Opcode.LOADX } shouldBe 1
instr.any { it.opcode == Opcode.LOAD && it.requireImmediateInt() == 3 } shouldBe true
}
test("dead store to the same register is removed") {
val irProg = makeIRProgram(listOf(
IRInstructions.load(IRDataType.BYTE, 1, 5),
IRInstructions.load(IRDataType.BYTE, 1, 10),
IRInstructions.unary(Opcode.INC, IRDataType.BYTE, 1),
IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 1, IRMemory.direct("sym")),
IRInstructions.returnVoid()
))
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.any { it.opcode == Opcode.LOAD && it.requireImmediateInt() == 5 } shouldBe false
instr.any { it.opcode == Opcode.LOAD && it.requireImmediateInt() == 10 } shouldBe true
}
test("loadr forwarding does not delete live-out source") {
// r1 is live-out to next chunk, so LOAD r1 should be kept even though r1 has single use in first chunk.
// The forwarder is conservative: it keeps the source live when it may be needed in a successor chunk,
// preventing the previous bug where the source LOAD was deleted.
val c1 = IRCodeChunk("p8b_main.p8s_start", null)
c1 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=42)
c1 += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=2, reg2=1)
c1 += IRInstruction(Opcode.JUMP, labelSymbol="next")
c1 += IRInstructions.load(IRDataType.BYTE, 1, 42)
c1 += IRInstructions.move(IRDataType.BYTE, 2, 1)
c1 += IRInstructions.jump(codeLabel("next"))
val c2 = IRCodeChunk("next", null)
c2 += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=3, reg2=1)
c2 += IRInstruction(Opcode.RETURN)
c2 += IRInstructions.move(IRDataType.BYTE, 3, 1)
c2 += IRInstructions.returnVoid()
val irProg = makeIRProgram(listOf(c1, c2))
val opt = IRPeepholeOptimizer(irProg, false)
opt.optimize(true, ErrorReporterForTests())
val all = irProg.chunks().flatMap { it.instructions }
// LOAD r1 must be kept because r1 is live-out; whether LOADR is forwarded to LOAD r2 is secondary.
all.count { it.opcode == Opcode.LOAD && it.reg1==1 } shouldBe 1
all.count { it.opcode == Opcode.LOAD && it.dest?.register == VirtualRegister.int(1) } shouldBe 1
}
})
+130 -132
View File
@@ -43,18 +43,19 @@ class TestRegisterPacker: FunSpec({
fun IRProgram.instructions(): List<IRInstruction> = chunks().flatMap { it.instructions }
fun IRProgram.registerAccesses(): List<RegisterOperand> = instructions().flatMap { it.registerAccesses }
test("simple coalescing") {
// r1=[0,1], r2=[2,3] non-overlapping -> same packed slot
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=10),
IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=1, labelSymbol = "sym"),
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate=20),
IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=2, labelSymbol = "sym2")
IRInstructions.load(IRDataType.BYTE, 1, 10),
IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 1, IRMemory.direct("sym")),
IRInstructions.load(IRDataType.BYTE, 2, 20),
IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 2, IRMemory.direct("sym2"))
))
RegisterPacker.pack(irProg)
val instrs = irProg.instructions()
val regs = instrs.mapNotNull { it.reg1 }
// After packing, all reg1 values should be the same (both r1 and r2 mapped to 1 slot)
val regs = irProg.registerAccesses().map { it.register }
// After packing, all register accesses should refer to the same register (both r1 and r2 mapped to 1 slot)
regs.toSet().size shouldBe 1
val used = irProg.registersUsed()
(used.readRegs.keys + used.writeRegs.keys).size shouldBe 1
@@ -62,17 +63,17 @@ class TestRegisterPacker: FunSpec({
test("no coalescing with overlapping intervals") {
// r1=[0,3], r2=[1,4] overlap -> 2 distinct slots needed
// LOADR uses both at same time -> must have reg1 != reg2
// LOADR uses both at same time -> destination must differ from source
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=10),
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate=20),
IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=1, labelSymbol="s"),
IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=1, reg2=2)
IRInstructions.load(IRDataType.BYTE, 1, 10),
IRInstructions.load(IRDataType.BYTE, 2, 20),
IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 1, IRMemory.direct("s")),
IRInstructions.move(IRDataType.BYTE, 1, 2)
))
RegisterPacker.pack(irProg)
val instrs = irProg.instructions()
val loadr = instrs.first { it.opcode == Opcode.LOADR }
loadr.reg1 shouldNotBe loadr.reg2
loadr.requireDest().register shouldNotBe loadr.requireSrcA().register
}
test("cross-chunk liveness") {
@@ -84,19 +85,18 @@ class TestRegisterPacker: FunSpec({
// Non-overlapping: r1=[0,2] and r2=[3,4] -> can share
// Overlapping: r2=[3,4] and r3=[3,5] -> must differ
val c1 = IRCodeChunk("p8b_main.p8s_start", null)
c1 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=10)
c1 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate=20)
c1 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=1, labelSymbol="sym")
c1 += IRInstructions.load(IRDataType.BYTE, 1, 10)
c1 += IRInstructions.load(IRDataType.BYTE, 2, 20)
c1 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 1, IRMemory.direct("sym"))
val c2 = IRCodeChunk(null, null)
c2 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=3, immediate=30)
c2 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=2, labelSymbol="sym2")
c2 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=3, labelSymbol="sym3")
c2 += IRInstructions.load(IRDataType.BYTE, 3, 30)
c2 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 2, IRMemory.direct("sym2"))
c2 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 3, IRMemory.direct("sym3"))
val irProg = makeIRProgram(listOf(c1, c2))
RegisterPacker.pack(irProg)
val instrs = irProg.instructions()
val distinctRegs = instrs.mapNotNull { it.reg1 }.toSet()
val distinctRegs = irProg.registerAccesses().map { it.register }.toSet()
// r1 and r2 can share a slot (non-overlapping), r3 must differ from r2 (overlapping)
// Results in exactly 2 distinct slots used
@@ -108,10 +108,10 @@ class TestRegisterPacker: FunSpec({
// Non-overlapping but different incompatible types.
// Packer must NOT assign both to slot 1 (would cause type conflict).
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=5, immediate=10),
IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=5, labelSymbol="s"),
IRInstruction(Opcode.LOAD, IRDataType.POINTER, reg1=1, immediate=0x1000),
IRInstruction(Opcode.STOREM, IRDataType.POINTER, reg1=1, labelSymbol="s2")
IRInstructions.load(IRDataType.BYTE, 5, 10),
IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 5, IRMemory.direct("s")),
IRInstructions.load(IRDataType.POINTER, 1, 0x1000),
IRInstructions.storeMemory(Opcode.STOREM, IRDataType.POINTER, 1, IRMemory.direct("s2"))
))
RegisterPacker.pack(irProg)
// After packing, usedRegisters() must not throw (internal type consistency)
@@ -126,16 +126,16 @@ class TestRegisterPacker: FunSpec({
// Chunk A -> unconditional jump -> chunk C
// Chunk B is unreachable from A (but still a predecessor of C via fall-through)
val c1 = IRCodeChunk("p8b_main.p8s_start", null)
c1 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=10)
c1 += IRInstruction(Opcode.JUMP, labelSymbol="label_c")
c1 += IRInstructions.load(IRDataType.BYTE, 1, 10)
c1 += IRInstructions.jump(codeLabel("label_c"))
val c2 = IRCodeChunk("label_b", null)
c2 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate=20)
c2 += IRInstructions.load(IRDataType.BYTE, 2, 20)
val c3 = IRCodeChunk("label_c", null)
c3 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=1, labelSymbol="sym")
c3 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate=99)
c3 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=2, labelSymbol="sym2")
c3 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 1, IRMemory.direct("sym"))
c3 += IRInstructions.load(IRDataType.BYTE, 2, 99)
c3 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 2, IRMemory.direct("sym2"))
val irProg = makeIRProgram(listOf(c1, c2, c3))
RegisterPacker.pack(irProg)
@@ -151,18 +151,18 @@ class TestRegisterPacker: FunSpec({
// Chunk A -> conditional branch to C, fall-through to B
// Liveness flows from A to both B and C
val c1 = IRCodeChunk("p8b_main.p8s_start", null)
c1 += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=1, immediate=0)
c1 += IRInstruction(Opcode.BSTNE, labelSymbol="label_c")
c1 += IRInstructions.compareImmediate(IRDataType.BYTE, 1, 0)
c1 += IRInstructions.branch(Opcode.BSTNE, codeLabel("label_c"))
val c2 = IRCodeChunk(null, null) // fall-through (B)
c2 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate=42)
c2 += IRInstruction(Opcode.JUMP, labelSymbol="label_end")
c2 += IRInstructions.load(IRDataType.BYTE, 2, 42)
c2 += IRInstructions.jump(codeLabel("label_end"))
val c3 = IRCodeChunk("label_c", null)
c3 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate=99)
c3 += IRInstructions.load(IRDataType.BYTE, 2, 99)
val c4 = IRCodeChunk("label_end", null)
c4 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=2, labelSymbol="sym")
c4 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 2, IRMemory.direct("sym"))
val irProg = makeIRProgram(listOf(c1, c2, c3, c4))
RegisterPacker.pack(irProg)
@@ -173,29 +173,28 @@ class TestRegisterPacker: FunSpec({
test("multi-chunk register reduction") {
// 3 chunks with 8 registers, non-overlapping patterns
val c1 = IRCodeChunk("p8b_main.p8s_start", null)
c1 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=10)
c1 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=1, labelSymbol="sym")
c1 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate=20)
c1 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=2, labelSymbol="sym2")
c1 += IRInstruction(Opcode.JUMP, labelSymbol="cont")
c1 += IRInstructions.load(IRDataType.BYTE, 1, 10)
c1 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 1, IRMemory.direct("sym"))
c1 += IRInstructions.load(IRDataType.BYTE, 2, 20)
c1 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 2, IRMemory.direct("sym2"))
c1 += IRInstructions.jump(codeLabel("cont"))
val c2 = IRCodeChunk("cont", null)
c2 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=3, immediate=30)
c2 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=3, labelSymbol="sym3")
c2 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=4, immediate=40)
c2 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=4, labelSymbol="sym4")
c2 += IRInstruction(Opcode.JUMP, labelSymbol="cont2")
c2 += IRInstructions.load(IRDataType.BYTE, 3, 30)
c2 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 3, IRMemory.direct("sym3"))
c2 += IRInstructions.load(IRDataType.BYTE, 4, 40)
c2 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 4, IRMemory.direct("sym4"))
c2 += IRInstructions.jump(codeLabel("cont2"))
val c3 = IRCodeChunk("cont2", null)
c3 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=5, immediate=50)
c3 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=5, labelSymbol="sym5")
c3 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=6, immediate=60)
c3 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=6, labelSymbol="sym6")
c3 += IRInstructions.load(IRDataType.BYTE, 5, 50)
c3 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 5, IRMemory.direct("sym5"))
c3 += IRInstructions.load(IRDataType.BYTE, 6, 60)
c3 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 6, IRMemory.direct("sym6"))
val irProg = makeIRProgram(listOf(c1, c2, c3))
RegisterPacker.pack(irProg)
val instrs = irProg.instructions()
val distinctRegs = instrs.mapNotNull { it.reg1 }.toSet()
val distinctRegs = irProg.registerAccesses().map { it.register }.toSet()
// Each chunk defines and uses 2 regs. Within each chunk the regs are non-overlapping
// (def rX at i, use at i+1, then def rY at i+2). Across chunks they don't overlap either
// (sequential, jumps between chunks). So all 6 regs can share 1 slot.
@@ -204,43 +203,40 @@ class TestRegisterPacker: FunSpec({
test("floating point registers packed independently") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = RegisterNum(1), immediateFp = 1.0),
IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = RegisterNum(1), labelSymbol="sym"),
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = RegisterNum(2), immediateFp = 2.0),
IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = RegisterNum(2), labelSymbol="sym2")
IRInstructions.loadFloat(1, 1.0),
IRInstructions.storeMemory(Opcode.STOREM, IRDataType.FLOAT, 1, IRMemory.direct("sym")),
IRInstructions.loadFloat(2, 2.0),
IRInstructions.storeMemory(Opcode.STOREM, IRDataType.FLOAT, 2, IRMemory.direct("sym2"))
))
RegisterPacker.pack(irProg)
val instrs = irProg.instructions()
// Both fp regs have non-overlapping intervals -> same packed slot
val fpRegs = instrs.mapNotNull { it.fpReg1?.value }
val fpRegs = irProg.registerAccesses().filter { it.isFloat }.map { it.register }
fpRegs.distinct().size shouldBe 1
}
test("function call args are remapped") {
val args = listOf(
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.BYTE, RegisterNum(1), null, null)),
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.BYTE, RegisterNum(2), null, null))
val callSite = CallSite(
CallTarget.SystemCall(0),
arguments = listOf(
Calls.argument(1, IRDataType.BYTE),
Calls.argument(2, IRDataType.BYTE)
),
results = listOf(Calls.result(3, IRDataType.BYTE))
)
val returns = listOf(
FunctionCallArgs.RegSpec(IRDataType.BYTE, RegisterNum(3), null, null)
)
val fcallArgs = FunctionCallArgs(args, returns)
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=10),
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate=20),
IRInstructions.load(IRDataType.BYTE, 1, 10),
IRInstructions.load(IRDataType.BYTE, 2, 20),
// Non-overlapping: r1=[0,0], r2=[1,1], r3=live only as return from SYSCALL
IRInstruction(Opcode.SYSCALL, immediate = 0, fcallArgs = fcallArgs)
IRInstructions.call(Opcode.SYSCALL, callSite)
))
RegisterPacker.pack(irProg)
val instrs = irProg.instructions()
val syscall = instrs.first { it.opcode == Opcode.SYSCALL }
syscall.fcallArgs shouldNotBe null
val remappedArgs = syscall.fcallArgs!!.arguments
val remappedReturns = syscall.fcallArgs!!.returns
val remapped = syscall.requireCallSite()
// Should have same packed register numbers
remappedArgs.map { it.reg.registerNum.value }.toSet().size shouldBe 2
remappedReturns.first().registerNum.value shouldBe 3
remapped.arguments.map { it.source.register }.toSet().size shouldBe 2
remapped.results.single().destination!!.registerNumber shouldBe 3
}
test("empty subroutine no crash") {
@@ -252,52 +248,56 @@ class TestRegisterPacker: FunSpec({
test("no registers used") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.NOP),
IRInstruction(Opcode.NOP)
IRInstructions.simple(Opcode.NOP),
IRInstructions.simple(Opcode.NOP)
))
RegisterPacker.pack(irProg)
irProg.instructions().size shouldBe 2
}
test("fcallArgs with mixed types must not share same slot") {
// Regression: fcallArgs registers with incompatible types (WORD vs LONG)
test("call arguments with mixed types must not share same slot") {
// Regression: call site registers with incompatible types (WORD vs LONG)
// in the same chunk must be assigned different packed slots.
// The conflict graph must track fcallArgs register types.
val args = listOf(
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(1), null, null)),
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.LONG, RegisterNum(2), null, null))
// The conflict graph must track call site register types.
val callSite = CallSite(
CallTarget.SystemCall(0),
arguments = listOf(
Calls.argument(1, IRDataType.WORD),
Calls.argument(2, IRDataType.LONG)
)
)
val fcallArgs = FunctionCallArgs(args, emptyList())
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=1, immediate=100),
IRInstruction(Opcode.SYSCALL, immediate = 0, fcallArgs = fcallArgs)
IRInstructions.load(IRDataType.WORD, 1, 100),
IRInstructions.call(Opcode.SYSCALL, callSite)
))
RegisterPacker.pack(irProg)
// usedRegisters() must not throw (type consistency within chunk)
val used = irProg.registersUsed()
val regs = (used.readRegs.keys + used.writeRegs.keys).map { it.value }.toSet()
val regs = (used.readRegs.keys + used.writeRegs.keys).map { it.num }.toSet()
// Must have at least 2 distinct packed slots (WORD and LONG cannot share)
regs.size shouldBe 2
}
test("fcallArgs returns with mixed types must not share same slot") {
test("call results with mixed types must not share same slot") {
// Same as above but for return registers
val returns = listOf(
FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(1), null, null),
FunctionCallArgs.RegSpec(IRDataType.LONG, RegisterNum(2), null, null)
val callSite = CallSite(
CallTarget.SystemCall(0),
results = listOf(
Calls.result(1, IRDataType.WORD),
Calls.result(2, IRDataType.LONG)
)
)
val fcallArgs = FunctionCallArgs(emptyList(), returns)
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.SYSCALL, immediate = 0, fcallArgs = fcallArgs),
IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=1, labelSymbol="s"),
IRInstruction(Opcode.STOREM, IRDataType.LONG, reg1=2, labelSymbol="s2")
IRInstructions.call(Opcode.SYSCALL, callSite),
IRInstructions.storeMemory(Opcode.STOREM, IRDataType.WORD, 1, IRMemory.direct("s")),
IRInstructions.storeMemory(Opcode.STOREM, IRDataType.LONG, 2, IRMemory.direct("s2"))
))
RegisterPacker.pack(irProg)
// usedRegisters() must not throw
val used = irProg.registersUsed()
val regs = (used.readRegs.keys + used.writeRegs.keys).map { it.value }.toSet()
val regs = (used.readRegs.keys + used.writeRegs.keys).map { it.num }.toSet()
regs.size shouldBe 2
}
@@ -307,10 +307,10 @@ class TestRegisterPacker: FunSpec({
// the same packed slot, because usedRegisters() enforces a single type
// per register number within a chunk.
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=1, immediate=100),
IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=1, labelSymbol="s"),
IRInstruction(Opcode.LOAD, IRDataType.LONG, reg1=2, immediate=0x10000),
IRInstruction(Opcode.STOREM, IRDataType.LONG, reg1=2, labelSymbol="s2")
IRInstructions.load(IRDataType.WORD, 1, 100),
IRInstructions.storeMemory(Opcode.STOREM, IRDataType.WORD, 1, IRMemory.direct("s")),
IRInstructions.load(IRDataType.LONG, 2, 0x10000),
IRInstructions.storeMemory(Opcode.STOREM, IRDataType.LONG, 2, IRMemory.direct("s2"))
))
RegisterPacker.pack(irProg)
// usedRegisters() must not throw
@@ -352,15 +352,15 @@ class TestRegisterPacker: FunSpec({
// Sub 1: POINTER interval (gets slot ≥ startSlot), and then the same slot again with WORD
val c1 = IRCodeChunk("p8b_main.sub1", null)
c1 += IRInstruction(Opcode.LOAD, IRDataType.POINTER, reg1=100, immediate=0x1000)
c1 += IRInstruction(Opcode.STOREM, IRDataType.POINTER, reg1=100, labelSymbol="sp")
c1 += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=101, immediate=200)
c1 += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=101, labelSymbol="sw")
c1 += IRInstructions.load(IRDataType.POINTER, 100, 0x1000)
c1 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.POINTER, 100, IRMemory.direct("sp"))
c1 += IRInstructions.load(IRDataType.WORD, 101, 200)
c1 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.WORD, 101, IRMemory.direct("sw"))
// Sub 2: LONG interval that should be able to share the same slot as sub 1's POINTER
val c2 = IRCodeChunk("p8b_main.sub2", null)
c2 += IRInstruction(Opcode.LOAD, IRDataType.LONG, reg1=200, immediate=0x100000)
c2 += IRInstruction(Opcode.STOREM, IRDataType.LONG, reg1=200, labelSymbol="sl")
c2 += IRInstructions.load(IRDataType.LONG, 200, 0x100000)
c2 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.LONG, 200, IRMemory.direct("sl"))
val irProg = makeMultiSubProgram(listOf(c1), listOf(c2))
RegisterPacker.pack(irProg)
@@ -377,20 +377,20 @@ class TestRegisterPacker: FunSpec({
// maxReg+1 to avoid this.
val irProg = makeIRProgram(listOf(
// Register 5 is used directly (not packed? actually it HAS an interval and gets packed)
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=5, immediate=99),
IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=5, labelSymbol="s"),
IRInstructions.load(IRDataType.BYTE, 5, 99),
IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 5, IRMemory.direct("s")),
// Register 1 and 2 can be packed
IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=1, immediate=100),
IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=1, labelSymbol="s2"),
IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=2, immediate=200),
IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=2, labelSymbol="s3")
IRInstructions.load(IRDataType.WORD, 1, 100),
IRInstructions.storeMemory(Opcode.STOREM, IRDataType.WORD, 1, IRMemory.direct("s2")),
IRInstructions.load(IRDataType.WORD, 2, 200),
IRInstructions.storeMemory(Opcode.STOREM, IRDataType.WORD, 2, IRMemory.direct("s3"))
))
RegisterPacker.pack(irProg)
// after packing:
// - all registers should have been packed to slots > 5 (since maxReg=5, startSlot=6)
// - no packed slot should equal an original register number
val used = irProg.registersUsed()
val allRegNums = (used.readRegs.keys + used.writeRegs.keys).map { it.value }.toSet()
val allRegNums = (used.readRegs.keys + used.writeRegs.keys).map { it.num }.toSet()
// packed slots start at 6, so no register should be 5 anymore
(5 in allRegNums) shouldBe false
// all registers should be >= 6
@@ -414,15 +414,15 @@ class TestRegisterPacker: FunSpec({
// Sub 1: uses r37 as POINTER
val sub1 = IRSubroutine("p8b_main.sub1", emptyList(), emptyList(), Position.DUMMY)
val c1 = IRCodeChunk("p8b_main.sub1", null)
c1 += IRInstruction(Opcode.LOAD, IRDataType.POINTER, reg1=37, immediate=0x1000)
c1 += IRInstruction(Opcode.STOREM, IRDataType.POINTER, reg1=37, labelSymbol="sp")
c1 += IRInstructions.load(IRDataType.POINTER, 37, 0x1000)
c1 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.POINTER, 37, IRMemory.direct("sp"))
sub1 += c1
// Sub 2: uses same r37 as BYTE (incompatible)
val sub2 = IRSubroutine("p8b_main.sub2", emptyList(), emptyList(), Position.DUMMY)
val c2 = IRCodeChunk("p8b_main.sub2", null)
c2 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=37, immediate=42)
c2 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=37, labelSymbol="sb")
c2 += IRInstructions.load(IRDataType.BYTE, 37, 42)
c2 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 37, IRMemory.direct("sb"))
sub2 += c2
block += sub1
@@ -460,13 +460,13 @@ class TestRegisterPacker: FunSpec({
val sub = IRSubroutine("p8b_main.sub1", emptyList(), emptyList(), Position.DUMMY)
val c1 = IRCodeChunk("p8b_main.sub1", null)
c1 += IRInstruction(Opcode.LOAD, IRDataType.POINTER, reg1=37, immediate=0x1000)
c1 += IRInstruction(Opcode.STOREM, IRDataType.POINTER, reg1=37, labelSymbol="sp")
c1 += IRInstructions.load(IRDataType.POINTER, 37, 0x1000)
c1 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.POINTER, 37, IRMemory.direct("sp"))
sub += c1
val c2 = IRCodeChunk("p8b_main.sub1.c2", c1)
c2 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=37, immediate=42)
c2 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=37, labelSymbol="sb")
c2 += IRInstructions.load(IRDataType.BYTE, 37, 42)
c2 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 37, IRMemory.direct("sb"))
sub += c2
block += sub
@@ -480,7 +480,7 @@ class TestRegisterPacker: FunSpec({
// registersUsed() in permissive mode: putIfAbsent, should not throw
val used = prog.registersUsed()
// r37 should still appear (was skipped by packer)
used.regsTypes.containsKey(RegisterNum(37)) shouldBe true
used.regsTypes.containsKey(VirtualRegister.int(37)) shouldBe true
}
test("disjoint intervals of same register must be merged into one contiguous range") {
@@ -501,20 +501,20 @@ class TestRegisterPacker: FunSpec({
// Chunk 1: write r1 (value should persist to chunk 3)
val c1 = IRCodeChunk("p8b_main.sub1", null)
c1 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=42)
c1 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=1, labelSymbol="s1")
c1 += IRInstructions.load(IRDataType.BYTE, 1, 42)
c1 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 1, IRMemory.direct("s1"))
sub += c1
// Chunk 2: write and read r2 (this interval falls in the gap between r1's uses)
val c2 = IRCodeChunk("p8b_main.sub1.c2", c1)
c2 += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=2, immediate=99)
c2 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=2, labelSymbol="s2")
c2 += IRInstructions.load(IRDataType.BYTE, 2, 99)
c2 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 2, IRMemory.direct("s2"))
sub += c2
// Chunk 3: read r1 (uses the value from chunk 1)
val c3 = IRCodeChunk("p8b_main.sub1.c3", c2)
c3 += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=3, reg2=1)
c3 += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=3, labelSymbol="s3")
c3 += IRInstructions.move(IRDataType.BYTE, 3, 1)
c3 += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, 3, IRMemory.direct("s3"))
sub += c3
block += sub
@@ -528,12 +528,10 @@ class TestRegisterPacker: FunSpec({
// merged interval spans [0..?] covering the gap, preventing r2
// from sharing the same slot.
val instructions = prog.instructions()
// Find the register numbers used for the STOREM to s1 (was r1) and s2 (was r2)
val s1Reg = instructions.first { it.labelSymbol=="s1" }.reg1
val s2Reg = instructions.first { it.labelSymbol=="s2" }.reg1
// Find the register operands used for the STOREM to s1 (was r1) and s2 (was r2)
val s1Reg = instructions.first { it.memory?.symbolName=="s1" }.requireSrcA().register
val s2Reg = instructions.first { it.memory?.symbolName=="s2" }.requireSrcA().register
// They must be different (can't share a slot)
s1Reg shouldNotBe null
s2Reg shouldNotBe null
s1Reg shouldNotBe s2Reg
}
})
+9 -7
View File
@@ -11,6 +11,8 @@ import prog8.codegen.vm.VmAssemblyProgram
import prog8.codegen.vm.VmCodeGen
import prog8.intermediate.IRSubroutine
import prog8.intermediate.Opcode
import prog8.intermediate.codeAddress
import prog8.intermediate.requireCallSite
import prog8.intermediate.toAddress
import prog8tests.helpers.ErrorReporterForTests
@@ -208,12 +210,12 @@ class TestVmCodeGen: FunSpec({
andrCount shouldBe 1
xorrCount shouldBe 1
// Verify that the immediate-only variants are NOT used with reg2
val wrongAdd = instructions.any { it.opcode == Opcode.ADD && it.reg2 != null }
val wrongSub = instructions.any { it.opcode == Opcode.SUB && it.reg2 != null }
val wrongOr = instructions.any { it.opcode == Opcode.OR && it.reg2 != null }
val wrongAnd = instructions.any { it.opcode == Opcode.AND && it.reg2 != null }
val wrongXor = instructions.any { it.opcode == Opcode.XOR && it.reg2 != null }
// Verify that the immediate-only variants are NOT used with a register source
val wrongAdd = instructions.any { it.opcode == Opcode.ADD && it.srcA != null }
val wrongSub = instructions.any { it.opcode == Opcode.SUB && it.srcA != null }
val wrongOr = instructions.any { it.opcode == Opcode.OR && it.srcA != null }
val wrongAnd = instructions.any { it.opcode == Opcode.AND && it.srcA != null }
val wrongXor = instructions.any { it.opcode == Opcode.XOR && it.srcA != null }
(wrongAdd || wrongSub || wrongOr || wrongAnd || wrongXor) shouldBe false
}
@@ -645,7 +647,7 @@ class TestVmCodeGen: FunSpec({
irChunks[0].instructions.size shouldBe 1
val callInstr = irChunks[0].instructions[0]
callInstr.opcode shouldBe Opcode.CALL
callInstr.address shouldBe 0x5000u.toAddress()
callInstr.requireCallSite().codeReference shouldBe codeAddress(0x5000u.toAddress())
}
test("ir codegen for target 'virtual' produces no prefixed names") {