fixed vm problem with branching instructions in global init chunk

This commit is contained in:
Irmen de Jong 2024-03-17 00:35:36 +01:00
parent 4db4a5f1b2
commit de3d0b40dc
8 changed files with 139 additions and 97 deletions

View File

@ -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)
}

View File

@ -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)

View File

@ -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
}
})

View File

@ -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)
}

View File

@ -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!
...

View File

@ -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

View File

@ -57,7 +57,10 @@ class IRProgram(val name: String,
fun allSubs(): Sequence<IRSubroutine> = blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }
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<sub.chunks.size-1) sub.chunks[index + 1] else null
val next = if(index<sub.chunks.size-1) sub.chunks[index + 1] else null
when (chunk) {
is IRCodeChunk -> {
// 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<Int>()
while(instr2.opcode!=Opcode.SYSCALL && instr2.opcode!=Opcode.CALL && i<chunk.instructions.size-1) {
if(instr2.reg1direction==OperandDirection.WRITE || instr2.reg1direction==OperandDirection.READWRITE) registers.add(instr2.reg1!!)
if(instr2.reg2direction==OperandDirection.WRITE || instr2.reg2direction==OperandDirection.READWRITE) registers.add(instr2.reg2!!)
if(instr2.reg3direction==OperandDirection.WRITE || instr2.reg3direction==OperandDirection.READWRITE) registers.add(instr2.reg3!!)
if(instr2.fpReg1direction==OperandDirection.WRITE || instr2.fpReg1direction==OperandDirection.READWRITE) registers.add(instr2.fpReg1!!)
if(instr2.fpReg2direction==OperandDirection.WRITE || instr2.fpReg2direction==OperandDirection.READWRITE) registers.add(instr2.fpReg2!!)
i++
instr2 = chunk.instructions[i]
}
// it could be that the actual call is only in another code chunk, so IF we find one, we can check. Otherwise just skip the check...
if(chunk.instructions[i].fcallArgs!=null) {
val expectedRegisterLoads = chunk.instructions[i].fcallArgs!!.arguments.map { it.reg.registerNum }
require(registers.containsAll(expectedRegisterLoads)) { "not all argument registers are given a value in the preparecall-call sequence" }
}
}
}
}
validateChunk(globalInits, null, true)
blocks.forEach { block ->
if(block.isNotEmpty()) {
block.children.filterIsInstance<IRInlineAsmChunk>().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<Int>()
while(instr2.opcode!=Opcode.SYSCALL && instr2.opcode!=Opcode.CALL && i<chunk.instructions.size-1) {
if(instr2.reg1direction==OperandDirection.WRITE || instr2.reg1direction==OperandDirection.READWRITE) registers.add(instr2.reg1!!)
if(instr2.reg2direction==OperandDirection.WRITE || instr2.reg2direction==OperandDirection.READWRITE) registers.add(instr2.reg2!!)
if(instr2.reg3direction==OperandDirection.WRITE || instr2.reg3direction==OperandDirection.READWRITE) registers.add(instr2.reg3!!)
if(instr2.fpReg1direction==OperandDirection.WRITE || instr2.fpReg1direction==OperandDirection.READWRITE) registers.add(instr2.fpReg1!!)
if(instr2.fpReg2direction==OperandDirection.WRITE || instr2.fpReg2direction==OperandDirection.READWRITE) registers.add(instr2.fpReg2!!)
i++
instr2 = chunk.instructions[i]
}
// it could be that the actual call is only in another code chunk, so IF we find one, we can check. Otherwise just skip the check...
if(chunk.instructions[i].fcallArgs!=null) {
val expectedRegisterLoads = chunk.instructions[i].fcallArgs!!.arguments.map { it.reg.registerNum }
require(registers.containsAll(expectedRegisterLoads)) { "not all argument registers are given a value in the preparecall-call sequence" }
}
}
}
}
sub.chunks.forEach { validateChunk(it, sub, false) }
}
}
}

View File

@ -63,11 +63,11 @@ class VmProgramLoader {
}
}
pass2translateSyscalls(programChunks)
pass2translateSyscalls(programChunks + irProgram.globalInits)
pass2replaceLabelsByProgIndex(programChunks, variableAddresses, subroutines)
phase2relinkReplacedChunks(chunkReplacements, programChunks)
programChunks.forEach {
(programChunks + irProgram.globalInits).forEach {
it.instructions.forEach { ins ->
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<Pair<IRCodeChunkBase, IRCodeChunk>>,
programChunks: MutableList<IRCodeChunk>
replacements: List<Pair<IRCodeChunkBase, IRCodeChunk>>,
programChunks: List<IRCodeChunk>
) {
replacements.forEach { (old, new) ->
programChunks.forEach { chunk ->
@ -97,7 +97,7 @@ class VmProgramLoader {
}
}
private fun pass2translateSyscalls(chunks: MutableList<IRCodeChunk>) {
private fun pass2translateSyscalls(chunks: List<IRCodeChunk>) {
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<IRCodeChunk>,
chunks: List<IRCodeChunk>,
variableAddresses: MutableMap<String, Int>,
subroutines: MutableMap<String, IRSubroutine>
) {