fix IR if expression sometimes lacking a cmpi after calculation of the condition value

VM/IR: add a returni immediate value return instruction to replace certain returnr's
This commit is contained in:
Irmen de Jong 2024-10-31 22:19:04 +01:00
parent 09cbdf410a
commit 4a47e15b1c
7 changed files with 124 additions and 36 deletions

View File

@ -1395,12 +1395,14 @@ class IRCodeGen(
private fun ifWithElse_IntegerCond(ifElse: PtIfElse): List<IRCodeChunkBase> {
val result = mutableListOf<IRCodeChunkBase>()
fun translateSimple(condition: PtExpression, jumpFalseOpcode: Opcode) {
fun translateSimple(condition: PtExpression, jumpFalseOpcode: Opcode, addCmpiZero: Boolean) {
if(condition is PtBuiltinFunctionCall && condition.name.startsWith("prog8_ifelse_bittest_"))
throw AssemblyError("IR codegen doesn't have special instructions for dedicated BIT tests and should just still use normal AND")
val tr = expressionEval.translateExpression(condition)
if(addCmpiZero)
tr.chunks.last().instructions.add(IRInstruction(Opcode.CMPI, tr.dt, reg1 = tr.resultReg, immediate = 0))
result += tr.chunks
if(ifElse.hasElse()) {
val elseLabel = createLabelName()
@ -1420,7 +1422,7 @@ class IRCodeGen(
fun translateBinExpr(condition: PtBinaryExpression) {
if(condition.operator in LogicalOperators)
return translateSimple(condition, Opcode.BSTEQ)
return translateSimple(condition, Opcode.BSTEQ, false)
val signed = condition.left.type in SignedDatatypes
val elseBranchFirstReg: Int
@ -1559,18 +1561,21 @@ class IRCodeGen(
when(val cond=ifElse.condition) {
is PtBool -> {
// normally this will be optimized away, but not with -noopt
translateSimple(cond, Opcode.BSTEQ)
translateSimple(cond, Opcode.BSTEQ, false)
}
is PtTypeCast -> {
require(cond.type==DataType.BOOL && cond.value.type in NumericDatatypes)
translateSimple(cond, Opcode.BSTEQ)
translateSimple(cond, Opcode.BSTEQ, false)
}
is PtIdentifier, is PtArrayIndexer, is PtBuiltinFunctionCall, is PtFunctionCall, is PtContainmentCheck -> {
translateSimple(cond, Opcode.BSTEQ)
is PtIdentifier, is PtArrayIndexer, is PtContainmentCheck -> {
translateSimple(cond, Opcode.BSTEQ, false)
}
is PtBuiltinFunctionCall, is PtFunctionCall -> {
translateSimple(cond, Opcode.BSTEQ, true)
}
is PtPrefix -> {
require(cond.operator=="not")
translateSimple(cond.value, Opcode.BSTNE)
translateSimple(cond.value, Opcode.BSTNE, false)
}
is PtBinaryExpression -> {
translateBinExpr(cond)
@ -1648,14 +1653,22 @@ class IRCodeGen(
addInstr(result, IRInstruction(Opcode.RETURN), null)
} else {
if(value.type==DataType.FLOAT) {
if(value is PtNumber) {
addInstr(result, IRInstruction(Opcode.RETURNI, IRDataType.FLOAT, immediateFp = value.number), null)
} else {
val tr = expressionEval.translateExpression(value)
addToResult(result, tr, -1, tr.resultFpReg)
addInstr(result, IRInstruction(Opcode.RETURNR, IRDataType.FLOAT, fpReg1 = tr.resultFpReg), null)
}
}
else {
if(value.asConstInteger()!=null) {
addInstr(result, IRInstruction(Opcode.RETURNI, irType(value.type), immediate = value.asConstInteger()), null)
} else {
val tr = expressionEval.translateExpression(value)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.RETURNR, irType(value.type) , reg1=tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.RETURNR, irType(value.type), reg1 = tr.resultReg), null)
}
}
}
return result

View File

@ -299,7 +299,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
}
// remove useless RETURN
if(idx>0 && (ins.opcode == Opcode.RETURN || ins.opcode==Opcode.RETURNR)) {
if(idx>0 && (ins.opcode == Opcode.RETURN || ins.opcode==Opcode.RETURNR || ins.opcode==Opcode.RETURNI)) {
val previous = chunk.instructions[idx-1]
if(previous.opcode in OpcodesThatJump) {
chunk.instructions.removeAt(idx)

View File

@ -1,9 +1,8 @@
TODO
====
VM/IR: fix return value passing from %ir routines? (diskio.exists routine always returns true)
make better zsmkit example
replace zsound example by a zsmkit example
contribute a short how-to to the zsmkit repo for building a suitable blob
write a howto for integrating third party library code like zsmkit and vtui
Improve register load order in subroutine call args assignments:

View File

@ -1,30 +1,60 @@
%import diskio
%import textio
;%import gfx_hires4
%import diskio
%option no_sysinit
%zeropage basicsafe
main {
sub start() {
txt.print_bool(diskio.exists("doesntexist.prg"))
txt.print_ub(exists_byte("test.prg"))
txt.spc()
txt.print_ub(exists_byte("doesntexist.xxx"))
txt.nl()
txt.print_bool(diskio.exists("test.prg"))
txt.print_bool(exists_bool("test.prg"))
txt.spc()
txt.print_bool(exists_bool("doesntexist.xxx"))
txt.nl()
txt.print_bool(exists1("test.prg"))
txt.spc()
txt.print_bool(exists1("doesntexist.xxx"))
txt.nl()
txt.print_bool(exists2("test.prg"))
txt.spc()
txt.print_bool(exists2("doesntexist.xxx"))
txt.nl()
}
diskio.f_open_w("dump.bin")
diskio.f_close_w()
diskio.f_close_w()
diskio.f_close_w()
diskio.f_close_w()
sub exists1(str filename) -> bool {
; -- returns true if the given file exists on the disk, otherwise false
if exists_byte(filename)!=0 {
return true
}
return false
}
; gfx_hires4.graphics_mode()
; gfx_hires4.circle(300, 250, 200, 3)
; gfx_hires4.rect(320, 10, 20, 200, 3)
; gfx_hires4.fill(310, 310, 2)
;
; repeat {
; }
sub exists2(str filename) -> bool {
; -- returns true if the given file exists on the disk, otherwise false
if exists_bool(filename) {
return true
}
return false
}
sub exists_bool(str name) -> bool {
%ir {{
loadm.w r65535,main.exists_bool.name
syscall 52 (r65535.w): r0.b
returnr.b r0
}}
}
sub exists_byte(str name) -> ubyte {
%ir {{
loadm.w r65535,main.exists_byte.name
syscall 52 (r65535.w): r0.b
returnr.b r0
}}
}
}

View File

@ -82,6 +82,7 @@ syscall number (argument register list) [: resultreg.type]
Always preceded by parameter setup and preparecall instructions
return - restore last saved instruction location and continue at that instruction. No return value.
returnr reg1 - like return, but also returns the value in reg1 to the caller
returni number - like return, but also returns the immediate value to the caller
@ -289,6 +290,7 @@ enum class Opcode {
SYSCALL,
RETURN,
RETURNR,
RETURNI,
BSTCC,
BSTCS,
@ -428,7 +430,8 @@ val OpcodesThatJump = arrayOf(
Opcode.JUMP,
Opcode.JUMPI,
Opcode.RETURN,
Opcode.RETURNR
Opcode.RETURNR,
Opcode.RETURNI
)
val OpcodesThatBranch = arrayOf(
@ -436,6 +439,7 @@ val OpcodesThatBranch = arrayOf(
Opcode.JUMPI,
Opcode.RETURN,
Opcode.RETURNR,
Opcode.RETURNI,
Opcode.CALLI,
Opcode.CALL,
Opcode.SYSCALL,
@ -647,7 +651,8 @@ val instructionFormats = mutableMapOf(
Opcode.CALL to InstructionFormat.from("N,call"),
Opcode.SYSCALL to InstructionFormat.from("N,syscall"),
Opcode.RETURN to InstructionFormat.from("N"),
Opcode.RETURNR to InstructionFormat.from("BW,>r1 | F,>fr1"),
Opcode.RETURNR to InstructionFormat.from("BW,<r1 | F,<fr1"),
Opcode.RETURNI to InstructionFormat.from("BW,<i | F,<i"),
Opcode.BSTCC to InstructionFormat.from("N,<a"),
Opcode.BSTCS to InstructionFormat.from("N,<a"),
Opcode.BSTEQ to InstructionFormat.from("N,<a"),

View File

@ -145,7 +145,7 @@ class IRProgram(val name: String,
// 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.opcode in OpcodesThatBranch && it.opcode!=Opcode.JUMPI && it.opcode!=Opcode.RETURN && it.opcode!=Opcode.RETURNR && it.opcode!=Opcode.RETURNI && it.labelSymbol!=null) {
if(it.labelSymbol.startsWith('$') || it.labelSymbol.first().isDigit()) {
// it's a call to an address (romsub most likely)
requireNotNull(it.address)

View File

@ -213,6 +213,7 @@ class VirtualMachine(irProgram: IRProgram) {
Opcode.SYSCALL -> InsSYSCALL(ins)
Opcode.RETURN -> InsRETURN()
Opcode.RETURNR -> InsRETURNR(ins)
Opcode.RETURNI -> InsRETURNI(ins)
Opcode.BSTCC -> InsBSTCC(ins)
Opcode.BSTCS -> InsBSTCS(ins)
Opcode.BSTEQ -> InsBSTEQ(ins)
@ -656,6 +657,46 @@ class VirtualMachine(irProgram: IRProgram) {
}
}
private fun InsRETURNI(i: IRInstruction) {
if(callStack.isEmpty())
exit(0)
else {
val context = callStack.removeLast()
val returns = context.fcallSpec.returns
when (i.type!!) {
IRDataType.BYTE -> {
if(returns.isNotEmpty())
registers.setUB(returns.single().registerNum, i.immediate!!.toUByte())
else {
val callInstr = context.returnChunk.instructions[context.returnIndex-1]
if(callInstr.opcode!=Opcode.CALL)
throw IllegalArgumentException("missing return value reg")
}
}
IRDataType.WORD -> {
if(returns.isNotEmpty())
registers.setUW(returns.single().registerNum, i.immediate!!.toUShort())
else {
val callInstr = context.returnChunk.instructions[context.returnIndex-1]
if(callInstr.opcode!=Opcode.CALL)
throw IllegalArgumentException("missing return value reg")
}
}
IRDataType.FLOAT -> {
if(returns.isNotEmpty())
registers.setFloat(returns.single().registerNum, i.immediateFp!!)
else {
val callInstr = context.returnChunk.instructions[context.returnIndex-1]
if(callInstr.opcode!=Opcode.CALL)
throw IllegalArgumentException("missing return value reg")
}
}
}
pcChunk = context.returnChunk
pcIndex = context.returnIndex
}
}
private fun InsRETURNR(i: IRInstruction) {
if(callStack.isEmpty())
exit(0)