mirror of
https://github.com/irmen/prog8.git
synced 2025-01-12 04:30:03 +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>()
|
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?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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") {
|
||||||
|
@ -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
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
----------
|
----------
|
||||||
|
@ -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 */ }
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user