diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt index 279d855be..8b5a0dd93 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt @@ -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 { diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt index f9e0e043f..5ec7d57b5 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt @@ -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) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt index 01956dbfa..a61c553cb 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt @@ -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) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt index c3c2b48f5..8d97d03b9 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt @@ -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) } diff --git a/compiler/test/TestCallgraph.kt b/compiler/test/TestCallgraph.kt index a4ff1706f..85164c87b 100644 --- a/compiler/test/TestCallgraph.kt +++ b/compiler/test/TestCallgraph.kt @@ -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()) } }) diff --git a/examples/test.p8 b/examples/test.p8 index 99f2b47ad..ef2797531 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -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++ } diff --git a/intermediate/src/prog8/intermediate/IRInstructions.kt b/intermediate/src/prog8/intermediate/IRInstructions.kt index dee803f19..15ce21f5e 100644 --- a/intermediate/src/prog8/intermediate/IRInstructions.kt +++ b/intermediate/src/prog8/intermediate/IRInstructions.kt @@ -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() == ",") diff --git a/intermediate/src/prog8/intermediate/Utils.kt b/intermediate/src/prog8/intermediate/Utils.kt index 0d2c70d1b..cd7ef7b49 100644 --- a/intermediate/src/prog8/intermediate/Utils.kt +++ b/intermediate/src/prog8/intermediate/Utils.kt @@ -196,12 +196,20 @@ fun parseIRCodeLine(line: String): Either { 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( diff --git a/intermediate/test/TestInstructions.kt b/intermediate/test/TestInstructions.kt index 2897304fc..2bdc0f897 100644 --- a/intermediate/test/TestInstructions.kt +++ b/intermediate/test/TestInstructions.kt @@ -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("labelsymbol offset inconsistency") { + IRInstruction(Opcode.ADDR, IRDataType.BYTE, reg1 = 1, reg2 = 2, symbolOffset = 99) + } + } }) diff --git a/virtualmachine/src/prog8/vm/VmProgramLoader.kt b/virtualmachine/src/prog8/vm/VmProgramLoader.kt index d9cd40451..9f9feeec3 100644 --- a/virtualmachine/src/prog8/vm/VmProgramLoader.kt +++ b/virtualmachine/src/prog8/vm/VmProgramLoader.kt @@ -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) } }