mirror of
https://github.com/irmen/prog8.git
synced 2024-12-24 01:29:28 +00:00
Merge branch 'master' into version_9
# Conflicts: # docs/source/todo.rst # examples/test.p8
This commit is contained in:
commit
1e469b3b0f
@ -63,7 +63,7 @@ class PtProgram(
|
||||
children.asSequence().filterIsInstance<PtBlock>()
|
||||
|
||||
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?
|
||||
}
|
||||
|
||||
|
||||
|
@ -53,6 +53,7 @@ class IRCodeGen(
|
||||
replaceMemoryMappedVars(irProg)
|
||||
ensureFirstChunkLabels(irProg)
|
||||
irProg.linkChunks()
|
||||
irProg.convertAsmChunks()
|
||||
|
||||
val optimizer = IRPeepholeOptimizer(irProg)
|
||||
optimizer.optimize(options.optimize, errors)
|
||||
|
@ -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<Exception> {
|
||||
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") {
|
||||
|
@ -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
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
----------
|
||||
|
@ -170,7 +170,10 @@ class IRProgram(val name: String,
|
||||
fun validate() {
|
||||
blocks.forEach { block ->
|
||||
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 ->
|
||||
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<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(
|
||||
@ -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 */ }
|
||||
|
@ -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<IRCodeChunk, Int>?, placeholders: MutableMap<Pair<IRCodeChunk, Int>, 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.
|
||||
fun parseIRCodeLine(line: String): Either<IRInstruction, String> {
|
||||
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<IRCodeChunk, Int>?, placeholder
|
||||
var address: Int? = null
|
||||
var labelSymbol: String? = null
|
||||
|
||||
fun parseValueOrPlaceholder(operand: String, location: Pair<IRCodeChunk, Int>?): 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<IRCodeChunk, Int>?, 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()
|
||||
}
|
||||
|
@ -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<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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user