mirror of
https://github.com/irmen/prog8.git
synced 2024-06-10 20:29:33 +00:00
1287 lines
60 KiB
Kotlin
1287 lines
60 KiB
Kotlin
package prog8.codegen.intermediate
|
|
|
|
import prog8.code.StMemVar
|
|
import prog8.code.StNode
|
|
import prog8.code.StStaticVariable
|
|
import prog8.code.SymbolTable
|
|
import prog8.code.ast.*
|
|
import prog8.code.core.*
|
|
import prog8.intermediate.*
|
|
import kotlin.io.path.readBytes
|
|
import kotlin.math.pow
|
|
|
|
|
|
class IRCodeGen(
|
|
internal val program: PtProgram,
|
|
internal val symbolTable: SymbolTable,
|
|
internal val options: CompilationOptions,
|
|
internal val errors: IErrorReporter
|
|
) {
|
|
|
|
private val expressionEval = ExpressionGen(this)
|
|
private val builtinFuncGen = BuiltinFuncGen(this, expressionEval)
|
|
private val assignmentGen = AssignmentGen(this, expressionEval)
|
|
internal val registers = RegisterPool()
|
|
|
|
fun generate(): IRProgram {
|
|
flattenLabelNames()
|
|
flattenNestedSubroutines()
|
|
|
|
// TODO: validateNames(program)
|
|
|
|
val irProg = IRProgram(program.name, IRSymbolTable(symbolTable), options, program.encoding)
|
|
|
|
if(options.evalStackBaseAddress!=null)
|
|
throw AssemblyError("IR doesn't use eval-stack")
|
|
|
|
if(!options.dontReinitGlobals) {
|
|
// collect global variables initializers
|
|
program.allBlocks().forEach {
|
|
val result = mutableListOf<IRCodeChunkBase>()
|
|
it.children.filterIsInstance<PtAssignment>().forEach { assign -> result += assignmentGen.translate(assign) }
|
|
result.forEach { chunk ->
|
|
if (chunk is IRCodeChunk) irProg.addGlobalInits(chunk)
|
|
else throw AssemblyError("only expect code chunk for global inits")
|
|
}
|
|
}
|
|
}
|
|
|
|
irProg.addAsmSymbols(options.symbolDefs)
|
|
|
|
for (block in program.allBlocks())
|
|
irProg.addBlock(translate(block))
|
|
|
|
replaceMemoryMappedVars(irProg)
|
|
ensureFirstChunkLabels(irProg)
|
|
irProg.linkChunks()
|
|
|
|
if(options.optimize) {
|
|
val optimizer = IRPeepholeOptimizer(irProg)
|
|
optimizer.optimize()
|
|
|
|
val remover = IRUnusedCodeRemover(irProg, errors)
|
|
do {
|
|
val numRemoved = remover.optimize()
|
|
} while(numRemoved>0 && errors.noErrors())
|
|
|
|
errors.report()
|
|
|
|
irProg.linkChunks() // re-link
|
|
}
|
|
|
|
irProg.validate()
|
|
return irProg
|
|
}
|
|
|
|
private fun validateNames(node: PtNode) {
|
|
when(node) {
|
|
is PtBuiltinFunctionCall -> require('.' in node.name) { "node $node name is not scoped: ${node.name}"}
|
|
is PtFunctionCall -> require('.' in node.name) { "node $node name is not scoped: ${node.name}"}
|
|
is PtIdentifier -> require('.' in node.name) { "node $node name is not scoped: ${node.name}"}
|
|
is PtAsmSub -> require('.' in node.name) { "node $node name is not scoped: ${node.name}"}
|
|
is PtBlock -> require('.' !in node.name) { "block name should not be scoped: ${node.name}"}
|
|
is PtConstant -> require('.' in node.name) { "node $node name is not scoped: ${node.name}"}
|
|
is PtLabel -> require('.' in node.name) { "node $node name is not scoped: ${node.name}"}
|
|
is PtMemMapped -> require('.' in node.name) { "node $node name is not scoped: ${node.name}"}
|
|
is PtSub -> require('.' in node.name) { "node $node name is not scoped: ${node.name}"}
|
|
is PtVariable -> require('.' in node.name) { "node $node name is not scoped: ${node.name}"}
|
|
is PtProgram -> require('.' !in node.name) { "program name should not be scoped: ${node.name}"}
|
|
is PtSubroutineParameter -> require('.' in node.name) { "node $node name is not scoped: ${node.name}"}
|
|
else -> { /* node has no name */ }
|
|
}
|
|
node.children.forEach{ validateNames(it) }
|
|
}
|
|
|
|
private fun ensureFirstChunkLabels(irProg: IRProgram) {
|
|
// make sure that first chunks in Blocks and Subroutines share the name of the block/sub as label.
|
|
|
|
irProg.blocks.forEach { block ->
|
|
block.children.firstOrNull { it is IRInlineAsmChunk }?.let { first->
|
|
first as IRInlineAsmChunk
|
|
if(first.label==null) {
|
|
val replacement = IRInlineAsmChunk(block.name, first.assembly, first.isIR, first.next)
|
|
block.children.removeAt(0)
|
|
block.children.add(0, replacement)
|
|
} else if(first.label != block.name) {
|
|
throw AssemblyError("first chunk in block has label that differs from block name")
|
|
}
|
|
}
|
|
|
|
block.children.filterIsInstance<IRSubroutine>().forEach { sub ->
|
|
if(sub.chunks.isNotEmpty()) {
|
|
val first = sub.chunks.first()
|
|
if(first.label==null) {
|
|
val replacement = when(first) {
|
|
is IRCodeChunk -> {
|
|
val replacement = IRCodeChunk(sub.label, first.next)
|
|
replacement.instructions += first.instructions
|
|
replacement
|
|
}
|
|
is IRInlineAsmChunk -> IRInlineAsmChunk(sub.label, first.assembly, first.isIR, first.next)
|
|
is IRInlineBinaryChunk -> IRInlineBinaryChunk(sub.label, first.data, first.next)
|
|
else -> throw AssemblyError("invalid chunk")
|
|
}
|
|
sub.chunks.removeAt(0)
|
|
sub.chunks.add(0, replacement)
|
|
} else if(first.label != sub.label) {
|
|
val next = if(first is IRCodeChunk) first else null
|
|
sub.chunks.add(0, IRCodeChunk(sub.label, next))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun replaceMemoryMappedVars(irProg: IRProgram) {
|
|
// replace memory mapped variable symbols with the memory address directly.
|
|
// note: we do still export the memory mapped symbols so a code generator can use those
|
|
// for instance when a piece of inlined assembly references them.
|
|
val replacements = mutableListOf<Triple<IRCodeChunkBase, Int, UInt>>()
|
|
irProg.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.flatMap { it.chunks }.forEach { chunk ->
|
|
chunk.instructions.withIndex().forEach {
|
|
(idx, instr) ->
|
|
val symbolExpr = instr.labelSymbol
|
|
if(symbolExpr!=null) {
|
|
val symbol: String
|
|
val index: UInt
|
|
if('+' in symbolExpr) {
|
|
val operands = symbolExpr.split('+', )
|
|
symbol = operands[0]
|
|
index = operands[1].toUInt()
|
|
} else {
|
|
symbol = symbolExpr
|
|
index = 0u
|
|
}
|
|
val target = symbolTable.flat[symbol]
|
|
if (target is StMemVar) {
|
|
replacements.add(Triple(chunk, idx, target.address+index))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
replacements.forEach {
|
|
val old = it.first.instructions[it.second]
|
|
it.first.instructions[it.second] = IRInstruction(
|
|
old.opcode,
|
|
old.type,
|
|
old.reg1,
|
|
old.reg2,
|
|
old.fpReg1,
|
|
old.fpReg2,
|
|
it.third.toInt(),
|
|
null,
|
|
null
|
|
)
|
|
}
|
|
}
|
|
|
|
private fun flattenLabelNames() {
|
|
val renameLabels = mutableListOf<Pair<PtNode, PtLabel>>()
|
|
|
|
fun flattenRecurse(node: PtNode) {
|
|
node.children.forEach {
|
|
if (it is PtLabel)
|
|
renameLabels += Pair(it.parent, it)
|
|
else
|
|
flattenRecurse(it)
|
|
}
|
|
}
|
|
|
|
flattenRecurse(program)
|
|
|
|
renameLabels.forEach { (_, label) -> label.name = label.scopedName }
|
|
}
|
|
|
|
private fun flattenNestedSubroutines() {
|
|
// this moves all nested subroutines up to the block scope.
|
|
// also changes the name to be the fully scoped one, so it becomes unique at the top level.
|
|
val flattenedSubs = mutableListOf<Pair<PtBlock, PtSub>>()
|
|
val flattenedAsmSubs = mutableListOf<Pair<PtBlock, PtAsmSub>>()
|
|
val removalsSubs = mutableListOf<Pair<PtSub, PtSub>>()
|
|
val removalsAsmSubs = mutableListOf<Pair<PtSub, PtAsmSub>>()
|
|
val renameSubs = mutableListOf<Pair<PtBlock, PtSub>>()
|
|
val renameAsmSubs = mutableListOf<Pair<PtBlock, PtAsmSub>>()
|
|
|
|
fun flattenNestedAsmSub(block: PtBlock, parentSub: PtSub, asmsub: PtAsmSub) {
|
|
val flattened = PtAsmSub(asmsub.scopedName,
|
|
asmsub.address,
|
|
asmsub.clobbers,
|
|
asmsub.parameters,
|
|
asmsub.returnTypes,
|
|
asmsub.retvalRegisters,
|
|
asmsub.inline,
|
|
asmsub.position)
|
|
asmsub.children.forEach { flattened.add(it) }
|
|
flattenedAsmSubs += Pair(block, flattened)
|
|
removalsAsmSubs += Pair(parentSub, asmsub)
|
|
}
|
|
|
|
fun flattenNestedSub(block: PtBlock, parentSub: PtSub, sub: PtSub) {
|
|
sub.children.filterIsInstance<PtSub>().forEach { subsub->flattenNestedSub(block, sub, subsub) }
|
|
sub.children.filterIsInstance<PtAsmSub>().forEach { asmsubsub->flattenNestedAsmSub(block, sub, asmsubsub) }
|
|
val flattened = PtSub(sub.scopedName,
|
|
sub.parameters,
|
|
sub.returntype,
|
|
sub.inline,
|
|
sub.position)
|
|
sub.children.forEach { if(it !is PtSub) flattened.add(it) }
|
|
flattenedSubs += Pair(block, flattened)
|
|
removalsSubs += Pair(parentSub, sub)
|
|
}
|
|
|
|
program.allBlocks().forEach { block ->
|
|
block.children.forEach {
|
|
if(it is PtSub) {
|
|
// Only regular subroutines can have nested subroutines.
|
|
it.children.filterIsInstance<PtSub>().forEach { subsub->flattenNestedSub(block, it, subsub)}
|
|
it.children.filterIsInstance<PtAsmSub>().forEach { asmsubsub->flattenNestedAsmSub(block, it, asmsubsub)}
|
|
renameSubs += Pair(block, it)
|
|
}
|
|
if(it is PtAsmSub)
|
|
renameAsmSubs += Pair(block, it)
|
|
}
|
|
}
|
|
|
|
removalsSubs.forEach { (parent, sub) -> parent.children.remove(sub) }
|
|
removalsAsmSubs.forEach { (parent, asmsub) -> parent.children.remove(asmsub) }
|
|
flattenedSubs.forEach { (block, sub) -> block.add(sub) }
|
|
flattenedAsmSubs.forEach { (block, asmsub) -> block.add(asmsub) }
|
|
renameSubs.forEach { (_, sub) -> sub.name = sub.scopedName }
|
|
renameAsmSubs.forEach { (_, sub) -> sub.name = sub.scopedName }
|
|
}
|
|
|
|
internal fun translateNode(node: PtNode): IRCodeChunks {
|
|
val chunks = when(node) {
|
|
is PtScopeVarsDecls -> emptyList() // vars should be looked up via symbol table
|
|
is PtVariable -> emptyList() // var should be looked up via symbol table
|
|
is PtMemMapped -> emptyList() // memmapped var should be looked up via symbol table
|
|
is PtConstant -> emptyList() // constants have all been folded into the code
|
|
is PtAssignment -> assignmentGen.translate(node)
|
|
is PtNodeGroup -> translateGroup(node.children)
|
|
is PtBuiltinFunctionCall -> translateBuiltinFunc(node, 0)
|
|
is PtFunctionCall -> expressionEval.translate(node, 0, 0)
|
|
is PtNop -> emptyList()
|
|
is PtReturn -> translate(node)
|
|
is PtJump -> translate(node)
|
|
is PtWhen -> translate(node)
|
|
is PtForLoop -> translate(node)
|
|
is PtIfElse -> translate(node)
|
|
is PtPostIncrDecr -> translate(node)
|
|
is PtRepeatLoop -> translate(node)
|
|
is PtLabel -> listOf(IRCodeChunk(node.name, null))
|
|
is PtBreakpoint -> {
|
|
val chunk = IRCodeChunk(null, null)
|
|
chunk += IRInstruction(Opcode.BREAKPOINT)
|
|
listOf(chunk)
|
|
}
|
|
is PtConditionalBranch -> translate(node)
|
|
is PtInlineAssembly -> listOf(IRInlineAsmChunk(null, node.assembly, node.isIR, null))
|
|
is PtIncludeBinary -> listOf(IRInlineBinaryChunk(null, readBinaryData(node), null))
|
|
is PtAddressOf,
|
|
is PtContainmentCheck,
|
|
is PtMemoryByte,
|
|
is PtProgram,
|
|
is PtArrayIndexer,
|
|
is PtBinaryExpression,
|
|
is PtIdentifier,
|
|
is PtWhenChoice,
|
|
is PtPrefix,
|
|
is PtRange,
|
|
is PtAssignTarget,
|
|
is PtTypeCast,
|
|
is PtSubroutineParameter,
|
|
is PtNumber,
|
|
is PtArray,
|
|
is PtBlock,
|
|
is PtString -> throw AssemblyError("should not occur as separate statement node ${node.position}")
|
|
is PtSub -> throw AssemblyError("nested subroutines should have been flattened ${node.position}")
|
|
else -> TODO("missing codegen for $node")
|
|
}
|
|
|
|
chunks.forEach { chunk ->
|
|
require(chunk.isNotEmpty() || chunk.label != null) {
|
|
"chunk should have instructions and/or a label"
|
|
}
|
|
}
|
|
|
|
return chunks
|
|
}
|
|
|
|
private fun readBinaryData(node: PtIncludeBinary): Collection<UByte> {
|
|
return node.file.readBytes()
|
|
.drop(node.offset?.toInt() ?: 0)
|
|
.take(node.length?.toInt() ?: Int.MAX_VALUE)
|
|
.map { it.toUByte() }
|
|
}
|
|
|
|
private fun translate(branch: PtConditionalBranch): IRCodeChunks {
|
|
val result = mutableListOf<IRCodeChunkBase>()
|
|
|
|
val goto = branch.trueScope.children.firstOrNull() as? PtJump
|
|
if(goto is PtJump && branch.falseScope.children.isEmpty()) {
|
|
// special case the form: if_cc <condition> goto <place>
|
|
val address = goto.address?.toInt()
|
|
if(address!=null) {
|
|
val branchIns = when(branch.condition) {
|
|
BranchCondition.CS -> IRInstruction(Opcode.BSTCS, value = address)
|
|
BranchCondition.CC -> IRInstruction(Opcode.BSTCC, value = address)
|
|
BranchCondition.EQ, BranchCondition.Z -> IRInstruction(Opcode.BSTEQ, value = address)
|
|
BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTNE, value = address)
|
|
BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTNEG, value = address)
|
|
BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTPOS, value = address)
|
|
BranchCondition.VC -> IRInstruction(Opcode.BSTVC, value = address)
|
|
BranchCondition.VS -> IRInstruction(Opcode.BSTVS, value = address)
|
|
}
|
|
addInstr(result, branchIns, null)
|
|
} else {
|
|
val label = if(goto.generatedLabel!=null) goto.generatedLabel else goto.identifier!!.name
|
|
val branchIns = when(branch.condition) {
|
|
BranchCondition.CS -> IRInstruction(Opcode.BSTCS, labelSymbol = label)
|
|
BranchCondition.CC -> IRInstruction(Opcode.BSTCC, labelSymbol = label)
|
|
BranchCondition.EQ, BranchCondition.Z -> IRInstruction(Opcode.BSTEQ, labelSymbol = label)
|
|
BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTNE, labelSymbol = label)
|
|
BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTNEG, labelSymbol = label)
|
|
BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTPOS, labelSymbol = label)
|
|
BranchCondition.VC -> IRInstruction(Opcode.BSTVC, labelSymbol = label)
|
|
BranchCondition.VS -> IRInstruction(Opcode.BSTVS, labelSymbol = label)
|
|
}
|
|
addInstr(result, branchIns, null)
|
|
}
|
|
return result
|
|
}
|
|
|
|
|
|
val elseLabel = createLabelName()
|
|
// note that the branch opcode used is the opposite as the branch condition, because the generated code jumps to the 'else' part
|
|
val branchIns = when(branch.condition) {
|
|
BranchCondition.CS -> IRInstruction(Opcode.BSTCC, labelSymbol = elseLabel)
|
|
BranchCondition.CC -> IRInstruction(Opcode.BSTCS, labelSymbol = elseLabel)
|
|
BranchCondition.EQ, BranchCondition.Z -> IRInstruction(Opcode.BSTNE, labelSymbol = elseLabel)
|
|
BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTEQ, labelSymbol = elseLabel)
|
|
BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTPOS, labelSymbol = elseLabel)
|
|
BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTNEG, labelSymbol = elseLabel)
|
|
BranchCondition.VC -> IRInstruction(Opcode.BSTVS, labelSymbol = elseLabel)
|
|
BranchCondition.VS -> IRInstruction(Opcode.BSTVC, labelSymbol = elseLabel)
|
|
}
|
|
addInstr(result, branchIns, null)
|
|
result += translateNode(branch.trueScope)
|
|
if(branch.falseScope.children.isNotEmpty()) {
|
|
val endLabel = createLabelName()
|
|
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
|
|
val chunks = translateNode(branch.falseScope)
|
|
result += labelFirstChunk(chunks, elseLabel)
|
|
result += IRCodeChunk(endLabel, null)
|
|
} else {
|
|
result += IRCodeChunk(elseLabel, null)
|
|
}
|
|
return result
|
|
}
|
|
|
|
private fun labelFirstChunk(chunks: IRCodeChunks, label: String): IRCodeChunks {
|
|
require(chunks.isNotEmpty() && label.isNotBlank())
|
|
val first = chunks[0]
|
|
if(first.label!=null) {
|
|
if(first.label==label)
|
|
return chunks
|
|
val newFirst = IRCodeChunk(label, first)
|
|
return listOf(newFirst) + chunks
|
|
}
|
|
val labeledFirstChunk = when(first) {
|
|
is IRCodeChunk -> {
|
|
val newChunk = IRCodeChunk(label, first.next)
|
|
newChunk.instructions += first.instructions
|
|
newChunk
|
|
}
|
|
is IRInlineAsmChunk -> {
|
|
IRInlineAsmChunk(label, first.assembly, first.isIR, first.next)
|
|
}
|
|
is IRInlineBinaryChunk -> {
|
|
IRInlineBinaryChunk(label, first.data, first.next)
|
|
}
|
|
else -> {
|
|
throw AssemblyError("invalid chunk")
|
|
}
|
|
}
|
|
return listOf(labeledFirstChunk) + chunks.drop(1)
|
|
}
|
|
|
|
private fun translate(whenStmt: PtWhen): IRCodeChunks {
|
|
if(whenStmt.choices.children.isEmpty())
|
|
return emptyList()
|
|
val result = mutableListOf<IRCodeChunkBase>()
|
|
val valueReg = registers.nextFree()
|
|
val choiceReg = registers.nextFree()
|
|
val valueDt = irType(whenStmt.value.type)
|
|
result += expressionEval.translateExpression(whenStmt.value, valueReg, -1)
|
|
val choices = whenStmt.choices.children.map {it as PtWhenChoice }
|
|
val endLabel = createLabelName()
|
|
for (choice in choices) {
|
|
if(choice.isElse) {
|
|
result += translateNode(choice.statements)
|
|
} else {
|
|
val skipLabel = createLabelName()
|
|
val values = choice.values.children.map {it as PtNumber}
|
|
if(values.size==1) {
|
|
val chunk = IRCodeChunk(null, null)
|
|
chunk += IRInstruction(Opcode.LOAD, valueDt, reg1=choiceReg, value=values[0].number.toInt())
|
|
chunk += IRInstruction(Opcode.BNE, valueDt, reg1=valueReg, reg2=choiceReg, labelSymbol = skipLabel)
|
|
result += chunk
|
|
result += translateNode(choice.statements)
|
|
if(choice.statements.children.last() !is PtReturn)
|
|
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
|
|
} else {
|
|
val matchLabel = createLabelName()
|
|
val chunk = IRCodeChunk(null, null)
|
|
for (value in values) {
|
|
chunk += IRInstruction(Opcode.LOAD, valueDt, reg1=choiceReg, value=value.number.toInt())
|
|
chunk += IRInstruction(Opcode.BEQ, valueDt, reg1=valueReg, reg2=choiceReg, labelSymbol = matchLabel)
|
|
}
|
|
chunk += IRInstruction(Opcode.JUMP, labelSymbol = skipLabel)
|
|
result += chunk
|
|
result += labelFirstChunk(translateNode(choice.statements), matchLabel)
|
|
if(choice.statements.children.last() !is PtReturn)
|
|
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
|
|
}
|
|
result += IRCodeChunk(skipLabel, null)
|
|
}
|
|
}
|
|
result += IRCodeChunk(endLabel, null)
|
|
return result
|
|
}
|
|
|
|
private fun translate(forLoop: PtForLoop): IRCodeChunks {
|
|
val loopvar = symbolTable.lookup(forLoop.variable.name)!!
|
|
val iterable = forLoop.iterable
|
|
val result = mutableListOf<IRCodeChunkBase>()
|
|
when(iterable) {
|
|
is PtRange -> {
|
|
if(iterable.from is PtNumber && iterable.to is PtNumber)
|
|
result += translateForInConstantRange(forLoop, loopvar)
|
|
else
|
|
result += translateForInNonConstantRange(forLoop, loopvar)
|
|
}
|
|
is PtIdentifier -> {
|
|
val iterableVar = symbolTable.lookup(iterable.name) as StStaticVariable
|
|
val loopvarSymbol = loopvar.scopedName
|
|
val indexReg = registers.nextFree()
|
|
val tmpReg = registers.nextFree()
|
|
val loopLabel = createLabelName()
|
|
val endLabel = createLabelName()
|
|
if(iterableVar.dt==DataType.STR) {
|
|
// iterate over a zero-terminated string
|
|
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=indexReg, value=0), null)
|
|
val chunk = IRCodeChunk(loopLabel, null)
|
|
chunk += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpReg, reg2=indexReg, labelSymbol = iterable.name)
|
|
chunk += IRInstruction(Opcode.BZ, IRDataType.BYTE, reg1=tmpReg, labelSymbol = endLabel)
|
|
chunk += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=tmpReg, labelSymbol = loopvarSymbol)
|
|
result += chunk
|
|
result += translateNode(forLoop.statements)
|
|
val jumpChunk = IRCodeChunk(null, null)
|
|
jumpChunk += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexReg)
|
|
jumpChunk += IRInstruction(Opcode.JUMP, labelSymbol = loopLabel)
|
|
result += jumpChunk
|
|
result += IRCodeChunk(endLabel, null)
|
|
} else {
|
|
// iterate over array
|
|
val elementDt = ArrayToElementTypes.getValue(iterable.type)
|
|
val elementSize = program.memsizer.memorySize(elementDt)
|
|
val lengthBytes = iterableVar.length!! * elementSize
|
|
if(lengthBytes<256) {
|
|
val lengthReg = registers.nextFree()
|
|
val chunk = IRCodeChunk(null, null)
|
|
chunk += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=indexReg, value=0)
|
|
chunk += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, value=lengthBytes)
|
|
result += chunk
|
|
val chunk2 = IRCodeChunk(loopLabel, null)
|
|
chunk2 += IRInstruction(Opcode.LOADX, irType(elementDt), reg1=tmpReg, reg2=indexReg, labelSymbol=iterable.name)
|
|
chunk2 += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=tmpReg, labelSymbol = loopvarSymbol)
|
|
result += chunk2
|
|
result += translateNode(forLoop.statements)
|
|
result += addConstReg(IRDataType.BYTE, indexReg, elementSize)
|
|
addInstr(result, IRInstruction(Opcode.BNE, IRDataType.BYTE, reg1=indexReg, reg2=lengthReg, labelSymbol = loopLabel), null)
|
|
} else if(lengthBytes==256) {
|
|
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=indexReg, value=0), null)
|
|
val chunk = IRCodeChunk(loopLabel, null)
|
|
chunk += IRInstruction(Opcode.LOADX, irType(elementDt), reg1=tmpReg, reg2=indexReg, labelSymbol=iterable.name)
|
|
chunk += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=tmpReg, labelSymbol = loopvarSymbol)
|
|
result += chunk
|
|
result += translateNode(forLoop.statements)
|
|
result += addConstReg(IRDataType.BYTE, indexReg, elementSize)
|
|
addInstr(result, IRInstruction(Opcode.BNZ, IRDataType.BYTE, reg1=indexReg, labelSymbol = loopLabel), null)
|
|
} else {
|
|
throw AssemblyError("iterator length should never exceed 256")
|
|
}
|
|
}
|
|
}
|
|
else -> throw AssemblyError("weird for iterable")
|
|
}
|
|
return result
|
|
}
|
|
|
|
private fun translateForInNonConstantRange(forLoop: PtForLoop, loopvar: StNode): IRCodeChunks {
|
|
val iterable = forLoop.iterable as PtRange
|
|
val step = iterable.step.number.toInt()
|
|
if (step==0)
|
|
throw AssemblyError("step 0")
|
|
val indexReg = registers.nextFree()
|
|
val endvalueReg = registers.nextFree()
|
|
val loopvarSymbol = loopvar.scopedName
|
|
val loopvarDt = when(loopvar) {
|
|
is StMemVar -> loopvar.dt
|
|
is StStaticVariable -> loopvar.dt
|
|
else -> throw AssemblyError("invalid loopvar node type")
|
|
}
|
|
val loopvarDtIr = irType(loopvarDt)
|
|
val loopLabel = createLabelName()
|
|
val result = mutableListOf<IRCodeChunkBase>()
|
|
|
|
result += expressionEval.translateExpression(iterable.to, endvalueReg, -1)
|
|
result += expressionEval.translateExpression(iterable.from, indexReg, -1)
|
|
|
|
val labelAfterFor = createLabelName()
|
|
val greaterOpcode = if(loopvarDt in SignedDatatypes) Opcode.BGTS else Opcode.BGT
|
|
addInstr(result, IRInstruction(greaterOpcode, loopvarDtIr, indexReg, endvalueReg, labelSymbol=labelAfterFor), null)
|
|
|
|
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=indexReg, labelSymbol=loopvarSymbol), null)
|
|
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
|
|
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
|
|
addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol), null)
|
|
// if endvalue >= index, iterate loop
|
|
val branchOpcode = if(loopvarDt in SignedDatatypes) Opcode.BGES else Opcode.BGE
|
|
addInstr(result, IRInstruction(branchOpcode, loopvarDtIr, reg1=endvalueReg, reg2=indexReg, labelSymbol=loopLabel), null)
|
|
|
|
result += IRCodeChunk(labelAfterFor, null)
|
|
return result
|
|
}
|
|
|
|
private fun translateForInConstantRange(forLoop: PtForLoop, loopvar: StNode): IRCodeChunks {
|
|
val loopLabel = createLabelName()
|
|
val loopvarSymbol = loopvar.scopedName
|
|
val indexReg = registers.nextFree()
|
|
val loopvarDt = when(loopvar) {
|
|
is StMemVar -> loopvar.dt
|
|
is StStaticVariable -> loopvar.dt
|
|
else -> throw AssemblyError("invalid loopvar node type")
|
|
}
|
|
val loopvarDtIr = irType(loopvarDt)
|
|
val iterable = forLoop.iterable as PtRange
|
|
val step = iterable.step.number.toInt()
|
|
val rangeStart = (iterable.from as PtNumber).number.toInt()
|
|
val rangeEndUntyped = (iterable.to as PtNumber).number.toInt() + step
|
|
if(step==0)
|
|
throw AssemblyError("step 0")
|
|
if(step>0 && rangeEndUntyped<rangeStart || step<0 && rangeEndUntyped>rangeStart)
|
|
throw AssemblyError("empty range")
|
|
val rangeEndWrapped = if(loopvarDtIr==IRDataType.BYTE) rangeEndUntyped and 255 else rangeEndUntyped and 65535
|
|
val endvalueReg: Int
|
|
val result = mutableListOf<IRCodeChunkBase>()
|
|
val chunk = IRCodeChunk(null, null)
|
|
if(rangeEndWrapped!=0) {
|
|
endvalueReg = registers.nextFree()
|
|
chunk += IRInstruction(Opcode.LOAD, loopvarDtIr, reg1 = endvalueReg, value = rangeEndWrapped)
|
|
} else {
|
|
endvalueReg = -1 // not used
|
|
}
|
|
chunk += IRInstruction(Opcode.LOAD, loopvarDtIr, reg1=indexReg, value=rangeStart)
|
|
chunk += IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=indexReg, labelSymbol=loopvarSymbol)
|
|
result += chunk
|
|
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
|
|
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
|
|
val chunk2 = IRCodeChunk(null, null)
|
|
chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol)
|
|
chunk2 += if(rangeEndWrapped==0) {
|
|
IRInstruction(Opcode.BNZ, loopvarDtIr, reg1 = indexReg, labelSymbol = loopLabel)
|
|
} else {
|
|
IRInstruction(Opcode.BNE, loopvarDtIr, reg1 = indexReg, reg2 = endvalueReg, labelSymbol = loopLabel)
|
|
}
|
|
result += chunk2
|
|
return result
|
|
}
|
|
|
|
private fun addConstReg(dt: IRDataType, reg: Int, value: Int): IRCodeChunk {
|
|
val code = IRCodeChunk(null, null)
|
|
when(value) {
|
|
0 -> { /* do nothing */ }
|
|
1 -> {
|
|
code += IRInstruction(Opcode.INC, dt, reg1=reg)
|
|
}
|
|
2 -> {
|
|
code += IRInstruction(Opcode.INC, dt, reg1=reg)
|
|
code += IRInstruction(Opcode.INC, dt, reg1=reg)
|
|
}
|
|
-1 -> {
|
|
code += IRInstruction(Opcode.DEC, dt, reg1=reg)
|
|
}
|
|
-2 -> {
|
|
code += IRInstruction(Opcode.DEC, dt, reg1=reg)
|
|
code += IRInstruction(Opcode.DEC, dt, reg1=reg)
|
|
}
|
|
else -> {
|
|
code += if(value>0) {
|
|
IRInstruction(Opcode.ADD, dt, reg1 = reg, value=value)
|
|
} else {
|
|
IRInstruction(Opcode.SUB, dt, reg1 = reg, value=-value)
|
|
}
|
|
}
|
|
}
|
|
return code
|
|
}
|
|
|
|
private fun addConstMem(dt: IRDataType, knownAddress: UInt?, symbol: String?, value: Int): IRCodeChunk {
|
|
val code = IRCodeChunk(null, null)
|
|
when(value) {
|
|
0 -> { /* do nothing */ }
|
|
1 -> {
|
|
code += if(knownAddress!=null)
|
|
IRInstruction(Opcode.INCM, dt, value=knownAddress.toInt())
|
|
else
|
|
IRInstruction(Opcode.INCM, dt, labelSymbol = symbol)
|
|
}
|
|
2 -> {
|
|
if(knownAddress!=null) {
|
|
code += IRInstruction(Opcode.INCM, dt, value = knownAddress.toInt())
|
|
code += IRInstruction(Opcode.INCM, dt, value = knownAddress.toInt())
|
|
} else {
|
|
code += IRInstruction(Opcode.INCM, dt, labelSymbol = symbol)
|
|
code += IRInstruction(Opcode.INCM, dt, labelSymbol = symbol)
|
|
}
|
|
}
|
|
-1 -> {
|
|
code += if(knownAddress!=null)
|
|
IRInstruction(Opcode.DECM, dt, value=knownAddress.toInt())
|
|
else
|
|
IRInstruction(Opcode.DECM, dt, labelSymbol = symbol)
|
|
}
|
|
-2 -> {
|
|
if(knownAddress!=null) {
|
|
code += IRInstruction(Opcode.DECM, dt, value = knownAddress.toInt())
|
|
code += IRInstruction(Opcode.DECM, dt, value = knownAddress.toInt())
|
|
} else {
|
|
code += IRInstruction(Opcode.DECM, dt, labelSymbol = symbol)
|
|
code += IRInstruction(Opcode.DECM, dt, labelSymbol = symbol)
|
|
}
|
|
}
|
|
else -> {
|
|
val valueReg = registers.nextFree()
|
|
if(value>0) {
|
|
code += IRInstruction(Opcode.LOAD, dt, reg1=valueReg, value=value)
|
|
code += if(knownAddress!=null)
|
|
IRInstruction(Opcode.ADDM, dt, reg1=valueReg, value=knownAddress.toInt())
|
|
else
|
|
IRInstruction(Opcode.ADDM, dt, reg1=valueReg, labelSymbol = symbol)
|
|
}
|
|
else {
|
|
code += IRInstruction(Opcode.LOAD, dt, reg1=valueReg, value=-value)
|
|
code += if(knownAddress!=null)
|
|
IRInstruction(Opcode.SUBM, dt, reg1=valueReg, value=knownAddress.toInt())
|
|
else
|
|
IRInstruction(Opcode.SUBM, dt, reg1=valueReg, labelSymbol = symbol)
|
|
}
|
|
}
|
|
}
|
|
return code
|
|
}
|
|
|
|
internal fun multiplyByConstFloat(fpReg: Int, factor: Float): IRCodeChunk {
|
|
val code = IRCodeChunk(null, null)
|
|
if(factor==1f)
|
|
return code
|
|
code += if(factor==0f) {
|
|
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = fpReg, fpValue = 0f)
|
|
} else {
|
|
IRInstruction(Opcode.MUL, IRDataType.FLOAT, fpReg1 = fpReg, fpValue=factor)
|
|
}
|
|
return code
|
|
}
|
|
|
|
internal fun multiplyByConstFloatInplace(knownAddress: Int?, symbol: String?, factor: Float): IRCodeChunk {
|
|
val code = IRCodeChunk(null, null)
|
|
if(factor==1f)
|
|
return code
|
|
if(factor==0f) {
|
|
code += if(knownAddress!=null)
|
|
IRInstruction(Opcode.STOREZM, IRDataType.FLOAT, value = knownAddress)
|
|
else
|
|
IRInstruction(Opcode.STOREZM, IRDataType.FLOAT, labelSymbol = symbol)
|
|
} else {
|
|
val factorReg = registers.nextFreeFloat()
|
|
code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1=factorReg, fpValue = factor)
|
|
code += if(knownAddress!=null)
|
|
IRInstruction(Opcode.MULM, IRDataType.FLOAT, fpReg1 = factorReg, value = knownAddress)
|
|
else
|
|
IRInstruction(Opcode.MULM, IRDataType.FLOAT, fpReg1 = factorReg, labelSymbol = symbol)
|
|
}
|
|
return code
|
|
}
|
|
|
|
internal val powersOfTwo = (0..16).map { 2.0.pow(it.toDouble()).toInt() }
|
|
|
|
internal fun multiplyByConst(dt: IRDataType, reg: Int, factor: Int): IRCodeChunk {
|
|
val code = IRCodeChunk(null, null)
|
|
if(factor==1)
|
|
return code
|
|
val pow2 = powersOfTwo.indexOf(factor)
|
|
if(pow2==1) {
|
|
// just shift 1 bit
|
|
code += IRInstruction(Opcode.LSL, dt, reg1=reg)
|
|
}
|
|
else if(pow2>=1) {
|
|
// just shift multiple bits
|
|
val pow2reg = registers.nextFree()
|
|
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, value=pow2)
|
|
code += IRInstruction(Opcode.LSLN, dt, reg1=reg, reg2=pow2reg)
|
|
} else {
|
|
code += if (factor == 0) {
|
|
IRInstruction(Opcode.LOAD, dt, reg1=reg, value=0)
|
|
} else {
|
|
IRInstruction(Opcode.MUL, dt, reg1=reg, value=factor)
|
|
}
|
|
}
|
|
return code
|
|
}
|
|
|
|
internal fun multiplyByConstInplace(dt: IRDataType, knownAddress: Int?, symbol: String?, factor: Int): IRCodeChunk {
|
|
val code = IRCodeChunk(null, null)
|
|
if(factor==1)
|
|
return code
|
|
val pow2 = powersOfTwo.indexOf(factor)
|
|
if(pow2==1) {
|
|
// just shift 1 bit
|
|
code += if(knownAddress!=null)
|
|
IRInstruction(Opcode.LSLM, dt, value = knownAddress)
|
|
else
|
|
IRInstruction(Opcode.LSLM, dt, labelSymbol = symbol)
|
|
}
|
|
else if(pow2>=1) {
|
|
// just shift multiple bits
|
|
val pow2reg = registers.nextFree()
|
|
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, value=pow2)
|
|
code += if(knownAddress!=null)
|
|
IRInstruction(Opcode.LSLNM, dt, reg1=pow2reg, value=knownAddress)
|
|
else
|
|
IRInstruction(Opcode.LSLNM, dt, reg1=pow2reg, labelSymbol = symbol)
|
|
} else {
|
|
if (factor == 0) {
|
|
code += if(knownAddress!=null)
|
|
IRInstruction(Opcode.STOREZM, dt, value=knownAddress)
|
|
else
|
|
IRInstruction(Opcode.STOREZM, dt, labelSymbol = symbol)
|
|
}
|
|
else {
|
|
val factorReg = registers.nextFree()
|
|
code += IRInstruction(Opcode.LOAD, dt, reg1=factorReg, value = factor)
|
|
code += if(knownAddress!=null)
|
|
IRInstruction(Opcode.MULM, dt, reg1=factorReg, value = knownAddress)
|
|
else
|
|
IRInstruction(Opcode.MULM, dt, reg1=factorReg, labelSymbol = symbol)
|
|
}
|
|
}
|
|
return code
|
|
}
|
|
|
|
internal fun divideByConstFloat(fpReg: Int, factor: Float): IRCodeChunk {
|
|
val code = IRCodeChunk(null, null)
|
|
if(factor==1f)
|
|
return code
|
|
code += if(factor==0f) {
|
|
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = fpReg, fpValue = Float.MAX_VALUE)
|
|
} else {
|
|
IRInstruction(Opcode.DIVS, IRDataType.FLOAT, fpReg1 = fpReg, fpValue=factor)
|
|
}
|
|
return code
|
|
}
|
|
|
|
internal fun divideByConstFloatInplace(knownAddress: Int?, symbol: String?, factor: Float): IRCodeChunk {
|
|
val code = IRCodeChunk(null, null)
|
|
if(factor==1f)
|
|
return code
|
|
if(factor==0f) {
|
|
val maxvalueReg = registers.nextFreeFloat()
|
|
code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = maxvalueReg, fpValue = Float.MAX_VALUE)
|
|
code += if(knownAddress!=null)
|
|
IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = maxvalueReg, value=knownAddress)
|
|
else
|
|
IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = maxvalueReg, labelSymbol = symbol)
|
|
} else {
|
|
val factorReg = registers.nextFreeFloat()
|
|
code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1=factorReg, fpValue = factor)
|
|
code += if(knownAddress!=null)
|
|
IRInstruction(Opcode.DIVSM, IRDataType.FLOAT, fpReg1 = factorReg, value=knownAddress)
|
|
else
|
|
IRInstruction(Opcode.DIVSM, IRDataType.FLOAT, fpReg1 = factorReg, labelSymbol = symbol)
|
|
}
|
|
return code
|
|
}
|
|
|
|
internal fun divideByConst(dt: IRDataType, reg: Int, factor: Int, signed: Boolean): IRCodeChunk {
|
|
val code = IRCodeChunk(null, null)
|
|
if(factor==1)
|
|
return code
|
|
val pow2 = powersOfTwo.indexOf(factor)
|
|
if(pow2==1 && !signed) {
|
|
code += IRInstruction(Opcode.LSR, dt, reg1=reg) // simple single bit shift
|
|
}
|
|
else if(pow2>=1 &&!signed) {
|
|
// just shift multiple bits
|
|
val pow2reg = registers.nextFree()
|
|
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, value=pow2)
|
|
code += if(signed)
|
|
IRInstruction(Opcode.ASRN, dt, reg1=reg, reg2=pow2reg)
|
|
else
|
|
IRInstruction(Opcode.LSRN, dt, reg1=reg, reg2=pow2reg)
|
|
} else {
|
|
code += if (factor == 0) {
|
|
IRInstruction(Opcode.LOAD, dt, reg1=reg, value=0xffff)
|
|
} else {
|
|
if(signed)
|
|
IRInstruction(Opcode.DIVS, dt, reg1=reg, value=factor)
|
|
else
|
|
IRInstruction(Opcode.DIV, dt, reg1=reg, value=factor)
|
|
}
|
|
}
|
|
return code
|
|
}
|
|
|
|
internal fun divideByConstInplace(dt: IRDataType, knownAddress: Int?, symbol: String?, factor: Int, signed: Boolean): IRCodeChunk {
|
|
val code = IRCodeChunk(null, null)
|
|
if(factor==1)
|
|
return code
|
|
val pow2 = powersOfTwo.indexOf(factor)
|
|
if(pow2==1 && !signed) {
|
|
// just simple bit shift
|
|
code += if(knownAddress!=null)
|
|
IRInstruction(Opcode.LSRM, dt, value=knownAddress)
|
|
else
|
|
IRInstruction(Opcode.LSRM, dt, labelSymbol = symbol)
|
|
}
|
|
else if(pow2>=1 && !signed) {
|
|
// just shift multiple bits
|
|
val pow2reg = registers.nextFree()
|
|
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, value=pow2)
|
|
code += if(signed) {
|
|
if(knownAddress!=null)
|
|
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, value = knownAddress)
|
|
else
|
|
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, labelSymbol = symbol)
|
|
}
|
|
else {
|
|
if(knownAddress!=null)
|
|
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, value = knownAddress)
|
|
else
|
|
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, labelSymbol = symbol)
|
|
}
|
|
} else {
|
|
if (factor == 0) {
|
|
val reg = registers.nextFree()
|
|
code += IRInstruction(Opcode.LOAD, dt, reg1=reg, value=0xffff)
|
|
code += if(knownAddress!=null)
|
|
IRInstruction(Opcode.STOREM, dt, reg1=reg, value=knownAddress)
|
|
else
|
|
IRInstruction(Opcode.STOREM, dt, reg1=reg, labelSymbol = symbol)
|
|
}
|
|
else {
|
|
val factorReg = registers.nextFree()
|
|
code += IRInstruction(Opcode.LOAD, dt, reg1=factorReg, value= factor)
|
|
code += if(signed) {
|
|
if(knownAddress!=null)
|
|
IRInstruction(Opcode.DIVSM, dt, reg1 = factorReg, value = knownAddress)
|
|
else
|
|
IRInstruction(Opcode.DIVSM, dt, reg1 = factorReg, labelSymbol = symbol)
|
|
}
|
|
else {
|
|
if(knownAddress!=null)
|
|
IRInstruction(Opcode.DIVM, dt, reg1 = factorReg, value = knownAddress)
|
|
else
|
|
IRInstruction(Opcode.DIVM, dt, reg1 = factorReg, labelSymbol = symbol)
|
|
}
|
|
}
|
|
}
|
|
return code
|
|
}
|
|
|
|
private fun translate(ifElse: PtIfElse): IRCodeChunks {
|
|
if(ifElse.condition.operator !in ComparisonOperators)
|
|
throw AssemblyError("if condition should only be a binary comparison expression")
|
|
|
|
val signed = ifElse.condition.left.type in SignedDatatypes
|
|
val irDt = irType(ifElse.condition.left.type)
|
|
|
|
val goto = ifElse.ifScope.children.firstOrNull() as? PtJump
|
|
if(goto!=null && ifElse.elseScope.children.isEmpty()) {
|
|
// special case the form: if <condition> goto <place>
|
|
val result = mutableListOf<IRCodeChunkBase>()
|
|
val leftRegNum = registers.nextFree()
|
|
val rightRegNum = registers.nextFree()
|
|
result += expressionEval.translateExpression(ifElse.condition.left, leftRegNum, -1)
|
|
result += expressionEval.translateExpression(ifElse.condition.right, rightRegNum, -1)
|
|
val opcode: Opcode
|
|
val firstReg: Int
|
|
val secondReg: Int
|
|
when(ifElse.condition.operator) {
|
|
"==" -> {
|
|
opcode = Opcode.BEQ
|
|
firstReg = leftRegNum
|
|
secondReg = rightRegNum
|
|
}
|
|
"!=" -> {
|
|
opcode = Opcode.BNE
|
|
firstReg = leftRegNum
|
|
secondReg = rightRegNum
|
|
}
|
|
"<" -> {
|
|
// swapped '>'
|
|
opcode = if(signed) Opcode.BGTS else Opcode.BGT
|
|
firstReg = rightRegNum
|
|
secondReg = leftRegNum
|
|
}
|
|
">" -> {
|
|
opcode = if(signed) Opcode.BGTS else Opcode.BGT
|
|
firstReg = leftRegNum
|
|
secondReg = rightRegNum
|
|
}
|
|
"<=" -> {
|
|
// swapped '>='
|
|
opcode = if(signed) Opcode.BGES else Opcode.BGE
|
|
firstReg = rightRegNum
|
|
secondReg = leftRegNum
|
|
}
|
|
">=" -> {
|
|
opcode = if(signed) Opcode.BGES else Opcode.BGE
|
|
firstReg = leftRegNum
|
|
secondReg = rightRegNum
|
|
}
|
|
else -> throw AssemblyError("invalid comparison operator")
|
|
}
|
|
if(goto.address!=null)
|
|
addInstr(result, IRInstruction(opcode, irDt, reg1=firstReg, reg2=secondReg, value = goto.address?.toInt()), null)
|
|
else if(goto.generatedLabel!=null)
|
|
addInstr(result, IRInstruction(opcode, irDt, reg1=firstReg, reg2=secondReg, labelSymbol = goto.generatedLabel), null)
|
|
else
|
|
addInstr(result, IRInstruction(opcode, irDt, reg1=firstReg, reg2=secondReg, labelSymbol = goto.identifier!!.name), null)
|
|
return result
|
|
}
|
|
|
|
fun translateNonZeroComparison(): IRCodeChunks {
|
|
val result = mutableListOf<IRCodeChunkBase>()
|
|
val leftRegNum = registers.nextFree()
|
|
val rightRegNum = registers.nextFree()
|
|
result += expressionEval.translateExpression(ifElse.condition.left, leftRegNum, -1)
|
|
result += expressionEval.translateExpression(ifElse.condition.right, rightRegNum, -1)
|
|
|
|
val elseBranchOpcode: Opcode
|
|
val elseBranchFirstReg: Int
|
|
val elseBranchSecondReg: Int
|
|
when(ifElse.condition.operator) {
|
|
"==" -> {
|
|
elseBranchOpcode = Opcode.BNE
|
|
elseBranchFirstReg = leftRegNum
|
|
elseBranchSecondReg = rightRegNum
|
|
}
|
|
"!=" -> {
|
|
elseBranchOpcode = Opcode.BEQ
|
|
elseBranchFirstReg = leftRegNum
|
|
elseBranchSecondReg = rightRegNum
|
|
}
|
|
"<" -> {
|
|
// else part when left >= right
|
|
elseBranchOpcode = if(signed) Opcode.BGES else Opcode.BGE
|
|
elseBranchFirstReg = leftRegNum
|
|
elseBranchSecondReg = rightRegNum
|
|
}
|
|
">" -> {
|
|
// else part when left <= right --> right >= left
|
|
elseBranchOpcode = if(signed) Opcode.BGES else Opcode.BGE
|
|
elseBranchFirstReg = rightRegNum
|
|
elseBranchSecondReg = leftRegNum
|
|
}
|
|
"<=" -> {
|
|
// else part when left > right
|
|
elseBranchOpcode = if(signed) Opcode.BGTS else Opcode.BGT
|
|
elseBranchFirstReg = leftRegNum
|
|
elseBranchSecondReg = rightRegNum
|
|
}
|
|
">=" -> {
|
|
// else part when left < right --> right > left
|
|
elseBranchOpcode = if(signed) Opcode.BGTS else Opcode.BGT
|
|
elseBranchFirstReg = rightRegNum
|
|
elseBranchSecondReg = leftRegNum
|
|
}
|
|
else -> throw AssemblyError("invalid comparison operator")
|
|
}
|
|
|
|
if(ifElse.elseScope.children.isNotEmpty()) {
|
|
// if and else parts
|
|
val elseLabel = createLabelName()
|
|
val afterIfLabel = createLabelName()
|
|
addInstr(result, IRInstruction(elseBranchOpcode, irDt, reg1=elseBranchFirstReg, reg2=elseBranchSecondReg, labelSymbol = elseLabel), null)
|
|
result += translateNode(ifElse.ifScope)
|
|
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
|
|
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
|
|
result += IRCodeChunk(afterIfLabel, null)
|
|
} else {
|
|
// only if part
|
|
val afterIfLabel = createLabelName()
|
|
addInstr(result, IRInstruction(elseBranchOpcode, irDt, reg1=elseBranchFirstReg, reg2=elseBranchSecondReg, labelSymbol = afterIfLabel), null)
|
|
result += translateNode(ifElse.ifScope)
|
|
result += IRCodeChunk(afterIfLabel, null)
|
|
}
|
|
return result
|
|
}
|
|
|
|
fun translateZeroComparison(): IRCodeChunks {
|
|
fun equalOrNotEqualZero(elseBranch: Opcode): IRCodeChunks {
|
|
val result = mutableListOf<IRCodeChunkBase>()
|
|
val leftReg = registers.nextFree()
|
|
result += expressionEval.translateExpression(ifElse.condition.left, leftReg, -1)
|
|
if(ifElse.elseScope.children.isNotEmpty()) {
|
|
// if and else parts
|
|
val elseLabel = createLabelName()
|
|
val afterIfLabel = createLabelName()
|
|
addInstr(result, IRInstruction(elseBranch, irDt, reg1=leftReg, labelSymbol = elseLabel), null)
|
|
result += translateNode(ifElse.ifScope)
|
|
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
|
|
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
|
|
result += IRCodeChunk(afterIfLabel, null)
|
|
} else {
|
|
// only if part
|
|
val afterIfLabel = createLabelName()
|
|
addInstr(result, IRInstruction(elseBranch, irDt, reg1=leftReg, labelSymbol = afterIfLabel), null)
|
|
result += translateNode(ifElse.ifScope)
|
|
result += IRCodeChunk(afterIfLabel, null)
|
|
}
|
|
return result
|
|
}
|
|
|
|
return when (ifElse.condition.operator) {
|
|
"==" -> equalOrNotEqualZero(Opcode.BNZ)
|
|
"!=" -> equalOrNotEqualZero(Opcode.BZ)
|
|
"<" -> if(signed) equalOrNotEqualZero(Opcode.BGEZS) else throw AssemblyError("unsigned < 0 shouldn't occur in codegen")
|
|
">" -> if(signed) equalOrNotEqualZero(Opcode.BLEZS) else throw AssemblyError("unsigned > 0 shouldn't occur in codegen")
|
|
"<=" -> if(signed) equalOrNotEqualZero(Opcode.BGZS) else throw AssemblyError("unsigned <= 0 shouldn't occur in codegen")
|
|
">=" -> if(signed) equalOrNotEqualZero(Opcode.BLZS) else throw AssemblyError("unsigned >= 0 shouldn't occur in codegen")
|
|
else -> throw AssemblyError("weird operator")
|
|
}
|
|
}
|
|
|
|
return if(constValue(ifElse.condition.right)==0.0)
|
|
translateZeroComparison()
|
|
else
|
|
translateNonZeroComparison()
|
|
}
|
|
|
|
private fun translate(postIncrDecr: PtPostIncrDecr): IRCodeChunks {
|
|
val operationMem: Opcode
|
|
val operationRegister: Opcode
|
|
when(postIncrDecr.operator) {
|
|
"++" -> {
|
|
operationMem = Opcode.INCM
|
|
operationRegister = Opcode.INC
|
|
}
|
|
"--" -> {
|
|
operationMem = Opcode.DECM
|
|
operationRegister = Opcode.DEC
|
|
}
|
|
else -> throw AssemblyError("weird operator")
|
|
}
|
|
val ident = postIncrDecr.target.identifier
|
|
val memory = postIncrDecr.target.memory
|
|
val array = postIncrDecr.target.array
|
|
val irDt = irType(postIncrDecr.target.type)
|
|
val result = mutableListOf<IRCodeChunkBase>()
|
|
if(ident!=null) {
|
|
addInstr(result, IRInstruction(operationMem, irDt, labelSymbol = ident.name), null)
|
|
} else if(memory!=null) {
|
|
if(memory.address is PtNumber) {
|
|
val address = (memory.address as PtNumber).number.toInt()
|
|
addInstr(result, IRInstruction(operationMem, irDt, value = address), null)
|
|
} else {
|
|
val incReg = registers.nextFree()
|
|
val addressReg = registers.nextFree()
|
|
result += expressionEval.translateExpression(memory.address, addressReg, -1)
|
|
val chunk = IRCodeChunk(null, null)
|
|
chunk += IRInstruction(Opcode.LOADI, irDt, reg1 = incReg, reg2 = addressReg)
|
|
chunk += IRInstruction(operationRegister, irDt, reg1 = incReg)
|
|
chunk += IRInstruction(Opcode.STOREI, irDt, reg1 = incReg, reg2 = addressReg)
|
|
result += chunk
|
|
}
|
|
} else if (array!=null) {
|
|
val variable = array.variable.name
|
|
val itemsize = program.memsizer.memorySize(array.type)
|
|
val fixedIndex = constIntValue(array.index)
|
|
if(fixedIndex!=null) {
|
|
val offset = fixedIndex*itemsize
|
|
addInstr(result, IRInstruction(operationMem, irDt, labelSymbol="$variable+$offset"), null)
|
|
} else {
|
|
val incReg = registers.nextFree()
|
|
val indexReg = registers.nextFree()
|
|
result += expressionEval.translateExpression(array.index, indexReg, -1)
|
|
val chunk = IRCodeChunk(null, null)
|
|
chunk += IRInstruction(Opcode.LOADX, irDt, reg1=incReg, reg2=indexReg, labelSymbol=variable)
|
|
chunk += IRInstruction(operationRegister, irDt, reg1=incReg)
|
|
chunk += IRInstruction(Opcode.STOREX, irDt, reg1=incReg, reg2=indexReg, labelSymbol=variable)
|
|
result += chunk
|
|
}
|
|
} else
|
|
throw AssemblyError("weird assigntarget")
|
|
|
|
return result
|
|
}
|
|
|
|
private fun translate(repeat: PtRepeatLoop): IRCodeChunks {
|
|
when (constIntValue(repeat.count)) {
|
|
0 -> return emptyList()
|
|
1 -> return translateGroup(repeat.children)
|
|
256 -> {
|
|
// 256 iterations can still be done with just a byte counter if you set it to zero as starting value.
|
|
repeat.children[0] = PtNumber(DataType.UBYTE, 0.0, repeat.count.position)
|
|
}
|
|
}
|
|
|
|
val repeatLabel = createLabelName()
|
|
val skipRepeatLabel = createLabelName()
|
|
val counterReg = registers.nextFree()
|
|
val irDt = irType(repeat.count.type)
|
|
val result = mutableListOf<IRCodeChunkBase>()
|
|
result += expressionEval.translateExpression(repeat.count, counterReg, -1)
|
|
addInstr(result, IRInstruction(Opcode.BZ, irDt, reg1=counterReg, labelSymbol = skipRepeatLabel), null)
|
|
result += labelFirstChunk(translateNode(repeat.statements), repeatLabel)
|
|
val chunk = IRCodeChunk(null, null)
|
|
chunk += IRInstruction(Opcode.DEC, irDt, reg1=counterReg)
|
|
chunk += IRInstruction(Opcode.BNZ, irDt, reg1=counterReg, labelSymbol = repeatLabel)
|
|
result += chunk
|
|
result += IRCodeChunk(skipRepeatLabel, null)
|
|
return result
|
|
}
|
|
|
|
private fun translate(jump: PtJump): IRCodeChunks {
|
|
val result = mutableListOf<IRCodeChunkBase>()
|
|
val instr = if(jump.address!=null) {
|
|
IRInstruction(Opcode.JUMPA, value = jump.address!!.toInt())
|
|
} else {
|
|
if (jump.generatedLabel != null)
|
|
IRInstruction(Opcode.JUMP, labelSymbol = jump.generatedLabel!!)
|
|
else if (jump.identifier != null)
|
|
IRInstruction(Opcode.JUMP, labelSymbol = jump.identifier!!.name)
|
|
else
|
|
throw AssemblyError("weird jump")
|
|
}
|
|
addInstr(result, instr, null)
|
|
return result
|
|
}
|
|
|
|
private fun translateGroup(group: List<PtNode>): IRCodeChunks {
|
|
val result = mutableListOf<IRCodeChunkBase>()
|
|
group.forEach { result += translateNode(it) }
|
|
return result
|
|
}
|
|
|
|
private fun translate(ret: PtReturn): IRCodeChunks {
|
|
val result = mutableListOf<IRCodeChunkBase>()
|
|
val value = ret.value
|
|
if(value!=null) {
|
|
// Call Convention: return value is always returned in r0 (or fr0 if float)
|
|
result += if(value.type==DataType.FLOAT)
|
|
expressionEval.translateExpression(value, -1, 0)
|
|
else
|
|
expressionEval.translateExpression(value, 0, -1)
|
|
}
|
|
addInstr(result, IRInstruction(Opcode.RETURN), null)
|
|
return result
|
|
}
|
|
|
|
private fun translate(block: PtBlock): IRBlock {
|
|
val irBlock = IRBlock(block.name, block.address, translate(block.alignment), block.position) // no use for other attributes yet?
|
|
for (child in block.children) {
|
|
when(child) {
|
|
is PtNop -> { /* nothing */ }
|
|
is PtAssignment -> { /* global variable initialization is done elsewhere */ }
|
|
is PtScopeVarsDecls -> { /* vars should be looked up via symbol table */ }
|
|
is PtSub -> {
|
|
val sub = IRSubroutine(child.name, translate(child.parameters), child.returntype, child.position)
|
|
for (subchild in child.children) {
|
|
translateNode(subchild).forEach { sub += it }
|
|
}
|
|
irBlock += sub
|
|
}
|
|
is PtAsmSub -> {
|
|
if(child.address!=null) {
|
|
// romsub. No codegen needed: calls to this are jumping straight to the address.
|
|
require(child.children.isEmpty()) {
|
|
"romsub should be empty at ${child.position}"
|
|
}
|
|
} else {
|
|
// regular asmsub
|
|
val assemblyChild = child.children.single() as PtInlineAssembly
|
|
val asmChunk = IRInlineAsmChunk(
|
|
child.name, assemblyChild.assembly, assemblyChild.isIR, null
|
|
)
|
|
irBlock += IRAsmSubroutine(
|
|
child.name,
|
|
child.address,
|
|
child.clobbers,
|
|
child.parameters.map { IRAsmSubroutine.IRAsmParam(it.second, it.first.type) }, // note: the name of the asmsub param is not used anymore.
|
|
child.returnTypes.zip(child.retvalRegisters).map { IRAsmSubroutine.IRAsmParam(it.second, it.first) },
|
|
asmChunk,
|
|
child.position
|
|
)
|
|
}
|
|
}
|
|
is PtInlineAssembly -> {
|
|
irBlock += IRInlineAsmChunk(null, child.assembly, child.isIR, null)
|
|
}
|
|
is PtIncludeBinary -> {
|
|
irBlock += IRInlineBinaryChunk(null, readBinaryData(child), null)
|
|
}
|
|
is PtLabel -> {
|
|
irBlock += IRCodeChunk(child.name, null)
|
|
}
|
|
else -> TODO("weird child node $child")
|
|
}
|
|
}
|
|
return irBlock
|
|
}
|
|
|
|
private fun translate(parameters: List<PtSubroutineParameter>) =
|
|
parameters.map {
|
|
val flattenedName = it.definingISub()!!.name + "." + it.name
|
|
val orig = symbolTable.flat.getValue(flattenedName) as StStaticVariable
|
|
IRSubroutine.IRParam(flattenedName, orig.dt)
|
|
}
|
|
|
|
private fun translate(alignment: PtBlock.BlockAlignment): IRBlock.BlockAlignment {
|
|
return when(alignment) {
|
|
PtBlock.BlockAlignment.NONE -> IRBlock.BlockAlignment.NONE
|
|
PtBlock.BlockAlignment.WORD -> IRBlock.BlockAlignment.WORD
|
|
PtBlock.BlockAlignment.PAGE -> IRBlock.BlockAlignment.PAGE
|
|
}
|
|
}
|
|
|
|
|
|
internal fun irType(type: DataType): IRDataType {
|
|
return when(type) {
|
|
DataType.BOOL,
|
|
DataType.UBYTE,
|
|
DataType.BYTE -> IRDataType.BYTE
|
|
DataType.UWORD,
|
|
DataType.WORD -> IRDataType.WORD
|
|
DataType.FLOAT -> IRDataType.FLOAT
|
|
in PassByReferenceDatatypes -> IRDataType.WORD
|
|
else -> throw AssemblyError("no IR datatype for $type")
|
|
}
|
|
}
|
|
|
|
private var labelSequenceNumber = 0
|
|
internal fun createLabelName(): String {
|
|
labelSequenceNumber++
|
|
return "prog8_label_gen_$labelSequenceNumber"
|
|
}
|
|
|
|
internal fun translateBuiltinFunc(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks =
|
|
builtinFuncGen.translate(call, resultRegister)
|
|
|
|
internal fun isZero(expression: PtExpression): Boolean = expression is PtNumber && expression.number==0.0
|
|
|
|
internal fun isOne(expression: PtExpression): Boolean = expression is PtNumber && expression.number==1.0
|
|
}
|