new IR call and return instructions to deal with returnregisters

This commit is contained in:
Irmen de Jong 2023-03-12 21:54:59 +01:00
parent 39132327cc
commit 78a097585d
15 changed files with 115 additions and 58 deletions

View File

@ -954,17 +954,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
}
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol=fcall.name), null)
if(fcall.type==DataType.FLOAT) {
if (!fcall.void && resultFpRegister != 0) {
// Call convention: result value is in fr0, so put it in the required register instead.
addInstr(result, IRInstruction(Opcode.LOADR, IRDataType.FLOAT, fpReg1 = resultFpRegister, fpReg2 = 0), null)
}
} else {
if (!fcall.void && resultRegister != 0) {
// Call convention: result value is in r0, so put it in the required register instead.
addInstr(result, IRInstruction(Opcode.LOADR, codeGen.irType(fcall.type), reg1 = resultRegister, reg2 = 0), null)
}
if(fcall.void)
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol=fcall.name), null)
else {
if(fcall.type==DataType.FLOAT)
addInstr(result, IRInstruction(Opcode.CALLRVAL, IRDataType.FLOAT, fpReg1=resultFpRegister, labelSymbol=fcall.name), null)
else
addInstr(result, IRInstruction(Opcode.CALLRVAL, codeGen.irType(fcall.type), reg1=resultRegister, labelSymbol=fcall.name), null)
}
return result
}
@ -983,6 +979,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addInstr(result, IRInstruction(Opcode.STORECPU, paramDt, reg1 = argReg, labelSymbol = paramRegStr), null)
}
}
// just a regular call without using Vm register call convention: the value is returned in CPU registers!
addInstr(result, IRInstruction(Opcode.CALL, value=callTarget.address.toInt()), null)
if(!fcall.void) {
when(callTarget.returns.size) {

View File

@ -68,6 +68,7 @@ class IRCodeGen(
}
if(options.optimize) {
// TODO integrate into peephole optimizer above
val opt = IROptimizer(irProg)
opt.optimize()
}
@ -1265,14 +1266,20 @@ class IRCodeGen(
private fun translate(ret: PtReturn): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val value = ret.value
if(value!=null) {
// Call Convention: return value is always returned in r0 (or fr0 if float)
result += if(value.type==DataType.FLOAT)
expressionEval.translateExpression(value, -1, 0)
else
expressionEval.translateExpression(value, 0, -1)
if(value==null) {
addInstr(result, IRInstruction(Opcode.RETURN), null)
} else {
if(value.type==DataType.FLOAT) {
val reg = registers.nextFreeFloat()
result += expressionEval.translateExpression(value, -1, reg)
addInstr(result, IRInstruction(Opcode.RETURNREG, IRDataType.FLOAT, fpReg1 = reg), null)
}
else {
val reg = registers.nextFree()
result += expressionEval.translateExpression(value, reg, -1)
addInstr(result, IRInstruction(Opcode.RETURNREG, irType(value.type) , reg1=reg), null)
}
}
addInstr(result, IRInstruction(Opcode.RETURN), null)
return result
}

View File

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

View File

@ -5,6 +5,7 @@ import prog8.intermediate.SyscallRegisterBase
internal class RegisterPool {
// reserve 0,1,2 for return values of subroutine calls and syscalls
// TODO set this back to 0 once 'resultRegister' has been removed everywhere?
private var firstFree: Int=3
private var firstFreeFloat: Int=3

View File

@ -2,6 +2,8 @@ package prog8.iroptimizer
import prog8.intermediate.*
// TODO integrate into peephole optimizer
internal class IROptimizer(val program: IRProgram) {
fun optimize() {
program.blocks.forEach { block ->
@ -36,7 +38,7 @@ internal class IROptimizer(val program: IRProgram) {
val i1 = first.value
val i2 = second.value
// replace call + return --> jump
if(i1.opcode==Opcode.CALL && i2.opcode==Opcode.RETURN) {
if((i1.opcode==Opcode.CALL || i1.opcode==Opcode.CALLRVAL) && i2.opcode==Opcode.RETURN) {
elt.instructions[first.index] = IRInstruction(Opcode.JUMP, value=i1.value, labelSymbol = i1.labelSymbol, branchTarget = i1.branchTarget)
elt.instructions[second.index] = IRInstruction(Opcode.NOP)
if(second.index==elt.instructions.size-1) {

View File

@ -198,7 +198,7 @@ sub str2uword(str string) -> uword {
%ir {{
loadm.w r65500,conv.str2uword.string
syscall 11
return
returnreg.w r0
}}
}
@ -209,7 +209,7 @@ sub str2word(str string) -> word {
%ir {{
loadm.w r65500,conv.str2word.string
syscall 12
return
returnreg.w r0
}}
}

View File

@ -21,7 +21,7 @@ sub pow(float value, float power) -> float {
loadm.f fr0,floats.pow.value
loadm.f fr1,floats.pow.power
fpow.f fr0,fr1
return
returnreg.f fr0
}}
}
@ -29,7 +29,7 @@ sub fabs(float value) -> float {
%ir {{
loadm.f fr0,floats.fabs.value
fabs.f fr0,fr0
return
returnreg.f fr0
}}
}
@ -37,7 +37,7 @@ sub sin(float angle) -> float {
%ir {{
loadm.f fr0,floats.sin.angle
fsin.f fr0,fr0
return
returnreg.f fr0
}}
}
@ -45,7 +45,7 @@ sub cos(float angle) -> float {
%ir {{
loadm.f fr0,floats.cos.angle
fcos.f fr0,fr0
return
returnreg.f fr0
}}
}
@ -53,7 +53,7 @@ sub tan(float value) -> float {
%ir {{
loadm.f fr0,floats.tan.value
ftan.f fr0,fr0
return
returnreg.f fr0
}}
}
@ -61,7 +61,7 @@ sub atan(float value) -> float {
%ir {{
loadm.f fr0,floats.atan.value
fatan.f fr0,fr0
return
returnreg.f fr0
}}
}
@ -69,7 +69,7 @@ sub ln(float value) -> float {
%ir {{
loadm.f fr0,floats.ln.value
fln.f fr0,fr0
return
returnreg.f fr0
}}
}
@ -77,7 +77,7 @@ sub log2(float value) -> float {
%ir {{
loadm.f fr0,floats.log2.value
flog.f fr0,fr0
return
returnreg.f fr0
}}
}
@ -85,7 +85,7 @@ sub sqrt(float value) -> float {
%ir {{
loadm.f fr0,floats.sqrt.value
sqrt.f fr0,fr0
return
returnreg.f fr0
}}
}
@ -103,7 +103,7 @@ sub round(float value) -> float {
%ir {{
loadm.f fr0,floats.round.value
fround.f fr0,fr0
return
returnreg.f fr0
}}
}
@ -111,7 +111,7 @@ sub floor(float value) -> float {
%ir {{
loadm.f fr0,floats.floor.value
ffloor.f fr0,fr0
return
returnreg.f fr0
}}
}
@ -120,14 +120,14 @@ sub ceil(float value) -> float {
%ir {{
loadm.f fr0,floats.ceil.value
fceil.f fr0,fr0
return
returnreg.f fr0
}}
}
sub rndf() -> float {
%ir {{
syscall 35
return
returnreg.f fr0
}}
}

View File

@ -162,14 +162,14 @@ math {
sub rnd() -> ubyte {
%ir {{
syscall 33
return
returnreg.b r0
}}
}
sub rndw() -> uword {
%ir {{
syscall 34
return
returnreg.w r0
}}
}

View File

@ -87,7 +87,7 @@ string {
loadm.w r65500,string.compare.st1
loadm.w r65501,string.compare.st2
syscall 29
return
returnreg.b r0
}}
}

View File

@ -108,7 +108,7 @@ sys {
loadm.w r65500,sys.gfx_getpixel.xx
loadm.w r65501,sys.gfx_getpixel.yy
syscall 30
return
returnreg.b r0
}}
}
}

View File

@ -124,7 +124,7 @@ sub input_chars (uword buffer) -> ubyte {
%ir {{
loadm.w r65500,txt.input_chars.buffer
syscall 6
return
returnreg.b r0
}}
}

View File

@ -3,9 +3,7 @@ TODO
For next minor release
^^^^^^^^^^^^^^^^^^^^^^
- IR: don't hardcode r0/fr0 as return registers.
instead have RETURN -> returns void, RETURNREG <register> -> return value from given register
also CALL -> void call, CALLRVAL -> specify register to put call result in. CALLRVAL r0, functionThatReturnsInt
- get rid of ResultRegister in IR codegen? as the calls now encode this into the new opcodes...
...

View File

@ -47,17 +47,13 @@ storezx reg1, address - store zero at memory address, indexed by
CONTROL FLOW
------------
Possible subroutine call convention:
Set parameters in Reg 0, 1, 2... before call. Return value set in Reg 0 before return.
But you can decide whatever you want because here we just care about jumping and returning the flow of control.
Saving/restoring registers is possible with PUSH and POP instructions.
jump location - continue running at instruction number given by location
jumpa address - continue running at memory address (note: only used to encode a physical cpu jump to fixed address instruction)
call location - save current instruction location+1, continue execution at instruction nr given by location
calli reg1 - save current instruction location+1, continue execution at instruction number in reg1
call location - save current instruction location+1, continue execution at instruction nr given by location. Expect no return value.
callrval reg1, location - like call but expects a return value from a returnreg instruction, and puts that in reg1
syscall value - do a systemcall identified by call number
return - restore last saved instruction location and continue at that instruction
return - restore last saved instruction location and continue at that instruction. No return value.
returnreg reg1 - like return, but also returns a value to the caller via reg1
BRANCHING and CONDITIONALS
@ -232,8 +228,10 @@ enum class Opcode {
JUMP,
JUMPA,
CALL,
CALLRVAL,
SYSCALL,
RETURN,
RETURNREG,
BSTCC,
BSTCS,
@ -363,14 +361,17 @@ enum class Opcode {
val OpcodesThatJump = setOf(
Opcode.JUMP,
Opcode.JUMPA,
Opcode.RETURN
Opcode.RETURN,
Opcode.RETURNREG
)
val OpcodesThatBranch = setOf(
Opcode.JUMP,
Opcode.JUMPA,
Opcode.RETURN,
Opcode.RETURNREG,
Opcode.CALL,
Opcode.CALLRVAL,
Opcode.SYSCALL,
Opcode.BSTCC,
Opcode.BSTCS,
@ -412,6 +413,7 @@ val OpcodesWithMemoryAddressAsValue = setOf(
Opcode.JUMP,
Opcode.JUMPA,
Opcode.CALL,
Opcode.CALLRVAL,
Opcode.BSTCC,
Opcode.BSTCS,
Opcode.BSTEQ,
@ -551,8 +553,10 @@ val instructionFormats = mutableMapOf(
Opcode.JUMP to InstructionFormat.from("N,<v"),
Opcode.JUMPA to InstructionFormat.from("N,<v"),
Opcode.CALL to InstructionFormat.from("N,<v"),
Opcode.CALLRVAL to InstructionFormat.from("BW,<r1,<v | F,<fr1,<v"),
Opcode.SYSCALL to InstructionFormat.from("N,<v"),
Opcode.RETURN to InstructionFormat.from("N"),
Opcode.RETURNREG to InstructionFormat.from("BW,<r1 | F,<fr1"),
Opcode.BSTCC to InstructionFormat.from("N,<v"),
Opcode.BSTCS to InstructionFormat.from("N,<v"),
Opcode.BSTEQ to InstructionFormat.from("N,<v"),

View File

@ -134,7 +134,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.RETURN && it.labelSymbol!=null)
if(it.opcode in OpcodesThatBranch && it.opcode!=Opcode.RETURN && it.opcode!=Opcode.RETURNREG && it.labelSymbol!=null)
it.branchTarget = labeledChunks.getValue(it.labelSymbol)
// note: branches with an address value cannot be linked to something...
}

View File

@ -31,10 +31,11 @@ class BreakpointException(val pcChunk: IRCodeChunk, val pcIndex: Int): Exception
@Suppress("FunctionName")
class VirtualMachine(irProgram: IRProgram) {
class CallSiteContext(val returnChunk: IRCodeChunk, val returnIndex: Int, val returnValueReg: Int?, val returnValueFpReg: Int?)
val memory = Memory()
val program: List<IRCodeChunk>
val registers = Registers()
val callStack = Stack<Pair<IRCodeChunk, Int>>()
val callStack = Stack<CallSiteContext>()
val valueStack = Stack<UByte>() // max 128 entries
var breakpointHandler: ((pcChunk: IRCodeChunk, pcIndex: Int) -> Unit)? = null // can set custom breakpoint handler
var pcChunk = IRCodeChunk(null, null)
@ -170,8 +171,10 @@ class VirtualMachine(irProgram: IRProgram) {
Opcode.JUMP -> InsJUMP(ins)
Opcode.JUMPA -> throw IllegalArgumentException("vm program can't jump to system memory address (JUMPA)")
Opcode.CALL -> InsCALL(ins)
Opcode.CALLRVAL -> InsCALLRVAL(ins)
Opcode.SYSCALL -> InsSYSCALL(ins)
Opcode.RETURN -> InsRETURN()
Opcode.RETURNREG -> InsRETURNREG(ins)
Opcode.BSTCC -> InsBSTCC(ins)
Opcode.BSTCS -> InsBSTCS(ins)
Opcode.BSTEQ -> InsBSTEQ(ins)
@ -578,7 +581,12 @@ class VirtualMachine(irProgram: IRProgram) {
}
private fun InsCALL(i: IRInstruction) {
callStack.push(Pair(pcChunk, pcIndex+1))
callStack.push(CallSiteContext(pcChunk, pcIndex+1, null, null))
branchTo(i)
}
private fun InsCALLRVAL(i: IRInstruction) {
callStack.push(CallSiteContext(pcChunk, pcIndex+1, i.reg1, i.fpReg1))
branchTo(i)
}
@ -586,9 +594,49 @@ class VirtualMachine(irProgram: IRProgram) {
if(callStack.isEmpty())
exit(0)
else {
val (chunk, idx) = callStack.pop()
pcChunk = chunk
pcIndex = idx
val context = callStack.pop()
pcChunk = context.returnChunk
pcIndex = context.returnIndex
// ignore any return values.
}
}
private fun InsRETURNREG(i: IRInstruction) {
if(callStack.isEmpty())
exit(0)
else {
val context = callStack.pop()
when (i.type!!) {
IRDataType.BYTE -> {
if(context.returnValueReg!=null)
registers.setUB(context.returnValueReg, registers.getUB(i.reg1!!))
else {
val callInstr = context.returnChunk.instructions[context.returnIndex-1]
if(callInstr.opcode!=Opcode.CALL)
throw IllegalArgumentException("missing return value reg")
}
}
IRDataType.WORD -> {
if(context.returnValueReg!=null)
registers.setUW(context.returnValueReg, registers.getUW(i.reg1!!))
else {
val callInstr = context.returnChunk.instructions[context.returnIndex-1]
if(callInstr.opcode!=Opcode.CALL)
throw IllegalArgumentException("missing return value reg")
}
}
IRDataType.FLOAT -> {
if(context.returnValueFpReg!=null)
registers.setFloat(context.returnValueFpReg, registers.getFloat(i.fpReg1!!))
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
}
}