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 dd445fcc6..a85b4ee54 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/examples/test.p8 b/examples/test.p8 index e68d2405f..6eba62776 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,24 +1,25 @@ %import textio -%option no_sysinit +%import string %zeropage basicsafe main { - ; TODO - asmsub derp(bool flag @Pc) -> bool @Pc { - %asm {{ - bcc + - lda #'1' - jmp cbm.CHROUT -+ lda #'0' - jmp cbm.CHROUT - }} - } + sub start() { - ubyte xx = 0 - ubyte yy=0 - void derp(xx+1) - void derp(xx+42) - void derp(xx-yy) + 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 c830135d3..c099349ea 100644 --- a/intermediate/src/prog8/intermediate/IRInstructions.kt +++ b/intermediate/src/prog8/intermediate/IRInstructions.kt @@ -27,6 +27,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}") - } - } }