diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt index affaeea0a..e93b58972 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt @@ -148,6 +148,7 @@ class IRUnusedCodeRemover( val entrypointSub = irprog.blocks.single { it.label=="main" } .children.single { it is IRSubroutine && it.label=="main.start" } val reachable = mutableSetOf((entrypointSub as IRSubroutine).chunks.first()) + reachable.add(irprog.globalInits) // all chunks referenced in array initializer values are also 'reachable': irprog.st.allVariables() @@ -230,6 +231,7 @@ class IRUnusedCodeRemover( } } + linkedChunks.add(irprog.globalInits) return removeUnlinkedChunks(linkedChunks) } diff --git a/compiler/test/TestOptimization.kt b/compiler/test/TestOptimization.kt index 04c3ae7e1..45724c698 100644 --- a/compiler/test/TestOptimization.kt +++ b/compiler/test/TestOptimization.kt @@ -69,6 +69,25 @@ class TestOptimization: FunSpec({ } } + test("don't remove empty subroutine if it's referenced in vardecl") { + val sourcecode = """ +main { + ubyte tw = other.width() + sub start() { + tw++ + } +} + +other { + sub width() -> ubyte { + cx16.r0++ + return 80 + } +}""" + compileText(C64Target(), true, sourcecode, writeAssembly = true) shouldNotBe null + compileText(VMTarget(), true, sourcecode, writeAssembly = true) shouldNotBe null + } + test("generated constvalue from typecast inherits proper parent linkage") { val number = NumericLiteral(DataType.UBYTE, 11.0, Position.DUMMY) val tc = TypecastExpression(number, DataType.BYTE, false, Position.DUMMY) diff --git a/compiler/test/vm/TestCompilerVirtual.kt b/compiler/test/vm/TestCompilerVirtual.kt index c8ec0eff7..3d81e8298 100644 --- a/compiler/test/vm/TestCompilerVirtual.kt +++ b/compiler/test/vm/TestCompilerVirtual.kt @@ -59,10 +59,11 @@ main { VmRunner().runProgram(virtfile.readText()) } - test("compile virtual: str args and return type") { + test("compile virtual: str args and return type, and global var init") { val src = """ main { - + ubyte @shared dvar = test.dummy() + sub start() { sub testsub(str s1) -> str { return "result" @@ -70,6 +71,13 @@ main { uword result = testsub("arg") } +} + +test { + sub dummy() -> ubyte { + cx16.r0++ + return 80 + } }""" val target = VMTarget() var result = compileText(target, false, src, writeAssembly = true)!! @@ -467,4 +475,6 @@ main { compileText(VMTarget(), true, src, writeAssembly = true) shouldNotBe null } + + }) \ No newline at end of file diff --git a/compilerAst/src/prog8/compiler/CallGraph.kt b/compilerAst/src/prog8/compiler/CallGraph.kt index 60a25226b..28218be75 100644 --- a/compilerAst/src/prog8/compiler/CallGraph.kt +++ b/compilerAst/src/prog8/compiler/CallGraph.kt @@ -69,10 +69,11 @@ class CallGraph(private val program: Program) : IAstVisitor { override fun visit(functionCallExpr: FunctionCallExpression) { val otherSub = functionCallExpr.target.targetSubroutine(program) if (otherSub != null) { - functionCallExpr.definingSubroutine?.let { thisSub -> - calls[thisSub] = calls.getValue(thisSub) + otherSub - calledBy[otherSub] = calledBy.getValue(otherSub) + functionCallExpr + val definingSub = functionCallExpr.definingSubroutine + if(definingSub!=null) { + calls[definingSub] = calls.getValue(definingSub) + otherSub } + calledBy[otherSub] = calledBy.getValue(otherSub) + functionCallExpr } super.visit(functionCallExpr) } diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 5485488c1..c5d314916 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -1,8 +1,6 @@ TODO ==== -fix ubyte width = text.width() text.width() gets removed as 'unused subroutine' - vm textelite: after 1 galaxy jump: galaxy maps shows wrong planet name until you redraw them a second time. Current planet name changes when showing maps and asking planet i)nfo! ... diff --git a/examples/test.p8 b/examples/test.p8 index f3f7daa32..cceb7b1b4 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,14 +1,16 @@ +%import textio %zeropage basicsafe %option no_sysinit main { - ubyte tw = text.width() + ubyte tw = other.width() sub start() { tw++ + txt.print_uw(tw) } } -text { +other { sub width() -> ubyte { cx16.r0++ return 80 diff --git a/intermediate/src/prog8/intermediate/IRProgram.kt b/intermediate/src/prog8/intermediate/IRProgram.kt index dcb003900..f72d536da 100644 --- a/intermediate/src/prog8/intermediate/IRProgram.kt +++ b/intermediate/src/prog8/intermediate/IRProgram.kt @@ -57,7 +57,10 @@ class IRProgram(val name: String, fun allSubs(): Sequence = blocks.asSequence().flatMap { it.children.filterIsInstance() } fun foreachSub(operation: (sub: IRSubroutine) -> Unit) = allSubs().forEach { operation(it) } - fun foreachCodeChunk(operation: (chunk: IRCodeChunkBase) -> Unit) = allSubs().flatMap { it.chunks }.forEach { operation(it) } + fun foreachCodeChunk(operation: (chunk: IRCodeChunkBase) -> Unit) { + allSubs().flatMap { it.chunks }.forEach { operation(it) } + operation(globalInits) + } fun getChunkWithLabel(label: String): IRCodeChunkBase { for(sub in allSubs()) { for(chunk in sub.chunks) { @@ -123,44 +126,46 @@ class IRProgram(val name: String, } } + fun linkCodeChunk(chunk: IRCodeChunk, next: IRCodeChunkBase?) { + // link sequential chunks + val jump = chunk.instructions.lastOrNull()?.opcode + if (jump == null || jump !in OpcodesThatJump) { + // no jump at the end, so link to next chunk (if it exists) + if(next!=null) { + if (next is IRCodeChunk && chunk.instructions.lastOrNull()?.opcode !in OpcodesThatJump) + chunk.next = next + else if(next is IRInlineAsmChunk) + chunk.next = next + else if(next is IRInlineBinaryChunk) + chunk.next =next + else + throw AssemblyError("code chunk followed by invalid chunk type $next") + } + } + + // link all jump and branching instructions to their target + chunk.instructions.forEach { + if(it.opcode in OpcodesThatBranch && it.opcode!=Opcode.JUMPI && it.opcode!=Opcode.RETURN && it.opcode!=Opcode.RETURNR && it.labelSymbol!=null) { + if(it.labelSymbol.startsWith('$') || it.labelSymbol.first().isDigit()) { + // it's a call to an address (romsub most likely) + require(it.address!=null) + } else { + it.branchTarget = labeledChunks.getValue(it.labelSymbol) + } + } + } + } + fun linkSubroutineChunks(sub: IRSubroutine) { sub.chunks.withIndex().forEach { (index, chunk) -> - fun nextChunk(): IRCodeChunkBase? = if(index { - // link sequential chunks - val jump = chunk.instructions.lastOrNull()?.opcode - if (jump == null || jump !in OpcodesThatJump) { - // no jump at the end, so link to next chunk (if it exists) - val next = nextChunk() - if(next!=null) { - if (next is IRCodeChunk && chunk.instructions.lastOrNull()?.opcode !in OpcodesThatJump) - chunk.next = next - else if(next is IRInlineAsmChunk) - chunk.next = next - else if(next is IRInlineBinaryChunk) - chunk.next =next - else - throw AssemblyError("code chunk followed by invalid chunk type $next") - } - } - - // link all jump and branching instructions to their target - chunk.instructions.forEach { - if(it.opcode in OpcodesThatBranch && it.opcode!=Opcode.JUMPI && it.opcode!=Opcode.RETURN && it.opcode!=Opcode.RETURNR && it.labelSymbol!=null) { - if(it.labelSymbol.startsWith('$') || it.labelSymbol.first().isDigit()) { - // it's a call to an address (romsub most likely) - require(it.address!=null) - } else { - it.branchTarget = labeledChunks.getValue(it.labelSymbol) - } - } - } + linkCodeChunk(chunk, next) } is IRInlineAsmChunk -> { - val next = nextChunk() if(next!=null) { val lastInstr = chunk.instructions.lastOrNull() if(lastInstr==null || lastInstr.opcode !in OpcodesThatJump) @@ -184,9 +189,64 @@ class IRProgram(val name: String, } } } + linkCodeChunk(globalInits, globalInits.next) } fun validate() { + fun validateChunk(chunk: IRCodeChunkBase, sub: IRSubroutine?, emptyChunkIsAllowed: Boolean) { + if (chunk is IRCodeChunk) { + if(!emptyChunkIsAllowed) + require(chunk.instructions.isNotEmpty() || chunk.label != null) + if(chunk.instructions.lastOrNull()?.opcode in OpcodesThatJump) + require(chunk.next == null) { "chunk ending with a jump or return shouldn't be linked to next" } + else if (sub!=null) { + // if chunk is NOT the last in the block, it needs to link to next. + val isLast = sub.chunks.last() === chunk + require(isLast || chunk.next != null) { "chunk needs to be linked to next" } + } + } + else { + require(chunk.instructions.isEmpty()) + if(chunk is IRInlineAsmChunk) + require(!chunk.isIR) { "inline IR-asm should have been converted into regular code chunk"} + } + chunk.instructions.withIndex().forEach { (index, instr) -> + if(instr.labelSymbol!=null && instr.opcode in OpcodesThatBranch) { + if(instr.opcode==Opcode.JUMPI) { + val pointervar = st.lookup(instr.labelSymbol)!! + when(pointervar) { + is IRStStaticVariable -> require(pointervar.dt==DataType.UWORD) + is IRStMemVar -> require(pointervar.dt==DataType.UWORD) + else -> throw AssemblyError("weird pointervar type") + } + } + else if(!instr.labelSymbol.startsWith('$') && !instr.labelSymbol.first().isDigit()) + require(instr.branchTarget != null) { "branching instruction to label should have branchTarget set" } + } + + if(instr.opcode==Opcode.PREPARECALL) { + var i = index+1 + var instr2 = chunk.instructions[i] + val registers = mutableSetOf() + while(instr2.opcode!=Opcode.SYSCALL && instr2.opcode!=Opcode.CALL && i if(block.isNotEmpty()) { block.children.filterIsInstance().forEach { chunk -> @@ -197,57 +257,7 @@ class IRProgram(val name: String, if(sub.chunks.isNotEmpty()) { require(sub.chunks.first().label == sub.label) { "first chunk in subroutine should have sub name (label) as its label" } } - sub.chunks.forEach { chunk -> - if (chunk is IRCodeChunk) { - require(chunk.instructions.isNotEmpty() || chunk.label != null) - if(chunk.instructions.lastOrNull()?.opcode in OpcodesThatJump) - require(chunk.next == null) { "chunk ending with a jump or return shouldn't be linked to next" } - else { - // if chunk is NOT the last in the block, it needs to link to next. - val isLast = sub.chunks.last() === chunk - require(isLast || chunk.next != null) { "chunk needs to be linked to next" } - } - } - else { - require(chunk.instructions.isEmpty()) - if(chunk is IRInlineAsmChunk) - require(!chunk.isIR) { "inline IR-asm should have been converted into regular code chunk"} - } - chunk.instructions.withIndex().forEach { (index, instr) -> - if(instr.labelSymbol!=null && instr.opcode in OpcodesThatBranch) { - if(instr.opcode==Opcode.JUMPI) { - val pointervar = st.lookup(instr.labelSymbol)!! - when(pointervar) { - is IRStStaticVariable -> require(pointervar.dt==DataType.UWORD) - is IRStMemVar -> require(pointervar.dt==DataType.UWORD) - else -> throw AssemblyError("weird pointervar type") - } - } - else if(!instr.labelSymbol.startsWith('$') && !instr.labelSymbol.first().isDigit()) - require(instr.branchTarget != null) { "branching instruction to label should have branchTarget set" } - } - - if(instr.opcode==Opcode.PREPARECALL) { - var i = index+1 - var instr2 = chunk.instructions[i] - val registers = mutableSetOf() - while(instr2.opcode!=Opcode.SYSCALL && instr2.opcode!=Opcode.CALL && i if (ins.labelSymbol != null && ins.opcode !in OpcodesThatBranch) require(ins.address != null) { "instruction with labelSymbol for a var should have value set to the memory address" } @@ -78,8 +78,8 @@ class VmProgramLoader { } private fun phase2relinkReplacedChunks( - replacements: MutableList>, - programChunks: MutableList + replacements: List>, + programChunks: List ) { replacements.forEach { (old, new) -> programChunks.forEach { chunk -> @@ -97,7 +97,7 @@ class VmProgramLoader { } } - private fun pass2translateSyscalls(chunks: MutableList) { + private fun pass2translateSyscalls(chunks: List) { chunks.forEach { chunk -> chunk.instructions.withIndex().forEach { (index, ins) -> if(ins.opcode == Opcode.SYSCALL) { @@ -147,7 +147,7 @@ class VmProgramLoader { } private fun pass2replaceLabelsByProgIndex( - chunks: MutableList, + chunks: List, variableAddresses: MutableMap, subroutines: MutableMap ) {