mirror of
https://github.com/irmen/prog8.git
synced 2025-02-22 16:29:05 +00:00
IR: fix problems with symbol offsets and unused subroutines/chunks
This commit is contained in:
parent
3b199a2a87
commit
968609d06d
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
}
|
||||
})
|
||||
|
@ -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++
|
||||
}
|
||||
|
||||
|
@ -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() == ",")
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user