Merge branch 'master' into version_9

# Conflicts:
#	docs/source/todo.rst
#	examples/test.p8
This commit is contained in:
Irmen de Jong 2023-05-09 22:45:21 +02:00
commit 1e469b3b0f
9 changed files with 107 additions and 67 deletions

View File

@ -63,7 +63,7 @@ class PtProgram(
children.asSequence().filterIsInstance<PtBlock>() children.asSequence().filterIsInstance<PtBlock>()
fun entrypoint(): PtSub? = 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?
} }

View File

@ -53,6 +53,7 @@ class IRCodeGen(
replaceMemoryMappedVars(irProg) replaceMemoryMappedVars(irProg)
ensureFirstChunkLabels(irProg) ensureFirstChunkLabels(irProg)
irProg.linkChunks() irProg.linkChunks()
irProg.convertAsmChunks()
val optimizer = IRPeepholeOptimizer(irProg) val optimizer = IRPeepholeOptimizer(irProg)
optimizer.optimize(options.optimize, errors) optimizer.optimize(options.optimize, errors)

View File

@ -5,6 +5,7 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.string.shouldNotContain
import prog8.ast.expressions.BuiltinFunctionCall import prog8.ast.expressions.BuiltinFunctionCall
import prog8.ast.statements.Assignment import prog8.ast.statements.Assignment
import prog8.code.target.C64Target import prog8.code.target.C64Target
@ -244,25 +245,26 @@ main {
val exc = shouldThrow<Exception> { val exc = shouldThrow<Exception> {
VmRunner().runProgram(virtfile.readText()) 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 = """ val src = """
main { main {
sub start() { sub start() {
%ir {{ %ir {{
loadr.b r1,r2
return return
}} }}
} }
}""" }"""
val othertarget = Cx16Target()
compileText(othertarget, true, src, writeAssembly = true) shouldNotBe null
val target = VMTarget() val target = VMTarget()
val result = compileText(target, false, src, writeAssembly = true)!! val result = compileText(target, false, src, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir") 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") { test("addresses from labels/subroutines not yet supported in VM") {

View File

@ -1,24 +1,25 @@
%import textio %import textio
%option no_sysinit %import string
%zeropage basicsafe %zeropage basicsafe
main { main {
; TODO
asmsub derp(bool flag @Pc) -> bool @Pc {
%asm {{
bcc +
lda #'1'
jmp cbm.CHROUT
+ lda #'0'
jmp cbm.CHROUT
}}
}
sub start() { sub start() {
ubyte xx = 0 uword seconds_uword = 1
ubyte yy=0 uword remainder = seconds_uword % $0003 ==0
void derp(xx+1) txt.print_uw(remainder)
void derp(xx+42)
void derp(xx-yy) blerp()
}
sub blerp() {
%ir {{
_xxx:
loadr r2,r3
_yyy:
loadr r3,r4
return
}}
} }
} }

View File

@ -68,6 +68,7 @@ class IRFileReader {
blocks.forEach{ program.addBlock(it) } blocks.forEach{ program.addBlock(it) }
program.linkChunks() program.linkChunks()
program.convertAsmChunks()
program.validate() program.validate()
return program return program
@ -302,7 +303,7 @@ class IRFileReader {
if(text.isNotBlank()) { if(text.isNotBlank()) {
text.lineSequence().forEach { line -> text.lineSequence().forEach { line ->
if (line.isNotBlank() && !line.startsWith(';')) { if (line.isNotBlank() && !line.startsWith(';')) {
val result = parseIRCodeLine(line, null, mutableMapOf()) val result = parseIRCodeLine(line)
result.fold( result.fold(
ifLeft = { ifLeft = {
chunk += it chunk += it

View File

@ -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). 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. 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 LOAD/STORE
---------- ----------

View File

@ -170,7 +170,10 @@ class IRProgram(val name: String,
fun validate() { fun validate() {
blocks.forEach { block -> blocks.forEach { block ->
if(block.isNotEmpty()) { if(block.isNotEmpty()) {
block.children.filterIsInstance<IRInlineAsmChunk>().forEach { chunk -> require(chunk.instructions.isEmpty()) } block.children.filterIsInstance<IRInlineAsmChunk>().forEach { chunk ->
require(chunk.instructions.isEmpty())
require(!chunk.isIR) { "inline IR-asm should have been converted into regular code chunk"}
}
block.children.filterIsInstance<IRSubroutine>().forEach { sub -> block.children.filterIsInstance<IRSubroutine>().forEach { sub ->
if(sub.chunks.isNotEmpty()) { if(sub.chunks.isNotEmpty()) {
require(sub.chunks.first().label == sub.label) { "first chunk in subroutine should have sub name (label) as its label" } 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) { if (chunk is IRCodeChunk) {
require(chunk.instructions.isNotEmpty() || chunk.label != null) require(chunk.instructions.isNotEmpty() || chunk.label != null)
if(chunk.instructions.lastOrNull()?.opcode in OpcodesThatJump) 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 { else {
// if chunk is NOT the last in the block, it needs to link to next. // if chunk is NOT the last in the block, it needs to link to next.
val isLast = sub.chunks.last() === chunk val isLast = sub.chunks.last() === chunk
require(isLast || chunk.next != null) { "chunk needs to be linked to next" } require(isLast || chunk.next != null) { "chunk needs to be linked to next" }
} }
} }
else else {
require(chunk.instructions.isEmpty()) 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 { chunk.instructions.forEach {
if(it.labelSymbol!=null && it.opcode in OpcodesThatBranch) if(it.labelSymbol!=null && it.opcode in OpcodesThatBranch)
require(it.branchTarget != null) { "branching instruction to label should have branchTarget set" } 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) return RegistersUsed(readRegsCounts, writeRegsCounts, readFpRegsCounts, writeFpRegsCounts, regsTypes)
} }
fun convertAsmChunks() {
fun convert(asmChunk: IRInlineAsmChunk): IRCodeChunks {
val chunks = mutableListOf<IRCodeChunkBase>()
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<Pair<IRCodeChunkBase, IRCodeChunks>>()
block.children.filterIsInstance<IRInlineAsmChunk>().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<IRSubroutine>().forEach { sub ->
val chunkReplacementsInSub = mutableListOf<Pair<IRCodeChunkBase, IRCodeChunks>>()
sub.chunks.filterIsInstance<IRInlineAsmChunk>().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( class IRBlock(
@ -418,7 +483,7 @@ private fun registersUsedInAssembly(isIR: Boolean, assembly: String): RegistersU
assembly.lineSequence().forEach { line -> assembly.lineSequence().forEach { line ->
val t = line.trim() val t = line.trim()
if(t.isNotEmpty()) { if(t.isNotEmpty()) {
val result = parseIRCodeLine(t, null, mutableMapOf()) val result = parseIRCodeLine(t)
result.fold( result.fold(
ifLeft = { it.addUsedRegistersCounts(readRegsCounts, writeRegsCounts,readFpRegsCounts, writeFpRegsCounts, regsTypes) }, ifLeft = { it.addUsedRegistersCounts(readRegsCounts, writeRegsCounts,readFpRegsCounts, writeFpRegsCounts, regsTypes) },
ifRight = { /* labels can be skipped */ } ifRight = { /* labels can be skipped */ }

View File

@ -78,9 +78,7 @@ fun parseIRValue(value: String): Float {
private val instructionPattern = Regex("""([a-z]+)(\.b|\.w|\.f)?(.*)""", RegexOption.IGNORE_CASE) private val instructionPattern = Regex("""([a-z]+)(\.b|\.w|\.f)?(.*)""", RegexOption.IGNORE_CASE)
private val labelPattern = Regex("""_([a-zA-Z\d\._]+):""") private val labelPattern = Regex("""_([a-zA-Z\d\._]+):""")
fun parseIRCodeLine(line: String, location: Pair<IRCodeChunk, Int>?, placeholders: MutableMap<Pair<IRCodeChunk, Int>, String>): Either<IRInstruction, String> { fun parseIRCodeLine(line: String): Either<IRInstruction, String> {
// 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.
val labelmatch = labelPattern.matchEntire(line.trim()) val labelmatch = labelPattern.matchEntire(line.trim())
if(labelmatch!=null) if(labelmatch!=null)
return right(labelmatch.groupValues[1]) // it's a label. return right(labelmatch.groupValues[1]) // it's a label.
@ -117,10 +115,8 @@ fun parseIRCodeLine(line: String, location: Pair<IRCodeChunk, Int>?, placeholder
var address: Int? = null var address: Int? = null
var labelSymbol: String? = null var labelSymbol: String? = null
fun parseValueOrPlaceholder(operand: String, location: Pair<IRCodeChunk, Int>?): Float? { fun parseValueOrPlaceholder(operand: String): Float? {
return if(operand[0].isLetter()) { return if(operand[0].isLetter()) {
if(location!=null)
placeholders[location] = operand
null null
} else { } else {
parseIRValue(operand) parseIRValue(operand)
@ -156,7 +152,7 @@ fun parseIRCodeLine(line: String, location: Pair<IRCodeChunk, Int>?, placeholder
if(!oper[0].isLetter()) if(!oper[0].isLetter())
throw IRParseException("expected symbol name: $oper") throw IRParseException("expected symbol name: $oper")
labelSymbol = oper labelSymbol = oper
val value = parseValueOrPlaceholder(oper, location) val value = parseValueOrPlaceholder(oper)
if(value!=null) if(value!=null)
address = value.toInt() address = value.toInt()
} }

View File

@ -44,20 +44,14 @@ class VmProgramLoader {
when(child) { when(child) {
is IRAsmSubroutine -> throw IRParseException("vm does not support asmsubs (use normal sub): ${child.label}") is IRAsmSubroutine -> throw IRParseException("vm does not support asmsubs (use normal sub): ${child.label}")
is IRCodeChunk -> programChunks += child is IRCodeChunk -> programChunks += child
is IRInlineAsmChunk -> { is IRInlineAsmChunk -> throw IRParseException("encountered unconverted inline assembly chunk")
val replacement = addAssemblyToProgram(child, programChunks, variableAddresses) is IRInlineBinaryChunk -> throw IRParseException("inline binary data not yet supported in the VM")
chunkReplacements += Pair(child, replacement)
}
is IRInlineBinaryChunk -> throw IRParseException("inline binary data not yet supported in the VM") // TODO
is IRSubroutine -> { is IRSubroutine -> {
subroutines[child.label] = child subroutines[child.label] = child
child.chunks.forEach { chunk -> child.chunks.forEach { chunk ->
when (chunk) { when (chunk) {
is IRInlineAsmChunk -> { is IRInlineAsmChunk -> throw IRParseException("encountered unconverted inline assembly chunk")
val replacement = addAssemblyToProgram(chunk, programChunks, variableAddresses) is IRInlineBinaryChunk -> throw IRParseException("inline binary data not yet supported in the VM")
chunkReplacements += Pair(chunk, replacement)
}
is IRInlineBinaryChunk -> throw IRParseException("inline binary data not yet supported in the VM") // TODO
is IRCodeChunk -> programChunks += chunk is IRCodeChunk -> programChunks += chunk
else -> throw AssemblyError("weird chunk type") 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." } require(variable.onetimeInitializationStringValue==null) { "in vm/ir, strings should have been converted into bytearrays." }
} }
} }
private fun addAssemblyToProgram(
asmChunk: IRInlineAsmChunk,
chunks: MutableList<IRCodeChunk>,
symbolAddresses: MutableMap<String, Int>,
): 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}")
}
}
} }