IR support for storing incbins and romsubs

This commit is contained in:
Irmen de Jong 2022-09-17 14:07:13 +02:00
parent 0e831d4b92
commit 2f3e7d1c27
11 changed files with 71 additions and 18 deletions

View File

@ -238,7 +238,11 @@ class StSub(name: String, val parameters: List<StSubroutineParameter>, val retur
} }
class StRomSub(name: String, val address: UInt, val parameters: List<StSubroutineParameter>, val returnTypes: List<DataType>, position: Position) : class StRomSub(name: String,
val address: UInt,
val parameters: List<StRomSubParameter>,
val returns: List<RegisterOrStatusflag>,
position: Position) :
StNode(name, StNodeType.ROMSUB, position) { StNode(name, StNodeType.ROMSUB, position) {
override fun printProperties() { override fun printProperties() {
print("$name address=${address.toHex()}") print("$name address=${address.toHex()}")
@ -247,6 +251,7 @@ class StRomSub(name: String, val address: UInt, val parameters: List<StSubroutin
class StSubroutineParameter(val name: String, val type: DataType) class StSubroutineParameter(val name: String, val type: DataType)
class StRomSubParameter(val register: RegisterOrStatusflag, val type: DataType)
class StArrayElement(val number: Double?, val addressOf: List<String>?) class StArrayElement(val number: Double?, val addressOf: List<String>?)
typealias StString = Pair<String, Encoding> typealias StString = Pair<String, Encoding>

View File

@ -913,7 +913,31 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
return code return code
} }
is StRomSub -> { is StRomSub -> {
throw AssemblyError("virtual machine doesn't yet support calling romsub $fcall") val code = IRCodeChunk(fcall.position)
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
val paramDt = codeGen.vmType(parameter.type)
val paramRegStr = if(parameter.register.registerOrPair!=null) parameter.register.registerOrPair.toString() else parameter.register.statusflag.toString()
if(codeGen.isZero(arg)) {
code += IRCodeInstruction(Opcode.STOREZCPU, paramDt, labelSymbol = paramRegStr)
} else {
if (paramDt == VmDataType.FLOAT)
throw AssemblyError("doesn't support float register argument in asm romsub")
val argReg = codeGen.vmRegisters.nextFree()
code += translateExpression(arg, argReg, -1)
code += IRCodeInstruction(Opcode.STORECPU, paramDt, reg1 = argReg, labelSymbol = paramRegStr)
}
}
code += IRCodeInstruction(Opcode.CALL, value=callTarget.address.toInt())
if(!fcall.void) {
if(callTarget.returns.size!=1)
throw AssemblyError("expect precisely 1 return value")
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.registerOrPair!=null) returns.registerOrPair.toString() else returns.statusflag.toString()
code += IRCodeInstruction(Opcode.LOADCPU, codeGen.vmType(fcall.type), reg1=resultRegister, labelSymbol = regStr)
}
return code
} }
else -> throw AssemblyError("invalid node type") else -> throw AssemblyError("invalid node type")
} }

View File

