Merge branch 'master' into version_9

# Conflicts:
#	codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt
#	docs/source/todo.rst
#	intermediate/src/prog8/intermediate/IRInstructions.kt
This commit is contained in:
Irmen de Jong 2023-05-14 20:47:09 +02:00
commit 85cf0e311c
21 changed files with 582 additions and 634 deletions

View File

@ -235,7 +235,7 @@ class StSub(name: String, val parameters: List<StSubroutineParameter>, 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<StRomSubParameter>,
val returns: List<StRomSubParameter>,
astNode: PtNode) :

View File

@ -40,14 +40,9 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
private fun addToSt(node: PtNode, scope: Stack<StNode>) {
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)

View File

@ -85,11 +85,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)
}
@ -121,13 +118,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val result = mutableListOf<IRCodeChunkBase>()
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)
}
@ -146,13 +139,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val result = mutableListOf<IRCodeChunkBase>()
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)
}
@ -295,12 +284,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val result = mutableListOf<IRCodeChunkBase>()
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)
}
@ -320,12 +306,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val result = mutableListOf<IRCodeChunkBase>()
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)
}

View File

@ -105,53 +105,35 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
private fun translate(check: PtContainmentCheck): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
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 +340,82 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
when (val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)) {
is StSub -> {
val result = mutableListOf<IRCodeChunkBase>()
for ((index, argspec) in fcall.args.zip(callTarget.parameters).withIndex()) {
val (arg, param) = argspec
val paramDt = irType(param.type)
// assign the arguments
val argRegisters = mutableListOf<FunctionCallArgs.ArgumentSpec>()
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
val paramDt = 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 = 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 = 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, irType(fcall.type), reg1=resultReg, labelSymbol=fcall.name), null)
}
ExpressionCodeResult(result, irType(fcall.type), resultReg, resultFpReg)
}
else if(fcall.type==DataType.FLOAT)
ExpressionCodeResult(result, returnRegSpec!!.dt, -1, returnRegSpec.registerNum)
else
ExpressionCodeResult(result, returnRegSpec!!.dt, returnRegSpec.registerNum, -1)
}
is StRomSub -> {
val result = mutableListOf<IRCodeChunkBase>()
// assign the arguments
val argRegisters = mutableListOf<FunctionCallArgs.ArgumentSpec>()
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
val paramDt = 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 = 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 = 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, 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, 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, irType(fcall.type), reg1=resultReg, labelSymbol = regStr), null)
} else {
throw AssemblyError("invalid number of return values from call")
}
}
}
}
}
return ExpressionCodeResult(result, if(fcall.void) IRDataType.BYTE else irType(fcall.type), resultReg, -1)
// create the call
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)
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")
}

View File

@ -1575,4 +1575,14 @@ class IRCodeGen(
irSymbolTable.add(staticVar)
return tempvar
}
fun makeSyscall(syscall: IMSyscall, params: List<Pair<IRDataType, Int>>, returns: Pair<IRDataType, Int>?, 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))
}
}
}

View File

@ -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
}

View File

@ -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)

View File

@ -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
}}
}

View File

@ -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
}}
}

View File

@ -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
}}
}

View File

@ -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
}}
}

View File

@ -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
}}
}

View File

@ -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
}}
}

View File

@ -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

View File

@ -1,24 +1,15 @@
%import textio
%import string
%zeropage basicsafe
main {
sub start() {
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
}}
}
}

View File

