mirror of
https://github.com/irmen/prog8.git
synced 2024-12-23 09:32:43 +00:00
fixed vm problem with branching instructions in global init chunk
This commit is contained in:
parent
4db4a5f1b2
commit
de3d0b40dc
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
})
|
@ -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)
|
||||
}
|
||||
|
@ -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!
|
||||
|
||||
...
|
||||
|
@ -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
|
||||
|
@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
) {
|
||||
|
Loading…
Reference in New Issue
Block a user