From 905921a684902ca8585a37c7b44b5b7188beb019 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Fri, 12 May 2023 23:26:36 +0200 Subject: [PATCH 1/3] IR: new (sys)call instructions that encapsulate the full subroutine call to fix the bugs resulting from nesting subroutine calls (as param to another call etc) --- .../codegen/intermediate/BuiltinFuncGen.kt | 43 +-- .../codegen/intermediate/ExpressionGen.kt | 216 +++++++-------- .../prog8/codegen/intermediate/IRCodeGen.kt | 10 + .../intermediate/IRPeepholeOptimizer.kt | 18 +- codeGenIntermediate/test/TestVmCodeGen.kt | 4 +- compiler/res/prog8lib/virtual/conv.p8 | 8 +- compiler/res/prog8lib/virtual/floats.p8 | 10 +- compiler/res/prog8lib/virtual/math.p8 | 12 +- compiler/res/prog8lib/virtual/string.p8 | 7 +- compiler/res/prog8lib/virtual/syslib.p8 | 32 +-- compiler/res/prog8lib/virtual/textio.p8 | 16 +- docs/source/todo.rst | 1 - examples/cx16/kefrenbars.p8 | 2 +- examples/test.p8 | 13 +- .../src/prog8/intermediate/IRInstructions.kt | 172 +++++++----- intermediate/src/prog8/intermediate/Utils.kt | 117 +++++--- intermediate/test/TestIRFileInOut.kt | 2 - virtualmachine/src/prog8/vm/SysCalls.kt | 252 +++++++++++------- virtualmachine/src/prog8/vm/VirtualMachine.kt | 80 ++---- .../src/prog8/vm/VmProgramLoader.kt | 32 +-- 20 files changed, 558 insertions(+), 489 deletions(-) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt index 6f6329c53..e4c81a5a5 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt @@ -82,11 +82,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val left = exprGen.translateExpression(call.args[0]) val right = exprGen.translateExpression(call.args[1]) addToResult(result, left, left.resultReg, -1) - addInstr(result, IRInstruction(Opcode.SETPARAM, IRDataType.WORD, reg1=left.resultReg, immediate = 0), null) addToResult(result, right, right.resultReg, -1) - addInstr(result, IRInstruction(Opcode.SETPARAM, IRDataType.WORD, reg1=right.resultReg, immediate = 1), null) - addInstr(result, IRInstruction(Opcode.SYSCALL, immediate = IMSyscall.COMPARE_STRINGS.number), null) - addInstr(result, IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=left.resultReg), null) + result += codeGen.makeSyscall(IMSyscall.COMPARE_STRINGS, listOf(IRDataType.WORD to left.resultReg, IRDataType.WORD to right.resultReg), IRDataType.BYTE to left.resultReg) return ExpressionCodeResult(result, IRDataType.BYTE, left.resultReg, -1) } @@ -118,13 +115,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val result = mutableListOf() val tr = exprGen.translateExpression(call.args[0]) addToResult(result, tr, tr.resultReg, -1) - result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.SETPARAM, IRDataType.WORD, reg1 = tr.resultReg, immediate = 0) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = tr.resultReg, immediate = array.length) - it += IRInstruction(Opcode.SETPARAM, IRDataType.BYTE, reg1 = tr.resultReg, immediate = 1) - it += IRInstruction(Opcode.SYSCALL, immediate = syscall.number) - it += IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=tr.resultReg) - } + val lengthReg = codeGen.registers.nextFree() + addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = array.length), null) + result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to tr.resultReg) return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1) } @@ -143,13 +136,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val result = mutableListOf() val tr = exprGen.translateExpression(call.args[0]) addToResult(result, tr, tr.resultReg, -1) - result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.SETPARAM, IRDataType.WORD, reg1 = tr.resultReg, immediate = 0) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = tr.resultReg, immediate = array.length) - it += IRInstruction(Opcode.SETPARAM, IRDataType.BYTE, reg1 = tr.resultReg, immediate = 1) - it += IRInstruction(Opcode.SYSCALL, immediate = syscall.number) - it += IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=tr.resultReg) - } + val lengthReg = codeGen.registers.nextFree() + addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = array.length), null) + result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to tr.resultReg) return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1) } @@ -271,12 +260,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val result = mutableListOf() val tr = exprGen.translateExpression(call.args[0]) addToResult(result, tr, tr.resultReg, -1) - result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.SETPARAM, IRDataType.WORD, reg1 = tr.resultReg, immediate = 0) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = tr.resultReg, immediate = if(array.dt==DataType.STR) array.length!!-1 else array.length) - it += IRInstruction(Opcode.SETPARAM, IRDataType.BYTE, reg1 = tr.resultReg, immediate = 1) - it += IRInstruction(Opcode.SYSCALL, immediate = syscall.number) - } + val lengthReg = codeGen.registers.nextFree() + addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(array.dt==DataType.STR) array.length!!-1 else array.length), null) + result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null) return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) } @@ -296,12 +282,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe val result = mutableListOf() val tr = exprGen.translateExpression(call.args[0]) addToResult(result, tr, tr.resultReg, -1) - result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.SETPARAM, IRDataType.WORD, reg1 = tr.resultReg, immediate = 0) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = tr.resultReg, immediate = if(array.dt==DataType.STR) array.length!!-1 else array.length) - it += IRInstruction(Opcode.SETPARAM, IRDataType.BYTE, reg1 = tr.resultReg, immediate = 1) - it += IRInstruction(Opcode.SYSCALL, immediate = syscall.number) - } + val lengthReg = codeGen.registers.nextFree() + addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(array.dt==DataType.STR) array.length!!-1 else array.length), null) + result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null) return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) } diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt index b302f61c4..9c8a09310 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt @@ -4,10 +4,7 @@ import prog8.code.StRomSub import prog8.code.StStaticVariable import prog8.code.StSub import prog8.code.ast.* -import prog8.code.core.AssemblyError -import prog8.code.core.DataType -import prog8.code.core.PassByValueDatatypes -import prog8.code.core.SignedDatatypes +import prog8.code.core.* import prog8.intermediate.* internal class ExpressionCodeResult(val chunks: IRCodeChunks, val dt: IRDataType, val resultReg: Int, val resultFpReg: Int) { @@ -105,53 +102,35 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { private fun translate(check: PtContainmentCheck): ExpressionCodeResult { val result = mutableListOf() - var tr = translateExpression(check.element) - addToResult(result, tr, tr.resultReg, -1) val iterable = codeGen.symbolTable.flat.getValue(check.iterable.name) as StStaticVariable when(iterable.dt) { DataType.STR -> { - tr = translateExpression(check.element) - addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.SETPARAM, IRDataType.BYTE, tr.resultReg, immediate = 0), null) - tr = translateExpression(check.iterable) - addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.SETPARAM, IRDataType.WORD, tr.resultReg, immediate = 1), null) - result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.SYSCALL, immediate = IMSyscall.STRING_CONTAINS.number) - it += IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=tr.resultReg) - } - return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1) + val elementTr = translateExpression(check.element) + addToResult(result, elementTr, elementTr.resultReg, -1) + val iterableTr = translateExpression(check.iterable) + addToResult(result, iterableTr, iterableTr.resultReg, -1) + result += codeGen.makeSyscall(IMSyscall.STRING_CONTAINS, listOf(IRDataType.BYTE to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg), IRDataType.BYTE to elementTr.resultReg) + return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1) } DataType.ARRAY_UB, DataType.ARRAY_B -> { - tr = translateExpression(check.element) - addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.SETPARAM, IRDataType.BYTE, tr.resultReg, immediate = 0), null) - tr = translateExpression(check.iterable) - addToResult(result, tr, tr.resultReg, -1) - result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.SETPARAM, IRDataType.WORD, tr.resultReg, immediate = 1) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=tr.resultReg, immediate = iterable.length!!) - it += IRInstruction(Opcode.SETPARAM, IRDataType.BYTE, tr.resultReg, immediate = 2) - it += IRInstruction(Opcode.SYSCALL, immediate = IMSyscall.BYTEARRAY_CONTAINS.number) - it += IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=tr.resultReg) - } - // SysCall call convention: return value in register r0 - return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1) + val elementTr = translateExpression(check.element) + addToResult(result, elementTr, elementTr.resultReg, -1) + val iterableTr = translateExpression(check.iterable) + addToResult(result, iterableTr, iterableTr.resultReg, -1) + val lengthReg = codeGen.registers.nextFree() + addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterable.length!!), null) + result += codeGen.makeSyscall(IMSyscall.BYTEARRAY_CONTAINS, listOf(IRDataType.BYTE to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to elementTr.resultReg) + return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1) } DataType.ARRAY_UW, DataType.ARRAY_W -> { - tr = translateExpression(check.element) - addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.SETPARAM, IRDataType.WORD, tr.resultReg, immediate = 0), null) - tr = translateExpression(check.iterable) - addToResult(result, tr, tr.resultReg, -1) - result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.SETPARAM, IRDataType.WORD, tr.resultReg, immediate = 1) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=tr.resultReg, immediate = iterable.length!!) - it += IRInstruction(Opcode.SETPARAM, IRDataType.BYTE, tr.resultReg, immediate = 2) - it += IRInstruction(Opcode.SYSCALL, immediate = IMSyscall.WORDARRAY_CONTAINS.number) - it += IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=tr.resultReg) - } - return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1) + val elementTr = translateExpression(check.element) + addToResult(result, elementTr, elementTr.resultReg, -1) + val iterableTr = translateExpression(check.iterable) + addToResult(result, iterableTr, iterableTr.resultReg, -1) + val lengthReg = codeGen.registers.nextFree() + addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterable.length!!), null) + result += codeGen.makeSyscall(IMSyscall.WORDARRAY_CONTAINS, listOf(IRDataType.WORD to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to elementTr.resultReg) + return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1) } DataType.ARRAY_F -> throw AssemblyError("containment check in float-array not supported") else -> throw AssemblyError("weird iterable dt ${iterable.dt} for ${check.iterable.name}") @@ -358,97 +337,94 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { when (val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)) { is StSub -> { val result = mutableListOf() - for ((index, argspec) in fcall.args.zip(callTarget.parameters).withIndex()) { - val (arg, param) = argspec - val paramDt = codeGen.irType(param.type) + // assign the arguments + val argRegisters = mutableListOf() + for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) { + val paramDt = codeGen.irType(parameter.type) val tr = translateExpression(arg) - result += tr.chunks if(paramDt==IRDataType.FLOAT) - addInstr(result, IRInstruction(Opcode.SETPARAM, paramDt, fpReg1 = tr.resultFpReg, immediate = index), null) + argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, tr.resultFpReg, null))) else - addInstr(result, IRInstruction(Opcode.SETPARAM, paramDt, reg1 = tr.resultReg, immediate = index), null) + argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(paramDt, tr.resultReg, null))) + result += tr.chunks } -// for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) { -// val paramDt = codeGen.irType(parameter.type) -// val symbol = "${fcall.name}.${parameter.name}" -// if(codeGen.isZero(arg)) { -// addInstr(result, IRInstruction(Opcode.STOREZM, paramDt, labelSymbol = symbol), null) -// } else { -// if (paramDt == IRDataType.FLOAT) { -// val tr = translateExpression(arg) -// addToResult(result, tr, -1, tr.resultFpReg) -// addInstr(result, IRInstruction(Opcode.STOREM, paramDt, fpReg1 = tr.resultFpReg, labelSymbol = symbol), null) -// } else { -// val tr = translateExpression(arg) -// addToResult(result, tr, tr.resultReg, -1) -// addInstr(result, IRInstruction(Opcode.STOREM, paramDt, reg1 = tr.resultReg, labelSymbol = symbol), null) -// } -// } -// } - return if(fcall.void) { - addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name), null) + // return value + val returnRegSpec = if(fcall.void) null else { + val returnIrType = codeGen.irType(callTarget.returnType!!) + if(returnIrType==IRDataType.FLOAT) + FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFreeFloat(), null) + else + FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFree(), null) + } + // create the call + val call = IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec)) + addInstr(result, call, null) + return if(fcall.void) ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) - } else { - var resultReg = -1 - var resultFpReg = -1 - if(fcall.type==DataType.FLOAT) { - resultFpReg = codeGen.registers.nextFreeFloat() - addInstr(result, IRInstruction(Opcode.CALLR, IRDataType.FLOAT, fpReg1=resultFpReg, labelSymbol=fcall.name), null) - } else { - resultReg = codeGen.registers.nextFree() - addInstr(result, IRInstruction(Opcode.CALLR, codeGen.irType(fcall.type), reg1=resultReg, labelSymbol=fcall.name), null) - } - ExpressionCodeResult(result, codeGen.irType(fcall.type), resultReg, resultFpReg) - } + else if(call.fcallArgs!!.returns!!.dt==IRDataType.FLOAT) + ExpressionCodeResult(result, codeGen.irType(fcall.type), -1, call.fcallArgs!!.returns!!.registerNum) + else + ExpressionCodeResult(result, codeGen.irType(fcall.type), call.fcallArgs!!.returns!!.registerNum, -1) } is StRomSub -> { val result = mutableListOf() + // assign the arguments + val argRegisters = mutableListOf() for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) { val paramDt = codeGen.irType(parameter.type) - val paramRegStr = if(parameter.register.registerOrPair!=null) parameter.register.registerOrPair.toString() else parameter.register.statusflag.toString() - if(codeGen.isZero(arg)) { - addInstr(result, IRInstruction(Opcode.STOREZCPU, paramDt, labelSymbol = paramRegStr), null) + val tr = translateExpression(arg) + if(paramDt==IRDataType.FLOAT) + argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, tr.resultFpReg, parameter.register))) + else + argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(paramDt, tr.resultReg, parameter.register))) + result += tr.chunks + } + // return value + val returnRegSpec = if(fcall.void) null else { + if(callTarget.returns.isEmpty()) + null + else if(callTarget.returns.size==1) { + val returns = callTarget.returns[0] + val returnIrType = codeGen.irType(returns.type) + if(returnIrType==IRDataType.FLOAT) + FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFreeFloat(), returns.register) + else + FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFree(), returns.register) } else { - if (paramDt == IRDataType.FLOAT) - throw AssemblyError("doesn't support float register argument in asm romsub") - val tr = translateExpression(arg) - addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(Opcode.STORECPU, paramDt, reg1 = tr.resultReg, labelSymbol = paramRegStr), null) + // multiple return values: take the first *register* (not status flag) return value and ignore the rest. + val returns = callTarget.returns.first { it.register.registerOrPair!=null } + val returnIrType = codeGen.irType(returns.type) + if(returnIrType==IRDataType.FLOAT) + FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFreeFloat(), returns.register) + else + FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFree(), returns.register) } } - // just a regular call without using Vm register call convention: the value is returned in CPU registers! - addInstr(result, IRInstruction(Opcode.CALL, address = callTarget.address.toInt()), null) - val resultReg = codeGen.registers.nextFree() - if(!fcall.void) { - when(callTarget.returns.size) { - 0 -> throw AssemblyError("expect a return value") - 1 -> { - if(fcall.type==DataType.FLOAT) - throw AssemblyError("doesn't support float register result in asm romsub") - val returns = callTarget.returns.single() - val regStr = if(returns.register.registerOrPair!=null) returns.register.registerOrPair.toString() else returns.register.statusflag.toString() - addInstr(result, IRInstruction(Opcode.LOADCPU, codeGen.irType(fcall.type), reg1=resultReg, labelSymbol = regStr), null) - } - else -> { - val returnRegister = callTarget.returns.singleOrNull{ it.register.registerOrPair!=null } - if(returnRegister!=null) { - // we skip the other values returned in the status flags. - val regStr = returnRegister.register.registerOrPair.toString() - addInstr(result, IRInstruction(Opcode.LOADCPU, codeGen.irType(fcall.type), reg1=resultReg, labelSymbol = regStr), null) - } else { - val firstReturnRegister = callTarget.returns.firstOrNull{ it.register.registerOrPair!=null } - if(firstReturnRegister!=null) { - // we just take the first register return value and ignore the rest. - val regStr = firstReturnRegister.register.registerOrPair.toString() - addInstr(result, IRInstruction(Opcode.LOADCPU, codeGen.irType(fcall.type), reg1=resultReg, labelSymbol = regStr), null) - } else { - throw AssemblyError("invalid number of return values from call") - } - } - } + // create the call + val call = IRInstruction(Opcode.CALL, address = callTarget.address.toInt(), fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec)) + addInstr(result, call, null) + if(fcall.void) { + return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) + } else { + val regStr = if(callTarget.returns.isEmpty()) + throw AssemblyError("expect a return value") + else if(callTarget.returns.size==1) { + val returns = callTarget.returns.single() + if(returns.register.registerOrPair!=null) returns.register.registerOrPair.toString() else returns.register.statusflag.toString() + } else { + // multiple return values: take the first *register* (not status flag) return value and ignore the rest. + callTarget.returns.first { it.register.registerOrPair!=null }.toString() + } + return if(fcall.type==DataType.FLOAT) { + val resultFpReg = codeGen.registers.nextFreeFloat() + addInstr(result, IRInstruction(Opcode.LOADCPU, IRDataType.FLOAT, fpReg1 = resultFpReg, labelSymbol = regStr), null) + ExpressionCodeResult(result, returnRegSpec!!.dt, -1, resultFpReg) + } else { + val resultReg = codeGen.registers.nextFree() + addInstr(result, IRInstruction(Opcode.LOADCPU, returnRegSpec!!.dt, reg1 = resultReg, labelSymbol = regStr), null) + ExpressionCodeResult(result, returnRegSpec.dt, resultReg, -1) } } - return ExpressionCodeResult(result, if(fcall.void) IRDataType.BYTE else codeGen.irType(fcall.type), resultReg, -1) } else -> throw AssemblyError("invalid node type") } diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt index 671ebbe75..0f2cabcb7 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt @@ -1589,4 +1589,14 @@ class IRCodeGen( irSymbolTable.add(staticVar) return tempvar } + + fun makeSyscall(syscall: IMSyscall, params: List>, returns: Pair?, label: String?=null): IRCodeChunk { + return IRCodeChunk(label, null).also { + val args = params.map { (dt, reg)-> + FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(dt, reg, null)) + } + val returnSpec = if(returns==null) null else FunctionCallArgs.RegSpec(returns.first, returns.second, null) + it += IRInstruction(Opcode.SYSCALL, immediate = syscall.number, fcallArgs = FunctionCallArgs(args, returnSpec)) + } + } } diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt index b81b34abb..1e33a44ad 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt @@ -250,14 +250,16 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) { } // replace call + return --> jump - if(idx>0 && ins.opcode==Opcode.RETURN) { - val previous = chunk.instructions[idx-1] - if(previous.opcode==Opcode.CALL || previous.opcode==Opcode.CALLR) { - chunk.instructions[idx-1] = IRInstruction(Opcode.JUMP, address = previous.address, labelSymbol = previous.labelSymbol, branchTarget = previous.branchTarget) - chunk.instructions.removeAt(idx) - changed = true - } - } + // This can no longer be done here on the IR level, with the current CALL opcode that encodes the full subroutine call setup. + // If machine code is ever generated from this IR, *that* should possibly optimize the JSR + RTS into a JMP. +// if(idx>0 && ins.opcode==Opcode.RETURN) { +// val previous = chunk.instructions[idx-1] +// if(previous.opcode==Opcode.CALL) { +// chunk.instructions[idx-1] = IRInstruction(Opcode.JUMP, address = previous.address, labelSymbol = previous.labelSymbol, branchTarget = previous.branchTarget) +// chunk.instructions.removeAt(idx) +// changed = true +// } +// } } return changed } diff --git a/codeGenIntermediate/test/TestVmCodeGen.kt b/codeGenIntermediate/test/TestVmCodeGen.kt index 535384995..040290466 100644 --- a/codeGenIntermediate/test/TestVmCodeGen.kt +++ b/codeGenIntermediate/test/TestVmCodeGen.kt @@ -441,7 +441,7 @@ class TestVmCodeGen: FunSpec({ irChunks.size shouldBe 1 } - test("romsub allowed in codegen") { + test("romsub allowed in ir-codegen") { //main { // romsub $5000 = routine() // @@ -452,7 +452,7 @@ class TestVmCodeGen: FunSpec({ val codegen = VmCodeGen() val program = PtProgram("test", DummyMemsizer, DummyStringEncoder) val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY) - val romsub = PtAsmSub("routine", 0x5000u, emptySet(), emptyList(), emptyList(), false, Position.DUMMY) + val romsub = PtAsmSub("routine", 0x5000u, setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY) block.add(romsub) val sub = PtSub("start", emptyList(), null, Position.DUMMY) val call = PtFunctionCall("main.routine", true, DataType.UNDEFINED, Position.DUMMY) diff --git a/compiler/res/prog8lib/virtual/conv.p8 b/compiler/res/prog8lib/virtual/conv.p8 index ce284f428..36f6f23ae 100644 --- a/compiler/res/prog8lib/virtual/conv.p8 +++ b/compiler/res/prog8lib/virtual/conv.p8 @@ -197,9 +197,7 @@ sub str2uword(str string) -> uword { ; (any non-digit character will terminate the number string that is parsed) %ir {{ loadm.w r65535,conv.str2uword.string - setparam.w r65535,0 - syscall 11 - pop.w r0 + syscall 11 (r65535.w) : r0.w returnr.w r0 }} } @@ -210,9 +208,7 @@ sub str2word(str string) -> word { ; (any non-digit character will terminate the number string that is parsed) %ir {{ loadm.w r65535,conv.str2word.string - setparam.w r65535,0 - syscall 12 - pop.w r0 + syscall 12 (r65535.w) : r0.w returnr.w r0 }} } diff --git a/compiler/res/prog8lib/virtual/floats.p8 b/compiler/res/prog8lib/virtual/floats.p8 index f310f7d47..d61bc5b96 100644 --- a/compiler/res/prog8lib/virtual/floats.p8 +++ b/compiler/res/prog8lib/virtual/floats.p8 @@ -11,8 +11,7 @@ sub print_f(float value) { ; ---- prints the floating point value (without a newline). %ir {{ loadm.f fr65535,floats.print_f.value - setparam.f fr65535,0 - syscall 25 + syscall 25 (fr65535.f) return }} } @@ -127,8 +126,7 @@ sub ceil(float value) -> float { sub rndf() -> float { %ir {{ - syscall 35 - pop.f fr0 + syscall 35 () : fr0.f returnr.f fr0 }} } @@ -136,8 +134,8 @@ sub rndf() -> float { sub rndseedf(float seed) { %ir {{ loadm.f fr65535,floats.rndseedf.seed - setparam.f fr65535,0 - syscall 32 + syscall 32 (fr65535.f) + return }} } diff --git a/compiler/res/prog8lib/virtual/math.p8 b/compiler/res/prog8lib/virtual/math.p8 index 5b94d8bbd..a86fbed7a 100644 --- a/compiler/res/prog8lib/virtual/math.p8 +++ b/compiler/res/prog8lib/virtual/math.p8 @@ -161,16 +161,14 @@ math { sub rnd() -> ubyte { %ir {{ - syscall 33 - pop.b r0 + syscall 33 (): r0.b returnr.b r0 }} } sub rndw() -> uword { %ir {{ - syscall 34 - pop.w r0 + syscall 34 (): r0.w returnr.w r0 }} } @@ -178,11 +176,9 @@ math { sub rndseed(uword seed1, uword seed2) { ; -- reset the pseudo RNG's seed values. Defaults are: $a55a, $7653. %ir {{ - loadm.w r65535,math.rndseed.seed1 - setparam.w r65535,0 + loadm.w r65534,math.rndseed.seed1 loadm.w r65535,math.rndseed.seed2 - setparam.w r65535,1 - syscall 31 + syscall 31 (r65534.w, r65535.w) return }} } diff --git a/compiler/res/prog8lib/virtual/string.p8 b/compiler/res/prog8lib/virtual/string.p8 index f18f0546b..f913d33c3 100644 --- a/compiler/res/prog8lib/virtual/string.p8 +++ b/compiler/res/prog8lib/virtual/string.p8 @@ -84,12 +84,9 @@ string { ; Note that you can also directly compare strings and string values with eachother using ; comparison operators ==, < etcetera (it will use strcmp for you under water automatically). %ir {{ - loadm.w r65535,string.compare.st1 - setparam.w r65535,0 + loadm.w r65534,string.compare.st1 loadm.w r65535,string.compare.st2 - setparam.w r65535,1 - syscall 29 - pop.b r0 + syscall 29 (r65534.w, r65535.w) : r0.b returnr.b r0 }} } diff --git a/compiler/res/prog8lib/virtual/syslib.p8 b/compiler/res/prog8lib/virtual/syslib.p8 index 8c529acd9..aa58e6476 100644 --- a/compiler/res/prog8lib/virtual/syslib.p8 +++ b/compiler/res/prog8lib/virtual/syslib.p8 @@ -8,7 +8,7 @@ sys { sub reset_system() { ; Soft-reset the system back to initial power-on Basic prompt. %ir {{ - syscall 0 + syscall 0 () }} } @@ -16,15 +16,14 @@ sys { ; --- wait approximately the given number of jiffies (1/60th seconds) %ir {{ loadm.w r65535,sys.wait.jiffies - setparam.w r65535,0 - syscall 13 + syscall 13 (r65535.w) }} } sub waitvsync() { ; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling. %ir {{ - syscall 14 + syscall 14() }} } @@ -64,8 +63,7 @@ sys { ; -- immediately exit the program with a return code in the A register %ir {{ loadm.b r65535,sys.exit.returnvalue - setparam.b r65535,0 - syscall 1 + syscall 1 (r65535.b) }} } @@ -85,39 +83,31 @@ sys { sub gfx_enable(ubyte mode) { %ir {{ loadm.b r65535,sys.gfx_enable.mode - setparam.b r65535,0 - syscall 8 + syscall 8 (r65535.b) }} } sub gfx_clear(ubyte color) { %ir {{ loadm.b r65535,sys.gfx_clear.color - setparam.b r65535,0 - syscall 9 + syscall 9 (r65535.b) }} } sub gfx_plot(uword xx, uword yy, ubyte color) { %ir {{ - loadm.w r65535,sys.gfx_plot.xx - setparam.w r65535,0 - loadm.w r65535,sys.gfx_plot.yy - setparam.w r65535,1 + loadm.w r65533,sys.gfx_plot.xx + loadm.w r65534,sys.gfx_plot.yy loadm.b r65535,sys.gfx_plot.color - setparam.b r65535,2 - syscall 10 + syscall 10 (r65533.w, r65534.w, r65535.b) }} } sub gfx_getpixel(uword xx, uword yy) -> ubyte { %ir {{ - loadm.w r65535,sys.gfx_getpixel.xx - setparam.w r65535,0 + loadm.w r65534,sys.gfx_getpixel.xx loadm.w r65535,sys.gfx_getpixel.yy - setparam.w r65535,1 - syscall 30 - pop.b r0 + syscall 30 (r65534.w, r65535.w): r0.b returnr.b r0 }} } diff --git a/compiler/res/prog8lib/virtual/textio.p8 b/compiler/res/prog8lib/virtual/textio.p8 index 539cc3c25..cda072808 100644 --- a/compiler/res/prog8lib/virtual/textio.p8 +++ b/compiler/res/prog8lib/virtual/textio.p8 @@ -16,8 +16,7 @@ sub clear_screen() { str @shared sequence = "\x1b[2J\x1B[H" %ir {{ load.w r65535,txt.clear_screen.sequence - setparam.w r65535,0 - syscall 3 + syscall 3 (r65535.w) }} } @@ -40,16 +39,14 @@ sub uppercase() { sub chrout(ubyte char) { %ir {{ loadm.b r65535,txt.chrout.char - setparam.b r65535,0 - syscall 2 + syscall 2 (r65535.b) }} } sub print (str text) { %ir {{ loadm.w r65535,txt.print.text - setparam.w r65535,0 - syscall 3 + syscall 3 (r65535.w) }} } @@ -125,12 +122,9 @@ sub input_chars (uword buffer) -> ubyte { ; ---- Input a string (max. 80 chars) from the keyboard. Returns length of input. (string is terminated with a 0 byte as well) ; It assumes the keyboard is selected as I/O channel! %ir {{ - loadm.w r65535,txt.input_chars.buffer - setparam.w r65535,0 + loadm.w r65534,txt.input_chars.buffer load.b r65535,80 - setparam.b r65535,1 - syscall 6 - pop.b r0 + syscall 6 (r65534.w, r65535.b): r0.b returnr.b r0 }} } diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 666287cfe..119a984ec 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -4,7 +4,6 @@ TODO For next minor release ^^^^^^^^^^^^^^^^^^^^^^ - fix VM problem with param passing: void string.copy(".prg", &output_filename + string.length(output_filename)) - some work on this is on a git shelf ... diff --git a/examples/cx16/kefrenbars.p8 b/examples/cx16/kefrenbars.p8 index b00b7cb06..4295fe174 100644 --- a/examples/cx16/kefrenbars.p8 +++ b/examples/cx16/kefrenbars.p8 @@ -34,7 +34,7 @@ main { irq { - const ubyte BAR_Y_OFFSET = 6 + const ubyte BAR_Y_OFFSET = 5 uword next_irq_line = 0 ubyte anim1 = 0 ubyte av1 = 0 diff --git a/examples/test.p8 b/examples/test.p8 index 6eba62776..5daf7ca0b 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -5,11 +5,14 @@ main { sub start() { - uword seconds_uword = 1 - uword remainder = seconds_uword % $0003 ==0 - txt.print_uw(remainder) - - blerp() + txt.chrout('!') + txt.print("test") + txt.nl() +; uword seconds_uword = 1 +; uword remainder = seconds_uword % $0003 ==0 +; txt.print_uw(remainder) +; +; blerp() } sub blerp() { diff --git a/intermediate/src/prog8/intermediate/IRInstructions.kt b/intermediate/src/prog8/intermediate/IRInstructions.kt index 87a00a0bb..e84a8bee8 100644 --- a/intermediate/src/prog8/intermediate/IRInstructions.kt +++ b/intermediate/src/prog8/intermediate/IRInstructions.kt @@ -1,5 +1,7 @@ package prog8.intermediate +import prog8.code.core.CpuRegister +import prog8.code.core.RegisterOrStatusflag import prog8.code.core.toHex /* @@ -54,14 +56,18 @@ CONTROL FLOW ------------ 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) -setparam reg1, argpos - sets reg1 as the value for the parameter in the given position for an upcoming function call (call, callr, syscall, or even jump opcode). -call location - save current instruction location+1, continue execution at instruction nr given by location. No return value is expected. -callr reg1, location - like call but expects the routine to return a value with a returnr instruction, it then puts that in reg1 -syscall value - do a systemcall identified by call number, result value(s) are pushed on value stack so need to be POPped off (depends on syscall) +call label(argument register list) [: resultreg.type] + - calls a subroutine with the given arguments and return value (optional). + save current instruction location+1, continue execution at instruction nr of the label. + the argument register list is positional and includes the datatype, ex.: r4.b,r5.w,fp1.f +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. 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 + BRANCHING and CONDITIONALS -------------------------- All have type b or w except the branches that only check status bits. @@ -232,15 +238,12 @@ enum class Opcode { STOREX, STOREIX, STOREZM, - STOREZCPU, STOREZI, STOREZX, JUMP, JUMPA, - SETPARAM, CALL, - CALLR, SYSCALL, RETURN, RETURNR, @@ -388,7 +391,6 @@ val OpcodesThatBranch = setOf( Opcode.RETURN, Opcode.RETURNR, Opcode.CALL, - Opcode.CALLR, Opcode.SYSCALL, Opcode.BSTCC, Opcode.BSTCS, @@ -418,8 +420,7 @@ val OpcodesThatBranch = setOf( val OpcodesForCpuRegisters = setOf( Opcode.LOADCPU, - Opcode.STORECPU, - Opcode.STOREZCPU + Opcode.STORECPU ) enum class IRDataType { @@ -442,7 +443,9 @@ data class InstructionFormat(val datatype: IRDataType?, val fpReg1: OperandDirection, val fpReg2: OperandDirection, val address: OperandDirection, - val immediate: Boolean) { + val immediate: Boolean, + val funcCall: Boolean, + val sysCall: Boolean) { companion object { fun from(spec: String): Map { val result = mutableMapOf() @@ -455,33 +458,37 @@ data class InstructionFormat(val datatype: IRDataType?, var immediate = false val splits = part.splitToSequence(',').iterator() val typespec = splits.next() + var funcCall = false + var sysCall = false while(splits.hasNext()) { when(splits.next()) { - " { reg1=OperandDirection.READ } - ">r1" -> { reg1=OperandDirection.WRITE } - "<>r1" -> { reg1=OperandDirection.READWRITE } + " reg1 = OperandDirection.READ + ">r1" -> reg1 = OperandDirection.WRITE + "<>r1" -> reg1 = OperandDirection.READWRITE " reg2 = OperandDirection.READ - " { fpreg1=OperandDirection.READ } - ">fr1" -> { fpreg1=OperandDirection.WRITE } - "<>fr1" -> { fpreg1=OperandDirection.READWRITE } + " fpreg1 = OperandDirection.READ + ">fr1" -> fpreg1 = OperandDirection.WRITE + "<>fr1" -> fpreg1 = OperandDirection.READWRITE " fpreg2 = OperandDirection.READ ">i", "<>i" -> throw IllegalArgumentException("can't write into an immediate value") " immediate = true " address = OperandDirection.READ ">a" -> address = OperandDirection.WRITE "<>a" -> address = OperandDirection.READWRITE + "call" -> funcCall = true + "syscall" -> sysCall = true else -> throw IllegalArgumentException(spec) } } if(typespec=="N") - result[null] = InstructionFormat(null, reg1, reg2, fpreg1, fpreg2, address, immediate) + result[null] = InstructionFormat(null, reg1, reg2, fpreg1, fpreg2, address, immediate, funcCall, sysCall) if('B' in typespec) - result[IRDataType.BYTE] = InstructionFormat(IRDataType.BYTE, reg1, reg2, fpreg1, fpreg2, address, immediate) + result[IRDataType.BYTE] = InstructionFormat(IRDataType.BYTE, reg1, reg2, fpreg1, fpreg2, address, immediate, funcCall, sysCall) if('W' in typespec) - result[IRDataType.WORD] = InstructionFormat(IRDataType.WORD, reg1, reg2, fpreg1, fpreg2, address, immediate) + result[IRDataType.WORD] = InstructionFormat(IRDataType.WORD, reg1, reg2, fpreg1, fpreg2, address, immediate, funcCall, sysCall) if('F' in typespec) - result[IRDataType.FLOAT] = InstructionFormat(IRDataType.FLOAT, reg1, reg2, fpreg1, fpreg2, address, immediate) + result[IRDataType.FLOAT] = InstructionFormat(IRDataType.FLOAT, reg1, reg2, fpreg1, fpreg2, address, immediate, funcCall, sysCall) } return result } @@ -513,15 +520,12 @@ val instructionFormats = mutableMapOf( Opcode.STOREX to InstructionFormat.from("BW,a | F,a"), Opcode.STOREIX to InstructionFormat.from("BW,a | F,a"), Opcode.STOREZM to InstructionFormat.from("BW,>a | F,>a"), - Opcode.STOREZCPU to InstructionFormat.from("BW"), Opcode.STOREZI to InstructionFormat.from("BW,a | F,a"), Opcode.JUMP to InstructionFormat.from("N,r1,fr1,r1 | F,>fr1"), Opcode.BSTCC to InstructionFormat.from("N,, + val returns: RegSpec? +) { + class RegSpec(val dt: IRDataType, val registerNum: Int, val cpuRegister: RegisterOrStatusflag?) + class ArgumentSpec(val name: String, val address: Int?, val reg: RegSpec) +} + data class IRInstruction( val opcode: Opcode, val type: IRDataType?=null, @@ -664,7 +676,8 @@ data class IRInstruction( val immediateFp: Float?=null, val address: Int?=null, // 0-$ffff val labelSymbol: String?=null, // symbolic label name as alternative to address (so only for Branch/jump/call Instructions!) - var branchTarget: IRCodeChunkBase? = null // Will be linked after loading in IRProgram.linkChunks()! This is the chunk that the branch labelSymbol points to. + var branchTarget: IRCodeChunkBase? = null, // Will be linked after loading in IRProgram.linkChunks()! This is the chunk that the branch labelSymbol points to. + val fcallArgs: FunctionCallArgs? = null // will be set for the CALL and SYSCALL instructions. ) { // reg1 and fpreg1 can be IN/OUT/INOUT (all others are readonly INPUT) // This knowledge is useful in IL assembly optimizers to see how registers are used. @@ -694,13 +707,10 @@ data class IRInstruction( if(format.fpReg1==OperandDirection.UNUSED) require(fpReg1==null) { "invalid fpReg1" } if(format.fpReg2==OperandDirection.UNUSED) require(fpReg2==null) { "invalid fpReg2" } if(format.immediate) { - if(type==IRDataType.FLOAT) { - if(opcode!=Opcode.SETPARAM) - require(immediateFp !=null) {"missing immediate fp value"} - else - require(immediateFp==null) {"setparam never has immediateFp only immediate"} - } - else require(immediate!=null || labelSymbol!=null) {"missing immediate value or labelsymbol"} + if(type==IRDataType.FLOAT) + require(immediateFp !=null) {"missing immediate fp value"} + else + require(immediate!=null || labelSymbol!=null) {"missing immediate value or labelsymbol"} } if(type!=IRDataType.FLOAT) require(fpReg1==null && fpReg2==null) {"int instruction can't use fp reg"} @@ -811,36 +821,70 @@ data class IRInstruction( IRDataType.FLOAT -> result.add(".f ") else -> result.add(" ") } - reg1?.let { - result.add("r$it") - result.add(",") - } - reg2?.let { - result.add("r$it") - result.add(",") - } - fpReg1?.let { - result.add("fr$it") - result.add(",") - } - fpReg2?.let { - result.add("fr$it") - result.add(",") - } - immediate?.let { - result.add(it.toHex()) - result.add(",") - } - immediateFp?.let { - result.add(it.toString()) - result.add(",") - } - address?.let { - result.add(it.toHex()) - result.add(",") - } - labelSymbol?.let { - result.add(it) + + if(this.fcallArgs!=null) { + immediate?.let { result.add(it.toHex()) } + labelSymbol?.let { result.add(it) } + result.add("(") + fcallArgs.arguments.forEach { + val location = if(it.address==null) { + if(it.name.isBlank()) "" else it.name+"=" + } else "${it.address}=" + + if(it.reg.cpuRegister!=null) + TODO("handle cpuregister ${it.reg.cpuRegister}") + + when(it.reg.dt) { + IRDataType.BYTE -> result.add("${location}r${it.reg.registerNum}.b,") + IRDataType.WORD -> result.add("${location}r${it.reg.registerNum}.w,") + IRDataType.FLOAT -> result.add("${location}fr${it.reg.registerNum}.f,") + } + } + if(result.last().endsWith(',')) { + result.add(result.removeLast().trimEnd(',')) + } + result.add(")") + fcallArgs.returns?.let { + result.add(":") + when(it.dt) { + IRDataType.BYTE -> result.add("r${it.registerNum}.b") + IRDataType.WORD -> result.add("r${it.registerNum}.w") + IRDataType.FLOAT -> result.add("fr${it.registerNum}.f") + } + } + } else { + + reg1?.let { + result.add("r$it") + result.add(",") + } + reg2?.let { + result.add("r$it") + result.add(",") + } + fpReg1?.let { + result.add("fr$it") + result.add(",") + } + fpReg2?.let { + result.add("fr$it") + result.add(",") + } + immediate?.let { + result.add(it.toHex()) + result.add(",") + } + immediateFp?.let { + result.add(it.toString()) + result.add(",") + } + address?.let { + result.add(it.toHex()) + result.add(",") + } + labelSymbol?.let { + result.add(it) + } } if(result.last() == ",") result.removeLast() diff --git a/intermediate/src/prog8/intermediate/Utils.kt b/intermediate/src/prog8/intermediate/Utils.kt index 4b8fb4368..64a13b04a 100644 --- a/intermediate/src/prog8/intermediate/Utils.kt +++ b/intermediate/src/prog8/intermediate/Utils.kt @@ -122,39 +122,47 @@ fun parseIRCodeLine(line: String): Either { parseIRValue(operand) } } - - operands.forEach { oper -> - if(oper[0] == '&') - throw IRParseException("address-of should be done with normal LOAD ") - else if(oper[0] in "rR") { - if(reg1==null) reg1 = oper.substring(1).toInt() - else if(reg2==null) reg2 = oper.substring(1).toInt() - else throw IRParseException("too many register operands") - } else if (oper[0] in "fF" && oper[1] in "rR") { - if(fpReg1==null) fpReg1 = oper.substring(2).toInt() - else if(fpReg2==null) fpReg2 = oper.substring(2).toInt() - else throw IRParseException("too many fp register operands") - } else if (oper[0].isDigit() || oper[0] == '$' || oper[0]=='%' || oper[0]=='-' || oper.startsWith("0x")) { - val value = parseIRValue(oper) - if(format.immediate) { - if(immediateInt==null && immediateFp==null) { - if (type == IRDataType.FLOAT) - immediateFp = value - else - immediateInt = value.toInt() + if(format.sysCall) { + val call = parseCall(rest) + val syscallNum = parseIRValue(call.target).toInt() + return left(IRInstruction(Opcode.SYSCALL, immediate = syscallNum, fcallArgs = FunctionCallArgs(call.args, call.returns))) + } else if (format.funcCall) { + val call = parseCall(rest) + return left(IRInstruction(Opcode.CALL, labelSymbol = call.target, fcallArgs = FunctionCallArgs(call.args, call.returns))) + } else { + operands.forEach { oper -> + if (oper[0] == '&') + throw IRParseException("address-of should be done with normal LOAD ") + else if (oper[0] in "rR") { + if (reg1 == null) reg1 = oper.substring(1).toInt() + else if (reg2 == null) reg2 = oper.substring(1).toInt() + else throw IRParseException("too many register operands") + } else if (oper[0] in "fF" && oper[1] in "rR") { + if (fpReg1 == null) fpReg1 = oper.substring(2).toInt() + else if (fpReg2 == null) fpReg2 = oper.substring(2).toInt() + else throw IRParseException("too many fp register operands") + } else if (oper[0].isDigit() || oper[0] == '$' || oper[0] == '%' || oper[0] == '-' || oper.startsWith("0x")) { + val value = parseIRValue(oper) + if (format.immediate) { + if (immediateInt == null && immediateFp == null) { + if (type == IRDataType.FLOAT) + immediateFp = value + else + immediateInt = value.toInt() + } else { + address = value.toInt() + } } else { address = value.toInt() } } else { - address = value.toInt() + if (!oper[0].isLetter()) + throw IRParseException("expected symbol name: $oper") + labelSymbol = oper + val value = parseValueOrPlaceholder(oper) + if (value != null) + address = value.toInt() } - } else { - if(!oper[0].isLetter()) - throw IRParseException("expected symbol name: $oper") - labelSymbol = oper - val value = parseValueOrPlaceholder(oper) - if(value!=null) - address = value.toInt() } } @@ -194,10 +202,6 @@ fun parseIRCodeLine(line: String): Either { null -> {} } } - if(format.immediate && opcode==Opcode.SETPARAM && immediateInt==null) { - immediateInt = immediateFp!!.toInt() - immediateFp = null - } if(format.address!=OperandDirection.UNUSED && address==null && labelSymbol==null) throw IRParseException("requires address or symbol for $line") @@ -224,3 +228,52 @@ fun parseIRCodeLine(line: String): Either { return left(IRInstruction(opcode, type, reg1, reg2, fpReg1, fpReg2, immediateInt, immediateFp, address, labelSymbol = labelSymbol)) } + +private class ParsedCall( + val target: String, + val args: List, + val returns: FunctionCallArgs.RegSpec? +) + +private fun parseCall(rest: String): ParsedCall { + + fun parseRegspec(reg: String): FunctionCallArgs.RegSpec { + val pattern = Regex("f?r([0-9]+)\\.(.)") + val match = pattern.matchEntire(reg) ?: throw IRParseException("invalid regspec $reg") + val num = match.groups[1]!!.value.toInt() + val type = when(match.groups[2]!!.value) { + "b" -> IRDataType.BYTE + "w" -> IRDataType.WORD + "f" -> IRDataType.FLOAT + else -> throw IRParseException("invalid type spec in $reg") + } + return FunctionCallArgs.RegSpec(type, num, null) // TODO parse cpu register + } + + fun parseArgs(args: String): List { + if(args.isBlank()) + return emptyList() + return args.split(',').map { + if(it.contains('=')) { + val (argVar, argReg) = it.split('=') + FunctionCallArgs.ArgumentSpec(argVar, null, parseRegspec(argReg)) // address will be set later + } else { + FunctionCallArgs.ArgumentSpec("", null, parseRegspec(it)) // address will be set later + } + } + } + + val pattern = Regex("(?.+?)\\((?.*?)\\)(:(?.+?))?") + val match = pattern.matchEntire(rest.replace(" ","")) ?: throw IRParseException("invalid call spec $rest") + val target = match.groups["target"]!!.value + val args = match.groups["arglist"]!!.value + val arguments = parseArgs(args) + val returns = match.groups["returns"]?.value + return ParsedCall( + target, + arguments, + if(returns==null) null else parseRegspec(returns) + ) +} + + diff --git a/intermediate/test/TestIRFileInOut.kt b/intermediate/test/TestIRFileInOut.kt index 0af7571c4..04aec5a15 100644 --- a/intermediate/test/TestIRFileInOut.kt +++ b/intermediate/test/TestIRFileInOut.kt @@ -92,8 +92,6 @@ uword sys.wait.jiffies loadm.w r0,sys.wait.jiffies - setparam.w r0,0 - syscall 13 return diff --git a/virtualmachine/src/prog8/vm/SysCalls.kt b/virtualmachine/src/prog8/vm/SysCalls.kt index f9aa3a649..f3d712b45 100644 --- a/virtualmachine/src/prog8/vm/SysCalls.kt +++ b/virtualmachine/src/prog8/vm/SysCalls.kt @@ -1,6 +1,8 @@ package prog8.vm import prog8.code.core.AssemblyError +import prog8.intermediate.FunctionCallArgs +import prog8.intermediate.IRDataType import kotlin.math.min /* @@ -89,21 +91,50 @@ enum class Syscall { } object SysCalls { - fun call(call: Syscall, vm: VirtualMachine) { + private fun getArgValues(argspec: List, vm: VirtualMachine): List> { + return argspec.map { + when(it.reg.dt) { + IRDataType.BYTE -> vm.registers.getUB(it.reg.registerNum) + IRDataType.WORD -> vm.registers.getUW(it.reg.registerNum) + IRDataType.FLOAT -> vm.registers.getFloat(it.reg.registerNum) + } + } + } + + private fun returnValue(returns: FunctionCallArgs.RegSpec, value: Comparable, vm: VirtualMachine) { + val vv: Float = when(value) { + is UByte -> value.toFloat() + is UShort -> value.toFloat() + is UInt -> value.toFloat() + is Byte -> value.toFloat() + is Short -> value.toFloat() + is Int -> value.toFloat() + is Float -> value + else -> (value as Number).toFloat() + } + when(returns.dt) { + IRDataType.BYTE -> vm.registers.setUB(returns.registerNum, vv.toInt().toUByte()) + IRDataType.WORD -> vm.registers.setUW(returns.registerNum, vv.toInt().toUShort()) + IRDataType.FLOAT -> vm.registers.setFloat(returns.registerNum, vv) + } + } + + fun call(call: Syscall, callspec: FunctionCallArgs, vm: VirtualMachine) { when(call) { Syscall.RESET -> { vm.reset(false) } Syscall.EXIT ->{ - vm.exit(vm.valueStack.pop().toInt()) + val exitValue = getArgValues(callspec.arguments, vm).single() as UByte + vm.exit(exitValue.toInt()) } Syscall.PRINT_C -> { - val char = vm.valueStack.pop().toInt() - print(Char(char)) + val char = getArgValues(callspec.arguments, vm).single() as UByte + print(Char(char.toInt())) } Syscall.PRINT_S -> { - var addr = vm.valueStack.popw().toInt() + var addr = (getArgValues(callspec.arguments, vm).single() as UShort).toInt() while(true) { val char = vm.memory.getUB(addr).toInt() if(char==0) @@ -113,35 +144,52 @@ object SysCalls { } } Syscall.PRINT_U8 -> { - print(vm.valueStack.pop()) + val value = getArgValues(callspec.arguments, vm).single() + print(value) } Syscall.PRINT_U16 -> { - print(vm.valueStack.popw()) + val value = getArgValues(callspec.arguments, vm).single() + print(value) } Syscall.INPUT -> { + val (address, maxlen) = getArgValues(callspec.arguments, vm) var input = readln() - val maxlen = vm.valueStack.pop().toInt() - if(maxlen>0) - input = input.substring(0, min(input.length, maxlen)) - vm.memory.setString(vm.valueStack.popw().toInt(), input, true) - vm.valueStack.push(input.length.toUByte()) + val maxlenvalue = (maxlen as UByte).toInt() + if(maxlenvalue>0) + input = input.substring(0, min(input.length, maxlenvalue)) + vm.memory.setString((address as UShort).toInt(), input, true) + returnValue(callspec.returns!!, input.length, vm) } Syscall.SLEEP -> { - val duration = vm.valueStack.popw().toLong() - Thread.sleep(duration) + val duration = getArgValues(callspec.arguments, vm).single() as UShort + Thread.sleep(duration.toLong()) + } + Syscall.GFX_ENABLE -> { + val mode = getArgValues(callspec.arguments, vm).single() as UByte + vm.gfx_enable(mode) + } + Syscall.GFX_CLEAR -> { + val color = getArgValues(callspec.arguments, vm).single() as UByte + vm.gfx_clear(color) + } + Syscall.GFX_PLOT -> { + val (x,y,color) = getArgValues(callspec.arguments, vm) + vm.gfx_plot(x as UShort, y as UShort, color as UByte) + } + Syscall.GFX_GETPIXEL -> { + val (x,y) = getArgValues(callspec.arguments, vm) + val color = vm.gfx_getpixel(x as UShort, y as UShort) + returnValue(callspec.returns!!, color, vm) } - Syscall.GFX_ENABLE -> vm.gfx_enable() - Syscall.GFX_CLEAR -> vm.gfx_clear() - Syscall.GFX_PLOT -> vm.gfx_plot() - Syscall.GFX_GETPIXEL ->vm.gfx_getpixel() Syscall.WAIT -> { - val millis = vm.valueStack.popw().toLong() * 1000/60 - Thread.sleep(millis) + val time = getArgValues(callspec.arguments, vm).single() as UShort + Thread.sleep(time.toLong() * 1000/60) } Syscall.WAITVSYNC -> vm.waitvsync() Syscall.SORT_UBYTE -> { - val length = vm.valueStack.pop().toInt() - val address = vm.valueStack.popw().toInt() + val (addressV, lengthV) = getArgValues(callspec.arguments, vm) + val address = (addressV as UShort).toInt() + val length = (lengthV as UByte).toInt() val array = IntProgression.fromClosedRange(address, address+length-1, 1).map { vm.memory.getUB(it) }.sorted() @@ -150,8 +198,9 @@ object SysCalls { } } Syscall.SORT_BYTE -> { - val length = vm.valueStack.pop().toInt() - val address = vm.valueStack.popw().toInt() + val (addressV, lengthV) = getArgValues(callspec.arguments, vm) + val address = (addressV as UShort).toInt() + val length = (lengthV as UByte).toInt() val array = IntProgression.fromClosedRange(address, address+length-1, 1).map { vm.memory.getSB(it) }.sorted() @@ -160,8 +209,9 @@ object SysCalls { } } Syscall.SORT_UWORD -> { - val length = vm.valueStack.pop().toInt() - val address = vm.valueStack.popw().toInt() + val (addressV, lengthV) = getArgValues(callspec.arguments, vm) + val address = (addressV as UShort).toInt() + val length = (lengthV as UByte).toInt() val array = IntProgression.fromClosedRange(address, address+length*2-2, 2).map { vm.memory.getUW(it) }.sorted() @@ -170,8 +220,9 @@ object SysCalls { } } Syscall.SORT_WORD -> { - val length = vm.valueStack.pop().toInt() - val address = vm.valueStack.popw().toInt() + val (addressV, lengthV) = getArgValues(callspec.arguments, vm) + val address = (addressV as UShort).toInt() + val length = (lengthV as UByte).toInt() val array = IntProgression.fromClosedRange(address, address+length*2-2, 2).map { vm.memory.getSW(it) }.sorted() @@ -180,8 +231,9 @@ object SysCalls { } } Syscall.REVERSE_BYTES -> { - val length = vm.valueStack.pop().toInt() - val address = vm.valueStack.popw().toInt() + val (addressV, lengthV) = getArgValues(callspec.arguments, vm) + val address = (addressV as UShort).toInt() + val length = (lengthV as UByte).toInt() val array = IntProgression.fromClosedRange(address, address+length-1, 1).map { vm.memory.getUB(it) }.reversed() @@ -190,8 +242,9 @@ object SysCalls { } } Syscall.REVERSE_WORDS -> { - val length = vm.valueStack.pop().toInt() - val address = vm.valueStack.popw().toInt() + val (addressV, lengthV) = getArgValues(callspec.arguments, vm) + val address = (addressV as UShort).toInt() + val length = (lengthV as UByte).toInt() val array = IntProgression.fromClosedRange(address, address+length*2-2, 2).map { vm.memory.getUW(it) }.reversed() @@ -200,8 +253,9 @@ object SysCalls { } } Syscall.REVERSE_FLOATS -> { - val length = vm.valueStack.pop().toInt() - val address = vm.valueStack.popw().toInt() + val (addressV, lengthV) = getArgValues(callspec.arguments, vm) + val address = (addressV as UShort).toInt() + val length = (lengthV as UByte).toInt() val array = IntProgression.fromClosedRange(address, address+length*4-2, 4).map { vm.memory.getFloat(it) }.reversed() @@ -210,154 +264,154 @@ object SysCalls { } } Syscall.ANY_BYTE -> { - val length = vm.valueStack.pop().toInt() - val address = vm.valueStack.popw().toInt() + val (addressV, lengthV) = getArgValues(callspec.arguments, vm) + val address = (addressV as UShort).toInt() + val length = (lengthV as UByte).toInt() val addresses = IntProgression.fromClosedRange(address, address+length*2-2, 2) if(addresses.any { vm.memory.getUB(it).toInt()!=0 }) - vm.valueStack.push(1u) + returnValue(callspec.returns!!, 1, vm) else - vm.valueStack.push(0u) + returnValue(callspec.returns!!, 0, vm) } Syscall.ANY_WORD -> { - val length = vm.valueStack.pop().toInt() - val address = vm.valueStack.popw().toInt() + val (addressV, lengthV) = getArgValues(callspec.arguments, vm) + val address = (addressV as UShort).toInt() + val length = (lengthV as UByte).toInt() val addresses = IntProgression.fromClosedRange(address, address+length*2-2, 2) if(addresses.any { vm.memory.getUW(it).toInt()!=0 }) - vm.valueStack.push(1u) + returnValue(callspec.returns!!, 1, vm) else - vm.valueStack.push(0u) + returnValue(callspec.returns!!, 0, vm) } Syscall.ANY_FLOAT -> { - val length = vm.valueStack.pop().toInt() - val address = vm.valueStack.popw().toInt() + val (addressV, lengthV) = getArgValues(callspec.arguments, vm) + val address = (addressV as UShort).toInt() + val length = (lengthV as UByte).toInt() val addresses = IntProgression.fromClosedRange(address, address+length*4-2, 4) if(addresses.any { vm.memory.getFloat(it).toInt()!=0 }) - vm.valueStack.push(1u) + returnValue(callspec.returns!!, 1, vm) else - vm.valueStack.push(0u) + returnValue(callspec.returns!!, 0, vm) } Syscall.ALL_BYTE -> { - val length = vm.valueStack.pop().toInt() - val address = vm.valueStack.popw().toInt() + val (addressV, lengthV) = getArgValues(callspec.arguments, vm) + val address = (addressV as UShort).toInt() + val length = (lengthV as UByte).toInt() val addresses = IntProgression.fromClosedRange(address, address+length*2-2, 2) if(addresses.all { vm.memory.getUB(it).toInt()!=0 }) - vm.valueStack.push(1u) + returnValue(callspec.returns!!, 1, vm) else - vm.valueStack.push(0u) + returnValue(callspec.returns!!, 0, vm) } Syscall.ALL_WORD -> { - val length = vm.valueStack.pop().toInt() - val address = vm.valueStack.popw().toInt() + val (addressV, lengthV) = getArgValues(callspec.arguments, vm) + val address = (addressV as UShort).toInt() + val length = (lengthV as UByte).toInt() val addresses = IntProgression.fromClosedRange(address, address+length*2-2, 2) if(addresses.all { vm.memory.getUW(it).toInt()!=0 }) - vm.valueStack.push(1u) + returnValue(callspec.returns!!, 1, vm) else - vm.valueStack.push(0u) + returnValue(callspec.returns!!, 0, vm) } Syscall.ALL_FLOAT -> { - val length = vm.valueStack.pop().toInt() - val address = vm.valueStack.popw().toInt() + val (addressV, lengthV) = getArgValues(callspec.arguments, vm) + val address = (addressV as UShort).toInt() + val length = (lengthV as UByte).toInt() val addresses = IntProgression.fromClosedRange(address, address+length*4-2, 4) if(addresses.all { vm.memory.getFloat(it).toInt()!=0 }) - vm.valueStack.push(1u) + returnValue(callspec.returns!!, 1, vm) else - vm.valueStack.push(0u) + returnValue(callspec.returns!!, 0, vm) } Syscall.PRINT_F -> { - print(vm.valueStack.popf()) + val value = getArgValues(callspec.arguments, vm).single() as Float + print(value) } Syscall.STR_TO_UWORD -> { - val stringAddr = vm.valueStack.popw() + val stringAddr = getArgValues(callspec.arguments, vm).single() as UShort val string = vm.memory.getString(stringAddr.toInt()).takeWhile { it.isDigit() } val value = try { string.toUShort() } catch(_: NumberFormatException) { 0u } - vm.valueStack.pushw(value) + returnValue(callspec.returns!!, value, vm) } Syscall.STR_TO_WORD -> { - val stringAddr = vm.valueStack.popw() + val stringAddr = getArgValues(callspec.arguments, vm).single() as UShort val memstring = vm.memory.getString(stringAddr.toInt()) - val match = Regex("^[+-]?\\d+").find(memstring) - if(match==null) { - vm.valueStack.pushw(0u) - return - } + val match = Regex("^[+-]?\\d+").find(memstring) ?: return returnValue(callspec.returns!!, 0, vm) val value = try { match.value.toShort() } catch(_: NumberFormatException) { 0 } - vm.valueStack.pushw(value.toUShort()) + return returnValue(callspec.returns!!, value, vm) } Syscall.COMPARE_STRINGS -> { - val secondAddr = vm.valueStack.popw() - val firstAddr = vm.valueStack.popw() + val (firstV, secondV) = getArgValues(callspec.arguments, vm) + val firstAddr = firstV as UShort + val secondAddr = secondV as UShort val first = vm.memory.getString(firstAddr.toInt()) val second = vm.memory.getString(secondAddr.toInt()) val comparison = first.compareTo(second) if(comparison==0) - vm.valueStack.push(0u) + returnValue(callspec.returns!!, 0, vm) else if(comparison<0) - vm.valueStack.push((-1).toUByte()) + returnValue(callspec.returns!!, -1, vm) else - vm.valueStack.push(1u) + returnValue(callspec.returns!!, 1, vm) } Syscall.RNDFSEED -> { - val seed = vm.valueStack.popf() + val seed = getArgValues(callspec.arguments, vm).single() as Float if(seed>0) // always use negative seed, this mimics the behavior on CBM machines vm.randomSeedFloat(-seed) else vm.randomSeedFloat(seed) } Syscall.RNDSEED -> { - val seed2 = vm.valueStack.popw() - val seed1 = vm.valueStack.popw() - vm.randomSeed(seed1, seed2) + val (seed1, seed2) = getArgValues(callspec.arguments, vm) + vm.randomSeed(seed1 as UShort, seed2 as UShort) } Syscall.RND -> { - vm.valueStack.push(vm.randomGenerator.nextInt().toUByte()) + returnValue(callspec.returns!!, vm.randomGenerator.nextInt().toUByte(), vm) } Syscall.RNDW -> { - vm.valueStack.pushw(vm.randomGenerator.nextInt().toUShort()) + returnValue(callspec.returns!!, vm.randomGenerator.nextInt().toUShort(), vm) } Syscall.RNDF -> { - vm.valueStack.pushf(vm.randomGeneratorFloats.nextFloat()) + returnValue(callspec.returns!!, vm.randomGeneratorFloats.nextFloat(), vm) } Syscall.STRING_CONTAINS -> { - val stringAddr = vm.valueStack.popw() - val char = vm.valueStack.pop().toInt().toChar() + val (charV, addr) = getArgValues(callspec.arguments, vm) + val stringAddr = addr as UShort + val char = (charV as UByte).toInt().toChar() val string = vm.memory.getString(stringAddr.toInt()) - vm.valueStack.push(if(char in string) 1u else 0u) + returnValue(callspec.returns!!, if(char in string) 1u else 0u, vm) } Syscall.BYTEARRAY_CONTAINS -> { - var length = vm.valueStack.pop() - var array = vm.valueStack.popw().toInt() - val value = vm.valueStack.pop() + val (value, arrayV, lengthV) = getArgValues(callspec.arguments, vm) + var length = lengthV as UByte + var array = (arrayV as UShort).toInt() while(length>0u) { - if(vm.memory.getUB(array)==value) { - vm.valueStack.push(1u) - return - } + if(vm.memory.getUB(array)==value) + return returnValue(callspec.returns!!, 1u, vm) array++ length-- } - vm.valueStack.push(0u) + returnValue(callspec.returns!!, 0u, vm) } Syscall.WORDARRAY_CONTAINS -> { - var length = vm.valueStack.pop() - var array = vm.valueStack.popw().toInt() - val value = vm.valueStack.popw() + val (value, arrayV, lengthV) = getArgValues(callspec.arguments, vm) + var length = lengthV as UByte + var array = (arrayV as UShort).toInt() while(length>0u) { - if(vm.memory.getUW(array)==value) { - vm.valueStack.push(1u) - return - } + if(vm.memory.getUW(array)==value) + return returnValue(callspec.returns!!, 1u, vm) array += 2 length-- } - vm.valueStack.push(0u) + returnValue(callspec.returns!!, 0u, vm) } else -> throw AssemblyError("missing syscall ${call.name}") } diff --git a/virtualmachine/src/prog8/vm/VirtualMachine.kt b/virtualmachine/src/prog8/vm/VirtualMachine.kt index f90ad90f0..77b54914e 100644 --- a/virtualmachine/src/prog8/vm/VirtualMachine.kt +++ b/virtualmachine/src/prog8/vm/VirtualMachine.kt @@ -32,7 +32,7 @@ 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?) + class CallSiteContext(val returnChunk: IRCodeChunk, val returnIndex: Int, val fcallSpec: FunctionCallArgs) val memory = Memory() val program: List val registers = Registers() @@ -176,8 +176,7 @@ class VirtualMachine(irProgram: IRProgram) { Opcode.STOREZI -> InsSTOREZI(ins) Opcode.JUMP -> InsJUMP(ins) Opcode.JUMPA -> throw IllegalArgumentException("vm program can't jump to system memory address (JUMPA)") - Opcode.SETPARAM -> InsSETPARAM(ins) - Opcode.CALL, Opcode.CALLR -> InsCALL(ins) + Opcode.CALL -> InsCALL(ins) Opcode.SYSCALL -> InsSYSCALL(ins) Opcode.RETURN -> InsRETURN() Opcode.RETURNR -> InsRETURNR(ins) @@ -286,7 +285,6 @@ class VirtualMachine(irProgram: IRProgram) { Opcode.SEC -> { statusCarry = true; nextPc() } Opcode.LOADCPU -> InsLOADCPU(ins) Opcode.STORECPU -> InsSTORECPU(ins) - Opcode.STOREZCPU -> InsSTOREZCPU(ins) Opcode.FFROMUB -> InsFFROMUB(ins) Opcode.FFROMSB -> InsFFROMSB(ins) @@ -363,7 +361,7 @@ class VirtualMachine(irProgram: IRProgram) { value.dt=null } val call = Syscall.fromInt(i.immediate!!) - SysCalls.call(call, this) // note: any result value(s) are pushed back on the value stack + SysCalls.call(call, i.fcallArgs!!, this) // note: any result value(s) are pushed back on the value stack nextPc() } @@ -415,11 +413,6 @@ class VirtualMachine(irProgram: IRProgram) { nextPc() } - private fun InsSTOREZCPU(i: IRInstruction) { - StoreCPU(0u, i.type!!, i.labelSymbol!!) - nextPc() - } - private fun StoreCPU(value: UInt, dt: IRDataType, regStr: String) { if(regStr.startsWith('r')) { val regnum = regStr.substring(1).toInt() @@ -603,30 +596,17 @@ class VirtualMachine(irProgram: IRProgram) { private class SyscallParamValue(var dt: IRDataType?, var value: Comparable<*>?) private val syscallParams = Array(100) { SyscallParamValue(null, null) } - private fun InsSETPARAM(i: IRInstruction) { - // store the argument value into the retrieved subroutine's parameter variable (already cached in the instruction's address) - // the reason this is a special instruction is to be flexible in implementing the call convention - val address = i.address - if(address==null) { - // this param is for a SYSCALL (that has no param variable address, instead it goes via the stack) - syscallParams[i.immediate!!].dt = i.type!! - syscallParams[i.immediate!!].value = when(i.type!!) { - IRDataType.BYTE -> registers.getUB(i.reg1!!) - IRDataType.WORD -> registers.getUW(i.reg1!!) - IRDataType.FLOAT -> registers.getFloat(i.fpReg1!!) - } - } else { - when (i.type!!) { - IRDataType.BYTE -> memory.setUB(address, registers.getUB(i.reg1!!)) - IRDataType.WORD -> memory.setUW(address, registers.getUW(i.reg1!!)) - IRDataType.FLOAT -> memory.setFloat(address, registers.getFloat(i.fpReg1!!)) + private fun InsCALL(i: IRInstruction) { + i.fcallArgs!!.arguments.forEach { arg -> + require(arg.address!=null) {"argument variable should have been given its memory address as well"} + when(arg.reg.dt) { + IRDataType.BYTE -> memory.setUB(arg.address!!, registers.getUB(arg.reg.registerNum)) + IRDataType.WORD -> memory.setUW(arg.address!!, registers.getUW(arg.reg.registerNum)) + IRDataType.FLOAT -> memory.setFloat(arg.address!!, registers.getFloat(arg.reg.registerNum)) } } - nextPc() - } - - private fun InsCALL(i: IRInstruction) { - callStack.push(CallSiteContext(pcChunk, pcIndex+1, i.reg1, i.fpReg1)) + // store the call site and jump + callStack.push(CallSiteContext(pcChunk, pcIndex+1, i.fcallArgs!!)) branchTo(i) } @@ -646,10 +626,11 @@ class VirtualMachine(irProgram: IRProgram) { exit(0) else { val context = callStack.pop() + val returns = context.fcallSpec.returns when (i.type!!) { IRDataType.BYTE -> { - if(context.returnValueReg!=null) - registers.setUB(context.returnValueReg, registers.getUB(i.reg1!!)) + if(returns!=null) + registers.setUB(returns.registerNum, registers.getUB(i.reg1!!)) else { val callInstr = context.returnChunk.instructions[context.returnIndex-1] if(callInstr.opcode!=Opcode.CALL) @@ -657,8 +638,8 @@ class VirtualMachine(irProgram: IRProgram) { } } IRDataType.WORD -> { - if(context.returnValueReg!=null) - registers.setUW(context.returnValueReg, registers.getUW(i.reg1!!)) + if(returns!=null) + registers.setUW(returns.registerNum, registers.getUW(i.reg1!!)) else { val callInstr = context.returnChunk.instructions[context.returnIndex-1] if(callInstr.opcode!=Opcode.CALL) @@ -666,8 +647,8 @@ class VirtualMachine(irProgram: IRProgram) { } } IRDataType.FLOAT -> { - if(context.returnValueFpReg!=null) - registers.setFloat(context.returnValueFpReg, registers.getFloat(i.fpReg1!!)) + if(returns!=null) + registers.setFloat(returns.registerNum, registers.getFloat(i.fpReg1!!)) else { val callInstr = context.returnChunk.instructions[context.returnIndex-1] if(callInstr.opcode!=Opcode.CALL) @@ -2302,8 +2283,8 @@ class VirtualMachine(irProgram: IRProgram) { private var window: GraphicsWindow? = null - fun gfx_enable() { - window = when(valueStack.pop().toInt()) { + fun gfx_enable(mode: UByte) { + window = when(mode.toInt()) { 0 -> GraphicsWindow(320, 240, 3) 1 -> GraphicsWindow(640, 480, 2) else -> throw IllegalArgumentException("invalid screen mode") @@ -2311,25 +2292,20 @@ class VirtualMachine(irProgram: IRProgram) { window!!.start() } - fun gfx_clear() { - window?.clear(valueStack.pop().toInt()) + fun gfx_clear(color: UByte) { + window?.clear(color.toInt()) } - fun gfx_plot() { - val color = valueStack.pop() - val y = valueStack.popw() - val x = valueStack.popw() + fun gfx_plot(x: UShort, y: UShort, color: UByte) { window?.plot(x.toInt(), y.toInt(), color.toInt()) } - fun gfx_getpixel() { - val y = valueStack.popw() - val x = valueStack.popw() - if(window==null) - valueStack.push(0u) + fun gfx_getpixel(x: UShort, y: UShort): UByte { + return if(window==null) + 0u else { val color = Color(window!!.getpixel(x.toInt(), y.toInt())) - valueStack.push(color.green.toUByte()) // gets called from a syscall, return value via stack. + color.green.toUByte() } } diff --git a/virtualmachine/src/prog8/vm/VmProgramLoader.kt b/virtualmachine/src/prog8/vm/VmProgramLoader.kt index d5b05a325..c3e3a0ada 100644 --- a/virtualmachine/src/prog8/vm/VmProgramLoader.kt +++ b/virtualmachine/src/prog8/vm/VmProgramLoader.kt @@ -168,27 +168,27 @@ class VmProgramLoader { } } - chunks.forEach { - it.instructions.withIndex().forEach { (index, ins) -> - if(ins.opcode==Opcode.SETPARAM && ins.address==null) { - val call = findCall(it, index) - if(call.opcode==Opcode.SYSCALL) { - // there is no variable to set, SYSCALLs get their args from the stack. - } else if(call.labelSymbol!=null) { - // set the address in the instruction to the subroutine's parameter variable's address - // this avoids having to look it up every time the SETPARAM instruction is encountered during execution - val target = subroutines.getValue(call.labelSymbol!!) - val paramVar = target.parameters[ins.immediate!!] - val address = variableAddresses.getValue(paramVar.name) - it.instructions[index] = ins.copy(address = address) - } else - throw IRParseException("weird call $call") + subroutines.forEach { + it.value.chunks.forEach { chunk -> + chunk.instructions.withIndex().forEach { (index, ins) -> + if(ins.opcode==Opcode.CALL) { + val fcallspec = ins.fcallArgs!! + val argsWithAddresses = fcallspec.arguments.map { arg -> + if(arg.address!=null) + arg + else { + val address = variableAddresses.getValue(ins.labelSymbol + "." + arg.name) + FunctionCallArgs.ArgumentSpec(arg.name, address, arg.reg) + } + } + fcallspec.arguments = argsWithAddresses + } } } } } - private val functionCallOpcodes = setOf(Opcode.CALL, Opcode.CALLR, Opcode.SYSCALL, Opcode.JUMP, Opcode.JUMPA) + private val functionCallOpcodes = setOf(Opcode.CALL, Opcode.SYSCALL, Opcode.JUMP, Opcode.JUMPA) private fun findCall(it: IRCodeChunk, startIndex: Int): IRInstruction { var idx = startIndex while(it.instructions[idx].opcode !in functionCallOpcodes) From 630c8a5faa21760fbf7340e6a57fa7197a685484 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sun, 14 May 2023 16:48:48 +0200 Subject: [PATCH 2/3] IR: fix romsub encoding --- codeCore/src/prog8/code/SymbolTable.kt | 2 +- codeCore/src/prog8/code/SymbolTableMaker.kt | 11 +-- .../codegen/intermediate/ExpressionGen.kt | 45 +++++------ docs/source/todo.rst | 2 +- examples/test.p8 | 22 ++---- .../src/prog8/intermediate/IRInstructions.kt | 55 ++++++------- intermediate/src/prog8/intermediate/Utils.kt | 15 ---- virtualmachine/src/prog8/vm/VirtualMachine.kt | 77 ------------------- .../src/prog8/vm/VmProgramLoader.kt | 14 ++-- 9 files changed, 62 insertions(+), 181 deletions(-) diff --git a/codeCore/src/prog8/code/SymbolTable.kt b/codeCore/src/prog8/code/SymbolTable.kt index 8900eb341..82635abae 100644 --- a/codeCore/src/prog8/code/SymbolTable.kt +++ b/codeCore/src/prog8/code/SymbolTable.kt @@ -235,7 +235,7 @@ class StSub(name: String, val parameters: List, val retur class StRomSub(name: String, - val address: UInt, + val address: UInt?, // null in case of asmsub, specified in case of romsub val parameters: List, val returns: List, astNode: PtNode) : diff --git a/codeCore/src/prog8/code/SymbolTableMaker.kt b/codeCore/src/prog8/code/SymbolTableMaker.kt index b793b4c1a..791add9ed 100644 --- a/codeCore/src/prog8/code/SymbolTableMaker.kt +++ b/codeCore/src/prog8/code/SymbolTableMaker.kt @@ -40,14 +40,9 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp private fun addToSt(node: PtNode, scope: Stack) { val stNode = when(node) { is PtAsmSub -> { - if(node.address==null) { - val params = node.parameters.map { StSubroutineParameter(it.second.name, it.second.type) } - StSub(node.name, params, node.returns.singleOrNull()?.second, node) - } else { - val parameters = node.parameters.map { StRomSubParameter(it.first, it.second.type) } - val returns = node.returns.map { StRomSubParameter(it.first, it.second) } - StRomSub(node.name, node.address, parameters, returns, node) - } + val parameters = node.parameters.map { StRomSubParameter(it.first, it.second.type) } + val returns = node.returns.map { StRomSubParameter(it.first, it.second) } + StRomSub(node.name, node.address, parameters, returns, node) } is PtBlock -> { StNode(node.name, StNodeType.BLOCK, node) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt index 9c8a09310..bc77efa9d 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt @@ -4,7 +4,10 @@ import prog8.code.StRomSub import prog8.code.StStaticVariable import prog8.code.StSub import prog8.code.ast.* -import prog8.code.core.* +import prog8.code.core.AssemblyError +import prog8.code.core.DataType +import prog8.code.core.PassByValueDatatypes +import prog8.code.core.SignedDatatypes import prog8.intermediate.* internal class ExpressionCodeResult(val chunks: IRCodeChunks, val dt: IRDataType, val resultReg: Int, val resultFpReg: Int) { @@ -361,10 +364,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { addInstr(result, call, null) return if(fcall.void) ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) - else if(call.fcallArgs!!.returns!!.dt==IRDataType.FLOAT) - ExpressionCodeResult(result, codeGen.irType(fcall.type), -1, call.fcallArgs!!.returns!!.registerNum) + else if(fcall.type==DataType.FLOAT) + ExpressionCodeResult(result, returnRegSpec!!.dt, -1, returnRegSpec.registerNum) else - ExpressionCodeResult(result, codeGen.irType(fcall.type), call.fcallArgs!!.returns!!.registerNum, -1) + ExpressionCodeResult(result, returnRegSpec!!.dt, returnRegSpec.registerNum, -1) } is StRomSub -> { val result = mutableListOf() @@ -401,30 +404,18 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { } } // create the call - val call = IRInstruction(Opcode.CALL, address = callTarget.address.toInt(), fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec)) + val call = + if(callTarget.address==null) + IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec)) + else + IRInstruction(Opcode.CALL, address = callTarget.address!!.toInt(), fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec)) addInstr(result, call, null) - if(fcall.void) { - return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) - } else { - val regStr = if(callTarget.returns.isEmpty()) - throw AssemblyError("expect a return value") - else if(callTarget.returns.size==1) { - val returns = callTarget.returns.single() - if(returns.register.registerOrPair!=null) returns.register.registerOrPair.toString() else returns.register.statusflag.toString() - } else { - // multiple return values: take the first *register* (not status flag) return value and ignore the rest. - callTarget.returns.first { it.register.registerOrPair!=null }.toString() - } - return if(fcall.type==DataType.FLOAT) { - val resultFpReg = codeGen.registers.nextFreeFloat() - addInstr(result, IRInstruction(Opcode.LOADCPU, IRDataType.FLOAT, fpReg1 = resultFpReg, labelSymbol = regStr), null) - ExpressionCodeResult(result, returnRegSpec!!.dt, -1, resultFpReg) - } else { - val resultReg = codeGen.registers.nextFree() - addInstr(result, IRInstruction(Opcode.LOADCPU, returnRegSpec!!.dt, reg1 = resultReg, labelSymbol = regStr), null) - ExpressionCodeResult(result, returnRegSpec.dt, resultReg, -1) - } - } + return if(fcall.void) + ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) + else if(fcall.type==DataType.FLOAT) + ExpressionCodeResult(result, returnRegSpec!!.dt, -1, returnRegSpec.registerNum) + else + ExpressionCodeResult(result, returnRegSpec!!.dt, returnRegSpec.registerNum, -1) } else -> throw AssemblyError("invalid node type") } diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 119a984ec..4ffefd935 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,7 +3,7 @@ TODO For next minor release ^^^^^^^^^^^^^^^^^^^^^^ -- fix VM problem with param passing: void string.copy(".prg", &output_filename + string.length(output_filename)) +- fix romsub encoding into IR ... diff --git a/examples/test.p8 b/examples/test.p8 index 5daf7ca0b..919149276 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,27 +1,15 @@ -%import textio -%import string %zeropage basicsafe main { sub start() { - txt.chrout('!') - txt.print("test") - txt.nl() -; uword seconds_uword = 1 -; uword remainder = seconds_uword % $0003 ==0 -; txt.print_uw(remainder) -; -; blerp() + ubyte @shared foo = derp(99) } - sub blerp() { - %ir {{ -_xxx: - loadr r2,r3 -_yyy: - loadr r3,r4 - return + asmsub derp(ubyte xx @Y) -> ubyte @ A { + %asm {{ + rts + }} } } diff --git a/intermediate/src/prog8/intermediate/IRInstructions.kt b/intermediate/src/prog8/intermediate/IRInstructions.kt index e84a8bee8..250317153 100644 --- a/intermediate/src/prog8/intermediate/IRInstructions.kt +++ b/intermediate/src/prog8/intermediate/IRInstructions.kt @@ -1,6 +1,5 @@ package prog8.intermediate -import prog8.code.core.CpuRegister import prog8.code.core.RegisterOrStatusflag import prog8.code.core.toHex @@ -39,15 +38,11 @@ loadi reg1, reg2 - load reg1 with value at memory indirect, loadx reg1, reg2, address - load reg1 with value at memory address indexed by value in reg2 loadix reg1, reg2, pointeraddr - load reg1 with value at memory indirect, pointed to by pointeraddr indexed by value in reg2 loadr reg1, reg2 - load reg1 with value in register reg2 -loadcpu reg1, cpureg - load reg1 with value from cpu register (register/registerpair/statusflag) - storem reg1, address - store reg1 at memory address -storecpu reg1, cpureg - store reg1 in cpu register (register/registerpair/statusflag) storei reg1, reg2 - store reg1 at memory indirect, memory pointed to by reg2 storex reg1, reg2, address - store reg1 at memory address, indexed by value in reg2 storeix reg1, reg2, pointeraddr - store reg1 at memory indirect, pointed to by pointeraddr indexed by value in reg2 storezm address - store zero at memory address -storezcpu cpureg - store zero in cpu register (register/registerpair/statusflag) storezi reg1 - store zero at memory pointed to by reg1 storezx reg1, address - store zero at memory address, indexed by value in reg @@ -60,6 +55,9 @@ call label(argument register list) [: resultreg.type] - calls a subroutine with the given arguments and return value (optional). save current instruction location+1, continue execution at instruction nr of the label. the argument register list is positional and includes the datatype, ex.: r4.b,r5.w,fp1.f + 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) 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. @@ -231,9 +229,7 @@ enum class Opcode { LOADX, LOADIX, LOADR, - LOADCPU, STOREM, - STORECPU, STOREI, STOREX, STOREIX, @@ -418,11 +414,6 @@ val OpcodesThatBranch = setOf( Opcode.BLES ) -val OpcodesForCpuRegisters = setOf( - Opcode.LOADCPU, - Opcode.STORECPU -) - enum class IRDataType { BYTE, WORD, @@ -513,9 +504,7 @@ val instructionFormats = mutableMapOf( Opcode.LOADX to InstructionFormat.from("BW,>r1,fr1,r1,fr1,r1,fr1,r1"), Opcode.STOREM to InstructionFormat.from("BW,a | F,a"), - Opcode.STORECPU to InstructionFormat.from("BW,a | F,a"), Opcode.STOREIX to InstructionFormat.from("BW,a | F,a"), @@ -823,33 +812,47 @@ data class IRInstruction( } if(this.fcallArgs!=null) { - immediate?.let { result.add(it.toHex()) } - labelSymbol?.let { result.add(it) } + immediate?.let { result.add(it.toHex()) } // syscall + labelSymbol?.let { result.add(it) } // regular subroutine call + address?.let { result.add(address.toHex()) } // romcall result.add("(") fcallArgs.arguments.forEach { val location = if(it.address==null) { if(it.name.isBlank()) "" else it.name+"=" } else "${it.address}=" - if(it.reg.cpuRegister!=null) - TODO("handle cpuregister ${it.reg.cpuRegister}") + val cpuReg = if(it.reg.cpuRegister==null) "" else { + if(it.reg.cpuRegister.registerOrPair!=null) + "@"+it.reg.cpuRegister.registerOrPair.toString() + else + "@"+it.reg.cpuRegister.statusflag.toString() + } when(it.reg.dt) { - IRDataType.BYTE -> result.add("${location}r${it.reg.registerNum}.b,") - IRDataType.WORD -> result.add("${location}r${it.reg.registerNum}.w,") - IRDataType.FLOAT -> result.add("${location}fr${it.reg.registerNum}.f,") + IRDataType.BYTE -> result.add("${location}r${it.reg.registerNum}.b$cpuReg,") + IRDataType.WORD -> result.add("${location}r${it.reg.registerNum}.w$cpuReg,") + IRDataType.FLOAT -> result.add("${location}fr${it.reg.registerNum}.f$cpuReg,") } } if(result.last().endsWith(',')) { result.add(result.removeLast().trimEnd(',')) } result.add(")") - fcallArgs.returns?.let { + val returns = fcallArgs.returns + if(returns!=null) { result.add(":") - when(it.dt) { - IRDataType.BYTE -> result.add("r${it.registerNum}.b") - IRDataType.WORD -> result.add("r${it.registerNum}.w") - IRDataType.FLOAT -> result.add("fr${it.registerNum}.f") + when (returns.dt) { + IRDataType.BYTE -> result.add("r${returns.registerNum}.b") + IRDataType.WORD -> result.add("r${returns.registerNum}.w") + IRDataType.FLOAT -> result.add("fr${returns.registerNum}.f") + } + if(returns.cpuRegister!=null) { + val cpuReg = + if(returns.cpuRegister.registerOrPair!=null) + returns.cpuRegister.registerOrPair.toString() + else + returns.cpuRegister.statusflag.toString() + result.add("@"+cpuReg) } } } else { diff --git a/intermediate/src/prog8/intermediate/Utils.kt b/intermediate/src/prog8/intermediate/Utils.kt index 64a13b04a..5f101fc39 100644 --- a/intermediate/src/prog8/intermediate/Utils.kt +++ b/intermediate/src/prog8/intermediate/Utils.kt @@ -211,21 +211,6 @@ fun parseIRCodeLine(line: String): Either { throw IRParseException("labelsymbol confused with register?: $labelSymbol") } - if(opcode in OpcodesForCpuRegisters) { - val reg = operands.last().lowercase() - if(reg !in setOf( - "a", "x", "y", - "ax", "ay", "xy", - "r0", "r1", "r2", "r3", - "r4", "r5", "r6", "r7", - "r8", "r9", "r10","r11", - "r12", "r13", "r14", "r15", - "pc", "pz", "pv","pn")) - throw IRParseException("invalid cpu reg: $reg") - - return left(IRInstruction(opcode, type, reg1, labelSymbol = reg)) - } - return left(IRInstruction(opcode, type, reg1, reg2, fpReg1, fpReg2, immediateInt, immediateFp, address, labelSymbol = labelSymbol)) } diff --git a/virtualmachine/src/prog8/vm/VirtualMachine.kt b/virtualmachine/src/prog8/vm/VirtualMachine.kt index 77b54914e..13c0c8a5b 100644 --- a/virtualmachine/src/prog8/vm/VirtualMachine.kt +++ b/virtualmachine/src/prog8/vm/VirtualMachine.kt @@ -283,8 +283,6 @@ class VirtualMachine(irProgram: IRProgram) { Opcode.BREAKPOINT -> InsBREAKPOINT() Opcode.CLC -> { statusCarry = false; nextPc() } Opcode.SEC -> { statusCarry = true; nextPc() } - Opcode.LOADCPU -> InsLOADCPU(ins) - Opcode.STORECPU -> InsSTORECPU(ins) Opcode.FFROMUB -> InsFFROMUB(ins) Opcode.FFROMSB -> InsFFROMSB(ins) @@ -373,81 +371,6 @@ class VirtualMachine(irProgram: IRProgram) { throw BreakpointException(pcChunk, pcIndex) } - private fun InsLOADCPU(i: IRInstruction) { - val reg = i.labelSymbol!! - val value: UInt - if(reg.startsWith('r')) { - val regnum = reg.substring(1).toInt() - val regAddr = cx16virtualregsBaseAddress + regnum*2 - value = memory.getUW(regAddr).toUInt() - } else { - value = when(reg) { - "a" -> registers.cpuA.toUInt() - "x" -> registers.cpuX.toUInt() - "y" -> registers.cpuY.toUInt() - "ax" -> (registers.cpuA.toUInt() shl 8) or registers.cpuX.toUInt() - "ay" -> (registers.cpuA.toUInt() shl 8) or registers.cpuY.toUInt() - "xy" -> (registers.cpuX.toUInt() shl 8) or registers.cpuY.toUInt() - "pc" -> if(statusCarry) 1u else 0u - "pz" -> if(statusZero) 1u else 0u - "pn" -> if(statusNegative) 1u else 0u - "pv" -> throw IllegalArgumentException("overflow status register not supported in VM") - else -> throw IllegalArgumentException("invalid cpu reg") - } - } - when(i.type!!) { - IRDataType.BYTE -> registers.setUB(i.reg1!!, value.toUByte()) - IRDataType.WORD -> registers.setUW(i.reg1!!, value.toUShort()) - else -> throw java.lang.IllegalArgumentException("invalid cpu reg type") - } - nextPc() - } - - private fun InsSTORECPU(i: IRInstruction) { - val value: UInt = when(i.type!!) { - IRDataType.BYTE -> registers.getUB(i.reg1!!).toUInt() - IRDataType.WORD -> registers.getUW(i.reg1!!).toUInt() - IRDataType.FLOAT -> throw IllegalArgumentException("there are no float cpu registers") - } - StoreCPU(value, i.type!!, i.labelSymbol!!) - nextPc() - } - - private fun StoreCPU(value: UInt, dt: IRDataType, regStr: String) { - if(regStr.startsWith('r')) { - val regnum = regStr.substring(1).toInt() - val regAddr = cx16virtualregsBaseAddress + regnum*2 - when(dt) { - IRDataType.BYTE -> memory.setUB(regAddr, value.toUByte()) - IRDataType.WORD -> memory.setUW(regAddr, value.toUShort()) - else -> throw IllegalArgumentException("invalid reg dt") - } - } else { - when (regStr) { - "a" -> registers.cpuA = value.toUByte() - "x" -> registers.cpuX = value.toUByte() - "y" -> registers.cpuY = value.toUByte() - "ax" -> { - registers.cpuA = (value and 255u).toUByte() - registers.cpuX = (value shr 8).toUByte() - } - "ay" -> { - registers.cpuA = (value and 255u).toUByte() - registers.cpuY = (value shr 8).toUByte() - } - "xy" -> { - registers.cpuX = (value and 255u).toUByte() - registers.cpuY = (value shr 8).toUByte() - } - "pc" -> statusCarry = value == 1u - "pz" -> statusZero = value == 1u - "pn" -> statusNegative = value == 1u - "pv" -> throw IllegalArgumentException("overflow status register not supported in VM") - else -> throw IllegalArgumentException("invalid cpu reg") - } - } - } - private fun InsLOAD(i: IRInstruction) { if(i.type==IRDataType.FLOAT) registers.setFloat(i.fpReg1!!, i.immediateFp!!) diff --git a/virtualmachine/src/prog8/vm/VmProgramLoader.kt b/virtualmachine/src/prog8/vm/VmProgramLoader.kt index c3e3a0ada..db2760131 100644 --- a/virtualmachine/src/prog8/vm/VmProgramLoader.kt +++ b/virtualmachine/src/prog8/vm/VmProgramLoader.kt @@ -67,7 +67,7 @@ class VmProgramLoader { programChunks.forEach { it.instructions.forEach { ins -> - if (ins.labelSymbol != null && ins.opcode !in OpcodesThatBranch && ins.opcode !in OpcodesForCpuRegisters) + 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" } } } @@ -152,16 +152,12 @@ class VmProgramLoader { // placeholder is not a variable, so it must be a label of a code chunk instead val target: IRCodeChunk? = chunks.firstOrNull { it.label==label } val opcode = chunk.instructions[line].opcode - if(target==null) { - // exception allowed: storecpu/loadcpu instructions that refer to CPU registers - if(opcode !in OpcodesForCpuRegisters) - throw IRParseException("placeholder not found in variables nor labels: $label") - } - else if(opcode in OpcodesThatBranch) { + if(target==null) + throw IRParseException("placeholder not found in variables nor labels: $label") + else if(opcode in OpcodesThatBranch) chunk.instructions[line] = chunk.instructions[line].copy(branchTarget = target, address = null) - } else { + else throw IRParseException("vm cannot yet load a label address as a value: ${chunk.instructions[line]}") // TODO - } } } else { chunk.instructions[line] = chunk.instructions[line].copy(address = replacement) From 0e3d75cfeb2cd72bb865ac3b97e75dc9ee9704fb Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sun, 14 May 2023 20:44:32 +0200 Subject: [PATCH 3/3] move irType() to intermediate module --- .../codegen/intermediate/AssignmentGen.kt | 8 +++--- .../codegen/intermediate/BuiltinFuncGen.kt | 6 ++--- .../codegen/intermediate/ExpressionGen.kt | 26 +++++++++---------- .../prog8/codegen/intermediate/IRCodeGen.kt | 14 ---------- docs/source/todo.rst | 2 -- intermediate/src/prog8/intermediate/Utils.kt | 14 ++++++++++ 6 files changed, 34 insertions(+), 36 deletions(-) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt index 4b1be02c4..29ad7f341 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt @@ -46,7 +46,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express assignment: PtAugmentedAssign ): IRCodeChunks { val value = assignment.value - val vmDt = codeGen.irType(value.type) + val vmDt = irType(value.type) return when(assignment.operator) { "+" -> expressionEval.operatorPlusInplace(address, null, vmDt, value) "-" -> expressionEval.operatorMinusInplace(address, null, vmDt, value) @@ -72,7 +72,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express private fun assignVarAugmented(symbol: String, assignment: PtAugmentedAssign): IRCodeChunks { val value = assignment.value - val targetDt = codeGen.irType(assignment.target.type) + val targetDt = irType(assignment.target.type) return when (assignment.operator) { "+=" -> expressionEval.operatorPlusInplace(null, symbol, targetDt, value) "-=" -> expressionEval.operatorMinusInplace(null, symbol, targetDt, value) @@ -161,8 +161,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express val targetIdent = assignment.target.identifier val targetMemory = assignment.target.memory val targetArray = assignment.target.array - val valueDt = codeGen.irType(assignment.value.type) - val targetDt = codeGen.irType(assignment.target.type) + val valueDt = irType(assignment.value.type) + val targetDt = irType(assignment.target.type) val result = mutableListOf() var valueRegister = -1 diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt index e4c81a5a5..4fede9675 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt @@ -93,7 +93,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe addToResult(result, leftTr, leftTr.resultReg, -1) val rightTr = exprGen.translateExpression(call.args[1]) addToResult(result, rightTr, rightTr.resultReg, -1) - val dt = codeGen.irType(call.args[0].type) + val dt = irType(call.args[0].type) result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.CMP, dt, reg1=leftTr.resultReg, reg2=rightTr.resultReg) } @@ -188,7 +188,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe private fun funcSgn(call: PtBuiltinFunctionCall): ExpressionCodeResult { val result = mutableListOf() - val vmDt = codeGen.irType(call.type) + val vmDt = irType(call.type) val tr = exprGen.translateExpression(call.args.single()) addToResult(result, tr, tr.resultReg, -1) val resultReg = codeGen.registers.nextFree() @@ -438,7 +438,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe } private fun funcRolRor(opcode: Opcode, call: PtBuiltinFunctionCall): ExpressionCodeResult { - val vmDt = codeGen.irType(call.args[0].type) + val vmDt = irType(call.args[0].type) val result = mutableListOf() val tr = exprGen.translateExpression(call.args[0]) addToResult(result, tr, tr.resultReg, -1) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt index bc77efa9d..d270f1786 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt @@ -28,10 +28,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { fun translateExpression(expr: PtExpression): ExpressionCodeResult { return when (expr) { is PtMachineRegister -> { - ExpressionCodeResult(emptyList(), codeGen.irType(expr.type), expr.register, -1) + ExpressionCodeResult(emptyList(), irType(expr.type), expr.register, -1) } is PtNumber -> { - val vmDt = codeGen.irType(expr.type) + val vmDt = irType(expr.type) val code = IRCodeChunk(null, null) if(vmDt==IRDataType.FLOAT) { val resultFpRegister = codeGen.registers.nextFreeFloat() @@ -45,7 +45,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { } } is PtIdentifier -> { - val vmDt = codeGen.irType(expr.type) + val vmDt = irType(expr.type) val code = IRCodeChunk(null, null) if (expr.type in PassByValueDatatypes) { if(vmDt==IRDataType.FLOAT) { @@ -66,7 +66,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { } } is PtAddressOf -> { - val vmDt = codeGen.irType(expr.type) + val vmDt = irType(expr.type) val symbol = expr.identifier.name // note: LOAD gets you the address of the symbol, whereas LOADM would get you the value stored at that location val code = IRCodeChunk(null, null) @@ -142,7 +142,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { private fun translate(arrayIx: PtArrayIndexer): ExpressionCodeResult { val eltSize = codeGen.program.memsizer.memorySize(arrayIx.type) - val vmDt = codeGen.irType(arrayIx.type) + val vmDt = irType(arrayIx.type) val result = mutableListOf() val arrayVarSymbol = arrayIx.variable.name @@ -192,7 +192,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val result = mutableListOf() val tr = translateExpression(expr.value) addToResult(result, tr, tr.resultReg, tr.resultFpReg) - val vmDt = codeGen.irType(expr.type) + val vmDt = irType(expr.type) when(expr.operator) { "+" -> { } "-" -> { @@ -308,12 +308,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { else -> throw AssemblyError("weird cast type") } - return ExpressionCodeResult(result, codeGen.irType(cast.type), actualResultReg2, actualResultFpReg2) + return ExpressionCodeResult(result, irType(cast.type), actualResultReg2, actualResultFpReg2) } private fun translate(binExpr: PtBinaryExpression): ExpressionCodeResult { require(!codeGen.options.useNewExprCode) - val vmDt = codeGen.irType(binExpr.left.type) + val vmDt = irType(binExpr.left.type) val signed = binExpr.left.type in SignedDatatypes return when(binExpr.operator) { "+" -> operatorPlus(binExpr, vmDt) @@ -343,7 +343,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { // assign the arguments val argRegisters = mutableListOf() for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) { - val paramDt = codeGen.irType(parameter.type) + val paramDt = irType(parameter.type) val tr = translateExpression(arg) if(paramDt==IRDataType.FLOAT) argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, tr.resultFpReg, null))) @@ -353,7 +353,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { } // return value val returnRegSpec = if(fcall.void) null else { - val returnIrType = codeGen.irType(callTarget.returnType!!) + val returnIrType = irType(callTarget.returnType!!) if(returnIrType==IRDataType.FLOAT) FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFreeFloat(), null) else @@ -374,7 +374,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { // assign the arguments val argRegisters = mutableListOf() for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) { - val paramDt = codeGen.irType(parameter.type) + val paramDt = irType(parameter.type) val tr = translateExpression(arg) if(paramDt==IRDataType.FLOAT) argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, tr.resultFpReg, parameter.register))) @@ -388,7 +388,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { null else if(callTarget.returns.size==1) { val returns = callTarget.returns[0] - val returnIrType = codeGen.irType(returns.type) + val returnIrType = irType(returns.type) if(returnIrType==IRDataType.FLOAT) FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFreeFloat(), returns.register) else @@ -396,7 +396,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { } else { // multiple return values: take the first *register* (not status flag) return value and ignore the rest. val returns = callTarget.returns.first { it.register.registerOrPair!=null } - val returnIrType = codeGen.irType(returns.type) + val returnIrType = irType(returns.type) if(returnIrType==IRDataType.FLOAT) FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFreeFloat(), returns.register) else diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt index 0f2cabcb7..d3f8a816b 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt @@ -1545,20 +1545,6 @@ class IRCodeGen( } } - - internal fun irType(type: DataType): IRDataType { - return when(type) { - DataType.BOOL, - DataType.UBYTE, - DataType.BYTE -> IRDataType.BYTE - DataType.UWORD, - DataType.WORD -> IRDataType.WORD - DataType.FLOAT -> IRDataType.FLOAT - in PassByReferenceDatatypes -> IRDataType.WORD - else -> throw AssemblyError("no IR datatype for $type") - } - } - private var labelSequenceNumber = 0 internal fun createLabelName(): String { labelSequenceNumber++ diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 4ffefd935..2a54c6c2e 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,8 +3,6 @@ TODO For next minor release ^^^^^^^^^^^^^^^^^^^^^^ -- fix romsub encoding into IR - ... diff --git a/intermediate/src/prog8/intermediate/Utils.kt b/intermediate/src/prog8/intermediate/Utils.kt index 5f101fc39..4355b9a6c 100644 --- a/intermediate/src/prog8/intermediate/Utils.kt +++ b/intermediate/src/prog8/intermediate/Utils.kt @@ -1,8 +1,10 @@ package prog8.intermediate import prog8.code.* +import prog8.code.core.AssemblyError import prog8.code.core.DataType import prog8.code.core.InternalCompilerException +import prog8.code.core.PassByReferenceDatatypes fun getTypeString(dt : DataType): String = when(dt) { @@ -262,3 +264,15 @@ private fun parseCall(rest: String): ParsedCall { } +fun irType(type: DataType): IRDataType { + return when(type) { + DataType.BOOL, + DataType.UBYTE, + DataType.BYTE -> IRDataType.BYTE + DataType.UWORD, + DataType.WORD -> IRDataType.WORD + DataType.FLOAT -> IRDataType.FLOAT + in PassByReferenceDatatypes -> IRDataType.WORD + else -> throw AssemblyError("no IR datatype for $type") + } +}