@ -399,8 +399,8 @@ private fun createAssemblyAndAssemble(program: Program,
// to help clean up the code that still depends on them. // to help clean up the code that still depends on them.
// removeAllVardeclsFromAst(program) // removeAllVardeclsFromAst(program)
// println("*********** AST RIGHT BEFORE ASM GENERATION *************") println("*********** AST RIGHT BEFORE ASM GENERATION *************")
// printProgram(program) printProgram(program)
val assembly = asmGeneratorFor(program, errors, symbolTable, compilerOptions).compileToAssembly() val assembly = asmGeneratorFor(program, errors, symbolTable, compilerOptions).compileToAssembly()
errors.report() errors.report()

View File

@ -230,7 +230,8 @@ class IntermediateAstMaker(val program: Program) {
val type = srcCall.inferType(program).getOrElse { val type = srcCall.inferType(program).getOrElse {
throw FatalAstException("unknown dt $srcCall") throw FatalAstException("unknown dt $srcCall")
} }
val call = PtFunctionCall(target, type==DataType.UNDEFINED, type, srcCall.position) val isVoid = type==DataType.UNDEFINED
val call = PtFunctionCall(target, isVoid, type, srcCall.position)
for (arg in srcCall.args) for (arg in srcCall.args)
call.add(transformExpression(arg)) call.add(transformExpression(arg))
return call return call

View File

@ -36,12 +36,13 @@ internal class SymbolTableMaker: IAstVisitor {
} }
override fun visit(subroutine: Subroutine) { override fun visit(subroutine: Subroutine) {
val parameters = subroutine.parameters.map { StSubroutineParameter(it.name, it.type) }
if(subroutine.asmAddress!=null) { if(subroutine.asmAddress!=null) {
val node = StRomSub(subroutine.name, subroutine.asmAddress!!, parameters, subroutine.returntypes, subroutine.position) val parameters = subroutine.parameters.zip(subroutine.asmParameterRegisters).map { StRomSubParameter(it.second, it.first.type) }
val node = StRomSub(subroutine.name, subroutine.asmAddress!!, parameters, subroutine.asmParameterRegisters, subroutine.position)
scopestack.peek().add(node) scopestack.peek().add(node)
// st.origAstLinks[subroutine] = node // st.origAstLinks[subroutine] = node
} else { } else {
val parameters = subroutine.parameters.map { StSubroutineParameter(it.name, it.type) }
val returnType = if(subroutine.returntypes.isEmpty()) null else subroutine.returntypes.first() val returnType = if(subroutine.returntypes.isEmpty()) null else subroutine.returntypes.first()
val node = StSub(subroutine.name, parameters, returnType, subroutine.position) val node = StSub(subroutine.name, parameters, returnType, subroutine.position)
scopestack.peek().add(node) scopestack.peek().add(node)

View File

@ -3,8 +3,9 @@ TODO
For next release For next release
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
- IR/VM: add proper memory mapped variables support - replace the symbol by the memory address in the IR code - VM Assembler: add support for translating symbols to address search for "TODO do we have to replace variable names by their allocated address" load.w r0,{_}txt.clear_screen.sequence
- IR/VM: add support for incbin (!binary) - IR/VM: add proper memory mapped variables support - replace the symbol by the memory address in the IR code.
- IR/VM: check that the above works ok now with the cx16 virtual registers.
- IR/VM: add proper memory slabs support - IR/VM: add proper memory slabs support
... ...
@ -21,9 +22,6 @@ Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
Compiler: Compiler:
- vm: implement remaining sin/cos functions in math.p8 and merge tables - vm: implement remaining sin/cos functions in math.p8 and merge tables
- vm: implement memory mapped variables properly in VariableAllocator
- vm: find a solution for the cx16.r0..r15 that "overlap" (r0, r0L, r0H etc) but in the vm each get their own separate variable location now. Maybe this gets solved by the previous item?
- vm: encode romsub & romsub call in VM IR (can just crash in virtualmachine itself because program is not in the simulated memory) ExpressionGen.kt
- vm: how to remove all unused subroutines? (the 6502 assembly codegen relies on 64tass solve this for us) - vm: how to remove all unused subroutines? (the 6502 assembly codegen relies on 64tass solve this for us)
- vm: rather than being able to jump to any 'address' (IPTR), use 'blocks' that have entry and exit points -> even better dead code elimination possible too - vm: rather than being able to jump to any 'address' (IPTR), use 'blocks' that have entry and exit points -> even better dead code elimination possible too
- vm: add more optimizations in VmPeepholeOptimizer - vm: add more optimizations in VmPeepholeOptimizer

View File

@ -12,6 +12,9 @@ main {
return return
}} }}
romsub $ee33 = myromsub(ubyte arg1 @A) clobbers() -> ubyte @Y
romsub $ee44 = myromsubmulti(ubyte arg1 @A) clobbers() -> ubyte @Y, ubyte @X, ubyte @Pc
asmsub testasmsub(ubyte arg1 @A) clobbers(Y) -> uword @AX { asmsub testasmsub(ubyte arg1 @A) clobbers(Y) -> uword @AX {
%asm {{ %asm {{
nop nop
@ -20,6 +23,8 @@ main {
} }
sub start() { sub start() {
void myromsubmulti(44)
global1 = myromsub(44)
sys.wait(1) sys.wait(1)
%asm {{ %asm {{

View File

@ -240,7 +240,7 @@ class IRFileReader(outputDir: Path, programName: String) {
private val blockPattern = Regex("<BLOCK NAME=(.+) ADDRESS=(.+) ALIGN=(.+) POS=(.+)>") private val blockPattern = Regex("<BLOCK NAME=(.+) ADDRESS=(.+) ALIGN=(.+) POS=(.+)>")
private val inlineAsmPattern = Regex("<INLINEASM POS=(.+)>") private val inlineAsmPattern = Regex("<INLINEASM POS=(.+)>")
private val asmsubPattern = Regex("<ASMSUB NAME=(.+) ADDRESS=(.+) CLOBBERS=(.+) RETURNS=(.+) POS=(.+)>") private val asmsubPattern = Regex("<ASMSUB NAME=(.+) ADDRESS=(.+) CLOBBERS=(.*) RETURNS=(.*) POS=(.+)>")
private val subPattern = Regex("<SUB NAME=(.+) RETURNTYPE=(.+) POS=(.+)>") private val subPattern = Regex("<SUB NAME=(.+) RETURNTYPE=(.+) POS=(.+)>")
private val posPattern = Regex("\\[(.+): line (.+) col (.+)-(.+)\\]") private val posPattern = Regex("\\[(.+): line (.+) col (.+)-(.+)\\]")
private val instructionPattern = Regex("""([a-z]+)(\.b|\.w|\.f)?(.*)""", RegexOption.IGNORE_CASE) private val instructionPattern = Regex("""([a-z]+)(\.b|\.w|\.f)?(.*)""", RegexOption.IGNORE_CASE)
@ -309,7 +309,7 @@ class IRFileReader(outputDir: Path, programName: String) {
val asm = parseInlineAssembly(line, lines) val asm = parseInlineAssembly(line, lines)
while(line!="</ASMSUB>") while(line!="</ASMSUB>")
line = lines.next() line = lines.next()
val clobberRegs = clobbers.split(',').map { CpuRegister.valueOf(it) } val clobberRegs = if(clobbers.isBlank()) emptyList() else clobbers.split(',').map { CpuRegister.valueOf(it) }
val returns = mutableListOf<Pair<DataType, RegisterOrStatusflag>>() val returns = mutableListOf<Pair<DataType, RegisterOrStatusflag>>()
returnSpec.split(',').forEach{ rs -> returnSpec.split(',').forEach{ rs ->
val (regstr, dtstr) = rs.split(':') val (regstr, dtstr) = rs.split(':')

View File

@ -29,12 +29,15 @@ 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 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 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 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 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 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 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 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 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 storezi reg1 - store zero at memory pointed to by reg1
storezx reg1, address - store zero at memory address, indexed by value in reg storezx reg1, address - store zero at memory address, indexed by value in reg
@ -196,7 +199,7 @@ msig [b, w] reg1, reg2 - reg1 becomes the most significant by
concat [b, w] reg1, reg2 - reg1 = concatenated lsb/lsw of reg1 (as lsb) and lsb/lsw of reg2 (as msb) into word or int (int not yet implemented; requires 32bits regs) concat [b, w] reg1, reg2 - reg1 = concatenated lsb/lsw of reg1 (as lsb) and lsb/lsw of reg2 (as msb) into word or int (int not yet implemented; requires 32bits regs)
push [b, w] reg1 - push value in reg1 on the stack push [b, w] reg1 - push value in reg1 on the stack
pop [b, w] reg1 - pop value from stack into reg1 pop [b, w] reg1 - pop value from stack into reg1
binarydata - 'instruction' to hold inlined binary data bytes
*/ */
enum class Opcode { enum class Opcode {
@ -207,11 +210,14 @@ enum class Opcode {
LOADX, LOADX,
LOADIX, LOADIX,
LOADR, LOADR,
LOADCPU,
STOREM, STOREM,
STORECPU,
STOREI, STOREI,
STOREX, STOREX,
STOREIX, STOREIX,
STOREZM, STOREZM,
STOREZCPU,
STOREZI, STOREZI,
STOREZX, STOREZX,
@ -338,7 +344,8 @@ enum class Opcode {
POP, POP,
MSIG, MSIG,
CONCAT, CONCAT,
BREAKPOINT BREAKPOINT,
BINARYDATA
} }
val OpcodesWithAddress = setOf( val OpcodesWithAddress = setOf(
@ -393,7 +400,8 @@ data class Instruction(
val fpReg2: Int?=null, // 0-$ffff val fpReg2: Int?=null, // 0-$ffff
val value: Int?=null, // 0-$ffff val value: Int?=null, // 0-$ffff
val fpValue: Float?=null, val fpValue: Float?=null,
val labelSymbol: List<String>?=null // symbolic label name as alternative to value (so only for Branch/jump/call Instructions!) val labelSymbol: List<String>?=null, // symbolic label name as alternative to value (so only for Branch/jump/call Instructions!)
val binaryData: ByteArray?=null
) { ) {
// reg1 and fpreg1 can be IN/OUT/INOUT (all others are readonly INPUT) // 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. // This knowledge is useful in IL assembly optimizers to see how registers are used.
@ -401,6 +409,9 @@ data class Instruction(
val fpReg1direction: OperandDirection val fpReg1direction: OperandDirection
init { init {
if(opcode==Opcode.BINARYDATA && binaryData==null || binaryData!=null && opcode!=Opcode.BINARYDATA)
throw IllegalArgumentException("binarydata inconsistency")
val formats = instructionFormats.getValue(opcode) val formats = instructionFormats.getValue(opcode)
if(type==null && !formats.containsKey(null)) if(type==null && !formats.containsKey(null))
throw IllegalArgumentException("missing type") throw IllegalArgumentException("missing type")
@ -560,11 +571,14 @@ val instructionFormats = mutableMapOf(
Opcode.LOADX to InstructionFormat.from("BW,>r1,<r2,<v | F,>fr1,<r1,<v"), Opcode.LOADX to InstructionFormat.from("BW,>r1,<r2,<v | F,>fr1,<r1,<v"),
Opcode.LOADIX to InstructionFormat.from("BW,>r1,<r2,<v | F,>fr1,<r1,<v"), Opcode.LOADIX to InstructionFormat.from("BW,>r1,<r2,<v | F,>fr1,<r1,<v"),
Opcode.LOADR to InstructionFormat.from("BW,>r1,<r2 | F,>fr1,<fr2"), 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,<v | F,<fr1,<v"), Opcode.STOREM to InstructionFormat.from("BW,<r1,<v | F,<fr1,<v"),
Opcode.STORECPU to InstructionFormat.from("BW,<r1"),
Opcode.STOREI to InstructionFormat.from("BW,<r1,<r2 | F,<fr1,<r1"), Opcode.STOREI to InstructionFormat.from("BW,<r1,<r2 | F,<fr1,<r1"),
Opcode.STOREX to InstructionFormat.from("BW,<r1,<r2,<v | F,<fr1,<r1,<v"), Opcode.STOREX to InstructionFormat.from("BW,<r1,<r2,<v | F,<fr1,<r1,<v"),
Opcode.STOREIX to InstructionFormat.from("BW,<r1,<r2,<v | F,<fr1,<r1,<v"), Opcode.STOREIX to InstructionFormat.from("BW,<r1,<r2,<v | F,<fr1,<r1,<v"),
Opcode.STOREZM to InstructionFormat.from("BW,<v | F,<v"), Opcode.STOREZM to InstructionFormat.from("BW,<v | F,<v"),
Opcode.STOREZCPU to InstructionFormat.from("BW"),
Opcode.STOREZI to InstructionFormat.from("BW,<r1 | F,<r1"), Opcode.STOREZI to InstructionFormat.from("BW,<r1 | F,<r1"),
Opcode.STOREZX to InstructionFormat.from("BW,<r1,<v | F,<r1,<v"), Opcode.STOREZX to InstructionFormat.from("BW,<r1,<v | F,<r1,<v"),
Opcode.JUMP to InstructionFormat.from("N,<v"), Opcode.JUMP to InstructionFormat.from("N,<v"),
@ -688,4 +702,5 @@ val instructionFormats = mutableMapOf(
Opcode.CLC to InstructionFormat.from("N"), Opcode.CLC to InstructionFormat.from("N"),
Opcode.SEC to InstructionFormat.from("N"), Opcode.SEC to InstructionFormat.from("N"),
Opcode.BREAKPOINT to InstructionFormat.from("N"), Opcode.BREAKPOINT to InstructionFormat.from("N"),
Opcode.BINARYDATA to InstructionFormat.from("N"),
) )

View File

@ -76,7 +76,10 @@ class Assembler {
val binarymatch = binaryPattern.matchEntire(line.trim()) val binarymatch = binaryPattern.matchEntire(line.trim())
if(binarymatch!=null) { if(binarymatch!=null) {
val hex = binarymatch.groups[1]!!.value val hex = binarymatch.groups[1]!!.value
TODO("binary ${hex}") val binary = hex.windowed(size=2, step=2).map {
it.toString().toByte(16)
}.toByteArray()
program.add(Instruction(Opcode.BINARYDATA, binaryData = binary))
} else { } else {
val labelmatch = labelPattern.matchEntire(line.trim()) val labelmatch = labelPattern.matchEntire(line.trim())
if (labelmatch == null) if (labelmatch == null)

View File

@ -217,6 +217,7 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
Opcode.BREAKPOINT -> InsBREAKPOINT() Opcode.BREAKPOINT -> InsBREAKPOINT()
Opcode.CLC -> { statusCarry = false; pc++ } Opcode.CLC -> { statusCarry = false; pc++ }
Opcode.SEC -> { statusCarry = true; pc++ } Opcode.SEC -> { statusCarry = true; pc++ }
Opcode.BINARYDATA -> TODO("BINARYDATA not yet supported in VM")
Opcode.FFROMUB -> InsFFROMUB(ins) Opcode.FFROMUB -> InsFFROMUB(ins)
Opcode.FFROMSB -> InsFFROMSB(ins) Opcode.FFROMSB -> InsFFROMSB(ins)