ir: adding register usage inspections

fix compiler problems with untrimmed inlined asm, and when only a single return statement is present in a subroutine
This commit is contained in:
Irmen de Jong 2022-09-30 20:18:14 +02:00
parent 5a8f97a0b6
commit af6be44676
16 changed files with 277 additions and 80 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,8 @@ TODO
For next release
^^^^^^^^^^^^^^^^
- ir: RegistersUsed should actually count the number of times a register is used too?
...

View File

@ -1,4 +1,7 @@
main {
sub start() {
ubyte aa = 42
ubyte bb = 99
aa += bb
}
}

View File

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

View File

@ -33,7 +33,28 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
out.write("</PROGRAM>\n")
out.close()
println("$numChunks code chunks and $numLines lines.")
val usedRegisters = mutableSetOf<Int>()
val usedFpRegisters = mutableSetOf<Int>()
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("</PARAMS>\n")
out.write("<INLINEASM IR=${it.isIR} POS=${it.position}>\n")
out.write(it.assembly.trimStart('\r', '\n').trimEnd(' ', '\r', '\n'))
out.write(it.assembly)
out.write("\n</INLINEASM>\n</ASMSUB>\n")
}
out.write("</BLOCK>\n")
@ -95,7 +116,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
private fun writeInlineAsm(chunk: IRInlineAsmChunk) {
out.write("<INLINEASM IR=${chunk.isIR} POS=${chunk.position}>\n")
out.write(chunk.assembly.trimStart('\r', '\n').trimEnd(' ', '\r', '\n'))
out.write(chunk.assembly)
out.write("\n</INLINEASM>\n")
}

View File

@ -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<IRDataType?, InstructionFormat> {
val result = mutableMapOf<IRDataType?, InstructionFormat>()
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()) {
"<r1" -> { reg1=true; reg1Direction=OperandDirection.INPUT }
">r1" -> { reg1=true; reg1Direction=OperandDirection.OUTPUT }
"<>r1" -> { reg1=true; reg1Direction=OperandDirection.INOUT }
"<r2" -> reg2 = true
"<fr1" -> { fpreg1=true; fpreg1Direction=OperandDirection.INPUT }
">fr1" -> { fpreg1=true; fpreg1Direction=OperandDirection.OUTPUT }
"<>fr1" -> { fpreg1=true; fpreg1Direction=OperandDirection.INOUT }
"<fr2" -> fpreg2=true
"<v" -> value = true
"<fv" -> fpvalue = true
"<r1" -> { reg1=OperandDirection.INPUT }
">r1" -> { reg1=OperandDirection.OUTPUT }
"<>r1" -> { reg1=OperandDirection.INOUT }
"<r2" -> reg2 = OperandDirection.INPUT
"<fr1" -> { fpreg1=OperandDirection.INPUT }
">fr1" -> { fpreg1=OperandDirection.OUTPUT }
"<>fr1" -> { fpreg1=OperandDirection.INOUT }
"<fr2" -> fpreg2 = OperandDirection.INPUT
"<v" -> valueIn = true
"<fv" -> 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<Int>()
val outputRegs = mutableSetOf<Int>()
val inputFpRegs = mutableSetOf<Int>()
val outputFpRegs = mutableSetOf<Int>()
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())

View File

@ -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<String, String>) {
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<IRCodeChunkBase>()
@ -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<UByte>): IRCodeLine()
class IRCodeInlineBinary(val data: Collection<UByte>): IRCodeLine() {
override fun usedRegisters() = RegistersUsed.EMPTY
}
abstract class IRCodeChunkBase(val position: Position) {
val lines = mutableListOf<IRCodeLine>()
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<Int>()
val outputRegs = mutableSetOf<Int>()
val inputFpRegs = mutableSetOf<Int>()
val outputFpRegs = mutableSetOf<Int>()
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<Int>,
val outputRegs: Set<Int>,
val inputFpRegs: Set<Int>,
val outputFpRegs: Set<Int>
) {
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<Int>()
val inputFpRegs = mutableSetOf<Int>()
val outputRegs = mutableSetOf<Int>()
val outputFpRegs = mutableSetOf<Int>()
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)
}

View File

@ -195,15 +195,15 @@ fun parseIRCodeLine(line: String, pc: Int, placeholders: MutableMap<Int, String>
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<Int, String>
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) {

View File

@ -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<IllegalArgumentException> {
@ -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"}
}
}
}
})

View File

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

View File

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