diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index 51f6d408e..f44d15ce9 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -4,6 +4,6 @@
-
+
\ No newline at end of file
diff --git a/codeCore/src/prog8/code/core/MemoryRegions.kt b/codeCore/src/prog8/code/core/MemoryRegions.kt
index dab32a398..2726eee4b 100644
--- a/codeCore/src/prog8/code/core/MemoryRegions.kt
+++ b/codeCore/src/prog8/code/core/MemoryRegions.kt
@@ -131,6 +131,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
}
+// TODO: this class is not yet used
class GoldenRam(options: CompilationOptions, val region: UIntRange): MemoryAllocator(options) {
private var nextLocation: UInt = region.first
diff --git a/codeCore/src/prog8/code/target/cx16/CX16MachineDefinition.kt b/codeCore/src/prog8/code/target/cx16/CX16MachineDefinition.kt
index 2ed4ea7c2..34b63c252 100644
--- a/codeCore/src/prog8/code/target/cx16/CX16MachineDefinition.kt
+++ b/codeCore/src/prog8/code/target/cx16/CX16MachineDefinition.kt
@@ -64,7 +64,7 @@ class CX16MachineDefinition: IMachineDefinition {
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = CX16Zeropage(compilerOptions)
- golden = GoldenRam(compilerOptions, 0x0600u until 0x0800u)
+ golden = GoldenRam(compilerOptions, 0x0400u until ESTACK_LO)
}
}
diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt
index ac3430db8..3051c496f 100644
--- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt
+++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt
@@ -1465,9 +1465,12 @@ class IRCodeGen(
}
} else {
// regular asmsub
- val assemblyChild = child.children.single() as PtInlineAssembly
+ if(child.children.map { (it as PtInlineAssembly).isIR }.toSet().size>1)
+ errors.err("asmsub mixes IR and non-IR assembly code (could be compiler-generated)", child.position)
+ val asmblocks = child.children.map { (it as PtInlineAssembly).assembly.trimEnd() }
+ val assembly = asmblocks.joinToString("\n")
val asmChunk = IRInlineAsmChunk(
- child.name, assemblyChild.assembly, assemblyChild.isIR, null
+ child.name, assembly, (child.children[0] as PtInlineAssembly).isIR , null
)
irBlock += IRAsmSubroutine(
child.name,
diff --git a/codeGenIntermediate/test/TestVmCodeGen.kt b/codeGenIntermediate/test/TestVmCodeGen.kt
index cac64f2a4..c75e06057 100644
--- a/codeGenIntermediate/test/TestVmCodeGen.kt
+++ b/codeGenIntermediate/test/TestVmCodeGen.kt
@@ -8,6 +8,7 @@ import prog8.code.target.VMTarget
import prog8.codegen.vm.VmAssemblyProgram
import prog8.codegen.vm.VmCodeGen
import prog8.intermediate.IRSubroutine
+import prog8.intermediate.Opcode
class TestVmCodeGen: FunSpec({
@@ -440,4 +441,33 @@ class TestVmCodeGen: FunSpec({
irChunks.size shouldBe 1
}
+ test("romsub allowed in codegen") {
+//main {
+// romsub $5000 = routine()
+//
+// sub start() {
+// routine()
+// }
+//}
+ val codegen = VmCodeGen()
+ val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
+ val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
+ val romsub = PtAsmSub("routine", 0x5000u, emptySet(), emptyList(), emptyList(), false, Position.DUMMY)
+ block.add(romsub)
+ val sub = PtSub("start", emptyList(), null, Position.DUMMY)
+ val call = PtFunctionCall("main.routine", true, DataType.UNDEFINED, Position.DUMMY)
+ sub.add(call)
+ block.add(sub)
+ program.add(block)
+
+ val options = getTestOptions()
+ val st = SymbolTableMaker(program, options).make()
+ val errors = ErrorReporterForTests()
+ val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
+ val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
+ irChunks.size shouldBe 1
+ val callInstr = irChunks.single().instructions.single()
+ callInstr.opcode shouldBe Opcode.CALL
+ callInstr.value shouldBe 0x5000
+ }
})
\ No newline at end of file
diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt
index 1d4ee27eb..55f3a708c 100644
--- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt
+++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt
@@ -326,8 +326,6 @@ internal class AstChecker(private val program: Program,
err("subroutines can only be defined in the scope of a block or within another subroutine")
if(subroutine.isAsmSubroutine) {
- if(compilerOptions.compTarget.name==VMTarget.NAME)
- err("cannot use asmsub for vm target, use regular subs")
if(subroutine.asmParameterRegisters.size != subroutine.parameters.size)
err("number of asm parameter registers is not the isSameAs as number of parameters")
if(subroutine.asmReturnvaluesRegisters.size != subroutine.returntypes.size)
diff --git a/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt b/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt
index 152ef229e..ca7bfe266 100644
--- a/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt
+++ b/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt
@@ -355,9 +355,9 @@ class IntermediateAstMaker(private val program: Program, private val options: Co
for (asm in srcSub.statements) {
asm as InlineAssembly
if(asm.isIR)
- combinedIrAsm += asm.assembly + "\n"
+ combinedIrAsm += asm.assembly.trimEnd() + "\n"
else
- combinedTrueAsm += asm.assembly + "\n"
+ combinedTrueAsm += asm.assembly.trimEnd() + "\n"
}
if(combinedTrueAsm.isNotEmpty()) {
diff --git a/compiler/test/vm/TestCompilerVirtual.kt b/compiler/test/vm/TestCompilerVirtual.kt
index 4cdb4d93b..11b83ca78 100644
--- a/compiler/test/vm/TestCompilerVirtual.kt
+++ b/compiler/test/vm/TestCompilerVirtual.kt
@@ -14,7 +14,6 @@ import prog8.intermediate.IRFileReader
import prog8.intermediate.IRSubroutine
import prog8.intermediate.Opcode
import prog8.vm.VmRunner
-import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
import kotlin.io.path.readText
@@ -219,54 +218,6 @@ main {
}
}
- test("asmsub for virtual target not supported") {
- val src = """
-main {
- sub start() {
- void test(42)
- }
-
- asmsub test(ubyte xx @A) -> ubyte @Y {
- %asm {{
- lda #99
- tay
- rts
- }}
- }
-}"""
- val othertarget = Cx16Target()
- compileText(othertarget, true, src, writeAssembly = true) shouldNotBe null
-
- val target = VMTarget()
- val errors = ErrorReporterForTests()
- compileText(target, false, src, writeAssembly = false, errors = errors) shouldBe null
- errors.errors.size shouldBe 1
- errors.errors[0] shouldContain "cannot use asmsub for vm target"
- }
-
- test("asmsub for virtual target not supported even with IR") {
- val src = """
-main {
- sub start() {
- void test(42)
- }
-
- asmsub test(ubyte xx @A) -> ubyte @Y {
- %ir {{
- return
- }}
- }
-}"""
- val othertarget = Cx16Target()
- compileText(othertarget, true, src, writeAssembly = true) shouldNotBe null
-
- val target = VMTarget()
- val errors = ErrorReporterForTests()
- compileText(target, false, src, writeAssembly = false, errors = errors) shouldBe null
- errors.errors.size shouldBe 1
- errors.errors[0] shouldContain "cannot use asmsub for vm target"
- }
-
test("inline asm for virtual target should be IR") {
val src = """
main {
diff --git a/docs/source/todo.rst b/docs/source/todo.rst
index 1c58e74c0..d3373db59 100644
--- a/docs/source/todo.rst
+++ b/docs/source/todo.rst
@@ -40,6 +40,9 @@ Compiler:
- ir: peephole opt: 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)
+- ir: write arguments for subroutine calls in IR differently?
+ don't load them explicitly into the variables but use a new special instruction to do it transparently.
+ (vm should take care of it based on the subroutine's parameter list)
- PtAst/IR: more complex common subexpression eliminations
- vm: somehow be able to load a label address as value? (VmProgramLoader) this may require storing the program as bytecodes in actual memory though...
- 6502 codegen: see if we can let for loops skip the loop if startvar>endvar, without adding a lot of code size/duplicating the loop condition.
diff --git a/intermediate/src/prog8/intermediate/IRProgram.kt b/intermediate/src/prog8/intermediate/IRProgram.kt
index 2bd682483..47b33c650 100644
--- a/intermediate/src/prog8/intermediate/IRProgram.kt
+++ b/intermediate/src/prog8/intermediate/IRProgram.kt
@@ -414,11 +414,14 @@ private fun registersUsedInAssembly(isIR: Boolean, assembly: String): RegistersU
if(isIR) {
assembly.lineSequence().forEach { line ->
- val result = parseIRCodeLine(line.trim(), null, mutableMapOf())
- result.fold(
- ifLeft = { it.addUsedRegistersCounts(readRegsCounts, writeRegsCounts,readFpRegsCounts, writeFpRegsCounts, regsTypes) },
- ifRight = { /* labels can be skipped */ }
- )
+ val t = line.trim()
+ if(t.isNotEmpty()) {
+ val result = parseIRCodeLine(t, null, mutableMapOf())
+ result.fold(
+ ifLeft = { it.addUsedRegistersCounts(readRegsCounts, writeRegsCounts,readFpRegsCounts, writeFpRegsCounts, regsTypes) },
+ ifRight = { /* labels can be skipped */ }
+ )
+ }
}
}
return RegistersUsed(readRegsCounts, writeRegsCounts, readFpRegsCounts, writeFpRegsCounts, regsTypes)
diff --git a/virtualmachine/src/prog8/vm/VirtualMachine.kt b/virtualmachine/src/prog8/vm/VirtualMachine.kt
index 2e0771a8c..58e9685dd 100644
--- a/virtualmachine/src/prog8/vm/VirtualMachine.kt
+++ b/virtualmachine/src/prog8/vm/VirtualMachine.kt
@@ -1,6 +1,7 @@
package prog8.vm
import prog8.code.StMemVar
+import prog8.code.core.toHex
import prog8.code.target.virtual.IVirtualMachineRunner
import prog8.intermediate.*
import java.awt.Color
@@ -147,7 +148,12 @@ class VirtualMachine(irProgram: IRProgram) {
pcChunk = target
pcIndex = 0
}
- null -> throw IllegalArgumentException("no branchtarget in $i")
+ null -> {
+ if(i.value!=null)
+ throw IllegalArgumentException("vm program can't jump to system memory address (${i.opcode} ${i.value!!.toHex()})")
+ else
+ throw IllegalArgumentException("no branchtarget in $i")
+ }
else -> throw IllegalArgumentException("VM can't execute code in a non-codechunk: $target")
}
}
diff --git a/virtualmachine/src/prog8/vm/VmProgramLoader.kt b/virtualmachine/src/prog8/vm/VmProgramLoader.kt
index b7d2b55c0..205a284e2 100644
--- a/virtualmachine/src/prog8/vm/VmProgramLoader.kt
+++ b/virtualmachine/src/prog8/vm/VmProgramLoader.kt
@@ -69,7 +69,7 @@ class VmProgramLoader {
programChunks.forEach {
it.instructions.forEach { ins ->
- if (ins.labelSymbol != null && ins.opcode !in OpcodesThatBranch)
+ if (ins.labelSymbol != null && ins.opcode !in OpcodesThatBranch && ins.opcode !in OpcodesForCpuRegisters)
require(ins.value != null) { "instruction with labelSymbol for a var should have value set to var's memory address" }
}
}
@@ -153,8 +153,11 @@ class VmProgramLoader {
// placeholder is not a variable, so it must be a label of a code chunk instead
val target: IRCodeChunk? = chunks.firstOrNull { it.label==label }
val opcode = chunk.instructions[line].opcode
- if(target==null)
- throw IRParseException("placeholder not found in variables nor labels: $label")
+ if(target==null) {
+ // exception allowed: storecpu/loadcpu instructions that refer to CPU registers
+ if(opcode !in OpcodesForCpuRegisters)
+ throw IRParseException("placeholder not found in variables nor labels: $label")
+ }
else if(opcode in OpcodesThatBranch) {
chunk.instructions[line] = chunk.instructions[line].copy(branchTarget = target, value = null)
} else if(opcode in OpcodesWithMemoryAddressAsValue) {