diff --git a/codeCore/src/prog8/code/ast/AstBase.kt b/codeCore/src/prog8/code/ast/AstBase.kt index ea32f1180..77c612dc8 100644 --- a/codeCore/src/prog8/code/ast/AstBase.kt +++ b/codeCore/src/prog8/code/ast/AstBase.kt @@ -98,6 +98,11 @@ class PtBlock(name: String, class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Position) : PtNode(position) { override fun printProperties() {} + + init { + require(!assembly.startsWith('\n') && !assembly.startsWith('\r')) { "inline assembly should be trimmed" } + require(!assembly.endsWith('\n') && !assembly.endsWith('\r')) { "inline assembly should be trimmed" } + } } diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt index eafd5687e..de2498560 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt @@ -909,8 +909,7 @@ $repeatLabel lda $counterVar } private fun translate(asm: InlineAssembly) { - val assembly = asm.assembly.trimEnd().trimStart('\r', '\n') - assemblyLines.add(assembly) + assemblyLines.add(asm.assembly.trimEnd().trimStart('\r', '\n')) } internal fun returnRegisterOfFunction(it: IdentifierReference): RegisterOrPair { diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt index c6ae19ef5..169cf20ed 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt @@ -52,6 +52,8 @@ class IRCodeGen( optimizer.optimize() } + irProg.validate() + return irProg } @@ -1003,7 +1005,7 @@ class IRCodeGen( parameters.map { val flattenedName = (it.definingSub()!!.scopedName + it.name) val orig = symbolTable.flat.getValue(flattenedName) as StStaticVariable - IRSubroutine.IRParam(flattenedName.joinToString("."), orig.dt, orig) + IRSubroutine.IRParam(flattenedName.joinToString("."), orig.dt) } private fun translate(alignment: PtBlock.BlockAlignment): IRBlock.BlockAlignment { diff --git a/codeOptimizers/src/prog8/optimizer/Inliner.kt b/codeOptimizers/src/prog8/optimizer/Inliner.kt index 5c329332e..7ab2a7558 100644 --- a/codeOptimizers/src/prog8/optimizer/Inliner.kt +++ b/codeOptimizers/src/prog8/optimizer/Inliner.kt @@ -38,6 +38,8 @@ class Inliner(val program: Program): AstWalker() { is Return -> { if(stmt.value is NumericLiteral) true + else if(stmt.value==null) + true else if (stmt.value is IdentifierReference) { makeFullyScoped(stmt.value as IdentifierReference) true diff --git a/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt b/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt index 12da2fdc8..fb2857e5b 100644 --- a/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt +++ b/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt @@ -197,7 +197,7 @@ class IntermediateAstMaker(val program: Program) { "%asminclude" -> { val result = loadAsmIncludeFile(directive.args[0].str!!, directive.definingModule.source) val assembly = result.getOrElse { throw it } - PtInlineAssembly(assembly, false, directive.position) + PtInlineAssembly(assembly.trimEnd().trimStart('\r', '\n'), false, directive.position) } else -> { // other directives don't output any code (but could end up in option flags somewhere else) @@ -251,8 +251,10 @@ class IntermediateAstMaker(val program: Program) { return ifelse } - private fun transform(srcNode: InlineAssembly): PtInlineAssembly = - PtInlineAssembly(srcNode.assembly, srcNode.isIR, srcNode.position) + private fun transform(srcNode: InlineAssembly): PtInlineAssembly { + val assembly = srcNode.assembly.trimEnd().trimStart('\r', '\n') + return PtInlineAssembly(assembly, srcNode.isIR, srcNode.position) + } private fun transform(srcJump: Jump): PtJump { val identifier = if(srcJump.identifier!=null) transform(srcJump.identifier!!) else null @@ -316,10 +318,14 @@ class IntermediateAstMaker(val program: Program) { combinedTrueAsm += asm.assembly + "\n" } - if(combinedTrueAsm.isNotEmpty()) + if(combinedTrueAsm.isNotEmpty()) { + combinedTrueAsm = combinedTrueAsm.trimEnd().trimStart('\r', '\n') sub.add(PtInlineAssembly(combinedTrueAsm, false, srcSub.statements[0].position)) - if(combinedIrAsm.isNotEmpty()) + } + if(combinedIrAsm.isNotEmpty()) { + combinedIrAsm = combinedIrAsm.trimEnd().trimStart('\r', '\n') sub.add(PtInlineAssembly(combinedIrAsm, true, srcSub.statements[0].position)) + } if(combinedIrAsm.isEmpty() && combinedTrueAsm.isEmpty()) sub.add(PtInlineAssembly("", true, srcSub.position)) } diff --git a/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt b/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt index 6cc0df373..e6dcbbba4 100644 --- a/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt +++ b/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt @@ -40,6 +40,7 @@ internal fun Prog8ANTLRParser.BlockContext.toAst(isInLibrary: Boolean) : Block { it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst() it.directive()!=null -> it.directive().toAst() it.inlineasm()!=null -> it.inlineasm().toAst() + it.inlineir()!=null -> it.inlineir().toAst() it.labeldef()!=null -> it.labeldef().toAst() else -> throw FatalAstException("weird block node $it") } diff --git a/docs/source/todo.rst b/docs/source/todo.rst index e3dd1f001..a63c39bd0 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,6 +3,8 @@ TODO For next release ^^^^^^^^^^^^^^^^ +- ir: RegistersUsed should actually count the number of times a register is used too? + ... diff --git a/examples/test.p8 b/examples/test.p8 index fb81add50..abc9d6d99 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,4 +1,7 @@ main { sub start() { + ubyte aa = 42 + ubyte bb = 99 + aa += bb } } diff --git a/intermediate/src/prog8/intermediate/IRFileReader.kt b/intermediate/src/prog8/intermediate/IRFileReader.kt index e13c706ed..8f522208e 100644 --- a/intermediate/src/prog8/intermediate/IRFileReader.kt +++ b/intermediate/src/prog8/intermediate/IRFileReader.kt @@ -43,6 +43,8 @@ class IRFileReader { val program = IRProgram(programName, st, options, options.compTarget) program.addGlobalInits(initGlobals) blocks.forEach{ program.addBlock(it) } + + program.validate() return program } @@ -412,8 +414,8 @@ class IRFileReader { return params val (datatype, name) = line.split(' ') val dt = parseDatatype(datatype, datatype.contains('[')) - val orig = variables.single { it.dt==dt && it.name==name} - params.add(IRSubroutine.IRParam(name, dt, orig)) + // val orig = variables.single { it.dt==dt && it.name==name} + params.add(IRSubroutine.IRParam(name, dt)) } } diff --git a/intermediate/src/prog8/intermediate/IRFileWriter.kt b/intermediate/src/prog8/intermediate/IRFileWriter.kt index 47bd72e1f..fe74a12f8 100644 --- a/intermediate/src/prog8/intermediate/IRFileWriter.kt +++ b/intermediate/src/prog8/intermediate/IRFileWriter.kt @@ -33,7 +33,28 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) { out.write("\n") out.close() - println("$numChunks code chunks and $numLines lines.") + val usedRegisters = mutableSetOf() + val usedFpRegisters = mutableSetOf() + irProgram.blocks.forEach { + it.inlineAssembly.forEach { chunk -> + val used = chunk.usedRegisters() + usedRegisters += used.inputRegs + used.outputRegs + usedFpRegisters += used.inputFpRegs + used.outputFpRegs + } + it.subroutines.forEach { sub -> + sub.chunks.forEach { chunk -> + val used = chunk.usedRegisters() + usedRegisters += used.inputRegs + used.outputRegs + usedFpRegisters += used.inputFpRegs + used.outputFpRegs + } + } + it.asmSubroutines.forEach { asmsub -> + val used = asmsub.usedRegisters() + usedRegisters += used.inputRegs + used.outputRegs + usedFpRegisters += used.inputFpRegs + used.outputFpRegs + } + } + println("($numLines lines in $numChunks code chunks, ${usedRegisters.size + usedFpRegisters.size} registers)") return outfile } @@ -86,7 +107,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) { } out.write("\n") out.write("\n") - out.write(it.assembly.trimStart('\r', '\n').trimEnd(' ', '\r', '\n')) + out.write(it.assembly) out.write("\n\n\n") } out.write("\n") @@ -95,7 +116,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) { private fun writeInlineAsm(chunk: IRInlineAsmChunk) { out.write("\n") - out.write(chunk.assembly.trimStart('\r', '\n').trimEnd(' ', '\r', '\n')) + out.write(chunk.assembly) out.write("\n\n") } diff --git a/intermediate/src/prog8/intermediate/IRInstructions.kt b/intermediate/src/prog8/intermediate/IRInstructions.kt index f5833c085..05c781ece 100644 --- a/intermediate/src/prog8/intermediate/IRInstructions.kt +++ b/intermediate/src/prog8/intermediate/IRInstructions.kt @@ -450,56 +450,55 @@ enum class IRDataType { } enum class OperandDirection { + UNUSED, INPUT, OUTPUT, INOUT } data class InstructionFormat(val datatype: IRDataType?, - val reg1: Boolean, val reg1direction: OperandDirection, // reg1 can be IN/OUT/INOUT - val reg2: Boolean, // always only IN - val fpReg1: Boolean, val fpReg1direction: OperandDirection, // fpreg1 can be IN/OUT/INOUT - val fpReg2: Boolean, // always only IN - val value: Boolean, // always only IN - val fpValue: Boolean // always only IN + val reg1: OperandDirection, + val reg2: OperandDirection, + val fpReg1: OperandDirection, + val fpReg2: OperandDirection, + val valueIn: Boolean, + val fpValueIn: Boolean ) { companion object { fun from(spec: String): Map { val result = mutableMapOf() for(part in spec.split('|').map{ it.trim() }) { - var reg1 = false // read/write/modify possible - var reg1Direction = OperandDirection.INPUT - var reg2 = false // strictly read-only - var fpreg1 = false // read/write/modify possible - var fpreg1Direction = OperandDirection.INPUT - var fpreg2 = false // strictly read-only - var value = false // strictly read-only - var fpvalue = false // strictly read-only + var reg1 = OperandDirection.UNUSED + var reg2 = OperandDirection.UNUSED + var fpreg1 = OperandDirection.UNUSED + var fpreg2 = OperandDirection.UNUSED + var valueIn = false + var fpvalueIn = false val splits = part.splitToSequence(',').iterator() val typespec = splits.next() while(splits.hasNext()) { when(splits.next()) { - " { reg1=true; reg1Direction=OperandDirection.INPUT } - ">r1" -> { reg1=true; reg1Direction=OperandDirection.OUTPUT } - "<>r1" -> { reg1=true; reg1Direction=OperandDirection.INOUT } - " reg2 = true - " { fpreg1=true; fpreg1Direction=OperandDirection.INPUT } - ">fr1" -> { fpreg1=true; fpreg1Direction=OperandDirection.OUTPUT } - "<>fr1" -> { fpreg1=true; fpreg1Direction=OperandDirection.INOUT } - " fpreg2=true - " value = true - " fpvalue = true + " { reg1=OperandDirection.INPUT } + ">r1" -> { reg1=OperandDirection.OUTPUT } + "<>r1" -> { reg1=OperandDirection.INOUT } + " reg2 = OperandDirection.INPUT + " { fpreg1=OperandDirection.INPUT } + ">fr1" -> { fpreg1=OperandDirection.OUTPUT } + "<>fr1" -> { fpreg1=OperandDirection.INOUT } + " fpreg2 = OperandDirection.INPUT + " valueIn = true + " fpvalueIn = true else -> throw IllegalArgumentException(spec) } } if(typespec=="N") - result[null] = InstructionFormat(null, reg1, reg1Direction, reg2, fpreg1, fpreg1Direction, fpreg2, value, fpvalue) + result[null] = InstructionFormat(null, reg1, reg2, fpreg1, fpreg2, valueIn, fpvalueIn) if('B' in typespec) - result[IRDataType.BYTE] = InstructionFormat(IRDataType.BYTE, reg1, reg1Direction, reg2, fpreg1, fpreg1Direction, fpreg2, value, fpvalue) + result[IRDataType.BYTE] = InstructionFormat(IRDataType.BYTE, reg1, reg2, fpreg1, fpreg2, valueIn, fpvalueIn) if('W' in typespec) - result[IRDataType.WORD] = InstructionFormat(IRDataType.WORD, reg1, reg1Direction, reg2, fpreg1, fpreg1Direction, fpreg2, value, fpvalue) + result[IRDataType.WORD] = InstructionFormat(IRDataType.WORD, reg1, reg2, fpreg1, fpreg2, valueIn, fpvalueIn) if('F' in typespec) - result[IRDataType.FLOAT] = InstructionFormat(IRDataType.FLOAT, reg1, reg1Direction, reg2, fpreg1, fpreg1Direction, fpreg2, value, fpvalue) + result[IRDataType.FLOAT] = InstructionFormat(IRDataType.FLOAT, reg1, reg2, fpreg1, fpreg2, valueIn, fpvalueIn) } return result } @@ -674,7 +673,10 @@ data class IRInstruction( // 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. val reg1direction: OperandDirection + val reg2direction: OperandDirection val fpReg1direction: OperandDirection + val fpReg2direction: OperandDirection + private val registersUsed: RegistersUsed init { require(labelSymbol?.first()!='_') {"label/symbol should not start with underscore $labelSymbol"} @@ -697,25 +699,28 @@ data class IRInstruction( val formats = instructionFormats.getValue(opcode) require (type != null || formats.containsKey(null)) { "missing type" } - val format = formats.getValue(type) - if(format.reg1) require(reg1!=null) { "missing reg1" } - if(format.reg2) require(reg2!=null) { "missing reg2" } - if(format.fpReg1) require(fpReg1!=null) { "missing fpReg1" } - if(format.fpReg2) require(fpReg2!=null) { "missing fpReg2" } - if(!format.reg1) require(reg1==null) { "invalid reg1" } - if(!format.reg2) require(reg2==null) { "invalid reg2" } - if(!format.fpReg1) require(fpReg1==null) { "invalid fpReg1" } - if(!format.fpReg2) require(fpReg2==null) { "invalid fpReg2" } + val format = formats.getOrElse(type) { throw IllegalArgumentException("type $type invalid for $opcode") } + if(format.reg1!=OperandDirection.UNUSED) require(reg1!=null) { "missing reg1" } + if(format.reg2!=OperandDirection.UNUSED) require(reg2!=null) { "missing reg2" } + if(format.fpReg1!=OperandDirection.UNUSED) require(fpReg1!=null) { "missing fpReg1" } + if(format.fpReg2!=OperandDirection.UNUSED) require(fpReg2!=null) { "missing fpReg2" } + if(format.reg1==OperandDirection.UNUSED) require(reg1==null) { "invalid reg1" } + if(format.reg2==OperandDirection.UNUSED) require(reg2==null) { "invalid reg2" } + if(format.fpReg1==OperandDirection.UNUSED) require(fpReg1==null) { "invalid fpReg1" } + if(format.fpReg2==OperandDirection.UNUSED) require(fpReg2==null) { "invalid fpReg2" } if (type==IRDataType.FLOAT) { - if(format.fpValue) require(fpValue!=null || labelSymbol!=null) {"missing a fp-value or labelsymbol"} + if(format.fpValueIn) require(fpValue!=null || labelSymbol!=null) {"missing a fp-value or labelsymbol"} } else { - if(format.value) require(value!=null || labelSymbol!=null) {"missing a value or labelsymbol"} + if(format.valueIn) require(value!=null || labelSymbol!=null) {"missing a value or labelsymbol"} require(fpReg1==null && fpReg2==null) {"integer point instruction can't use floating point registers"} } - reg1direction = format.reg1direction - fpReg1direction = format.fpReg1direction + + reg1direction = format.reg1 + reg2direction = format.reg2 + fpReg1direction = format.fpReg1 + fpReg2direction = format.fpReg2 if(opcode in setOf(Opcode.BEQ, Opcode.BNE, Opcode.BLT, Opcode.BLTS, Opcode.BGT, Opcode.BGTS, Opcode.BLE, Opcode.BLES, @@ -728,8 +733,46 @@ data class IRInstruction( else require(reg1!=reg2) {"$opcode: reg1 and reg2 should be different"} } + + val inputRegs = mutableSetOf() + val outputRegs = mutableSetOf() + val inputFpRegs = mutableSetOf() + val outputFpRegs = mutableSetOf() + + when (reg1direction) { + OperandDirection.UNUSED -> {} + OperandDirection.INPUT -> inputRegs += reg1!! + OperandDirection.OUTPUT -> outputRegs += reg1!! + OperandDirection.INOUT -> { + inputRegs += reg1!! + outputRegs += reg1!! + } + } + when (reg2direction) { + OperandDirection.UNUSED -> {} + OperandDirection.INPUT -> inputRegs += reg2!! + else -> throw IllegalArgumentException("reg2 can only be input") + } + when (fpReg1direction) { + OperandDirection.UNUSED -> {} + OperandDirection.INPUT -> inputFpRegs += fpReg1!! + OperandDirection.OUTPUT -> outputFpRegs += fpReg1!! + OperandDirection.INOUT -> { + inputFpRegs += fpReg1!! + outputFpRegs += fpReg1!! + } + } + when (fpReg2direction) { + OperandDirection.UNUSED -> {} + OperandDirection.INPUT -> inputFpRegs += fpReg2!! + else -> throw IllegalArgumentException("fpReg2 can only be input") + } + + registersUsed = RegistersUsed(inputRegs, outputRegs, inputFpRegs, outputFpRegs) } + override fun usedRegisters() = registersUsed + override fun toString(): String { val result = mutableListOf(opcode.name.lowercase()) diff --git a/intermediate/src/prog8/intermediate/IRProgram.kt b/intermediate/src/prog8/intermediate/IRProgram.kt index 1e4fb1a1d..a8079c2cf 100644 --- a/intermediate/src/prog8/intermediate/IRProgram.kt +++ b/intermediate/src/prog8/intermediate/IRProgram.kt @@ -1,6 +1,5 @@ package prog8.intermediate -import prog8.code.StStaticVariable import prog8.code.core.* /* @@ -64,6 +63,19 @@ class IRProgram(val name: String, fun addAsmSymbols(symbolDefs: Map) { asmSymbols += symbolDefs } + + fun validate() { + blocks.forEach { + it.inlineAssembly.forEach { chunk -> + require(chunk.lines.isEmpty()) + } + it.subroutines.forEach { sub -> + sub.chunks.forEach { chunk -> + if (chunk is IRInlineAsmChunk) { require(chunk.lines.isEmpty()) } + } + } + } + } } class IRBlock( @@ -94,7 +106,7 @@ class IRSubroutine(val name: String, val returnType: DataType?, val position: Position) { - class IRParam(val name: String, val dt: DataType, val orig: StStaticVariable) + class IRParam(val name: String, val dt: DataType) val chunks = mutableListOf() @@ -123,28 +135,57 @@ class IRAsmSubroutine( init { require('.' in name) { "subroutine name is not scoped: $name" } require(!name.startsWith("main.main.")) { "subroutine name invalid main prefix: $name" } + require(!assembly.startsWith('\n') && !assembly.startsWith('\r')) { "inline assembly should be trimmed" } + require(!assembly.endsWith('\n') && !assembly.endsWith('\r')) { "inline assembly should be trimmed" } } + + private val registersUsed by lazy { registersUsedInAssembly(isIR, assembly) } + + fun usedRegisters() = registersUsed } -sealed class IRCodeLine +sealed class IRCodeLine { + abstract fun usedRegisters(): RegistersUsed +} -class IRCodeLabel(val name: String): IRCodeLine() +class IRCodeLabel(val name: String): IRCodeLine() { + override fun usedRegisters() = RegistersUsed.EMPTY +} -class IRCodeComment(val comment: String): IRCodeLine() +class IRCodeComment(val comment: String): IRCodeLine() { + override fun usedRegisters() = RegistersUsed.EMPTY +} -class IRCodeInlineBinary(val data: Collection): IRCodeLine() +class IRCodeInlineBinary(val data: Collection): IRCodeLine() { + override fun usedRegisters() = RegistersUsed.EMPTY +} abstract class IRCodeChunkBase(val position: Position) { val lines = mutableListOf() abstract fun isEmpty(): Boolean abstract fun isNotEmpty(): Boolean + abstract fun usedRegisters(): RegistersUsed } class IRCodeChunk(position: Position): IRCodeChunkBase(position) { override fun isEmpty() = lines.isEmpty() override fun isNotEmpty() = lines.isNotEmpty() + override fun usedRegisters(): RegistersUsed { + val inputRegs = mutableSetOf() + val outputRegs = mutableSetOf() + val inputFpRegs = mutableSetOf() + val outputFpRegs = mutableSetOf() + lines.forEach { + val used = it.usedRegisters() + inputRegs += used.inputRegs + outputRegs += used.outputRegs + inputFpRegs += used.inputFpRegs + outputFpRegs += used.outputFpRegs + } + return RegistersUsed(inputRegs, outputRegs, inputFpRegs, outputFpRegs) + } operator fun plusAssign(line: IRCodeLine) { lines.add(line) @@ -155,9 +196,54 @@ class IRCodeChunk(position: Position): IRCodeChunkBase(position) { } } +class RegistersUsed( + val inputRegs: Set, + val outputRegs: Set, + val inputFpRegs: Set, + val outputFpRegs: Set +) { + override fun toString(): String { + return "input=$inputRegs, output=$outputRegs, inputFp=$inputFpRegs, outputFp=$outputFpRegs" + } + + fun isEmpty() = inputRegs.isEmpty() && outputRegs.isEmpty() && inputFpRegs.isEmpty() && outputFpRegs.isEmpty() + fun isNotEmpty() = !isEmpty() + + companion object { + val EMPTY = RegistersUsed(emptySet(), emptySet(), emptySet(), emptySet()) + } +} + class IRInlineAsmChunk(val assembly: String, val isIR: Boolean, position: Position): IRCodeChunkBase(position) { // note: no lines, asm is in the property override fun isEmpty() = assembly.isBlank() override fun isNotEmpty() = assembly.isNotBlank() + private val registersUsed by lazy { registersUsedInAssembly(isIR, assembly) } + + init { + require(!assembly.startsWith('\n') && !assembly.startsWith('\r')) { "inline assembly should be trimmed" } + require(!assembly.endsWith('\n') && !assembly.endsWith('\r')) { "inline assembly should be trimmed" } + } + + override fun usedRegisters() = registersUsed } +private fun registersUsedInAssembly(isIR: Boolean, assembly: String): RegistersUsed { + val inputRegs = mutableSetOf() + val inputFpRegs = mutableSetOf() + val outputRegs = mutableSetOf() + val outputFpRegs = mutableSetOf() + + if(isIR) { + assembly.lineSequence().forEach { line -> + val code = parseIRCodeLine(line.trim(), 0, mutableMapOf()) + val used = code.usedRegisters() + inputRegs += used.inputRegs + outputRegs += used.outputRegs + inputFpRegs += used.inputFpRegs + outputFpRegs += used.outputFpRegs + } + } + + return RegistersUsed(inputRegs, outputRegs, inputFpRegs, outputFpRegs) +} diff --git a/intermediate/src/prog8/intermediate/Utils.kt b/intermediate/src/prog8/intermediate/Utils.kt index bd06c55b3..fd4e63a03 100644 --- a/intermediate/src/prog8/intermediate/Utils.kt +++ b/intermediate/src/prog8/intermediate/Utils.kt @@ -195,15 +195,15 @@ fun parseIRCodeLine(line: String, pc: Int, placeholders: MutableMap if(type!=null && type !in formats) throw IRParseException("invalid type code for $line") - if(format.reg1 && reg1==null) + if(format.reg1!=OperandDirection.UNUSED && reg1==null) throw IRParseException("needs reg1 for $line") - if(format.reg2 && reg2==null) + if(format.reg2!=OperandDirection.UNUSED && reg2==null) throw IRParseException("needs reg2 for $line") - if(format.value && value==null && labelSymbol==null) + if(format.valueIn && value==null && labelSymbol==null) throw IRParseException("needs value or symbol for $line") - if(!format.reg1 && reg1!=null) + if(format.reg1==OperandDirection.UNUSED && reg1!=null) throw IRParseException("invalid reg1 for $line") - if(!format.reg2 && reg2!=null) + if(format.reg2==OperandDirection.UNUSED && reg2!=null) throw IRParseException("invalid reg2 for $line") if(value!=null && opcode !in OpcodesWithAddress) { when (type) { @@ -222,9 +222,9 @@ fun parseIRCodeLine(line: String, pc: Int, placeholders: MutableMap var floatValue: Float? = null var intValue: Int? = null - if(format.value && value!=null) + if(format.valueIn && value!=null) intValue = value.toInt() - if(format.fpValue && value!=null) + if(format.fpValueIn && value!=null) floatValue = value if(opcode in OpcodesForCpuRegisters) { diff --git a/intermediate/test/TestInstructions.kt b/intermediate/test/TestInstructions.kt index 5d530d4f3..70ed1877e 100644 --- a/intermediate/test/TestInstructions.kt +++ b/intermediate/test/TestInstructions.kt @@ -12,12 +12,12 @@ class TestInstructions: FunSpec({ val ins = IRInstruction(Opcode.NOP) ins.opcode shouldBe Opcode.NOP ins.type shouldBe null + ins.reg1direction shouldBe OperandDirection.UNUSED + ins.fpReg1direction shouldBe OperandDirection.UNUSED ins.reg1 shouldBe null ins.reg2 shouldBe null ins.value shouldBe null ins.labelSymbol shouldBe null - ins.reg1direction shouldBe OperandDirection.INPUT - ins.fpReg1direction shouldBe OperandDirection.INPUT ins.toString() shouldBe "nop" } @@ -25,12 +25,12 @@ class TestInstructions: FunSpec({ val ins = IRInstruction(Opcode.BZ, IRDataType.BYTE, reg1=42, value = 99) ins.opcode shouldBe Opcode.BZ ins.type shouldBe IRDataType.BYTE + ins.reg1direction shouldBe OperandDirection.INPUT + ins.fpReg1direction shouldBe OperandDirection.UNUSED ins.reg1 shouldBe 42 ins.reg2 shouldBe null ins.value shouldBe 99 ins.labelSymbol shouldBe null - ins.reg1direction shouldBe OperandDirection.INPUT - ins.fpReg1direction shouldBe OperandDirection.INPUT ins.toString() shouldBe "bz.b r42,99" } @@ -38,12 +38,12 @@ class TestInstructions: FunSpec({ val ins = IRInstruction(Opcode.BZ, IRDataType.WORD, reg1=11, labelSymbol = "a.b.c") ins.opcode shouldBe Opcode.BZ ins.type shouldBe IRDataType.WORD + ins.reg1direction shouldBe OperandDirection.INPUT + ins.fpReg1direction shouldBe OperandDirection.UNUSED ins.reg1 shouldBe 11 ins.reg2 shouldBe null ins.value shouldBe null ins.labelSymbol shouldBe "a.b.c" - ins.reg1direction shouldBe OperandDirection.INPUT - ins.fpReg1direction shouldBe OperandDirection.INPUT ins.toString() shouldBe "bz.w r11,_a.b.c" } @@ -51,26 +51,47 @@ class TestInstructions: FunSpec({ val ins = IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1=11, reg2=22) ins.opcode shouldBe Opcode.ADDR ins.type shouldBe IRDataType.WORD + ins.reg1direction shouldBe OperandDirection.INOUT + ins.reg2direction shouldBe OperandDirection.INPUT + ins.fpReg1direction shouldBe OperandDirection.UNUSED + ins.fpReg2direction shouldBe OperandDirection.UNUSED ins.reg1 shouldBe 11 ins.reg2 shouldBe 22 ins.value shouldBe null ins.labelSymbol shouldBe null - ins.reg1direction shouldBe OperandDirection.INOUT - ins.fpReg1direction shouldBe OperandDirection.INPUT ins.toString() shouldBe "addr.w r11,r22" val ins2 = IRInstruction(Opcode.SQRT, IRDataType.BYTE, reg1=11, reg2=22) ins2.opcode shouldBe Opcode.SQRT ins2.type shouldBe IRDataType.BYTE + ins2.reg1direction shouldBe OperandDirection.OUTPUT + ins2.reg2direction shouldBe OperandDirection.INPUT + ins2.fpReg1direction shouldBe OperandDirection.UNUSED + ins2.fpReg2direction shouldBe OperandDirection.UNUSED ins2.reg1 shouldBe 11 ins2.reg2 shouldBe 22 ins2.value shouldBe null ins2.labelSymbol shouldBe null - ins2.reg1direction shouldBe OperandDirection.OUTPUT - ins2.fpReg1direction shouldBe OperandDirection.INPUT ins2.toString() shouldBe "sqrt.b r11,r22" } + test("with float regs") { + val ins = IRInstruction(Opcode.FSIN, IRDataType.FLOAT, fpReg1 = 1, fpReg2 = 2) + ins.opcode shouldBe Opcode.FSIN + ins.type shouldBe IRDataType.FLOAT + ins.reg1direction shouldBe OperandDirection.UNUSED + ins.reg2direction shouldBe OperandDirection.UNUSED + ins.fpReg1direction shouldBe OperandDirection.OUTPUT + ins.fpReg2direction shouldBe OperandDirection.INPUT + ins.fpReg1 shouldBe 1 + ins.fpReg2 shouldBe 2 + ins.reg1 shouldBe null + ins.reg2 shouldBe null + ins.value shouldBe null + ins.labelSymbol shouldBe null + ins.toString() shouldBe "fsin.f fr1,fr2" + } + test("missing type should fail") { shouldThrow { @@ -93,7 +114,11 @@ class TestInstructions: FunSpec({ test("all instructionformats") { instructionFormats.size shouldBe Opcode.values().size Opcode.values().forEach { - instructionFormats[it] shouldNotBe null + val fmt = instructionFormats.getValue(it) + fmt.values.forEach { format -> + require(format.reg2==OperandDirection.UNUSED || format.reg2==OperandDirection.INPUT) {"reg2 can only be used as input"} + require(format.fpReg2==OperandDirection.UNUSED || format.fpReg2==OperandDirection.INPUT) {"fpReg2 can only be used as input"} + } } } }) diff --git a/scripts/clean.sh b/scripts/clean.sh index 6e798c626..cc8d891d7 100755 --- a/scripts/clean.sh +++ b/scripts/clean.sh @@ -2,5 +2,5 @@ rm -f *.bin *.xex *.jar *.asm *.prg *.vm.txt *.vice-mon-list *.list *.p8ir a.out imgui.ini rm -rf build out -rm -rf compiler/build codeGenCpu6502/build codeGenExperimental/build codeOptimizers/build compilerAst/build dbusCompilerService/build httpCompilerService/build parser/build parser/src/prog8/parser +rm -rf compiler/build codeGenCpu6502/build codeGenExperimental/build codeGenIntermediate/build intermediate/build virtualmachine/build codeOptimizers/build compilerAst/build dbusCompilerService/build httpCompilerService/build parser/build parser/src/prog8/parser diff --git a/virtualmachine/src/prog8/vm/VmProgramLoader.kt b/virtualmachine/src/prog8/vm/VmProgramLoader.kt index e0ca98fc7..9757b1772 100644 --- a/virtualmachine/src/prog8/vm/VmProgramLoader.kt +++ b/virtualmachine/src/prog8/vm/VmProgramLoader.kt @@ -238,7 +238,7 @@ class VmProgramLoader { symbolAddresses[parsed.name] = program.size } } else { - throw IRParseException("vm currently does not support real inlined assembly (only IR)': ${asmChunk.position}") + throw IRParseException("vm currently does not support real inlined assembly (only IR): ${asmChunk.position}") } } }