@ -1,8 +1,6 @@
package prog8.intermediate
import prog8.code.core.AssemblyError
import prog8.code.core.DataType
import prog8.code.core.PassByReferenceDatatypes
import prog8.code.core.RegisterOrStatusflag
import prog8.code.core.toHex
/*
@ -40,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
@ -57,14 +51,21 @@ 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
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.
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.
@ -228,22 +229,17 @@ enum class Opcode {
LOADX,
LOADIX,
LOADR,
LOADCPU,
STOREM,
STORECPU,
STOREI,
STOREX,
STOREIX,
STOREZM,
STOREZCPU,
STOREZI,
STOREZX,
JUMP,
JUMPA,
SETPARAM,
CALL,
CALLR,
SYSCALL,
RETURN,
RETURNR,
@ -391,7 +387,6 @@ val OpcodesThatBranch = setOf(
Opcode.RETURN,
Opcode.RETURNR,
Opcode.CALL,
Opcode.CALLR,
Opcode.SYSCALL,
Opcode.BSTCC,
Opcode.BSTCS,
@ -419,12 +414,6 @@ val OpcodesThatBranch = setOf(
Opcode.BLES
)
val OpcodesForCpuRegisters = setOf(
Opcode.LOADCPU,
Opcode.STORECPU,
Opcode.STOREZCPU
)
enum class IRDataType {
BYTE,
WORD,
@ -445,7 +434,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<IRDataType?, InstructionFormat> {
val result = mutableMapOf<IRDataType?, InstructionFormat>()
@ -458,33 +449,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()) {
"<r1" -> { reg1=OperandDirection.READ }
">r1" -> { reg1=OperandDirection.WRITE }
"<>r1" -> { reg1=OperandDirection.READWRITE }
"<r1" -> reg1 = OperandDirection.READ
">r1" -> reg1 = OperandDirection.WRITE
"<>r1" -> reg1 = OperandDirection.READWRITE
"<r2" -> reg2 = OperandDirection.READ
"<fr1" -> { fpreg1=OperandDirection.READ }
">fr1" -> { fpreg1=OperandDirection.WRITE }
"<>fr1" -> { fpreg1=OperandDirection.READWRITE }
"<fr1" -> fpreg1 = OperandDirection.READ
">fr1" -> fpreg1 = OperandDirection.WRITE
"<>fr1" -> fpreg1 = OperandDirection.READWRITE
"<fr2" -> fpreg2 = OperandDirection.READ
">i", "<>i" -> throw IllegalArgumentException("can't write into an immediate value")
"<i" -> immediate = true
"<a" -> 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
}
@ -509,22 +504,17 @@ val instructionFormats = mutableMapOf(
Opcode.LOADX to InstructionFormat.from("BW,>r1,<r2,<a | F,>fr1,<r1,<a"),
Opcode.LOADIX to InstructionFormat.from("BW,>r1,<r2,<a | F,>fr1,<r1,<a"),
Opcode.LOADR to InstructionFormat.from("BW,>r1,<r2 | F,>fr1,<fr2"),
Opcode.LOADCPU to InstructionFormat.from("BW,>r1"),
Opcode.STOREM to InstructionFormat.from("BW,<r1,>a | F,<fr1,>a"),
Opcode.STORECPU to InstructionFormat.from("BW,<r1"),
Opcode.STOREI to InstructionFormat.from("BW,<r1,<r2 | F,<fr1,<r1"),
Opcode.STOREX to InstructionFormat.from("BW,<r1,<r2,>a | F,<fr1,<r1,>a"),
Opcode.STOREIX to InstructionFormat.from("BW,<r1,<r2,>a | F,<fr1,<r1,>a"),
Opcode.STOREZM to InstructionFormat.from("BW,>a | F,>a"),
Opcode.STOREZCPU to InstructionFormat.from("BW"),
Opcode.STOREZI to InstructionFormat.from("BW,<r1 | F,<r1"),
Opcode.STOREZX to InstructionFormat.from("BW,<r1,>a | F,<r1,>a"),
Opcode.JUMP to InstructionFormat.from("N,<a"),
Opcode.JUMPA to InstructionFormat.from("N,<a"),
Opcode.SETPARAM to InstructionFormat.from("BW,<r1,<i | F,<fr1,<i"),
Opcode.CALL to InstructionFormat.from("N,<a"),
Opcode.CALLR to InstructionFormat.from("BW,>r1,<a | F,>fr1,<a"),
Opcode.SYSCALL to InstructionFormat.from("N,<i"),
Opcode.CALL to InstructionFormat.from("N,call"),
Opcode.SYSCALL to InstructionFormat.from("N,syscall"),
Opcode.RETURN to InstructionFormat.from("N"),
Opcode.RETURNR to InstructionFormat.from("BW,>r1 | F,>fr1"),
Opcode.BSTCC to InstructionFormat.from("N,<a"),
@ -656,6 +646,14 @@ val instructionFormats = mutableMapOf(
)
class FunctionCallArgs(
var arguments: List<ArgumentSpec>,
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,
@ -667,7 +665,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.
@ -697,13 +696,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"}
@ -814,53 +810,87 @@ 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()) } // 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}="
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$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(")")
val returns = fcallArgs.returns
if(returns!=null) {
result.add(":")
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 {
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()
return result.joinToString("").trimEnd()
}
}
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")
}
}

View File

@ -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) {
@ -122,39 +124,47 @@ fun parseIRCodeLine(line: String): Either<IRInstruction, String> {
parseIRValue(operand)
}
}
operands.forEach { oper ->
if(oper[0] == '&')
throw IRParseException("address-of should be done with normal LOAD <symbol>")
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 <symbol>")
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 +204,6 @@ fun parseIRCodeLine(line: String): Either<IRInstruction, String> {
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")
@ -207,20 +213,66 @@ fun parseIRCodeLine(line: String): Either<IRInstruction, String> {
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))
}
private class ParsedCall(
val target: String,
val args: List<FunctionCallArgs.ArgumentSpec>,
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<FunctionCallArgs.ArgumentSpec> {
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("(?<target>.+?)\\((?<arglist>.*?)\\)(:(?<returns>.+?))?")
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)
)
}
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")
}
}

View File

@ -92,8 +92,6 @@ uword sys.wait.jiffies
</PARAMS>
<INLINEASM LABEL="sys.wait" IR="true" POS="[library:/prog8lib/virtual/syslib.p8: line 17 col 10-13]">
loadm.w r0,sys.wait.jiffies
setparam.w r0,0
syscall 13
</INLINEASM>
<CODE>
return

View File

@ -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<FunctionCallArgs.ArgumentSpec>, vm: VirtualMachine): List<Comparable<Nothing>> {
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<Nothing>, 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}")
}

View File

@ -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<IRCodeChunk>
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)
@ -284,9 +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.STOREZCPU -> InsSTOREZCPU(ins)
Opcode.FFROMUB -> InsFFROMUB(ins)
Opcode.FFROMSB -> InsFFROMSB(ins)
@ -363,7 +359,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()
}
@ -375,86 +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 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()
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!!)
@ -603,30 +519,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 +549,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 +561,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 +570,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 +2206,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 +2215,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()
}
}

View File

@ -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,43 +152,39 @@ 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)
}
}
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)