mirror of
https://github.com/irmen/prog8.git
synced 2024-06-01 06:41:42 +00:00
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:
parent
5a8f97a0b6
commit
af6be44676
|
@ -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" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ TODO
|
|||
|
||||
For next release
|
||||
^^^^^^^^^^^^^^^^
|
||||
- ir: RegistersUsed should actually count the number of times a register is used too?
|
||||
|
||||
...
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
main {
|
||||
sub start() {
|
||||
ubyte aa = 42
|
||||
ubyte bb = 99
|
||||
aa += bb
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user