mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
structured IR
This commit is contained in:
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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
@@ -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") {
|
||||
|
||||
Reference in new issue
Block a user