IR: fix problems with symbol offsets and unused subroutines/chunks

This commit is contained in:
Irmen de Jong 2024-01-13 13:55:16 +01:00
parent 3b199a2a87
commit 968609d06d
10 changed files with 131 additions and 52 deletions

View File

@ -229,11 +229,11 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
if(fixedIndex!=null) {
val chunk = IRCodeChunk(null, null).also {
if(targetArray.splitWords) {
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, immediate = arrayLength, labelSymbol = "${variable}_lsb+$fixedIndex")
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, immediate = arrayLength, labelSymbol = "${variable}_msb+$fixedIndex")
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, immediate = arrayLength, labelSymbol = "${variable}_lsb", symbolOffset = fixedIndex)
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, immediate = arrayLength, labelSymbol = "${variable}_msb", symbolOffset = fixedIndex)
}
else
it += IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = "$variable+${fixedIndex*itemsize}")
it += IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = variable, symbolOffset = fixedIndex*itemsize)
}
result += chunk
} else {
@ -253,7 +253,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
if(fixedIndex!=null) {
val offset = fixedIndex*itemsize
val chunk = IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, targetDt, fpReg1 = valueFpRegister, labelSymbol = "$variable+$offset")
it += IRInstruction(Opcode.STOREM, targetDt, fpReg1 = valueFpRegister, labelSymbol = variable, symbolOffset = offset)
}
result += chunk
} else {
@ -268,12 +268,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val chunk = IRCodeChunk(null, null).also {
if(targetArray.splitWords) {
val msbReg = codeGen.registers.nextFree()
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = valueRegister, immediate = arrayLength, labelSymbol = "${variable}_lsb+$fixedIndex")
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = valueRegister, immediate = arrayLength, labelSymbol = "${variable}_lsb", symbolOffset = fixedIndex)
it += IRInstruction(Opcode.MSIG, IRDataType.BYTE, reg1 = msbReg, reg2 = valueRegister)
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = msbReg, immediate = arrayLength, labelSymbol = "${variable}_msb+$fixedIndex")
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = msbReg, immediate = arrayLength, labelSymbol = "${variable}_msb", symbolOffset = fixedIndex)
}
else
it += IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = "$variable+${fixedIndex*itemsize}")
it += IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = variable, symbolOffset = fixedIndex*itemsize)
}
result += chunk
} else {

View File

@ -188,8 +188,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val memOffset = (arrayIx.index as PtNumber).number.toInt()
result += IRCodeChunk(null, null).also {
val tmpRegMsb = codeGen.registers.nextFree()
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=tmpRegMsb, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_msb+$memOffset")
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=resultRegister, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb+$memOffset")
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=tmpRegMsb, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_msb", symbolOffset = memOffset)
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=resultRegister, immediate = arrayLength, labelSymbol= "${arrayVarSymbol}_lsb", symbolOffset = memOffset)
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=tmpRegMsb, reg3=resultRegister)
}
} else {
@ -207,14 +207,14 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
var resultFpRegister = -1
if(arrayIx.index is PtNumber) {
val memOffset = ((arrayIx.index as PtNumber).number.toInt() * eltSize).toString()
val memOffset = ((arrayIx.index as PtNumber).number.toInt() * eltSize)
if(vmDt==IRDataType.FLOAT) {
resultFpRegister = codeGen.registers.nextFreeFloat()
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1=resultFpRegister, labelSymbol = "$arrayVarSymbol+$memOffset"), null)
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1=resultFpRegister, labelSymbol = arrayVarSymbol, symbolOffset = memOffset), null)
}
else {
resultRegister = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOADM, vmDt, reg1=resultRegister, labelSymbol = "$arrayVarSymbol+$memOffset"), null)
addInstr(result, IRInstruction(Opcode.LOADM, vmDt, reg1=resultRegister, labelSymbol = arrayVarSymbol, symbolOffset = memOffset), null)
}
} else {
val tr = translateExpression(arrayIx.index)

View File

@ -143,19 +143,10 @@ class IRCodeGen(
(idx, instr) ->
val symbolExpr = instr.labelSymbol
if(symbolExpr!=null) {
val symbol: String
val index: UInt
if('+' in symbolExpr) {
val operands = symbolExpr.split('+', )
symbol = operands[0]
index = operands[1].toUInt()
} else {
symbol = symbolExpr
index = 0u
}
val target = symbolTable.flat[symbol]
val index = instr.labelSymbolOffset ?: 0
val target = symbolTable.flat[symbolExpr]
if (target is StMemVar) {
replacements.add(Triple(chunk, idx, target.address+index))
replacements.add(Triple(chunk, idx, target.address+index.toUInt()))
}
}
}
@ -1451,21 +1442,21 @@ class IRCodeGen(
when(postIncrDecr.operator) {
"++" -> {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = "${variable}_lsb+$fixedIndex")
it += IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = "${variable}_lsb", symbolOffset = fixedIndex)
it += IRInstruction(Opcode.BSTNE, labelSymbol = skipLabel)
it += IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = "${variable}_msb+$fixedIndex")
it += IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = "${variable}_msb", symbolOffset = fixedIndex)
}
result += IRCodeChunk(skipLabel, null)
}
"--" -> {
val valueReg=registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=valueReg, labelSymbol = "${variable}_lsb+$fixedIndex")
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=valueReg, labelSymbol = "${variable}_lsb", symbolOffset = fixedIndex)
it += IRInstruction(Opcode.BSTNE, labelSymbol = skipLabel)
it += IRInstruction(Opcode.DECM, IRDataType.BYTE, labelSymbol = "${variable}_msb+$fixedIndex")
it += IRInstruction(Opcode.DECM, IRDataType.BYTE, labelSymbol = "${variable}_msb", symbolOffset = fixedIndex)
}
result += IRCodeChunk(skipLabel, null).also {
it += IRInstruction(Opcode.DECM, IRDataType.BYTE, labelSymbol = "${variable}_lsb+$fixedIndex")
it += IRInstruction(Opcode.DECM, IRDataType.BYTE, labelSymbol = "${variable}_lsb", symbolOffset = fixedIndex)
}
}
else -> throw AssemblyError("weird operator")
@ -1556,7 +1547,7 @@ class IRCodeGen(
it += IRInstruction(Opcode.STOREIX, irDt, reg1 = dataReg, reg2 = indexReg, labelSymbol = variable)
}
} else {
addInstr(result, IRInstruction(operationMem, irDt, labelSymbol = "$variable+$offset"), null)
addInstr(result, IRInstruction(operationMem, irDt, labelSymbol = variable, symbolOffset = offset), null)
}
} else {
val indexTr = expressionEval.translateExpression(array.index)

View File

@ -167,7 +167,13 @@ class IRUnusedCodeRemover(
it.next?.let { next -> new += next }
it.instructions.forEach { instr ->
if (instr.branchTarget == null)
instr.labelSymbol?.let { label -> allLabeledChunks[label]?.let { chunk -> new += chunk } }
instr.labelSymbol?.let { label ->
val chunk = allLabeledChunks[label.substringBeforeLast('.')]
if(chunk!=null)
new+=chunk
else
allLabeledChunks[label]?.let { new += it }
}
else
new += instr.branchTarget!!
}
@ -214,6 +220,16 @@ class IRUnusedCodeRemover(
linkedChunks += chunk
}
// make sure that chunks that are only used as a prefix of a label, are also marked as linked
linkedChunks.forEach { chunk ->
chunk.instructions.forEach {
if(it.labelSymbol!=null) {
val chunkName = it.labelSymbol!!.substringBeforeLast('.')
allLabeledChunks[chunkName]?.let { linkedChunks+=it }
}
}
}
return removeUnlinkedChunks(linkedChunks)
}

View File

@ -15,7 +15,9 @@ import prog8.code.target.C64Target
import prog8.code.target.VMTarget
import prog8.compiler.CallGraph
import prog8.parser.Prog8Parser.parseModule
import prog8.vm.VmRunner
import prog8tests.helpers.*
import kotlin.io.path.readText
class TestCallgraph: FunSpec({
test("testGraphForEmptySubs") {
@ -224,13 +226,13 @@ class TestCallgraph: FunSpec({
errors.errors.size shouldBe 0
errors.warnings.size shouldBe 0
}
test("subs that aren't called but only used as scope aren't unused") {
test("subs that aren't called but only used as scope aren't unused (6502)") {
val src="""
main {
sub start() {
cx16.r0L = main.scopesub.variable
cx16.r0L = main.scopesub.array[1]
cx16.r1L = main.scopesub.array[1]
cx16.r0++
}
@ -238,14 +240,49 @@ main {
ubyte variable
ubyte[] array = [1,2,3]
cx16.r0++
variable++
}
}"""
val result = compileText(VMTarget(), true, src)!!
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
val result = compileText(C64Target(), true, src, errors=errors)!!
val callgraph = CallGraph(result.compilerAst)
val scopeSub = result.compilerAst.entrypoint.lookup(listOf("main", "scopesub")) as Subroutine
scopeSub.name shouldBe "scopesub"
callgraph.notCalledButReferenced shouldContain scopeSub
callgraph.unused(scopeSub) shouldBe false
errors.warnings.any { "unused" in it } shouldBe false
errors.infos.any { "unused" in it } shouldBe false
}
test("subs that aren't called but only used as scope aren't unused (IR/VM)") {
val src="""
main {
sub start() {
cx16.r0L = main.scopesub.variable
cx16.r1L = main.scopesub.array[1]
cx16.r0++
}
sub scopesub() {
ubyte variable
ubyte[] array = [1,2,3]
variable++
}
}"""
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
val result = compileText(VMTarget(), true, src, errors=errors)!!
val callgraph = CallGraph(result.compilerAst)
val scopeSub = result.compilerAst.entrypoint.lookup(listOf("main", "scopesub")) as Subroutine
scopeSub.name shouldBe "scopesub"
callgraph.notCalledButReferenced shouldContain scopeSub
callgraph.unused(scopeSub) shouldBe false
errors.warnings.any { "unused" in it } shouldBe false
errors.infos.any { "unused" in it } shouldBe false
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
VmRunner().runProgram(virtfile.readText())
}
})

View File

@ -1,12 +1,11 @@
%import textio
%import palette
%zeropage basicsafe
%option no_sysinit
main {
sub start() {
cx16.r0L = main.dummy.variable
cx16.r0L = main.dummy.array[1]
cx16.r1L = main.dummy.array[1]
cx16.r0++
}

View File

@ -732,6 +732,7 @@ data class IRInstruction(
val immediateFp: Double?=null,
val address: Int?=null, // 0-$ffff
val labelSymbol: String?=null, // symbolic label name as alternative to address (so only for Branch/jump/call Instructions!)
private val symbolOffset: Int? = null, // offset to add on labelSymbol (used to index into an array variable)
var branchTarget: IRCodeChunkBase? = null, // Will be linked after loading in IRProgram.linkChunks()! This is the chunk that the branch labelSymbol points to.
val fcallArgs: FunctionCallArgs? = null // will be set for the CALL and SYSCALL instructions.
) {
@ -742,9 +743,16 @@ data class IRInstruction(
val reg3direction: OperandDirection
val fpReg1direction: OperandDirection
val fpReg2direction: OperandDirection
val labelSymbolOffset = if(symbolOffset==0) null else symbolOffset
init {
require(labelSymbol?.first()!='_') {"label/symbol should not start with underscore $labelSymbol"}
if(labelSymbol!=null) {
require(labelSymbol.first() != '_') { "label/symbol should not start with underscore $labelSymbol" }
require(labelSymbol.all { it.isJavaIdentifierStart() || it.isJavaIdentifierPart() || it=='.' }) {
"label/symbol contains invalid character $labelSymbol"
}
}
if(labelSymbolOffset!=null) require(labelSymbolOffset>0 && labelSymbol!=null) {"labelsymbol offset inconsistency"}
require(reg1==null || reg1 in 0..65536) {"reg1 out of bounds"}
require(reg2==null || reg2 in 0..65536) {"reg2 out of bounds"}
require(reg3==null || reg3 in 0..65536) {"reg3 out of bounds"}
@ -941,7 +949,12 @@ data class IRInstruction(
if(this.fcallArgs!=null) {
immediate?.let { result.add(it.toHex()) } // syscall
labelSymbol?.let { result.add(it) } // regular subroutine call
if(labelSymbol!=null) {
// regular subroutine call
result.add(labelSymbol)
if(labelSymbolOffset!=null)
result.add("+$labelSymbolOffset")
}
address?.let { result.add(address.toHex()) } // romcall
result.add("(")
fcallArgs.arguments.forEach {
@ -1028,6 +1041,8 @@ data class IRInstruction(
}
labelSymbol?.let {
result.add(it)
if(labelSymbolOffset!=null)
result.add("+$labelSymbolOffset")
}
}
if(result.last() == ",")

View File

@ -196,12 +196,20 @@ fun parseIRCodeLine(line: String): Either<IRInstruction, String> {
if(format.address!=OperandDirection.UNUSED && address==null && labelSymbol==null)
throw IRParseException("requires address or symbol for $line")
var offset: Int? = null
if(labelSymbol!=null) {
if (labelSymbol!![0] == 'r' && labelSymbol!![1].isDigit())
throw IRParseException("labelsymbol confused with register?: $labelSymbol")
if('+' in labelSymbol!!) {
val offsetStr = labelSymbol!!.substringAfterLast('+')
if (offsetStr.isNotEmpty()) {
offset = offsetStr.toInt()
labelSymbol = labelSymbol!!.substringBeforeLast('+')
}
}
}
return left(IRInstruction(opcode, type, reg1, reg2, reg3, fpReg1, fpReg2, immediateInt, immediateFp, address, labelSymbol = labelSymbol))
return left(IRInstruction(opcode, type, reg1, reg2, reg3, fpReg1, fpReg2, immediateInt, immediateFp, address, labelSymbol = labelSymbol, symbolOffset = offset))
}
private class ParsedCall(

View File

@ -131,5 +131,19 @@ class TestInstructions: FunSpec({
require(format.fpReg2==OperandDirection.UNUSED || format.fpReg2==OperandDirection.READ) {"fpReg2 can only be used as input"}
}
}
}
}
test("with symbol offset") {
val i1 = IRInstruction(Opcode.ADDM, IRDataType.BYTE, reg1 = 1, labelSymbol = "symbol", symbolOffset = 99)
i1.labelSymbol shouldBe "symbol"
i1.labelSymbolOffset shouldBe 99
val i2 = IRInstruction(Opcode.ADDM, IRDataType.BYTE, reg1 = 1, labelSymbol = "symbol", symbolOffset = 0)
i2.labelSymbol shouldBe "symbol"
i2.labelSymbolOffset shouldBe null
shouldThrowWithMessage<IllegalArgumentException>("labelsymbol offset inconsistency") {
IRInstruction(Opcode.ADDR, IRDataType.BYTE, reg1 = 1, reg2 = 2, symbolOffset = 99)
}
}
})

View File

@ -148,26 +148,25 @@ class VmProgramLoader {
for((ref, label) in placeholders) {
val (chunk, line) = ref
val replacement = variableAddresses[label]
val instr = chunk.instructions[line]
val offset = instr.labelSymbolOffset ?: 0
if(replacement==null) {
// it could be an address + index: symbol+42
if('+' in label) {
val (symbol, indexStr) = label.split('+')
val index = indexStr.toInt()
val address = variableAddresses.getValue(symbol) + index
chunk.instructions[line] = chunk.instructions[line].copy(address = address)
if(offset>0) {
val address = variableAddresses.getValue(label) + offset
chunk.instructions[line] = instr.copy(address = address)
} else {
// placeholder is not a variable, so it must be a label of a code chunk instead
val target: IRCodeChunk? = chunks.firstOrNull { it.label==label }
val opcode = chunk.instructions[line].opcode
if(target==null)
throw IRParseException("placeholder not found in variables nor labels: $label")
else if(opcode in OpcodesThatBranch)
chunk.instructions[line] = chunk.instructions[line].copy(branchTarget = target, address = null)
else if(instr.opcode in OpcodesThatBranch)
chunk.instructions[line] = instr.copy(branchTarget = target, address = null)
else
throw IRParseException("vm cannot yet load a label address as a value: ${chunk.instructions[line]}")
throw IRParseException("vm cannot yet load a label address as a value: ${instr}")
}
} else {
chunk.instructions[line] = chunk.instructions[line].copy(address = replacement)
chunk.instructions[line] = instr.copy(address = replacement + offset)
}
}