From bff3c4f95cac6a38d874f55bfa0ad32bff011aa3 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Tue, 9 May 2023 21:04:31 +0200 Subject: [PATCH] IR now converts IRInlineAsmChunk (of type IR) into regular code chunks directly. .p8ir files usually won't contain nodes any longer --- codeCore/src/prog8/code/ast/AstBase.kt | 2 +- .../prog8/codegen/intermediate/IRCodeGen.kt | 1 + compiler/test/vm/TestCompilerVirtual.kt | 14 ++-- docs/source/todo.rst | 3 +- examples/test.p8 | 12 +++ .../src/prog8/intermediate/IRFileReader.kt | 3 +- .../src/prog8/intermediate/IRInstructions.kt | 2 + .../src/prog8/intermediate/IRProgram.kt | 73 ++++++++++++++++++- intermediate/src/prog8/intermediate/Utils.kt | 10 +-- .../src/prog8/vm/VmProgramLoader.kt | 36 +-------- 10 files changed, 104 insertions(+), 52 deletions(-) diff --git a/codeCore/src/prog8/code/ast/AstBase.kt b/codeCore/src/prog8/code/ast/AstBase.kt index 457d32f58..2765ead5d 100644 --- a/codeCore/src/prog8/code/ast/AstBase.kt +++ b/codeCore/src/prog8/code/ast/AstBase.kt @@ -63,7 +63,7 @@ class PtProgram( children.asSequence().filterIsInstance() fun entrypoint(): PtSub? = - allBlocks().firstOrNull { it.name == "main" }?.children?.firstOrNull { it is PtSub && it.name == "start" } as PtSub? + allBlocks().firstOrNull { it.name == "main" }?.children?.firstOrNull { it is PtSub && (it.name == "start" || it.name=="main.start") } as PtSub? } diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt index 408bcc544..671ebbe75 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt @@ -53,6 +53,7 @@ class IRCodeGen( replaceMemoryMappedVars(irProg) ensureFirstChunkLabels(irProg) irProg.linkChunks() + irProg.convertAsmChunks() val optimizer = IRPeepholeOptimizer(irProg) optimizer.optimize(options.optimize, errors) diff --git a/compiler/test/vm/TestCompilerVirtual.kt b/compiler/test/vm/TestCompilerVirtual.kt index 1c7f5a787..b92242121 100644 --- a/compiler/test/vm/TestCompilerVirtual.kt +++ b/compiler/test/vm/TestCompilerVirtual.kt @@ -5,6 +5,7 @@ import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.string.shouldContain +import io.kotest.matchers.string.shouldNotContain import prog8.ast.expressions.BuiltinFunctionCall import prog8.ast.statements.Assignment import prog8.code.target.C64Target @@ -244,25 +245,26 @@ main { val exc = shouldThrow { VmRunner().runProgram(virtfile.readText()) } - exc.message shouldContain("does not support real inlined assembly") + exc.message shouldContain("encountered unconverted inline assembly chunk") } - test("inline asm for virtual target with IR is accepted") { + test("inline asm for virtual target with IR is accepted and converted to regular instructions") { val src = """ main { sub start() { %ir {{ + loadr.b r1,r2 return }} } }""" - val othertarget = Cx16Target() - compileText(othertarget, true, src, writeAssembly = true) shouldNotBe null - val target = VMTarget() val result = compileText(target, false, src, writeAssembly = true)!! val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir") - VmRunner().runProgram(virtfile.readText()) + val irSrc = virtfile.readText() + irSrc.shouldContain("loadr.b r1,r2") + irSrc.shouldNotContain("INLINEASM") + VmRunner().runProgram(irSrc) } test("addresses from labels/subroutines not yet supported in VM") { diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 13e79a6e1..666287cfe 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -4,7 +4,7 @@ TODO For next minor release ^^^^^^^^^^^^^^^^^^^^^^ - fix VM problem with param passing: void string.copy(".prg", &output_filename + string.length(output_filename)) -- try to optimize newexpr a bit more + some work on this is on a git shelf ... @@ -33,6 +33,7 @@ Compiler: - ir: peephole opt: (maybe just integrate this in the variable/register allocator though?) reuse registers in chunks (but keep result registers in mind that pass values out! and don't renumber registers above SyscallRegisterBase!) - ir: add more optimizations in IRPeepholeOptimizer - ir: for expressions with array indexes that occur multiple times, can we avoid loading them into new virtualregs everytime and just reuse a single virtualreg as indexer? (simple form of common subexpression elimination) +- try to optimize newexpr a bit more? Although maybe just spend effort on a new codegen based on the IR. - PtAst/IR: more complex common subexpression eliminations - generate WASM to eventually run prog8 on a browser canvas? Use binaryen toolkit or my binaryen kotlin library? - can we get rid of pieces of asmgen.AssignmentAsmGen by just reusing the AugmentableAssignment ? generated code should not suffer diff --git a/examples/test.p8 b/examples/test.p8 index 6375333dd..6eba62776 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -8,6 +8,18 @@ main { uword seconds_uword = 1 uword remainder = seconds_uword % $0003 ==0 txt.print_uw(remainder) + + blerp() + } + + sub blerp() { + %ir {{ +_xxx: + loadr r2,r3 +_yyy: + loadr r3,r4 + return + }} } } diff --git a/intermediate/src/prog8/intermediate/IRFileReader.kt b/intermediate/src/prog8/intermediate/IRFileReader.kt index a62cebdf7..f0c6bf858 100644 --- a/intermediate/src/prog8/intermediate/IRFileReader.kt +++ b/intermediate/src/prog8/intermediate/IRFileReader.kt @@ -68,6 +68,7 @@ class IRFileReader { blocks.forEach{ program.addBlock(it) } program.linkChunks() + program.convertAsmChunks() program.validate() return program @@ -302,7 +303,7 @@ class IRFileReader { if(text.isNotBlank()) { text.lineSequence().forEach { line -> if (line.isNotBlank() && !line.startsWith(';')) { - val result = parseIRCodeLine(line, null, mutableMapOf()) + val result = parseIRCodeLine(line) result.fold( ifLeft = { chunk += it diff --git a/intermediate/src/prog8/intermediate/IRInstructions.kt b/intermediate/src/prog8/intermediate/IRInstructions.kt index 115562d00..87a00a0bb 100644 --- a/intermediate/src/prog8/intermediate/IRInstructions.kt +++ b/intermediate/src/prog8/intermediate/IRInstructions.kt @@ -24,6 +24,8 @@ There is no distinction between signed and unsigned integers. Instead, a different instruction is used if a distinction should be made (for example div and divs). Floating point operations are just 'f' typed regular instructions, however there are a few unique fp conversion instructions. +NOTE: Labels in source text should always start with an underscore. + LOAD/STORE ---------- diff --git a/intermediate/src/prog8/intermediate/IRProgram.kt b/intermediate/src/prog8/intermediate/IRProgram.kt index d5ec37ad0..d83cb3abf 100644 --- a/intermediate/src/prog8/intermediate/IRProgram.kt +++ b/intermediate/src/prog8/intermediate/IRProgram.kt @@ -170,7 +170,10 @@ class IRProgram(val name: String, fun validate() { blocks.forEach { block -> if(block.isNotEmpty()) { - block.children.filterIsInstance().forEach { chunk -> require(chunk.instructions.isEmpty()) } + block.children.filterIsInstance().forEach { chunk -> + require(chunk.instructions.isEmpty()) + require(!chunk.isIR) { "inline IR-asm should have been converted into regular code chunk"} + } block.children.filterIsInstance().forEach { sub -> if(sub.chunks.isNotEmpty()) { require(sub.chunks.first().label == sub.label) { "first chunk in subroutine should have sub name (label) as its label" } @@ -179,15 +182,18 @@ class IRProgram(val name: String, if (chunk is IRCodeChunk) { require(chunk.instructions.isNotEmpty() || chunk.label != null) if(chunk.instructions.lastOrNull()?.opcode in OpcodesThatJump) - require(chunk.next == null) { "chunk ending with a jump shouldn't be linked to next" } + require(chunk.next == null) { "chunk ending with a jump or return shouldn't be linked to next" } else { // if chunk is NOT the last in the block, it needs to link to next. val isLast = sub.chunks.last() === chunk require(isLast || chunk.next != null) { "chunk needs to be linked to next" } } } - else + else { require(chunk.instructions.isEmpty()) + if(chunk is IRInlineAsmChunk) + require(!chunk.isIR) { "inline IR-asm should have been converted into regular code chunk"} + } chunk.instructions.forEach { if(it.labelSymbol!=null && it.opcode in OpcodesThatBranch) require(it.branchTarget != null) { "branching instruction to label should have branchTarget set" } @@ -235,6 +241,65 @@ class IRProgram(val name: String, } return RegistersUsed(readRegsCounts, writeRegsCounts, readFpRegsCounts, writeFpRegsCounts, regsTypes) } + + fun convertAsmChunks() { + fun convert(asmChunk: IRInlineAsmChunk): IRCodeChunks { + val chunks = mutableListOf() + var chunk = IRCodeChunk(asmChunk.label, null) + asmChunk.assembly.lineSequence().forEach { + val parsed = parseIRCodeLine(it.trim()) + parsed.fold( + ifLeft = { instruction -> chunk += instruction }, + ifRight = { label -> + val lastChunk = chunk + if(chunk.isNotEmpty() || chunk.label!=null) + chunks += chunk + chunk = IRCodeChunk(label, null) + val lastInstr = lastChunk.instructions.lastOrNull() + if(lastInstr==null || lastInstr.opcode !in OpcodesThatJump) + lastChunk.next = chunk + } + ) + } + if(chunk.isNotEmpty() || chunk.label!=null) + chunks += chunk + chunks.lastOrNull()?.let { + val lastInstr = it.instructions.lastOrNull() + if(lastInstr==null || lastInstr.opcode !in OpcodesThatJump) + it.next = asmChunk.next + } + return chunks + } + + blocks.forEach { block -> + val chunkReplacementsInBlock = mutableListOf>() + block.children.filterIsInstance().forEach { asmchunk -> + if(asmchunk.isIR) chunkReplacementsInBlock += asmchunk to convert(asmchunk) + // non-IR asm cannot be converted + } + chunkReplacementsInBlock.reversed().forEach { (old, new) -> + val index = block.children.indexOf(old) + block.children.removeAt(index) + new.reversed().forEach { block.children.add(index, it) } + } + chunkReplacementsInBlock.clear() + + block.children.filterIsInstance().forEach { sub -> + val chunkReplacementsInSub = mutableListOf>() + sub.chunks.filterIsInstance().forEach { asmchunk -> + if(asmchunk.isIR) chunkReplacementsInSub += asmchunk to convert(asmchunk) + // non-IR asm cannot be converted + } + + chunkReplacementsInSub.reversed().forEach { (old, new) -> + val index = sub.chunks.indexOf(old) + sub.chunks.removeAt(index) + new.reversed().forEach { sub.chunks.add(index, it) } + } + chunkReplacementsInSub.clear() + } + } + } } class IRBlock( @@ -418,7 +483,7 @@ private fun registersUsedInAssembly(isIR: Boolean, assembly: String): RegistersU assembly.lineSequence().forEach { line -> val t = line.trim() if(t.isNotEmpty()) { - val result = parseIRCodeLine(t, null, mutableMapOf()) + val result = parseIRCodeLine(t) result.fold( ifLeft = { it.addUsedRegistersCounts(readRegsCounts, writeRegsCounts,readFpRegsCounts, writeFpRegsCounts, regsTypes) }, ifRight = { /* labels can be skipped */ } diff --git a/intermediate/src/prog8/intermediate/Utils.kt b/intermediate/src/prog8/intermediate/Utils.kt index 0410e65f8..4b8fb4368 100644 --- a/intermediate/src/prog8/intermediate/Utils.kt +++ b/intermediate/src/prog8/intermediate/Utils.kt @@ -78,9 +78,7 @@ fun parseIRValue(value: String): Float { private val instructionPattern = Regex("""([a-z]+)(\.b|\.w|\.f)?(.*)""", RegexOption.IGNORE_CASE) private val labelPattern = Regex("""_([a-zA-Z\d\._]+):""") -fun parseIRCodeLine(line: String, location: Pair?, placeholders: MutableMap, String>): Either { - // Note: this function is used from multiple places: - // the IR File Reader but also the VirtualMachine itself to make sense of any inline vmasm blocks. +fun parseIRCodeLine(line: String): Either { val labelmatch = labelPattern.matchEntire(line.trim()) if(labelmatch!=null) return right(labelmatch.groupValues[1]) // it's a label. @@ -117,10 +115,8 @@ fun parseIRCodeLine(line: String, location: Pair?, placeholder var address: Int? = null var labelSymbol: String? = null - fun parseValueOrPlaceholder(operand: String, location: Pair?): Float? { + fun parseValueOrPlaceholder(operand: String): Float? { return if(operand[0].isLetter()) { - if(location!=null) - placeholders[location] = operand null } else { parseIRValue(operand) @@ -156,7 +152,7 @@ fun parseIRCodeLine(line: String, location: Pair?, placeholder if(!oper[0].isLetter()) throw IRParseException("expected symbol name: $oper") labelSymbol = oper - val value = parseValueOrPlaceholder(oper, location) + val value = parseValueOrPlaceholder(oper) if(value!=null) address = value.toInt() } diff --git a/virtualmachine/src/prog8/vm/VmProgramLoader.kt b/virtualmachine/src/prog8/vm/VmProgramLoader.kt index aa9546439..d5b05a325 100644 --- a/virtualmachine/src/prog8/vm/VmProgramLoader.kt +++ b/virtualmachine/src/prog8/vm/VmProgramLoader.kt @@ -44,20 +44,14 @@ class VmProgramLoader { when(child) { is IRAsmSubroutine -> throw IRParseException("vm does not support asmsubs (use normal sub): ${child.label}") is IRCodeChunk -> programChunks += child - is IRInlineAsmChunk -> { - val replacement = addAssemblyToProgram(child, programChunks, variableAddresses) - chunkReplacements += Pair(child, replacement) - } - is IRInlineBinaryChunk -> throw IRParseException("inline binary data not yet supported in the VM") // TODO + is IRInlineAsmChunk -> throw IRParseException("encountered unconverted inline assembly chunk") + is IRInlineBinaryChunk -> throw IRParseException("inline binary data not yet supported in the VM") is IRSubroutine -> { subroutines[child.label] = child child.chunks.forEach { chunk -> when (chunk) { - is IRInlineAsmChunk -> { - val replacement = addAssemblyToProgram(chunk, programChunks, variableAddresses) - chunkReplacements += Pair(chunk, replacement) - } - is IRInlineBinaryChunk -> throw IRParseException("inline binary data not yet supported in the VM") // TODO + is IRInlineAsmChunk -> throw IRParseException("encountered unconverted inline assembly chunk") + is IRInlineBinaryChunk -> throw IRParseException("inline binary data not yet supported in the VM") is IRCodeChunk -> programChunks += chunk else -> throw AssemblyError("weird chunk type") } @@ -333,26 +327,4 @@ class VmProgramLoader { require(variable.onetimeInitializationStringValue==null) { "in vm/ir, strings should have been converted into bytearrays." } } } - - - private fun addAssemblyToProgram( - asmChunk: IRInlineAsmChunk, - chunks: MutableList, - symbolAddresses: MutableMap, - ): IRCodeChunk { - if(asmChunk.isIR) { - val chunk = IRCodeChunk(asmChunk.label, asmChunk.next) - asmChunk.assembly.lineSequence().forEach { - val parsed = parseIRCodeLine(it.trim(), Pair(chunk, chunk.instructions.size), placeholders) - parsed.fold( - ifLeft = { instruction -> chunk += instruction }, - ifRight = { label -> symbolAddresses[label] = chunk.instructions.size } - ) - } - chunks += chunk - return chunk - } else { - throw IRParseException("vm currently does not support real inlined assembly (only IR): ${asmChunk.label}") - } - } }