making IR file reader

This commit is contained in:
Irmen de Jong 2022-08-28 16:43:15 +02:00
parent 12ed07a607
commit 3c315703c0
12 changed files with 905 additions and 113 deletions

View File

@ -221,7 +221,7 @@ class StMemorySlab(
name: String,
val size: UInt,
val align: UInt,
val allocatedAddress: UInt? = null, // this is used (for now) in the VM code generator
val allocatedAddress: UInt? = null, // this is used (for now) in the VM code generator. TODO remove this once no longer used
position: Position
):
StNode(name, StNodeType.MEMORYSLAB, position) {

View File

@ -0,0 +1,103 @@
package prog8.codegen.experimental
import prog8.code.core.AssemblyError
import prog8.code.core.CompilationOptions
import prog8.code.core.IAssemblyProgram
import prog8.intermediate.*
import java.io.BufferedWriter
import kotlin.io.path.bufferedWriter
import kotlin.io.path.div
class AssemblyProgram(override val name: String, val irProgram: IRProgram): IAssemblyProgram {
// TODO once this is working, replace the codeGenVirtual by codeGenExperimental
// after that,
override fun assemble(options: CompilationOptions): Boolean {
val outfile = options.outputDir / ("$name.p8virt")
println("write code to $outfile")
// at last, allocate the variables in memory.
val allocations = VariableAllocator(irProgram.st, irProgram.encoding, irProgram.options.compTarget)
outfile.bufferedWriter().use { out ->
allocations.asVmMemory().forEach { (name, alloc) ->
out.write("; ${name.joinToString(".")}\n")
out.write(alloc + "\n")
}
// with(irProgram.st) {
// allVariables.forEach {
// //TODO("var $it")
// }
// allMemMappedVariables.forEach {
// println("MAPPED ${it.name} ${it.address}")
// // TODO implement proper memory mapped variable in VM - for now put them as regular variable to get it to compile
//
// }
// allMemorySlabs.forEach {
// TODO("implement proper memory slab allocation in VM")
// }
// }
out.write("------PROGRAM------\n")
if(!options.dontReinitGlobals) {
out.write("; global var inits\n")
irProgram.globalInits.forEach { out.writeLine(it) }
}
irProgram.blocks.firstOrNull()?.let {
if(it.subroutines.any { it.name=="main.start" }) {
// there is a "main.start" entrypoint, jump to it
out.writeLine(IRCodeInstruction(Opcode.JUMP, labelSymbol = listOf("main.start")))
}
}
out.write("; actual program code\n")
irProgram.blocks.forEach { block ->
if(block.address!=null)
TODO("blocks can't have a load address for vm")
out.write("; BLOCK ${block.name} ${block.position}\n")
block.subroutines.forEach { sub ->
out.write("; SUB ${sub.name} ${sub.position}\n")
out.write("_${sub.name}:\n")
sub.chunks.forEach { chunk ->
chunk.lines.forEach { out.writeLine(it) }
}
out.write("; END SUB ${sub.name}\n")
}
block.asmSubroutines.forEach { sub ->
out.write("; ASMSUB ${sub.name} ${sub.position}\n")
out.write("_${sub.name}:\n")
out.write(sub.assembly)
out.write("\n; END ASMSUB ${sub.name}\n")
}
out.write("; END BLOCK ${block.name}\n")
}
}
return true
}
}
private fun BufferedWriter.writeLine(line: IRCodeLine) {
when(line) {
is IRCodeComment -> {
write("; ${line.comment}\n")
}
is IRCodeInstruction -> {
write(line.ins.toString() + "\n")
}
is IRCodeInlineBinary -> {
write("incbin \"${line.file}\"")
if(line.offset!=null)
write(",${line.offset}")
if(line.length!=null)
write(",${line.length}")
write("\n")
}
is IRCodeLabel -> {
write("_" + line.name.joinToString(".") + ":\n")
}
else -> throw AssemblyError("invalid IR code line")
}
}

View File

@ -40,14 +40,13 @@ class CodeGen(internal val program: PtProgram,
internal val errors: IErrorReporter
): IAssemblyGenerator {
internal val allocations = VariableAllocator(symbolTable, program.memsizer)
internal val allocations = VariableAllocator(symbolTable, program.encoding, program.memsizer)
private val expressionEval = ExpressionGen(this)
private val builtinFuncGen = BuiltinFuncGen(this, expressionEval)
private val assignmentGen = AssignmentGen(this, expressionEval)
internal val vmRegisters = VmRegisterPool()
override fun compileToAssembly(): IAssemblyProgram? {
flattenNestedSubroutines()
val irProg = IRProgram(program.name, symbolTable, options, program.encoding)
@ -66,8 +65,6 @@ class CodeGen(internal val program: PtProgram,
if(options.evalStackBaseAddress!=null)
throw AssemblyError("virtual target doesn't use eval-stack")
// TODO flatten nested subroutines
for (block in program.allBlocks()) {
irProg.addBlock(translate(block))
}
@ -77,18 +74,24 @@ class CodeGen(internal val program: PtProgram,
optimizer.optimize()
}
println("IR codegen: virtual registers=${vmRegisters.peekNext()}")
// create IR file on disk and read it back.
// TODO: this makes sure those I/O routines are correct, but this step should be skipped eventually.
IRFileWriter(irProg).writeFile()
return DummyAssemblyProgram(irProg.name)
val irProgFromDisk = IRFileReader(options.outputDir, irProg.name).readFile()
return AssemblyProgram(irProgFromDisk.name, irProgFromDisk)
}
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.
// also moves the start() entrypoint as first subroutine.
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>>()
val entrypoint = program.entrypoint()
fun flattenNestedAsm(block: PtBlock, parentSub: PtSub, asmsub: PtAsmSub) {
val flattened = PtAsmSub(asmsub.scopedName.joinToString("."),
@ -123,7 +126,10 @@ class CodeGen(internal val program: PtProgram,
// Only regular subroutines can have nested subroutines.
it.children.filterIsInstance<PtSub>().forEach { subsub->flattenNested(block, it, subsub)}
it.children.filterIsInstance<PtAsmSub>().forEach { asmsubsub->flattenNestedAsm(block, it, asmsubsub)}
renameSubs += Pair(block, it)
}
if(it is PtAsmSub)
renameAsmSubs += Pair(block, it)
}
}
@ -131,9 +137,32 @@ class CodeGen(internal val program: PtProgram,
removalsAsmSubs.forEach { (parent, asmsub) -> parent.children.remove(asmsub) }
flattenedSubs.forEach { (block, sub) -> block.add(sub) }
flattenedAsmSubs.forEach { (block, asmsub) -> block.add(asmsub) }
renameSubs.forEach { (parent, sub) ->
val renamedSub = PtSub(sub.scopedName.joinToString("."), sub.parameters, sub.returntype, sub.inline, sub.position)
sub.children.forEach { renamedSub.add(it) }
parent.children.remove(sub)
if (sub === entrypoint) {
// entrypoint sub must be first sub
val firstsub = parent.children.withIndex().first { it.value is PtSub || it.value is PtAsmSub }
parent.add(firstsub.index, renamedSub)
} else {
parent.add(renamedSub)
}
}
renameAsmSubs.forEach { (parent, sub) ->
val renamedSub = PtAsmSub(sub.scopedName.joinToString("."),
sub.address,
sub.clobbers,
sub.parameters,
sub.returnTypes,
sub.retvalRegisters,
sub.inline,
sub.position)
parent.children.remove(sub)
parent.add(renamedSub)
}
}
internal fun translateNode(node: PtNode): IRCodeChunk {
val code = when(node) {
is PtScopeVarsDecls -> IRCodeChunk(node.position) // vars should be looked up via symbol table
@ -824,14 +853,16 @@ class CodeGen(internal val program: PtProgram,
is PtScopeVarsDecls -> { /* vars should be looked up via symbol table */ }
is PtSub -> {
val vmsub = IRSubroutine(child.name, child.returntype, child.position)
for (line in child.children) {
vmsub += translateNode(line)
for (subchild in child.children) {
val translated = translateNode(subchild)
if(translated.isNotEmpty())
vmsub += translated
}
vmblock += vmsub
}
is PtAsmSub -> {
val assembly = if(child.children.isEmpty()) "" else (child.children.single() as PtInlineAssembly).assembly
vmblock += IRAsmSubroutine(child.scopedName, child.position, child.address, assembly)
vmblock += IRAsmSubroutine(child.name, child.position, child.address, assembly)
}
is PtInlineAssembly -> {
vmblock += IRInlineAsmChunk(child.assembly, child.position)

View File

@ -1,12 +0,0 @@
package prog8.codegen.experimental
import prog8.code.core.CompilationOptions
import prog8.code.core.IAssemblyProgram
class DummyAssemblyProgram(override val name: String): IAssemblyProgram {
override fun assemble(options: CompilationOptions): Boolean {
println("TODO WRITE ASSEMBLY")
return false
}
}

View File

@ -3,7 +3,7 @@ package prog8.codegen.experimental
import prog8.code.SymbolTable
import prog8.code.core.*
class VariableAllocator(st: SymbolTable, memsizer: IMemSizer) {
class VariableAllocator(val st: SymbolTable, val encoding: IStringEncoding, memsizer: IMemSizer) {
internal val allocations = mutableMapOf<List<String>, Int>()
private var freeMemoryStart: Int
@ -43,4 +43,64 @@ class VariableAllocator(st: SymbolTable, memsizer: IMemSizer) {
}
fun get(name: List<String>) = allocations.getValue(name)
fun asVmMemory(): List<Pair<List<String>, String>> {
val mm = mutableListOf<Pair<List<String>, String>>()
for (variable in st.allVariables) {
val location = allocations.getValue(variable.scopedName)
val typeStr = when(variable.dt) {
DataType.UBYTE, DataType.ARRAY_UB, DataType.STR -> "ubyte"
DataType.BYTE, DataType.ARRAY_B -> "byte"
DataType.UWORD, DataType.ARRAY_UW -> "uword"
DataType.WORD, DataType.ARRAY_W -> "word"
DataType.FLOAT, DataType.ARRAY_F -> "float"
else -> throw InternalCompilerException("weird dt")
}
val value = when(variable.dt) {
DataType.FLOAT -> (variable.onetimeInitializationNumericValue ?: 0.0).toString()
in NumericDatatypes -> (variable.onetimeInitializationNumericValue ?: 0).toHex()
DataType.STR -> {
val encoded = encoding.encodeString(variable.onetimeInitializationStringValue!!.first, variable.onetimeInitializationStringValue!!.second) + listOf(0u)
encoded.joinToString(",") { it.toInt().toHex() }
}
DataType.ARRAY_F -> {
if(variable.onetimeInitializationArrayValue!=null) {
variable.onetimeInitializationArrayValue!!.joinToString(",") { it.number!!.toString() }
} else {
(1..variable.length!!).joinToString(",") { "0" }
}
}
in ArrayDatatypes -> {
if(variable.onetimeInitializationArrayValue!==null) {
variable.onetimeInitializationArrayValue!!.joinToString(",") { it.number!!.toHex() }
} else {
(1..variable.length!!).joinToString(",") { "0" }
}
}
else -> throw InternalCompilerException("weird dt")
}
mm.add(Pair(variable.scopedName, "$location $typeStr $value"))
}
for (variable in st.allMemMappedVariables) {
val location = allocations.getValue(variable.scopedName)
val typeStr = when(variable.dt) {
DataType.UBYTE, DataType.ARRAY_UB, DataType.STR -> "ubyte"
DataType.BYTE, DataType.ARRAY_B -> "byte"
DataType.UWORD, DataType.ARRAY_UW -> "uword"
DataType.WORD, DataType.ARRAY_W -> "word"
DataType.FLOAT, DataType.ARRAY_F -> "float"
else -> throw InternalCompilerException("weird dt")
}
val value = when(variable.dt) {
DataType.FLOAT -> "0.0"
in NumericDatatypes -> "0"
DataType.ARRAY_F -> (1..variable.length!!).joinToString(",") { "0.0" }
in ArrayDatatypes -> (1..variable.length!!).joinToString(",") { "0" }
else -> throw InternalCompilerException("weird dt for mem mapped var")
}
mm.add(Pair(variable.scopedName, "$location $typeStr $value"))
}
return mm
}
}

View File

@ -72,7 +72,7 @@ class CodeGen(internal val program: PtProgram,
optimizer.optimize()
}
println("Vm codegen: virtual registers=${vmRegisters.peekNext()} memory usage=${allocations.freeMem}")
println("Vm codegen: memory usage=${allocations.freeMem}")
return vmprog
}

View File

@ -1,19 +1,46 @@
%zpreserved 10,20
%zpreserved 30,40
main {
uword global1 = 1234
%asm {{
nop
nop
return
}}
asmsub testasmsub(ubyte arg1 @A) clobbers(Y) -> uword @AX {
%asm {{
nop
return
}}
}
sub start() {
sys.wait(1)
%asm {{
nop
}}
; TODO should generate address
uword @shared slab1 = memory("slab 1", 2000, 0)
uword @shared slab2 = memory("slab 1", 2000, 0)
uword @shared slab3 = memory("other # slab", 2000, 64)
&uword mapped = $c000
&ubyte[20] mappedarray = $c100
uword @shared zz = slab1+slab2+slab3
uword @shared qq = zz
uword @shared qq2 = &zz
uword @shared @zp qq = zz
uword @shared @zp qq2 = &zz
str @shared namestring = "irmen"
uword[] @shared wordarray1 = [1111,2222,3333,4444]
uword[4] @shared wordarray2 = 12345
uword[4] @shared wordzeroarray
qq=4242 ; TODO should generate symbol not allocated address
mapped = 42 ; TODO wrong VMASM code generated... should generate mapped memory address
@ -23,7 +50,6 @@ main {
nested()
main.start.nested.nested2()
; TODO flatten nested subroutines in codegen
sub nested() {
qq++
txt.print("zzz")

View File

@ -0,0 +1,521 @@
package prog8.intermediate
import prog8.code.StMemVar
import prog8.code.StMemorySlab
import prog8.code.StStaticVariable
import prog8.code.SymbolTable
import prog8.code.ast.PtBlock
import prog8.code.core.*
import prog8.code.target.*
import java.nio.file.Path
import kotlin.io.path.bufferedReader
import kotlin.io.path.div
class IRFileReader(outputDir: Path, programName: String) {
private val infile = outputDir / ("${programName}.p8ir")
fun readFile(): IRProgram {
println("Reading intermediate representation from $infile")
infile.bufferedReader().use { reader ->
val lines = reader.readText().lines()
return parseProgram(lines.iterator())
}
}
private fun parseProgram(lines: Iterator<String>): IRProgram {
val programPattern = Regex("<PROGRAM NAME=(.+)>")
val line = lines.next()
val match = programPattern.matchEntire(line) ?: throw IRParseException("invalid PROGRAM")
val programName = match.groups[1]!!.value
val options = parseOptions(lines)
val variables = parseVariables(lines)
val memorymapped = parseMemMapped(lines)
val slabs = parseSlabs(lines)
val initGlobals = parseInitGlobals(lines)
val blocks = parseBlocksUntilProgramEnd(lines)
val st = SymbolTable()
variables.forEach { st.add(it) }
memorymapped.forEach { st.add(it) }
slabs.forEach { st.add(it) }
val program = IRProgram(programName, st, options, options.compTarget)
program.addGlobalInits(initGlobals)
blocks.forEach{ program.addBlock(it) }
return program
}
private fun parseOptions(lines: Iterator<String>): CompilationOptions {
var line = lines.next()
while(line.isBlank())
line = lines.next()
var target: ICompilationTarget = VMTarget()
var outputType = OutputType.PRG
var launcher = CbmPrgLauncherType.NONE
var zeropage = ZeropageType.FULL
val zpReserved = mutableListOf<UIntRange>()
var loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
var dontReinitGlobals = false
var evalStackBaseAddress: UInt? = null
if(line!="<OPTIONS>")
throw IRParseException("invalid OPTIONS")
while(true) {
line = lines.next()
if(line=="</OPTIONS>")
break
val (name, value) = line.split('=', limit=2)
when(name) {
"compTarget" -> {
target = when(value) {
VMTarget.NAME -> VMTarget()
C64Target.NAME -> C64Target()
C128Target.NAME -> C128Target()
AtariTarget.NAME -> AtariTarget()
Cx16Target.NAME -> Cx16Target()
else -> throw IRParseException("invalid target $value")
}
}
"output" -> outputType = OutputType.valueOf(value)
"launcher" -> launcher = CbmPrgLauncherType.valueOf(value)
"zeropage" -> zeropage = ZeropageType.valueOf(value)
"loadAddress" -> loadAddress = value.toUInt()
"dontReinitGlobals" -> dontReinitGlobals = value.toBoolean()
"evalStackBaseAddress" -> evalStackBaseAddress = if(value=="null") null else value.toUInt()
"zpReserved" -> {
val (start, end) = value.split(',')
zpReserved.add(UIntRange(start.toUInt(), end.toUInt()))
}
else -> throw IRParseException("illegal OPTION $name")
}
}
return CompilationOptions(
outputType,
launcher,
zeropage,
zpReserved,
false,
false,
target,
loadAddress,
dontReinitGlobals = dontReinitGlobals,
evalStackBaseAddress = evalStackBaseAddress
)
}
private fun parseVariables(lines: Iterator<String>): List<StStaticVariable> {
var line = lines.next()
while(line.isBlank())
line = lines.next()
if(line!="<VARIABLES>")
throw IRParseException("invalid VARIABLES")
val variables = mutableListOf<StStaticVariable>()
val varPattern = Regex("(.+?)(\\[.+?\\])? (.+)=(.+?) (zp=(.+))?")
while(true) {
line = lines.next()
if(line=="</VARIABLES>")
break
// examples:
// uword main.start.qq2=0 zp=REQUIRE_ZP
// ubyte[6] main.start.namestring=105,114,109,101,110,0
val match = varPattern.matchEntire(line) ?: throw IRParseException("invalid VARIABLE $line")
val (type, arrayspec, name, value, _, zpwish) = match.destructured
val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null
val dt: DataType = parseDatatype(type, arraysize!=null)
val zp = if(zpwish.isBlank()) ZeropageWish.DONTCARE else ZeropageWish.valueOf(zpwish)
variables.add(StStaticVariable(name, dt, null, null, null, arraysize, zp, Position.DUMMY))
}
return variables
}
private fun parseMemMapped(lines: Iterator<String>): List<StMemVar> {
var line = lines.next()
while(line.isBlank())
line = lines.next()
if(line!="<MEMORYMAPPEDVARIABLES>")
throw IRParseException("invalid MEMORYMAPPEDVARIABLES")
val memvars = mutableListOf<StMemVar>()
val mappedPattern = Regex("&(.+?)(\\[.+?\\])? (.+)=(.+)")
while(true) {
line = lines.next()
if(line=="</MEMORYMAPPEDVARIABLES>")
break
// examples:
// &uword main.start.mapped=49152
// &ubyte[20] main.start.mappedarray=49408
val match = mappedPattern.matchEntire(line) ?: throw IRParseException("invalid MEMORYMAPPEDVARIABLES $line")
val (type, arrayspec, name, address) = match.destructured
val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null
val dt: DataType = parseDatatype(type, arraysize!=null)
memvars.add(StMemVar(name, dt, address.toUInt(), arraysize, Position.DUMMY))
}
return memvars
}
private fun parseDatatype(type: String, isArray: Boolean): DataType {
if(isArray) {
return when(type) {
"byte" -> DataType.ARRAY_B
"ubyte", "str" -> DataType.ARRAY_UB
"word" -> DataType.ARRAY_W
"uword" -> DataType.ARRAY_UW
"float" -> DataType.ARRAY_F
"bool" -> DataType.ARRAY_B
else -> throw IRParseException("invalid dt")
}
} else {
return when(type) {
"byte" -> DataType.BYTE
"ubyte" -> DataType.UBYTE
"word" -> DataType.WORD
"uword" -> DataType.UWORD
"float" -> DataType.FLOAT
"bool" -> DataType.BOOL
else -> throw IRParseException("invalid dt")
}
}
}
private fun parseSlabs(lines: Iterator<String>): List<StMemorySlab> {
var line = lines.next()
while(line.isBlank())
line = lines.next()
if(line!="<MEMORYSLABS>")
throw IRParseException("invalid MEMORYSLABS")
val slabs = mutableListOf<StMemorySlab>()
val slabPattern = Regex("SLAB (.+) (.+) (.+)")
while(true) {
line = lines.next()
if(line=="</MEMORYSLABS>")
break
// example: "SLAB slabname 4096 0"
val match = slabPattern.matchEntire(line) ?: throw IRParseException("invalid SLAB $line")
val (name, size, align) = match.destructured
slabs.add(StMemorySlab(name, size.toUInt(), align.toUInt(), null, Position.DUMMY))
}
return slabs
}
private fun parseInitGlobals(lines: Iterator<String>): IRCodeChunk {
var line = lines.next()
while(line.isBlank())
line = lines.next()
if(line!="<INITGLOBALS>")
throw IRParseException("invalid INITGLOBALS")
val chunk = parseCodeChunk(lines.next(), lines)!!
line = lines.next()
if(line!="</INITGLOBALS>")
throw IRParseException("missing INITGLOBALS close tag")
return chunk
}
private fun parseBlocksUntilProgramEnd(lines: Iterator<String>): List<IRBlock> {
val blocks = mutableListOf<IRBlock>()
while(true) {
var line = lines.next()
while (line.isBlank())
line = lines.next()
if (line == "</PROGRAM>")
break
blocks.add(parseBlock(line, lines))
}
return blocks
}
private val blockPattern = Regex("<BLOCK NAME=(.+) ADDRESS=(.+) ALIGN=(.+) POS=(.+)>")
private val inlineAsmPattern = Regex("<INLINEASM POS=(.+)>")
private val asmsubPattern = Regex("<ASMSUB NAME=(.+) ADDRESS=(.+) POS=(.+)>")
private val subPattern = Regex("<SUB NAME=(.+) RETURNTYPE=(.+) POS=(.+)>")
private val posPattern = Regex("\\[(.+): line (.+) col (.+)-(.+)\\]")
private val instructionPattern = Regex("""([a-z]+)(\.b|\.w|\.f)?(.*)""", RegexOption.IGNORE_CASE)
private val labelPattern = Regex("""_([a-zA-Z\d\._]+):""")
private fun parseBlock(startline: String, lines: Iterator<String>): IRBlock {
var line = startline
if(!line.startsWith("<BLOCK "))
throw IRParseException("invalid BLOCK")
val match = blockPattern.matchEntire(line) ?: throw IRParseException("invalid BLOCK")
val (name, address, align, position) = match.destructured
val addressNum = if(address=="null") null else address.toUInt()
val block = IRBlock(name, addressNum, PtBlock.BlockAlignment.valueOf(align), parsePosition(position))
while(true) {
line = lines.next()
if(line.isBlank())
continue
if(line=="</BLOCK>")
return block
if(line.startsWith("<SUB ")) {
val sub = parseSubroutine(line, lines)
block += sub
} else if(line.startsWith("<ASMSUB ")) {
val sub = parseAsmSubroutine(line, lines)
block += sub
} else if(line.startsWith("<INLINEASM ")) {
val asm = parseInlineAssembly(line, lines)
block += asm
} else
throw IRParseException("invalid line in BLOCK")
}
}
private fun parseInlineAssembly(startline: String, lines: Iterator<String>): IRInlineAsmChunk {
// <INLINEASM POS=[examples/test.p8: line 8 col 6-9]>
val match = inlineAsmPattern.matchEntire(startline) ?: throw IRParseException("invalid INLINEASM")
val pos = parsePosition(match.groupValues[1])
val asmlines = mutableListOf<String>()
var line = lines.next()
while(line!="</INLINEASM>") {
asmlines.add(line)
line = lines.next()
}
return IRInlineAsmChunk(asmlines.joinToString("\n"), pos)
}
private fun parseAsmSubroutine(startline: String, lines: Iterator<String>): IRAsmSubroutine {
// <ASMSUB NAME=main.testasmsub ADDRESS=null POS=[examples/test.p8: line 14 col 6-11]>
// TODO parse more signature stuff once it's there.
val match = asmsubPattern.matchEntire(startline) ?: throw IRParseException("invalid ASMSUB")
val (scopedname, address, pos) = match.destructured
var line = lines.next()
val asm = parseInlineAssembly(line, lines)
while(line!="</ASMSUB>")
line = lines.next()
return IRAsmSubroutine(scopedname, parsePosition(pos), if(address=="null") null else address.toUInt(), asm.asm)
}
private fun parseSubroutine(startline: String, lines: Iterator<String>): IRSubroutine {
// <SUB NAME=main.start.nested.nested2 RETURNTYPE=null POS=[examples/test.p8: line 54 col 14-16]>
// TODO parse more signature stuff once it's there.
val match = subPattern.matchEntire(startline) ?: throw IRParseException("invalid SUB")
val (name, returntype, pos) = match.destructured
val sub = IRSubroutine(name, if(returntype=="null") null else parseDatatype(returntype, false), parsePosition(pos))
while(true) {
val line = lines.next()
if(line=="</SUB>")
return sub
val chunk = if(line=="<CODE>")
parseCodeChunk(line, lines)
else if(line.startsWith("<INLINEASM "))
parseInlineAssembly(line, lines)
else
throw IRParseException("invalid sub child node")
if (chunk == null)
break
else
sub += chunk
}
val line = lines.next()
if(line=="</SUB>")
throw IRParseException("missing SUB close tag")
return sub
}
private fun parseCodeChunk(firstline: String, lines: Iterator<String>): IRCodeChunk? {
if(firstline!="<CODE>") {
if(firstline=="</SUB>")
return null
else
throw IRParseException("invalid or empty CODE chunk")
}
val chunk = IRCodeChunk(Position.DUMMY)
while(true) {
val line = lines.next()
if (line == "</CODE>")
return chunk
if (line.isBlank() || line.startsWith(';'))
continue
chunk += parseCodeLine(line)
}
}
private fun parseCodeLine(line: String): IRCodeLine {
val match = instructionPattern.matchEntire(line.trim())
if(match==null) {
// it's a label.
val labelmatch = labelPattern.matchEntire(line.trim()) ?: throw IRParseException("invalid label")
return IRCodeLabel(labelmatch.groupValues[1].split('.'))
}
// it's an instruction.
val (_, instr, typestr, rest) = match.groupValues
if(instr=="incbin") {
TODO("incbin")
}
val opcode = try {
Opcode.valueOf(instr.uppercase())
} catch (ax: IllegalArgumentException) {
throw IRParseException("invalid vmasm instruction: $instr")
}
var type: VmDataType? = convertVmType(typestr)
val formats = instructionFormats.getValue(opcode)
val format: InstructionFormat
if(type !in formats) {
type = VmDataType.BYTE
format = if(type !in formats)
formats.getValue(null)
else
formats.getValue(type)
} else {
format = formats.getValue(type)
}
// parse the operands
val operands = rest.lowercase().split(",").toMutableList()
var reg1: Int? = null
var reg2: Int? = null
var reg3: Int? = null
var fpReg1: Int? = null
var fpReg2: Int? = null
var fpReg3: Int? = null
var value: Float? = null
var operand: String?
var labelSymbol: List<String>? = null
if(operands.isNotEmpty() && operands[0].isNotEmpty()) {
operand = operands.removeFirst().trim()
if(operand[0]=='r')
reg1 = operand.substring(1).toInt()
else if(operand[0]=='f' && operand[1]=='r')
fpReg1 = operand.substring(2).toInt()
else {
if(operand.startsWith('_')) {
// it's a label.
labelSymbol = rest.split(",")[0].trim().substring(1).split('.') // keep the original case
value = null
} else {
value = parseValue(operand)
}
operands.clear()
}
if(operands.isNotEmpty()) {
operand = operands.removeFirst().trim()
if(operand[0]=='r')
reg2 = operand.substring(1).toInt()
else if(operand[0]=='f' && operand[1]=='r')
fpReg2 = operand.substring(2).toInt()
else {
if(operand.startsWith('_')) {
// it's a label.
labelSymbol = rest.split(",")[1].trim().substring(1).split('.') // keep the original case
value = null
} else {
value = parseValue(operand)
}
operands.clear()
}
if(operands.isNotEmpty()) {
operand = operands.removeFirst().trim()
if(operand[0]=='r')
reg3 = operand.substring(1).toInt()
else if(operand[0]=='f' && operand[1]=='r')
fpReg3 = operand.substring(2).toInt()
else {
if(operand.startsWith('_')) {
// it's a label.
labelSymbol = rest.split(",")[2].trim().split('.') // keep the original case
value = null
} else {
value = parseValue(operand)
}
operands.clear()
}
if(operands.isNotEmpty()) {
operand = operands.removeFirst().trim()
if(operand.startsWith('_')) {
// it's a label.
labelSymbol = rest.split(",")[3].trim().split('.') // keep the original case
value = null
} else {
value = parseValue(operand)
}
}
}
}
}
// shift the operands back into place
while(reg1==null && reg2!=null) {
reg1 = reg2
reg2 = reg3
reg3 = null
}
while(fpReg1==null && fpReg2!=null) {
fpReg1 = fpReg2
fpReg2 = fpReg3
fpReg3 = null
}
if(reg3!=null)
throw IRParseException("too many reg arguments $line")
if(fpReg3!=null)
throw IRParseException("too many fpreg arguments $line")
if(type!=null && type !in formats)
throw IRParseException("invalid type code for $line")
if(format.reg1 && reg1==null)
throw IRParseException("needs reg1 for $line")
if(format.reg2 && reg2==null)
throw IRParseException("needs reg2 for $line")
if(format.value && value==null && labelSymbol==null)
throw IRParseException("needs value or label for $line")
if(!format.reg1 && reg1!=null)
throw IRParseException("invalid reg1 for $line")
if(!format.reg2 && reg2!=null)
throw IRParseException("invalid reg2 for $line")
if(value!=null && opcode !in OpcodesWithAddress) {
when (type) {
VmDataType.BYTE -> {
if (value < -128 || value > 255)
throw IRParseException("value out of range for byte: $value")
}
VmDataType.WORD -> {
if (value < -32768 || value > 65535)
throw IRParseException("value out of range for word: $value")
}
VmDataType.FLOAT -> {}
null -> {}
}
}
var floatValue: Float? = null
var intValue: Int? = null
if(format.value && value!=null)
intValue = value.toInt()
if(format.fpValue && value!=null)
floatValue = value
return IRCodeInstruction(opcode, type, reg1, reg2, fpReg1, fpReg2, intValue, floatValue, labelSymbol)
}
private fun convertVmType(typestr: String): VmDataType? {
return when(typestr.lowercase()) {
"" -> null
".b" -> VmDataType.BYTE
".w" -> VmDataType.WORD
".f" -> VmDataType.FLOAT
else -> throw IRParseException("invalid type $typestr")
}
}
private fun parseValue(value: String): Float {
return if(value.startsWith("-"))
-parseValue(value.substring(1))
else if(value.startsWith('$'))
value.substring(1).toInt(16).toFloat()
else if(value.startsWith('%'))
value.substring(1).toInt(2).toFloat()
else if(value.startsWith("0x"))
value.substring(2).toInt(16).toFloat()
else if(value.startsWith('_'))
throw IRParseException("attempt to parse a label as value")
else
return value.toFloat()
}
private fun parsePosition(strpos: String): Position {
// example: "[library:/prog8lib/virtual/textio.p8: line 5 col 2-4]"
val match = posPattern.matchEntire(strpos) ?: throw IRParseException("invalid Position")
val (file, line, startCol, endCol) = match.destructured
return Position(file, line.toInt(), startCol.toInt(), endCol.toInt())
}
}

View File

@ -1,10 +1,16 @@
package prog8.intermediate
import prog8.code.StMemVar
import prog8.code.StStaticVariable
import prog8.code.core.*
import java.io.BufferedWriter
import kotlin.io.path.bufferedWriter
import kotlin.io.path.div
// TODO incbins
class IRFileWriter(private val irProgram: IRProgram) {
private val outfile = irProgram.options.outputDir / ("${irProgram.name}.p8ir")
private val out = outfile.bufferedWriter()
@ -17,9 +23,9 @@ class IRFileWriter(private val irProgram: IRProgram) {
if(!irProgram.options.dontReinitGlobals) {
// note: this a block of code that loads values and stores them into the global variables to reset their values.
out.write("\n<INITGLOBALS>\n")
out.write("\n<INITGLOBALS>\n<CODE>\n")
irProgram.globalInits.forEach { out.writeLine(it) }
out.write("</INITGLOBALS>\n")
out.write("</CODE>\n</INITGLOBALS>\n")
}
writeBlocks()
out.write("</PROGRAM>\n")
@ -30,36 +36,53 @@ class IRFileWriter(private val irProgram: IRProgram) {
irProgram.blocks.forEach { block ->
out.write("\n<BLOCK NAME=${block.name} ADDRESS=${block.address} ALIGN=${block.alignment} POS=${block.position}>\n")
block.inlineAssembly.forEach {
out.write("<INLINEASM POS=${it.position}>\n")
out.write(it.asm)
if(!it.asm.endsWith('\n'))
out.write("\n")
out.write("</INLINEASM>\n")
writeInlineAsm(it)
}
block.subroutines.forEach {
out.write("<SUB NAME=${it.name} RETURNTYPE=${it.returnType} POS=${it.position}>\n")
it.lines.forEach { line -> out.writeLine(line) }
out.write("<SUB NAME=${it.name} RETURNTYPE=${it.returnType.toString().lowercase()} POS=${it.position}>\n")
// TODO rest of the signature
it.chunks.forEach { chunk ->
if(chunk is IRInlineAsmChunk) {
writeInlineAsm(chunk)
} else {
out.write("<CODE>\n")
if (chunk.lines.isEmpty())
throw InternalCompilerException("empty code chunk in ${it.name} ${it.position}")
chunk.lines.forEach { line -> out.writeLine(line) }
out.write("</CODE>\n")
}
}
out.write("</SUB>\n")
}
block.asmSubroutines.forEach {
out.write("<ASMSUB SCOPEDNAME=${it.scopedName.joinToString(".")} ADDRESS=${it.address} POS=${it.position}>\n")
it.lines.forEach { line -> out.writeLine(line) }
out.write("</ASMSUB>\n")
out.write("<ASMSUB NAME=${it.name} ADDRESS=${it.address} POS=${it.position}>\n")
// TODO rest of the signature
out.write("<INLINEASM POS=${it.position}>\n")
out.write(it.assembly.trimStart('\n').trimEnd(' ', '\n'))
out.write("\n</INLINEASM>\n</ASMSUB>\n")
}
out.write("</BLOCK>\n")
}
}
private fun writeInlineAsm(chunk: IRInlineAsmChunk) {
out.write("<INLINEASM POS=${chunk.position}>\n")
out.write(chunk.asm.trimStart('\n').trimEnd(' ', '\n'))
out.write("\n</INLINEASM>\n")
}
private fun writeOptions() {
out.write("<OPTIONS>\n")
out.write("compTarget = ${irProgram.options.compTarget.name}\n")
out.write("output = ${irProgram.options.output}\n")
out.write("launcher = ${irProgram.options.launcher}\n")
out.write("zeropage = ${irProgram.options.zeropage}\n")
out.write("zpReserved = ${irProgram.options.zpReserved}\n")
out.write("loadAddress = ${irProgram.options.loadAddress}\n")
out.write("dontReinitGlobals = ${irProgram.options.dontReinitGlobals}\n")
out.write("evalStackBaseAddress = ${irProgram.options.evalStackBaseAddress}\n")
out.write("compTarget=${irProgram.options.compTarget.name}\n")
out.write("output=${irProgram.options.output}\n")
out.write("launcher=${irProgram.options.launcher}\n")
out.write("zeropage=${irProgram.options.zeropage}\n")
for(range in irProgram.options.zpReserved) {
out.write("zpReserved=${range.first},${range.last}\n")
}
out.write("loadAddress=${irProgram.options.loadAddress}\n")
out.write("dontReinitGlobals=${irProgram.options.dontReinitGlobals}\n")
out.write("evalStackBaseAddress=${irProgram.options.evalStackBaseAddress}\n")
// other options not yet useful here?
out.write("</OPTIONS>\n")
}
@ -67,20 +90,13 @@ class IRFileWriter(private val irProgram: IRProgram) {
private fun writeVariableAllocations() {
out.write("\n<VARIABLES>\n")
for (variable in irProgram.st.allVariables) {
val typeStr = when(variable.dt) {
DataType.UBYTE, DataType.ARRAY_UB, DataType.STR -> "ubyte"
DataType.BYTE, DataType.ARRAY_B -> "byte"
DataType.UWORD, DataType.ARRAY_UW -> "uword"
DataType.WORD, DataType.ARRAY_W -> "word"
DataType.FLOAT, DataType.ARRAY_F -> "float"
else -> throw InternalCompilerException("weird dt")
}
val typeStr = getTypeString(variable)
val value = when(variable.dt) {
DataType.FLOAT -> (variable.onetimeInitializationNumericValue ?: 0.0).toString()
in NumericDatatypes -> (variable.onetimeInitializationNumericValue ?: 0).toHex()
in NumericDatatypes -> (variable.onetimeInitializationNumericValue ?: 0)
DataType.STR -> {
val encoded = irProgram.encoding.encodeString(variable.onetimeInitializationStringValue!!.first, variable.onetimeInitializationStringValue!!.second) + listOf(0u)
encoded.joinToString(",") { it.toInt().toHex() }
encoded.joinToString(",") { it.toInt().toString() }
}
DataType.ARRAY_F -> {
if(variable.onetimeInitializationArrayValue!=null) {
@ -91,29 +107,22 @@ class IRFileWriter(private val irProgram: IRProgram) {
}
in ArrayDatatypes -> {
if(variable.onetimeInitializationArrayValue!==null) {
variable.onetimeInitializationArrayValue!!.joinToString(",") { it.number!!.toHex() }
variable.onetimeInitializationArrayValue!!.joinToString(",") { it.number!!.toInt().toString() }
} else {
(1..variable.length!!).joinToString(",") { "0" }
}
}
else -> throw InternalCompilerException("weird dt")
}
// TODO have uninitialized variables? (BSS SECTION)
out.write("VAR ${variable.scopedName.joinToString(".")} $typeStr = $value\n")
// TODO have uninitialized variables and arrays? (BSS SECTION)
out.write("$typeStr ${variable.scopedName.joinToString(".")}=$value zp=${variable.zpwish}\n")
}
out.write("</VARIABLES>\n")
out.write("\n<MEMORYMAPPEDVARIABLES>\n")
for (variable in irProgram.st.allMemMappedVariables) {
val typeStr = when(variable.dt) {
DataType.UBYTE, DataType.ARRAY_UB, DataType.STR -> "ubyte"
DataType.BYTE, DataType.ARRAY_B -> "byte"
DataType.UWORD, DataType.ARRAY_UW -> "uword"
DataType.WORD, DataType.ARRAY_W -> "word"
DataType.FLOAT, DataType.ARRAY_F -> "float"
else -> throw InternalCompilerException("weird dt")
}
out.write("MAP ${variable.scopedName.joinToString(".")} $typeStr ${variable.address}\n")
val typeStr = getTypeString(variable)
out.write("&$typeStr ${variable.scopedName.joinToString(".")}=${variable.address}\n")
}
out.write("</MEMORYMAPPEDVARIABLES>\n")
@ -122,6 +131,38 @@ class IRFileWriter(private val irProgram: IRProgram) {
out.write("</MEMORYSLABS>\n")
}
private fun getTypeString(memvar: StMemVar): String {
return when(memvar.dt) {
DataType.UBYTE -> "ubyte"
DataType.BYTE -> "byte"
DataType.UWORD -> "uword"
DataType.WORD -> "word"
DataType.FLOAT -> "float"
DataType.ARRAY_UB, DataType.STR -> "ubyte[${memvar.length}]"
DataType.ARRAY_B -> "byte[${memvar.length}]"
DataType.ARRAY_UW -> "uword[${memvar.length}]"
DataType.ARRAY_W -> "word[${memvar.length}]"
DataType.ARRAY_F -> "float[${memvar.length}]"
else -> throw InternalCompilerException("weird dt")
}
}
private fun getTypeString(variable : StStaticVariable): String {
return when(variable.dt) {
DataType.UBYTE -> "ubyte"
DataType.BYTE -> "byte"
DataType.UWORD -> "uword"
DataType.WORD -> "word"
DataType.FLOAT -> "float"
DataType.ARRAY_UB, DataType.STR -> "ubyte[${variable.length}]"
DataType.ARRAY_B -> "byte[${variable.length}]"
DataType.ARRAY_UW -> "uword[${variable.length}]"
DataType.ARRAY_W -> "word[${variable.length}]"
DataType.ARRAY_F -> "float[${variable.length}]"
else -> throw InternalCompilerException("weird dt")
}
}
private fun BufferedWriter.writeLine(line: IRCodeLine) {
when(line) {
is IRCodeComment -> write("; ${line.comment}\n")

View File

@ -0,0 +1,3 @@
package prog8.intermediate
class IRParseException(message: String) : Exception(message)

View File

@ -2,36 +2,24 @@ package prog8.intermediate
class IRPeepholeOptimizer(private val vmprog: IRProgram) {
fun optimize() {
vmprog.blocks.forEach { block ->
block.subroutines.forEach { sub ->
/*
sub.forEach { child ->
when (child) {
is IRCodeChunk -> {
do {
val indexedInstructions = child.lines.withIndex()
.filter { it.value is IRCodeInstruction }
.map { IndexedValue(it.index, (it.value as IRCodeInstruction).ins) }
val changed = removeNops(child, indexedInstructions)
|| removeDoubleLoadsAndStores(
child,
indexedInstructions
) // TODO not yet implemented
|| removeUselessArithmetic(child, indexedInstructions)
|| removeWeirdBranches(child, indexedInstructions)
|| removeDoubleSecClc(child, indexedInstructions)
|| cleanupPushPop(child, indexedInstructions)
// TODO other optimizations:
// more complex optimizations such as unused registers
} while (changed)
}
else -> {
TODO("block child $child")
}
}
}
*/
vmprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
sub.chunks.forEach { chunk ->
do {
val indexedInstructions = chunk.lines.withIndex()
.filter { it.value is IRCodeInstruction }
.map { IndexedValue(it.index, (it.value as IRCodeInstruction).ins) }
val changed = removeNops(chunk, indexedInstructions)
|| removeDoubleLoadsAndStores(
chunk,
indexedInstructions
) // TODO not yet implemented
|| removeUselessArithmetic(chunk, indexedInstructions)
|| removeWeirdBranches(chunk, indexedInstructions)
|| removeDoubleSecClc(chunk, indexedInstructions)
|| cleanupPushPop(chunk, indexedInstructions)
// TODO other optimizations:
// more complex optimizations such as unused registers
} while (changed)
}
}
}

View File

@ -14,22 +14,29 @@ PROGRAM:
VARIABLES (from Symboltable)
MEMORYMAPPEDVARIABLES (from Symboltable)
MEMORYSLABS (from Symboltable)
GLOBALINITS
CODE-LINE (assignment to initialize a variable)
CODE-LINE (assignment to initialize a variable)
...
INITGLOBALS
CODE
CODE-LINE (assignment to initialize a variable)
...
BLOCK
INLINEASM
INLINEASM
SUB
CODE-LINE (label, instruction, comment, inlinebinary)
CODE-LINE
CODE-LINE
INLINEASM
INLINEASM
CODE
CODE-LINE (label, instruction, comment, inlinebinary)
CODE-LINE
...
CODE
CODE
...
SUB
SUB
ASMSUB
INLINEASM
ASMSUB
INLINEASM
...
BLOCK
BLOCK
@ -45,7 +52,11 @@ class IRProgram(val name: String,
val blocks = mutableListOf<IRBlock>()
fun addGlobalInits(chunk: IRCodeChunk) = globalInits.addAll(chunk.lines)
fun addBlock(block: IRBlock) = blocks.add(block)
fun addBlock(block: IRBlock) {
if(blocks.any { it.name==block.name })
throw IllegalArgumentException("duplicate block ${block.name} ${block.position}")
blocks.add(block)
}
}
class IRBlock(
@ -68,17 +79,30 @@ class IRBlock(
class IRSubroutine(val name: String,
val returnType: DataType?,
val position: Position) {
val lines = mutableListOf<IRCodeLine>()
val chunks = mutableListOf<IRCodeChunk>()
operator fun plusAssign(chunk: IRCodeChunk) { lines += chunk.lines }
init {
if(!name.contains('.'))
throw IllegalArgumentException("subroutine name is not scoped: $name")
if(name.startsWith("main.main."))
throw IllegalArgumentException("subroutine name invalid main prefix: $name")
}
operator fun plusAssign(chunk: IRCodeChunk) { chunks+= chunk }
}
class IRAsmSubroutine(val scopedName: List<String>,
class IRAsmSubroutine(val name: String,
val position: Position,
val address: UInt?,
val assembly: String) {
val lines = mutableListOf<IRCodeLine>()
init {
if(!name.contains('.'))
throw IllegalArgumentException("subroutine name is not scoped: $name")
if(name.startsWith("main.main."))
throw IllegalArgumentException("subroutine name invalid main prefix: $name")
}
}
sealed class IRCodeLine
@ -131,6 +155,9 @@ class IRCodeInlineBinary(val file: Path, val offset: UInt?, val length: UInt?):
open class IRCodeChunk(val position: Position) {
val lines = mutableListOf<IRCodeLine>()
open fun isEmpty() = lines.isEmpty()
open fun isNotEmpty() = lines.isNotEmpty()
operator fun plusAssign(line: IRCodeLine) {
lines.add(line)
}
@ -140,5 +167,9 @@ open class IRCodeChunk(val position: Position) {
}
}
class IRInlineAsmChunk(val asm: String, position: Position): IRCodeChunk(position) // note: no lines, asm is in the property
class IRInlineAsmChunk(val asm: String, position: Position): IRCodeChunk(position) {
// note: no lines, asm is in the property
override fun isEmpty() = asm.isBlank()
override fun isNotEmpty() = asm.isNotBlank()
}