prog8/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt

1659 lines
79 KiB
Kotlin

package prog8.codegen.intermediate
import prog8.code.*
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 {
makeAllNodenamesScoped(program)
moveAllNestedSubroutinesToBlockScope(program)
verifyNameScoping(program, symbolTable)
val irSymbolTable = IRSymbolTable.fromStDuringCodegen(symbolTable)
val irProg = IRProgram(program.name, irSymbolTable, options, program.encoding)
// 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()
irProg.convertAsmChunks()
val optimizer = IRPeepholeOptimizer(irProg)
optimizer.optimize(options.optimize, errors)
irProg.validate()
val regOptimizer = IRRegisterOptimizer(irProg)
regOptimizer.optimize()
return irProg
}
private fun verifyNameScoping(program: PtProgram, symbolTable: SymbolTable) {
fun verifyPtNode(node: PtNode) {
when (node) {
is PtBuiltinFunctionCall -> require('.' !in node.name) { "builtin function call name should not be 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 { verifyPtNode(it) }
}
fun verifyStNode(node: StNode) {
require('.' !in node.name) { "st node name should not be scoped: ${node.name}"}
node.children.forEach {
require(it.key==it.value.name)
verifyStNode(it.value)
}
}
verifyPtNode(program)
verifyStNode(symbolTable)
}
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 ->
if(block.isNotEmpty()) {
val firstAsm = block.children[0] as? IRInlineAsmChunk
if(firstAsm!=null) {
if(firstAsm.label==null) {
val replacement = IRInlineAsmChunk(block.label, firstAsm.assembly, firstAsm.isIR, firstAsm.next)
block.children.removeAt(0)
block.children.add(0, replacement)
} else if(firstAsm.label != block.label) {
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.foreachCodeChunk { chunk ->
chunk.instructions.withIndex().forEach {
(idx, instr) ->
val symbolExpr = instr.labelSymbol
if(symbolExpr!=null) {
val index = instr.labelSymbolOffset ?: 0
val target = symbolTable.flat[symbolExpr]
if (target is StMemVar) {
replacements.add(Triple(chunk, idx, target.address+index.toUInt()))
}
}
}
}
replacements.forEach {
val old = it.first.instructions[it.second]
val formats = instructionFormats.getValue(old.opcode)
val format = formats.getOrElse(old.type) { throw IllegalArgumentException("type ${old.type} invalid for ${old.opcode}") }
val immediateValue = if(format.immediate) it.third.toInt() else null
val addressValue = if(format.immediate) null else it.third.toInt()
it.first.instructions[it.second] = IRInstruction(
old.opcode,
old.type,
old.reg1,
old.reg2,
old.reg3,
old.fpReg1,
old.fpReg2,
immediate = immediateValue,
null,
address = addressValue,
null,
null
)
}
}
internal fun translateNode(node: PtNode): IRCodeChunks {
val chunks = when(node) {
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 PtAugmentedAssign -> assignmentGen.translate(node)
is PtNodeGroup -> translateGroup(node.children)
is PtBuiltinFunctionCall -> {
val result = translateBuiltinFunc(node)
result.chunks // it's not an expression so no result value.
}
is PtFunctionCall -> {
val result = expressionEval.translate(node)
result.chunks // it's not an expression so no result value
}
is PtNop -> emptyList()
is PtReturn -> translate(node)
is PtJump -> translate(node)
is PtWhen -> translate(node)
is PtForLoop -> translate(node)
is PtIfElse -> 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 PtBool,
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")
}
val nonEmptyChunks = chunks.filter { it.isNotEmpty() || it.label != null }
nonEmptyChunks.filterIsInstance<IRCodeChunk>().firstOrNull()?.appendSrcPosition(node.position)
return nonEmptyChunks
}
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) {
// special case the form: if_cc goto <place> (with optional else)
val address = goto.address?.toInt()
if(address!=null) {
val branchIns = when(branch.condition) {
BranchCondition.CS -> IRInstruction(Opcode.BSTCS, address = address)
BranchCondition.CC -> IRInstruction(Opcode.BSTCC, address = address)
BranchCondition.EQ, BranchCondition.Z -> IRInstruction(Opcode.BSTEQ, address = address)
BranchCondition.NE, BranchCondition.NZ -> IRInstruction(Opcode.BSTNE, address = address)
BranchCondition.MI, BranchCondition.NEG -> IRInstruction(Opcode.BSTNEG, address = address)
BranchCondition.PL, BranchCondition.POS -> IRInstruction(Opcode.BSTPOS, address = address)
BranchCondition.VC -> IRInstruction(Opcode.BSTVC, address = address)
BranchCondition.VS -> IRInstruction(Opcode.BSTVS, address = address)
}
addInstr(result, branchIns, null)
} else {
val label = 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)
}
if(branch.falseScope.children.isNotEmpty())
result += translateNode(branch.falseScope)
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 {
if(chunks.isEmpty()) {
return listOf(
IRCodeChunk(label, null)
)
}
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 {
val result = mutableListOf<IRCodeChunkBase>()
val valueDt = irType(whenStmt.value.type)
val valueTr = expressionEval.translateExpression(whenStmt.value)
addToResult(result, valueTr, valueTr.resultReg, -1)
val choices = mutableListOf<Pair<String, PtWhenChoice>>()
val endLabel = createLabelName()
whenStmt.choices.children.forEach {
val choice = it as PtWhenChoice
if(choice.isElse) {
result += translateNode(choice.statements)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
} else {
if(choice.statements.children.isEmpty()) {
// no statements for this choice value, jump to the end immediately
choice.values.children.map { it as PtNumber }.sortedBy { it.number }.forEach { value ->
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt())
it += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
}
}
} else {
val choiceLabel = createLabelName()
choices.add(choiceLabel to choice)
choice.values.children.map { it as PtNumber }.sortedBy { it.number }.forEach { value ->
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt())
it += IRInstruction(Opcode.BSTEQ, labelSymbol = choiceLabel)
}
}
}
}
}
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
choices.forEach { (label, choice) ->
result += labelFirstChunk(translateNode(choice.statements), label)
val lastStatement = choice.statements.children.last()
if(lastStatement !is PtReturn && lastStatement !is PtJump)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), 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 -> {
require(forLoop.variable.name == loopvar.scopedName)
val iterableLength = symbolTable.getLength(iterable.name)
val loopvarSymbol = forLoop.variable.name
val indexReg = registers.nextFree()
val tmpReg = registers.nextFree()
val loopLabel = createLabelName()
val endLabel = createLabelName()
when (iterable.type) {
DataType.STR -> {
// iterate over a zero-terminated string
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1 = tmpReg, reg2 = indexReg, labelSymbol = iterable.name)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tmpReg, labelSymbol = loopvarSymbol)
}
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)
}
in SplitWordArrayTypes -> {
// iterate over lsb/msb split word array
val elementDt = ArrayToElementTypes.getValue(iterable.type)
if(elementDt !in WordDatatypes)
throw AssemblyError("weird dt")
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
val tmpRegLsb = registers.nextFree()
val tmpRegMsb = registers.nextFree()
val concatReg = registers.nextFree()
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2=indexReg, labelSymbol=iterable.name+"_msb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegLsb, reg2=indexReg, labelSymbol=iterable.name+"_lsb")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=concatReg, reg2=tmpRegMsb, reg3=tmpRegLsb)
it += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=concatReg, labelSymbol = loopvarSymbol)
}
result += translateNode(forLoop.statements)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexReg)
if(iterableLength!=256) {
// for length 256, the compare is actually against 0, which doesn't require a separate CMP instruction
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=indexReg, immediate = iterableLength)
}
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
}
else -> {
// iterate over regular array
val elementDt = ArrayToElementTypes.getValue(iterable.type)
val elementSize = program.memsizer.memorySize(elementDt)
val lengthBytes = iterableLength!! * elementSize
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
it += IRInstruction(Opcode.LOADX, irType(elementDt), reg1=tmpReg, reg2=indexReg, labelSymbol=iterable.name)
it += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=tmpReg, labelSymbol = loopvarSymbol)
}
result += translateNode(forLoop.statements)
result += addConstReg(IRDataType.BYTE, indexReg, elementSize)
result += IRCodeChunk(null, null).also {
if(lengthBytes!=256) {
// for length 256, the compare is actually against 0, which doesn't require a separate CMP instruction
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=indexReg, immediate = lengthBytes)
}
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
}
}
}
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")
require(forLoop.variable.name == loopvar.scopedName)
val loopvarSymbol = forLoop.variable.name
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>()
val toTr = expressionEval.translateExpression(iterable.to)
addToResult(result, toTr, toTr.resultReg, -1)
val fromTr = expressionEval.translateExpression(iterable.from)
addToResult(result, fromTr, fromTr.resultReg, -1)
val labelAfterFor = createLabelName()
val precheckInstruction = if(loopvarDt in SignedDatatypes) {
if(step>0)
IRInstruction(Opcode.BGTSR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor)
else
IRInstruction(Opcode.BGTSR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor)
} else {
if(step>0)
IRInstruction(Opcode.BGTR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor)
else
IRInstruction(Opcode.BGTR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor)
}
addInstr(result, precheckInstruction, null)
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null)
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
if(step==1 || step==-1) {
// if endvalue == loopvar, stop loop, else iterate
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.CMP, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = labelAfterFor)
}
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = loopLabel), null)
} else {
// ind/dec index, then:
// ascending: if endvalue >= loopvar, iterate
// descending: if loopvar >= endvalue, iterate
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol = loopvarSymbol), null)
if(step > 0)
addInstr(result, IRInstruction(Opcode.CMP, loopvarDtIr, reg1 = toTr.resultReg, fromTr.resultReg), null)
else
addInstr(result, IRInstruction(Opcode.CMP, loopvarDtIr, reg1 = fromTr.resultReg, toTr.resultReg), null)
addInstr(result, IRInstruction(Opcode.BSTPOS, labelSymbol = loopLabel), null)
}
result += IRCodeChunk(labelAfterFor, null)
return result
}
private fun translateForInConstantRange(forLoop: PtForLoop, loopvar: StNode): IRCodeChunks {
val loopLabel = createLabelName()
require(forLoop.variable.name == loopvar.scopedName)
val loopvarSymbol = forLoop.variable.name
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).toConstantIntegerRange()!!
if(iterable.isEmpty())
throw AssemblyError("empty range")
if(iterable.step==0)
throw AssemblyError("step 0")
val rangeEndExclusiveUntyped = iterable.last + iterable.step
val rangeEndExclusiveWrapped = if(loopvarDtIr==IRDataType.BYTE) rangeEndExclusiveUntyped and 255 else rangeEndExclusiveUntyped and 65535
val result = mutableListOf<IRCodeChunkBase>()
val chunk = IRCodeChunk(null, null)
chunk += IRInstruction(Opcode.LOAD, loopvarDtIr, reg1=indexReg, immediate = iterable.first)
chunk += IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=indexReg, labelSymbol=loopvarSymbol)
result += chunk
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
val chunk2 = addConstMem(loopvarDtIr, null, loopvarSymbol, iterable.step)
chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol)
chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = rangeEndExclusiveWrapped)
chunk2 += IRInstruction(Opcode.BSTNE, 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, immediate = value)
} else {
IRInstruction(Opcode.SUB, dt, reg1 = reg, immediate = -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, address = knownAddress.toInt())
else
IRInstruction(Opcode.INCM, dt, labelSymbol = symbol)
}
2 -> {
if(knownAddress!=null) {
code += IRInstruction(Opcode.INCM, dt, address = knownAddress.toInt())
code += IRInstruction(Opcode.INCM, dt, address = 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, address = knownAddress.toInt())
else
IRInstruction(Opcode.DECM, dt, labelSymbol = symbol)
}
-2 -> {
if(knownAddress!=null) {
code += IRInstruction(Opcode.DECM, dt, address = knownAddress.toInt())
code += IRInstruction(Opcode.DECM, dt, address = 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, immediate = value)
code += if(knownAddress!=null)
IRInstruction(Opcode.ADDM, dt, reg1=valueReg, address = knownAddress.toInt())
else
IRInstruction(Opcode.ADDM, dt, reg1=valueReg, labelSymbol = symbol)
}
else {
code += IRInstruction(Opcode.LOAD, dt, reg1=valueReg, immediate = -value)
code += if(knownAddress!=null)
IRInstruction(Opcode.SUBM, dt, reg1=valueReg, address = knownAddress.toInt())
else
IRInstruction(Opcode.SUBM, dt, reg1=valueReg, labelSymbol = symbol)
}
}
}
return code
}
internal fun multiplyByConstFloat(fpReg: Int, factor: Double): IRCodeChunk {
val code = IRCodeChunk(null, null)
if(factor==1.0)
return code
code += if(factor==0.0) {
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = 0.0)
} else {
IRInstruction(Opcode.MUL, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = factor)
}
return code
}
internal fun multiplyByConstFloatInplace(knownAddress: Int?, symbol: String?, factor: Double): IRCodeChunk {
val code = IRCodeChunk(null, null)
if(factor==1.0)
return code
if(factor==0.0) {
code += if(knownAddress!=null)
IRInstruction(Opcode.STOREZM, IRDataType.FLOAT, address = knownAddress)
else
IRInstruction(Opcode.STOREZM, IRDataType.FLOAT, labelSymbol = symbol)
} else {
val factorReg = registers.nextFreeFloat()
code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1=factorReg, immediateFp = factor)
code += if(knownAddress!=null)
IRInstruction(Opcode.MULM, IRDataType.FLOAT, fpReg1 = factorReg, address = 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, immediate = pow2)
code += IRInstruction(Opcode.LSLN, dt, reg1=reg, reg2=pow2reg)
} else {
code += if (factor == 0) {
IRInstruction(Opcode.LOAD, dt, reg1=reg, immediate = 0)
} else {
IRInstruction(Opcode.MUL, dt, reg1=reg, immediate = 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, address = 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, immediate = pow2)
code += if(knownAddress!=null)
IRInstruction(Opcode.LSLNM, dt, reg1=pow2reg, address = knownAddress)
else
IRInstruction(Opcode.LSLNM, dt, reg1=pow2reg, labelSymbol = symbol)
} else {
if (factor == 0) {
code += if(knownAddress!=null)
IRInstruction(Opcode.STOREZM, dt, address = knownAddress)
else
IRInstruction(Opcode.STOREZM, dt, labelSymbol = symbol)
}
else {
val factorReg = registers.nextFree()
code += IRInstruction(Opcode.LOAD, dt, reg1=factorReg, immediate = factor)
code += if(knownAddress!=null)
IRInstruction(Opcode.MULM, dt, reg1=factorReg, address = knownAddress)
else
IRInstruction(Opcode.MULM, dt, reg1=factorReg, labelSymbol = symbol)
}
}
return code
}
internal fun divideByConstFloat(fpReg: Int, factor: Double): IRCodeChunk {
val code = IRCodeChunk(null, null)
if(factor==1.0)
return code
code += if(factor==0.0) {
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = Double.MAX_VALUE)
} else {
IRInstruction(Opcode.DIVS, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = factor)
}
return code
}
internal fun divideByConstFloatInplace(knownAddress: Int?, symbol: String?, factor: Double): IRCodeChunk {
val code = IRCodeChunk(null, null)
if(factor==1.0)
return code
if(factor==0.0) {
val maxvalueReg = registers.nextFreeFloat()
code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = maxvalueReg, immediateFp = Double.MAX_VALUE)
code += if(knownAddress!=null)
IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = maxvalueReg, address = knownAddress)
else
IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = maxvalueReg, labelSymbol = symbol)
} else {
val factorReg = registers.nextFreeFloat()
code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1=factorReg, immediateFp = factor)
code += if(knownAddress!=null)
IRInstruction(Opcode.DIVSM, IRDataType.FLOAT, fpReg1 = factorReg, address = 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, immediate = 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, immediate = 0xffff)
} else {
if(signed)
IRInstruction(Opcode.DIVS, dt, reg1=reg, immediate = factor)
else
IRInstruction(Opcode.DIV, dt, reg1=reg, immediate = 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, address = 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, immediate = pow2)
code += if(signed) {
if(knownAddress!=null)
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, address = knownAddress)
else
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, labelSymbol = symbol)
}
else {
if(knownAddress!=null)
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, address = 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, immediate = 0xffff)
code += if(knownAddress!=null)
IRInstruction(Opcode.STOREM, dt, reg1=reg, address = knownAddress)
else
IRInstruction(Opcode.STOREM, dt, reg1=reg, labelSymbol = symbol)
}
else {
val factorReg = registers.nextFree()
code += IRInstruction(Opcode.LOAD, dt, reg1=factorReg, immediate = factor)
code += if(signed) {
if(knownAddress!=null)
IRInstruction(Opcode.DIVSM, dt, reg1 = factorReg, address = knownAddress)
else
IRInstruction(Opcode.DIVSM, dt, reg1 = factorReg, labelSymbol = symbol)
}
else {
if(knownAddress!=null)
IRInstruction(Opcode.DIVM, dt, reg1 = factorReg, address = knownAddress)
else
IRInstruction(Opcode.DIVM, dt, reg1 = factorReg, labelSymbol = symbol)
}
}
}
return code
}
private fun translate(ifElse: PtIfElse): IRCodeChunks {
val goto = ifElse.ifScope.children.firstOrNull() as? PtJump
return if(goto!=null && ifElse.elseScope.children.isEmpty()) {
translateIfFollowedByJustGoto(ifElse, goto)
} else {
translateIfElse(ifElse)
}
}
private fun translateIfFollowedByJustGoto(ifElse: PtIfElse, goto: PtJump): MutableList<IRCodeChunkBase> {
val condition = ifElse.condition as? PtBinaryExpression
if(condition==null || condition.left.type!=DataType.FLOAT) {
return if(isIndirectJump(goto))
ifWithOnlyIndirectJump_IntegerCond(ifElse, goto)
else
ifWithOnlyNormalJump_IntegerCond(ifElse, goto)
}
// we assume only a binary expression can contain a floating point.
val result = mutableListOf<IRCodeChunkBase>()
val leftTr = expressionEval.translateExpression(condition.left)
addToResult(result, leftTr, -1, leftTr.resultFpReg)
val rightTr = expressionEval.translateExpression(condition.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
var afterIfLabel = ""
result += IRCodeChunk(null, null).also {
val compResultReg = registers.nextFree()
it += IRInstruction(
Opcode.FCOMP,
IRDataType.FLOAT,
reg1 = compResultReg,
fpReg1 = leftTr.resultFpReg,
fpReg2 = rightTr.resultFpReg
)
if(isIndirectJump(goto)) {
// indirect jump to target so the if has to jump past it instead
afterIfLabel = createLabelName()
when(condition.operator) {
"==" -> {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
it += IRInstruction(Opcode.BSTNE, labelSymbol = afterIfLabel)
}
"!=" -> {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = afterIfLabel)
}
else -> {
val gotoOpcode = when (condition.operator) {
"<" -> Opcode.BGES
">" -> Opcode.BLES
"<=" -> Opcode.BGTS
">=" -> Opcode.BLTS
else -> throw AssemblyError("weird operator")
}
it += IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = afterIfLabel)
}
}
it += IRInstruction(Opcode.JUMPI, labelSymbol = goto.identifier!!.name)
} else {
// normal jump, directly to target with branch opcode
when(condition.operator) {
"==" -> {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
it += branchInstr(goto, Opcode.BSTEQ)
}
"!=" -> {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
it += branchInstr(goto, Opcode.BSTNE)
}
else -> {
val gotoOpcode = when (condition.operator) {
"<" -> Opcode.BLTS
">" -> Opcode.BGTS
"<=" -> Opcode.BLES
">=" -> Opcode.BGES
else -> throw AssemblyError("weird operator")
}
it += if (goto.address != null)
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, address = goto.address?.toInt())
else
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = goto.identifier!!.name)
}
}
}
}
if(afterIfLabel.isNotEmpty())
result += IRCodeChunk(afterIfLabel, null)
return result
}
private fun branchInstr(goto: PtJump, branchOpcode: Opcode): IRInstruction {
return if (goto.address != null)
IRInstruction(branchOpcode, address = goto.address?.toInt())
else {
require(!isIndirectJump(goto)) { "indirect jumps cannot be expressed using a branch opcode"}
IRInstruction(branchOpcode, labelSymbol = goto.identifier!!.name)
}
}
private fun ifWithOnlyIndirectJump_IntegerCond(ifElse: PtIfElse, goto: PtJump): MutableList<IRCodeChunkBase> {
// indirect jump to target so the if has to jump past it instead
val result = mutableListOf<IRCodeChunkBase>()
val afterIfLabel = createLabelName()
val gotoSymbol = goto.identifier!!.name
fun ifNonZeroIntThenJump_BinExpr(condition: PtBinaryExpression) {
if(condition.operator in LogicalOperators) {
val trCond = expressionEval.translateExpression(condition)
result += trCond.chunks
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = afterIfLabel), null)
return
}
val leftTr = expressionEval.translateExpression(condition.left)
val irDt = leftTr.dt
val signed = condition.left.type in SignedDatatypes
addToResult(result, leftTr, leftTr.resultReg, -1)
val number = (condition.right as? PtNumber)?.number?.toInt()
if(number!=null) {
val firstReg = leftTr.resultReg
when(condition.operator) {
"==" -> {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, irDt, reg1 = firstReg, immediate = number)
it += IRInstruction(Opcode.BSTNE, labelSymbol = afterIfLabel)
}
}
"!=" -> {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, irDt, reg1 = firstReg, immediate = number)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = afterIfLabel)
}
}
else -> {
val opcode = when (condition.operator) {
"<" -> if(signed) Opcode.BGES else Opcode.BGE
">" -> if(signed) Opcode.BLES else Opcode.BLE
"<=" -> if(signed) Opcode.BGTS else Opcode.BGT
">=" -> if(signed) Opcode.BLTS else Opcode.BLT
else -> throw AssemblyError("invalid comparison operator")
}
addInstr(result, IRInstruction(opcode, irDt, reg1 = firstReg, immediate = number, labelSymbol = afterIfLabel), null)
}
}
} else {
val rightTr = expressionEval.translateExpression(condition.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
val firstReg: Int
val secondReg: Int
val opcode: Opcode
var useCmp = false
when (condition.operator) {
"==" -> {
useCmp = true
opcode = Opcode.BSTNE
firstReg = leftTr.resultReg
secondReg = rightTr.resultReg
}
"!=" -> {
useCmp = true
opcode = Opcode.BSTEQ
firstReg = leftTr.resultReg
secondReg = rightTr.resultReg
}
"<" -> {
opcode = if (signed) Opcode.BGESR else Opcode.BGER
firstReg = leftTr.resultReg
secondReg = rightTr.resultReg
}
">" -> {
// swapped operands
opcode = if (signed) Opcode.BGESR else Opcode.BGER
firstReg = rightTr.resultReg
secondReg = leftTr.resultReg
}
"<=" -> {
opcode = if (signed) Opcode.BGTSR else Opcode.BGTR
firstReg = leftTr.resultReg
secondReg = rightTr.resultReg
}
">=" -> {
// swapped operands
opcode = if (signed) Opcode.BGTSR else Opcode.BGTR
firstReg = rightTr.resultReg
secondReg = leftTr.resultReg
}
else -> throw AssemblyError("invalid comparison operator")
}
if(useCmp) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMP, irDt, reg1 = firstReg, reg2 = secondReg)
it += IRInstruction(opcode, labelSymbol = afterIfLabel)
}
} else {
addInstr(result, IRInstruction(opcode, irDt, reg1 = firstReg, reg2 = secondReg, labelSymbol = afterIfLabel), null)
}
}
}
when(val cond = ifElse.condition) {
is PtTypeCast -> {
require(cond.type==DataType.BOOL && cond.value.type in NumericDatatypes)
val tr = expressionEval.translateExpression(cond)
result += tr.chunks
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = afterIfLabel), null)
}
is PtIdentifier, is PtArrayIndexer, is PtBuiltinFunctionCall, is PtFunctionCall, is PtContainmentCheck -> {
val tr = expressionEval.translateExpression(cond)
result += tr.chunks
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = afterIfLabel), null)
}
is PtPrefix -> {
require(cond.operator=="not")
val tr = expressionEval.translateExpression(cond.value)
result += tr.chunks
addInstr(result, IRInstruction(Opcode.BSTNE, labelSymbol = afterIfLabel), null)
}
is PtBinaryExpression -> {
ifNonZeroIntThenJump_BinExpr(cond)
}
else -> throw AssemblyError("weird if condition ${ifElse.condition}")
}
addInstr(result, IRInstruction(Opcode.JUMPI, labelSymbol = gotoSymbol), null)
result += IRCodeChunk(afterIfLabel, null)
return result
}
private fun ifWithOnlyNormalJump_IntegerCond(ifElse: PtIfElse, goto: PtJump): MutableList<IRCodeChunkBase> {
// normal goto after if, using branch instructions
val result = mutableListOf<IRCodeChunkBase>()
fun ifNonZeroIntThenJump_BinExpr(condition: PtBinaryExpression) {
if(condition.operator in LogicalOperators) {
val trCond = expressionEval.translateExpression(condition)
result += trCond.chunks
addInstr(result, branchInstr(goto, Opcode.BSTNE), null)
return
}
val leftTr = expressionEval.translateExpression(condition.left)
val irDt = leftTr.dt
val signed = condition.left.type in SignedDatatypes
addToResult(result, leftTr, leftTr.resultReg, -1)
val number = (condition.right as? PtNumber)?.number?.toInt()
if(number!=null) {
val firstReg = leftTr.resultReg
when(condition.operator) {
"==" -> {
addInstr(result, IRInstruction(Opcode.CMPI, irDt, reg1 = firstReg, immediate = number), null)
addInstr(result, branchInstr(goto, Opcode.BSTEQ), null)
}
"!=" -> {
addInstr(result, IRInstruction(Opcode.CMPI, irDt, reg1 = firstReg, immediate = number), null)
addInstr(result, branchInstr(goto, Opcode.BSTNE), null)
}
else -> {
val opcode = when (condition.operator) {
"<" -> if(signed) Opcode.BLTS else Opcode.BLT
">" -> if(signed) Opcode.BGTS else Opcode.BGT
"<=" -> if(signed) Opcode.BLES else Opcode.BLE
">=" -> if(signed) Opcode.BGES else Opcode.BGE
else -> throw AssemblyError("invalid comparison operator")
}
if (goto.address != null)
addInstr(result, IRInstruction(opcode, irDt, reg1 = firstReg, immediate = number, address = goto.address?.toInt()), null)
else
addInstr(result, IRInstruction(opcode, irDt, reg1 = firstReg, immediate = number, labelSymbol = goto.identifier!!.name), null)
}
}
} else {
val rightTr = expressionEval.translateExpression(condition.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
val firstReg: Int
val secondReg: Int
val opcode: Opcode
var useCmp = false
when (condition.operator) {
"==" -> {
useCmp = true
opcode = Opcode.BSTEQ
firstReg = leftTr.resultReg
secondReg = rightTr.resultReg
}
"!=" -> {
useCmp = true
opcode = Opcode.BSTNE
firstReg = leftTr.resultReg
secondReg = rightTr.resultReg
}
"<" -> {
// swapped '>'
opcode = if (signed) Opcode.BGTSR else Opcode.BGTR
firstReg = rightTr.resultReg
secondReg = leftTr.resultReg
}
">" -> {
opcode = if (signed) Opcode.BGTSR else Opcode.BGTR
firstReg = leftTr.resultReg
secondReg = rightTr.resultReg
}
"<=" -> {
// swapped '>='
opcode = if (signed) Opcode.BGESR else Opcode.BGER
firstReg = rightTr.resultReg
secondReg = leftTr.resultReg
}
">=" -> {
opcode = if (signed) Opcode.BGESR else Opcode.BGER
firstReg = leftTr.resultReg
secondReg = rightTr.resultReg
}
else -> throw AssemblyError("invalid comparison operator")
}
if(useCmp) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMP, irDt, reg1 = firstReg, reg2 = secondReg)
it += branchInstr(goto, opcode)
}
} else {
if (goto.address != null)
addInstr(result, IRInstruction(opcode, irDt, reg1 = firstReg, reg2 = secondReg, address = goto.address?.toInt()), null)
else
addInstr(result, IRInstruction(opcode, irDt, reg1 = firstReg, reg2 = secondReg, labelSymbol = goto.identifier!!.name), null)
}
}
}
when(val cond = ifElse.condition) {
is PtTypeCast -> {
require(cond.type==DataType.BOOL && cond.value.type in NumericDatatypes)
val tr = expressionEval.translateExpression(cond)
result += tr.chunks
addInstr(result, branchInstr(goto, Opcode.BSTNE), null)
}
is PtIdentifier, is PtArrayIndexer, is PtBuiltinFunctionCall, is PtFunctionCall, is PtContainmentCheck -> {
val tr = expressionEval.translateExpression(cond)
result += tr.chunks
addInstr(result, branchInstr(goto, Opcode.BSTNE), null)
}
is PtPrefix -> {
require(cond.operator=="not")
val tr = expressionEval.translateExpression(cond.value)
result += tr.chunks
addInstr(result, branchInstr(goto, Opcode.BSTEQ), null)
}
is PtBinaryExpression -> ifNonZeroIntThenJump_BinExpr(cond)
else -> throw AssemblyError("weird if condition ${ifElse.condition}")
}
return result
}
private fun translateIfElse(ifElse: PtIfElse): IRCodeChunks {
val condition = ifElse.condition as? PtBinaryExpression
if(condition==null || condition.left.type != DataType.FLOAT) {
return ifWithElse_IntegerCond(ifElse)
}
// we assume only a binary expression can contain a floating point.
val result = mutableListOf<IRCodeChunkBase>()
val leftTr = expressionEval.translateExpression(condition.left)
addToResult(result, leftTr, -1, leftTr.resultFpReg)
val rightTr = expressionEval.translateExpression(condition.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
val compResultReg = registers.nextFree()
addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1 = compResultReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
val elseBranch: Opcode
var useCmpi = false // for the branch opcodes that have been converted to CMPI + BSTxx form already
when (condition.operator) {
"==" -> {
elseBranch = Opcode.BSTNE
useCmpi = true
}
"!=" -> {
elseBranch = Opcode.BSTEQ
useCmpi = true
}
"<" -> elseBranch = Opcode.BGES
">" -> elseBranch = Opcode.BLES
"<=" -> elseBranch = Opcode.BGTS
">=" -> elseBranch = Opcode.BLTS
else -> throw AssemblyError("weird operator")
}
if (ifElse.hasElse()) {
// if and else parts
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
it += IRInstruction(elseBranch, labelSymbol = elseLabel)
}
} else
addInstr(result, IRInstruction(elseBranch, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, 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()
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
it += IRInstruction(elseBranch, labelSymbol = afterIfLabel)
}
} else
addInstr(result, IRInstruction(elseBranch, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = afterIfLabel), null)
result += translateNode(ifElse.ifScope)
result += IRCodeChunk(afterIfLabel, null)
}
return result
}
private fun ifWithElse_IntegerCond(ifElse: PtIfElse): List<IRCodeChunkBase> {
val result = mutableListOf<IRCodeChunkBase>()
fun translateSimple(condition: PtExpression, jumpFalseOpcode: Opcode) {
val tr = expressionEval.translateExpression(condition)
result += tr.chunks
if(ifElse.hasElse()) {
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
addInstr(result, IRInstruction(jumpFalseOpcode, 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 {
val afterIfLabel = createLabelName()
addInstr(result, IRInstruction(jumpFalseOpcode, labelSymbol = afterIfLabel), null)
result += translateNode(ifElse.ifScope)
result += IRCodeChunk(afterIfLabel, null)
}
}
fun translateBinExpr(condition: PtBinaryExpression) {
if(condition.operator in LogicalOperators)
return translateSimple(condition, Opcode.BSTEQ)
val signed = condition.left.type in SignedDatatypes
val elseBranchFirstReg: Int
val elseBranchSecondReg: Int
val number = (condition.right as? PtNumber)?.number?.toInt()
val leftTr = expressionEval.translateExpression(condition.left)
val branchDt = leftTr.dt
addToResult(result, leftTr, leftTr.resultReg, -1)
if (number!=null) {
val elseBranch: Opcode
var useCmpi = false // for the branch opcodes that have been converted to CMPI + BSTxx form already
when (condition.operator) {
"==" -> {
elseBranch = Opcode.BSTNE
useCmpi = true
}
"!=" -> {
elseBranch = Opcode.BSTEQ
useCmpi = true
}
"<" -> elseBranch = if(signed) Opcode.BGES else Opcode.BGE
">" -> elseBranch = if(signed) Opcode.BLES else Opcode.BLE
"<=" -> elseBranch = if(signed) Opcode.BGTS else Opcode.BGT
">=" -> elseBranch = if(signed) Opcode.BLTS else Opcode.BLT
else -> throw AssemblyError("invalid comparison operator")
}
if (ifElse.hasElse()) {
// if and else parts
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, branchDt, reg1 = leftTr.resultReg, immediate = number)
it += IRInstruction(elseBranch, labelSymbol = elseLabel)
}
} else {
addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = leftTr.resultReg, immediate = number, 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()
if(useCmpi) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, branchDt, reg1 = leftTr.resultReg, immediate = number)
it += IRInstruction(elseBranch, labelSymbol = afterIfLabel)
}
} else
addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = leftTr.resultReg, immediate = number, labelSymbol = afterIfLabel), null)
result += translateNode(ifElse.ifScope)
result += IRCodeChunk(afterIfLabel, null)
}
} else {
val rightTr = expressionEval.translateExpression(condition.right)
val elseBranch: Opcode
var useCmp = false
addToResult(result, rightTr, rightTr.resultReg, -1)
when (condition.operator) {
"==" -> {
useCmp = true
elseBranch = Opcode.BSTNE
elseBranchFirstReg = leftTr.resultReg
elseBranchSecondReg = rightTr.resultReg
}
"!=" -> {
useCmp = true
elseBranch = Opcode.BSTEQ
elseBranchFirstReg = leftTr.resultReg
elseBranchSecondReg = rightTr.resultReg
}
"<" -> {
// else part when left >= right
elseBranch = if (signed) Opcode.BGESR else Opcode.BGER
elseBranchFirstReg = leftTr.resultReg
elseBranchSecondReg = rightTr.resultReg
}
">" -> {
// else part when left <= right --> right >= left
elseBranch = if (signed) Opcode.BGESR else Opcode.BGER
elseBranchFirstReg = rightTr.resultReg
elseBranchSecondReg = leftTr.resultReg
}
"<=" -> {
// else part when left > right
elseBranch = if (signed) Opcode.BGTSR else Opcode.BGTR
elseBranchFirstReg = leftTr.resultReg
elseBranchSecondReg = rightTr.resultReg
}
">=" -> {
// else part when left < right --> right > left
elseBranch = if (signed) Opcode.BGTSR else Opcode.BGTR
elseBranchFirstReg = rightTr.resultReg
elseBranchSecondReg = leftTr.resultReg
}
else -> throw AssemblyError("invalid comparison operator")
}
if (ifElse.hasElse()) {
// if and else parts
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
if(useCmp) {
result += IRCodeChunk(null,null).also {
it += IRInstruction(Opcode.CMP, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg)
it += IRInstruction(elseBranch, labelSymbol = elseLabel)
}
} else {
addInstr(result, IRInstruction(elseBranch, branchDt, 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()
if(useCmp) {
result += IRCodeChunk(null,null).also {
it += IRInstruction(Opcode.CMP, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg)
it += IRInstruction(elseBranch, labelSymbol = afterIfLabel)
}
} else {
addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg, labelSymbol = afterIfLabel), null)
}
result += translateNode(ifElse.ifScope)
result += IRCodeChunk(afterIfLabel, null)
}
}
}
when(val cond=ifElse.condition) {
is PtTypeCast -> {
require(cond.type==DataType.BOOL && cond.value.type in NumericDatatypes)
translateSimple(cond, Opcode.BSTEQ)
}
is PtIdentifier, is PtArrayIndexer, is PtBuiltinFunctionCall, is PtFunctionCall, is PtContainmentCheck -> {
translateSimple(cond, Opcode.BSTEQ)
}
is PtPrefix -> {
require(cond.operator=="not")
translateSimple(cond.value, Opcode.BSTNE)
}
is PtBinaryExpression -> {
translateBinExpr(cond)
}
else -> throw AssemblyError("weird if condition ${ifElse.condition}")
}
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 irDt = irType(repeat.count.type)
val result = mutableListOf<IRCodeChunkBase>()
val countTr = expressionEval.translateExpression(repeat.count)
addToResult(result, countTr, countTr.resultReg, -1)
if(constIntValue(repeat.count)==null) {
// check if the counter is already zero
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = skipRepeatLabel), null)
}
result += labelFirstChunk(translateNode(repeat.statements), repeatLabel)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.DEC, irDt, reg1 = countTr.resultReg) // sets status bits
it += IRInstruction(Opcode.BSTNE, labelSymbol = repeatLabel)
}
result += IRCodeChunk(skipRepeatLabel, null)
return result
}
private fun translate(jump: PtJump): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val chunk = IRCodeChunk(null, null)
if(jump.address!=null) {
chunk += IRInstruction(Opcode.JUMP, address = jump.address!!.toInt())
} else {
if (jump.identifier != null) {
if(isIndirectJump(jump)) {
chunk += IRInstruction(Opcode.JUMPI, labelSymbol = jump.identifier!!.name)
} else {
chunk += IRInstruction(Opcode.JUMP, labelSymbol = jump.identifier!!.name)
}
}
else
throw AssemblyError("weird jump")
}
result += chunk
return result
}
private fun isIndirectJump(jump: PtJump): Boolean {
if(jump.identifier==null)
return false
val symbol = symbolTable.lookup(jump.identifier!!.name)
return symbol?.type==StNodeType.MEMVAR || symbol?.type==StNodeType.STATICVAR
}
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) {
addInstr(result, IRInstruction(Opcode.RETURN), null)
} else {
if(value.type==DataType.FLOAT) {
val tr = expressionEval.translateExpression(value)
addToResult(result, tr, -1, tr.resultFpReg)
addInstr(result, IRInstruction(Opcode.RETURNR, IRDataType.FLOAT, fpReg1 = tr.resultFpReg), null)
}
else {
val tr = expressionEval.translateExpression(value)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.RETURNR, irType(value.type) , reg1=tr.resultReg), null)
}
}
return result
}
private fun translate(block: PtBlock): IRBlock {
val irBlock = IRBlock(block.name, block.library,
IRBlock.Options(
block.options.address,
block.options.forceOutput,
block.options.noSymbolPrefixing,
block.options.veraFxMuls,
block.options.ignoreUnused,
translate(block.options.alignment)
), block.position)
for (child in block.children) {
when(child) {
is PtNop -> { /* nothing */ }
is PtAssignment, is PtAugmentedAssign -> { /* global variable initialization is done elsewhere */ }
is PtVariable, is PtConstant, is PtMemMapped -> { /* 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
if(child.children.map { (it as PtInlineAssembly).isIR }.toSet().size>1)
errors.err("asmsub mixes IR and non-IR assembly code (could be compiler-generated)", child.position)
val asmblocks = child.children.map { (it as PtInlineAssembly).assembly.trimEnd() }
val assembly = asmblocks.joinToString("\n")
val asmChunk = IRInlineAsmChunk(
child.name, assembly, (child.children[0] as PtInlineAssembly).isIR , null
)
irBlock += IRAsmSubroutine(
child.name,
child.address,
child.clobbers,
child.parameters.map { IRAsmSubroutine.IRAsmParam(it.first, it.second.type) }, // note: the name of the asmsub param is not used here anymore
child.returns.map { IRAsmSubroutine.IRAsmParam(it.first, it.second)},
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 block child node $child")
}
}
return irBlock
}
private fun translate(parameters: List<PtSubroutineParameter>) =
parameters.map {
val flattenedName = it.definingSub()!!.name + "." + it.name
if(symbolTable.lookup(flattenedName)==null)
TODO("fix missing lookup for: $flattenedName parameter")
val orig = symbolTable.lookup(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
}
}
private var labelSequenceNumber = 0
internal fun createLabelName(): String {
labelSequenceNumber++
return "label_gen_$labelSequenceNumber"
}
internal fun translateBuiltinFunc(call: PtBuiltinFunctionCall): ExpressionCodeResult
= builtinFuncGen.translate(call)
internal fun isZero(expression: PtExpression): Boolean = (expression as? PtNumber)?.number==0.0 || (expression as? PtBool)?.value==false
internal fun isOne(expression: PtExpression): Boolean = (expression as? PtNumber)?.number==1.0 || (expression as? PtBool)?.value==true
fun makeSyscall(syscall: IMSyscall, params: List<Pair<IRDataType, Int>>, returns: Pair<IRDataType, Int>?, label: String?=null): IRCodeChunk {
return IRCodeChunk(label, null).also {
val args = params.map { (dt, reg)->
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(dt, reg, null))
}
// for now, syscalls have 0 or 1 return value
val returnSpec = if(returns==null) emptyList() else listOf(FunctionCallArgs.RegSpec(returns.first, returns.second, null))
it += IRInstruction(Opcode.SYSCALL, immediate = syscall.number, fcallArgs = FunctionCallArgs(args, returnSpec))
}
}
}