IR: get rid of problematic PREPARECALL pseudo instruction

This commit is contained in:
Irmen de Jong
2025-04-24 21:38:44 +02:00
parent 170f8dd092
commit cd63a58ad9
10 changed files with 19 additions and 48 deletions

View File

@@ -78,7 +78,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcCallfar(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val bankTr = exprGen.translateExpression(call.args[0])
val addressTr = exprGen.translateExpression(call.args[1])
val argumentwordTr = exprGen.translateExpression(call.args[2])
@@ -91,7 +90,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcCallfar2(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val bankTr = exprGen.translateExpression(call.args[0])
val addressTr = exprGen.translateExpression(call.args[1])
val argumentA = exprGen.translateExpression(call.args[2])
@@ -143,7 +141,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcStringCompare(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val left = exprGen.translateExpression(call.args[0])
val right = exprGen.translateExpression(call.args[1])
addToResult(result, left, left.resultReg, -1)
@@ -278,7 +275,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcClamp(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val type = irType(call.type)
val valueTr = exprGen.translateExpression(call.args[0])
val minimumTr = exprGen.translateExpression(call.args[1])

View File

@@ -289,7 +289,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val haystackVar = check.haystackHeapVar!!
when {
haystackVar.type.isString -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(haystackVar)
@@ -300,7 +299,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
haystackVar.type.isByteArray -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(haystackVar)
@@ -314,7 +312,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
haystackVar.type.isWordArray -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(haystackVar)
@@ -328,7 +325,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
haystackVar.type.isFloatArray -> {
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
val elementTr = translateExpression(check.needle)
addToResult(result, elementTr, -1, elementTr.resultFpReg)
val iterableTr = translateExpression(haystackVar)
@@ -618,7 +614,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
when (callTarget) {
is StSub -> {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
// assign the arguments
val argRegisters = mutableListOf<FunctionCallArgs.ArgumentSpec>()
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
@@ -670,7 +665,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
is StExtSub -> {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
// assign the arguments
val argRegisters = mutableListOf<FunctionCallArgs.ArgumentSpec>()
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {

View File

@@ -48,6 +48,7 @@ class IRCodeGen(
irProg.linkChunks()
irProg.convertAsmChunks()
// the optimizer also does 1 essential step regardless of optimizations: joining adjacent chunks.
val optimizer = IRPeepholeOptimizer(irProg)
optimizer.optimize(options.optimize, errors)
irProg.validate()

View File

@@ -23,6 +23,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
}
private fun optimizeOnlyJoinChunks() {
// this chunk-joining is REQUIRED (optimization or no) to end up with a structurally sound chunk list
irprog.foreachSub { sub ->
joinChunks(sub)
removeEmptyChunks(sub)

View File

@@ -554,11 +554,8 @@ class TestVmCodeGen: FunSpec({
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
irChunks.size shouldBe 1
irChunks[0].instructions.size shouldBe 2
val preparecallInstr = irChunks[0].instructions[0]
preparecallInstr.opcode shouldBe Opcode.PREPARECALL
preparecallInstr.immediate shouldBe 0
val callInstr = irChunks[0].instructions[1]
irChunks[0].instructions.size shouldBe 1
val callInstr = irChunks[0].instructions[0]
callInstr.opcode shouldBe Opcode.CALL
callInstr.address shouldBe 0x5000
}

View File

@@ -31,6 +31,7 @@ Future Things and Ideas
IR/VM
-----
- BUG Key main.start.the_loop is missing in the map when compiling shell into IR. See test.p8 in commit that added this line here
- getting it in shape for code generation...: the IR file should be able to encode every detail about a prog8 program (the VM doesn't have to actually be able to run all of it though!)
- fix call() return value handling
- proper code gen for the CALLI instruction and that it (optionally) returns a word value that needs to be assigned to a reg

View File

@@ -1,10 +1,15 @@
main {
ubyte @shared banknumber
extsub @bank 10 $C04B = otherbank() clobbers(A,X,Y)
extsub @bank banknumber $C04B = otherbankvar() clobbers(A,X,Y)
sub start() {
otherbank()
otherbankvar()
nmi_handler()
cx16.r0++
the_loop:
repeat {
cx16.r0++
}
}
sub nmi_handler() {;forcefully kills the running process and returns to the shell prompt.
cx16.r0++
goto main.start.the_loop
}
}

View File

@@ -71,7 +71,6 @@ CONTROL FLOW
------------
jump location - continue running at instruction at 'location' (label/memory address)
jumpi reg1 - continue running at memory address in reg1 (indirect jump)
preparecall numparams - indicator that the next instructions are the param setup and function call/syscall with <numparams> parameters, does nothing by itself
calli reg1 - calls a subroutine (without arguments and without return valus) at memory addres in reg1 (indirect jsr)
call label(argument register list) [: resultreg.type]
- calls a subroutine with the given arguments and return value (optional).
@@ -80,13 +79,13 @@ call label(argument register list) [: resultreg.type]
If the call is to a rom-routine, 'label' will be a hexadecimal address instead such as $ffd2
If the arguments should be passed in CPU registers, they'll have a @REGISTER postfix.
For example: call $ffd2(r5.b@A)
Always preceded by parameter setup and preparecall instructions
Always preceded by parameter setup
callfar bank, address Call a subroutine at the given memory address, in the given RAM/ROM bank (switches both banks at the same time)
callfarvb reg1 address Call a subroutine at the given memory address, in the RAM/ROM bank in reg1.b (switches both banks at the same time)
syscall number (argument register list) [: resultreg.type]
- do a systemcall identified by number, result value(s) are pushed on value stack by the syscall code so
will be POPped off into the given resultregister if any.
Always preceded by parameter setup and preparecall instructions.
Always preceded by parameter setup
All register types (arguments + result register) are ALWAYS WORDS.
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
@@ -283,7 +282,6 @@ enum class Opcode {
JUMP,
JUMPI,
PREPARECALL,
CALLI,
CALL,
CALLFAR,
@@ -626,7 +624,6 @@ val instructionFormats = mutableMapOf(
Opcode.STOREHFACONE to InstructionFormat.from("F,<fr1"),
Opcode.JUMP to InstructionFormat.from("N,<a"),
Opcode.JUMPI to InstructionFormat.from("N,<r1"),
Opcode.PREPARECALL to InstructionFormat.from("N,<i"),
Opcode.CALLI to InstructionFormat.from("N,<r1"),
Opcode.CALL to InstructionFormat.from("N,call"),
Opcode.CALLFAR to InstructionFormat.from("N,<i,<a"),

View File

@@ -217,7 +217,7 @@ class IRProgram(val name: String,
if(chunk is IRInlineAsmChunk)
require(!chunk.isIR) { "inline IR-asm should have been converted into regular code chunk"}
}
chunk.instructions.withIndex().forEach { (index, instr) ->
chunk.instructions.forEach { instr ->
if(instr.labelSymbol!=null && instr.opcode in OpcodesThatBranch) {
if(instr.opcode==Opcode.JUMPI) {
when(val pointervar = st.lookup(instr.labelSymbol)!!) {
@@ -229,26 +229,6 @@ class IRProgram(val name: String,
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" }
}
}
}
}

View File

@@ -216,7 +216,6 @@ class VirtualMachine(irProgram: IRProgram) {
Opcode.STOREHFACONE-> InsSTOREHFACONE(ins)
Opcode.JUMP -> InsJUMP(ins)
Opcode.JUMPI -> InsJUMPI(ins)
Opcode.PREPARECALL -> nextPc()
Opcode.CALLI -> throw IllegalArgumentException("VM cannot run code from memory bytes")
Opcode.CALL -> InsCALL(ins)
Opcode.CALLFAR, Opcode.CALLFARVB -> throw IllegalArgumentException("VM cannot run code from another ram/rombank")