Compare commits

...

40 Commits
v8.12 ... v8.14

Author SHA1 Message Date
e55a675d2e release 8.14 2023-05-29 23:00:43 +02:00
6e07602d77 fix psg initial envelope maxvol setting 2023-05-28 22:30:34 +02:00
0eb2d437e2 fix compiler error and codegen fault on signed value bitwise operation 2023-05-28 13:13:11 +02:00
d9e13201dd fix kotlin version IDE warning 2023-05-26 19:14:19 +02:00
48864ad6cf add a unit test that checks for 64tass availability 2023-05-23 20:42:36 +02:00
b5255444cd irq-safe irqd handling for RDTIM16 2023-05-22 20:36:33 +02:00
8e5c67b4b2 ir: don't refuse complicated array lookup expressions 2023-05-21 16:07:19 +02:00
c69c17de42 cx16 avoid ram bank issue with RDTIM in sys.wait() and c64.RDTIM16() 2023-05-21 15:03:33 +02:00
125ce3240f expr operands assignment refactor 2023-05-20 18:04:46 +02:00
7215efe167 fix expr eval error in certain situations
such as pokew() with 2 complex operands
2023-05-20 17:42:35 +02:00
7ceb76cff5 fix compiler crash on certain operands type mismatch 2023-05-18 22:46:00 +02:00
7e734214dc v8_maintenance branch made 2023-05-15 23:01:43 +02:00
dea7f37553 vm: fix % result when dividing by 0 2023-05-15 20:33:20 +02:00
415c599310 update cx16 keyhandler example to r43 keyboard changes 2023-05-14 23:38:16 +02:00
70cd4fedbe Revert "update cx16 keyhandler example to r43 keyboard changes"
This reverts commit 1e6d7673bc.
2023-05-14 23:29:04 +02:00
1e6d7673bc update cx16 keyhandler example to r43 keyboard changes 2023-05-14 23:11:24 +02:00
0371ffa4ce 'amiga' example using iso font 2023-05-14 21:55:35 +02:00
88ce9300bc fix parse cpureg in IR regspec 2023-05-14 21:02:40 +02:00
0e3d75cfeb move irType() to intermediate module 2023-05-14 20:44:32 +02:00
630c8a5faa IR: fix romsub encoding 2023-05-14 18:08:06 +02:00
905921a684 IR: new (sys)call instructions that encapsulate the full subroutine call
to fix the bugs resulting from nesting subroutine calls (as param to another call etc)
2023-05-14 15:20:25 +02:00
bff3c4f95c IR now converts IRInlineAsmChunk (of type IR) into regular code chunks directly.
.p8ir files usually won't contain <INLINEASM> nodes any longer
2023-05-09 21:04:31 +02:00
4c8898a639 fix typecheck crash on certain byte to word assignments 2023-05-08 23:02:48 +02:00
97df33ab1a IR: fix byte to word assignment not doing value extension 2023-05-08 22:47:00 +02:00
ef46fb2685 refactor 2023-05-08 21:51:55 +02:00
d5d6dd3614 optimize typecast expr 2023-05-08 03:30:14 +02:00
6c233c6a0a optimize add/sub expr 2023-05-08 02:41:34 +02:00
6db715d879 optimize multiplication expr 2023-05-08 02:10:54 +02:00
ab02e8a546 optimize more carry flag assembly 2023-05-07 23:55:34 +02:00
8cbfe64f19 optimize some carry flag assembly 2023-05-07 23:27:49 +02:00
68336a76c5 optimized word comparison expressions 2023-05-07 20:40:48 +02:00
393e914a86 optimized word equality comparison expressions 2023-05-07 18:55:17 +02:00
ffb54110e9 optimized byte comparison expressions 2023-05-07 15:15:58 +02:00
533d825f1a optimized ubyte comparison expressions 2023-05-07 14:47:31 +02:00
c65279b672 optimized logical expressions more 2023-05-07 13:29:45 +02:00
f9926beeef fix cx16.psg irq issue 2023-05-04 00:16:24 +02:00
add8a777d8 IR: binarydata fixes 2023-05-03 22:31:04 +02:00
3fc49c001e IR: fix for-loop codegen when step<0 2023-05-02 23:12:11 +02:00
24f37e2062 fix 2023-05-02 01:19:36 +02:00
f465b2e2a0 some improvements to IR peephole optimizer 2023-05-02 00:29:04 +02:00
50 changed files with 1690 additions and 1364 deletions

2
.idea/kotlinc.xml generated
View File

@ -4,6 +4,6 @@
<option name="jvmTarget" value="11" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.8.20-release-327" />
<option name="version" value="1.8.21-release-380" />
</component>
</project>

View File

@ -235,7 +235,7 @@ class StSub(name: String, val parameters: List<StSubroutineParameter>, val retur
class StRomSub(name: String,
val address: UInt,
val address: UInt?, // null in case of asmsub, specified in case of romsub
val parameters: List<StRomSubParameter>,
val returns: List<StRomSubParameter>,
astNode: PtNode) :

View File

@ -40,14 +40,9 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
private fun addToSt(node: PtNode, scope: Stack<StNode>) {
val stNode = when(node) {
is PtAsmSub -> {
if(node.address==null) {
val params = node.parameters.map { StSubroutineParameter(it.second.name, it.second.type) }
StSub(node.name, params, node.returns.singleOrNull()?.second, node)
} else {
val parameters = node.parameters.map { StRomSubParameter(it.first, it.second.type) }
val returns = node.returns.map { StRomSubParameter(it.first, it.second) }
StRomSub(node.name, node.address, parameters, returns, node)
}
val parameters = node.parameters.map { StRomSubParameter(it.first, it.second.type) }
val returns = node.returns.map { StRomSubParameter(it.first, it.second) }
StRomSub(node.name, node.address, parameters, returns, node)
}
is PtBlock -> {
StNode(node.name, StNodeType.BLOCK, node)

View File

@ -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?
}

View File

@ -1639,14 +1639,7 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
if(left.isSimple()) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.UBYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
out(" pla")
}
assignByteOperandsToAAndVar(left, right, "P8ZP_SCRATCH_B1")
return code("P8ZP_SCRATCH_B1")
}
@ -1680,14 +1673,7 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
if(left.isSimple()) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.UBYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
out(" pla")
}
assignByteOperandsToAAndVar(left, right, "P8ZP_SCRATCH_B1")
return code("P8ZP_SCRATCH_B1")
}
@ -1723,15 +1709,7 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
if(left.isSimple()) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD)
assignExpressionToRegister(left, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.UWORD, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
assignWordOperandsToAYAndVar(left, right, "P8ZP_SCRATCH_W2")
return out(" jsr prog8_lib.reg_less_uw | beq $jumpIfFalseLabel")
}
@ -1769,15 +1747,7 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
if(left.isSimple()) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.WORD)
assignExpressionToRegister(left, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.WORD, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.WORD)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
assignWordOperandsToAYAndVar(left, right, "P8ZP_SCRATCH_W2")
return out(" jsr prog8_lib.reg_less_w | beq $jumpIfFalseLabel")
}
@ -1816,14 +1786,7 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
if(left.isSimple()) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.UBYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
out(" pla")
}
assignByteOperandsToAAndVar(left, right, "P8ZP_SCRATCH_B1")
return code("P8ZP_SCRATCH_B1")
}
@ -1859,14 +1822,7 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
if(left.isSimple()) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.BYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE)
out(" pla")
}
assignByteOperandsToAAndVar(left, right, "P8ZP_SCRATCH_B1")
return code("P8ZP_SCRATCH_B1")
}
@ -1908,15 +1864,7 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
if(left.isSimple()) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD)
assignExpressionToRegister(left, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.UWORD, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
assignWordOperandsToAYAndVar(left, right, "P8ZP_SCRATCH_W2")
return code("P8ZP_SCRATCH_W2+1", "P8ZP_SCRATCH_W2")
}
@ -1959,15 +1907,7 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleLeftOperand(left, right, ::code))
return
if(right.isSimple()) {
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.WORD)
assignExpressionToRegister(right, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.WORD, right)
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.WORD)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
assignWordOperandsToAYAndVar(right, left, "P8ZP_SCRATCH_W2")
return out(" jsr prog8_lib.reg_less_w | beq $jumpIfFalseLabel")
}
@ -2007,14 +1947,7 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
if(left.isSimple()) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.UBYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
out(" pla")
}
assignByteOperandsToAAndVar(left, right, "P8ZP_SCRATCH_B1")
return code("P8ZP_SCRATCH_B1")
}
@ -2050,14 +1983,7 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
if(left.isSimple()) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.BYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE)
out(" pla")
}
assignByteOperandsToAAndVar(left, right, "P8ZP_SCRATCH_B1")
return code("P8ZP_SCRATCH_B1")
}
@ -2101,15 +2027,7 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
if(left.isSimple()) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD)
assignExpressionToRegister(left, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.UWORD, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.UWORD)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
assignWordOperandsToAYAndVar(left, right, "P8ZP_SCRATCH_W2")
return out(" jsr prog8_lib.reg_lesseq_uw | beq $jumpIfFalseLabel")
}
@ -2156,15 +2074,7 @@ $repeatLabel lda $counterVar
return code(asmVariableName(left))
}
if(right.isSimple()) {
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.WORD)
assignExpressionToRegister(right, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.WORD, right)
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.WORD)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
assignWordOperandsToAYAndVar(right, left, "P8ZP_SCRATCH_W2")
return out(" jsr prog8_lib.reg_lesseq_w | beq $jumpIfFalseLabel")
}
@ -2200,14 +2110,7 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
if(left.isSimple()) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.UBYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
out(" pla")
}
assignByteOperandsToAAndVar(left, right, "P8ZP_SCRATCH_B1")
return code("P8ZP_SCRATCH_B1")
}
@ -2240,15 +2143,31 @@ $repeatLabel lda $counterVar
if(byteJumpForSimpleRightOperand(left, right, ::code))
return
assignByteOperandsToAAndVar(left, right, "P8ZP_SCRATCH_B1")
return code("P8ZP_SCRATCH_B1")
}
internal fun assignByteOperandsToAAndVar(left: PtExpression, right: PtExpression, rightVarName: String) {
if(left.isSimple()) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE)
assignExpressionToVariable(right, rightVarName, DataType.UBYTE)
assignExpressionToRegister(left, RegisterOrPair.A)
} else {
pushCpuStack(DataType.BYTE, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.BYTE)
pushCpuStack(DataType.UBYTE, left)
assignExpressionToVariable(right, rightVarName, DataType.UBYTE)
out(" pla")
}
return code("P8ZP_SCRATCH_B1")
}
internal fun assignWordOperandsToAYAndVar(left: PtExpression, right: PtExpression, rightVarname: String) {
if(left.isSimple()) {
assignExpressionToVariable(right, rightVarname, DataType.UWORD)
assignExpressionToRegister(left, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.UWORD, left)
assignExpressionToVariable(right, rightVarname, DataType.UWORD)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
}
private fun translateUwordGreaterOrEqualOrJumpElsewhere(left: PtExpression, right: PtExpression, leftConstVal: PtNumber?, rightConstVal: PtNumber?, jumpIfFalseLabel: String) {
@ -2282,15 +2201,7 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
if(right.isSimple()) {
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD)
assignExpressionToRegister(right, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.UWORD, right)
assignExpressionToVariable(left, "P8ZP_SCRATCH_W2", DataType.UWORD)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
assignWordOperandsToAYAndVar(right, left, "P8ZP_SCRATCH_W2")
return out(" jsr prog8_lib.reg_lesseq_uw | beq $jumpIfFalseLabel")
}
@ -2328,15 +2239,7 @@ $repeatLabel lda $counterVar
if(wordJumpForSimpleRightOperands(left, right, ::code))
return
if(left.isSimple()) {
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.WORD)
assignExpressionToRegister(left, RegisterOrPair.AY)
} else {
pushCpuStack(DataType.WORD, left)
assignExpressionToVariable(right, "P8ZP_SCRATCH_W2", DataType.WORD)
restoreRegisterStack(CpuRegister.Y, false)
restoreRegisterStack(CpuRegister.A, false)
}
assignWordOperandsToAYAndVar(left, right, "P8ZP_SCRATCH_W2")
return out(" jsr prog8_lib.reg_lesseq_w | beq $jumpIfFalseLabel")
}

View File

@ -96,8 +96,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
private fun funcDivmodW(fcall: PtBuiltinFunctionCall) {
assignAsmGen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", DataType.UWORD)
assignAsmGen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY, false)
asmgen.assignWordOperandsToAYAndVar(fcall.args[1], fcall.args[0], "P8ZP_SCRATCH_W1")
// math.divmod_uw_asm: -- divide two unsigned words (16 bit each) into 16 bit results
// input: P8ZP_SCRATCH_W1 in ZP: 16 bit number, A/Y: 16 bit divisor
// output: P8ZP_SCRATCH_W2 in ZP: 16 bit remainder, A/Y: 16 bit division result
@ -114,8 +113,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
private fun funcStringCompare(fcall: PtBuiltinFunctionCall, resultToStack: Boolean) {
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", DataType.UWORD)
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY, false)
asmgen.assignWordOperandsToAYAndVar(fcall.args[0], fcall.args[1], "P8ZP_SCRATCH_W2")
asmgen.out(" jsr prog8_lib.strcmp_mem")
if(resultToStack)
asmgen.out(" sta P8ESTACK_LO,x | dex")
@ -207,27 +205,13 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp ${arg2.address.asConstInteger()!!.toHex()}")
} else {
if(arg1.isSimple()) {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE)
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp P8ZP_SCRATCH_B1")
} else {
asmgen.pushCpuStack(DataType.UBYTE, arg1)
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE)
asmgen.out(" pla | cmp P8ZP_SCRATCH_B1")
}
asmgen.assignByteOperandsToAAndVar(arg1, arg2, "P8ZP_SCRATCH_B1")
asmgen.out(" cmp P8ZP_SCRATCH_B1")
}
}
else -> {
if(arg1.isSimple()) {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE)
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp P8ZP_SCRATCH_B1")
} else {
asmgen.pushCpuStack(DataType.UBYTE, arg1)
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE)
asmgen.out(" pla | cmp P8ZP_SCRATCH_B1")
}
asmgen.assignByteOperandsToAAndVar(arg1, arg2, "P8ZP_SCRATCH_B1")
asmgen.out(" cmp P8ZP_SCRATCH_B1")
}
}
} else
@ -253,25 +237,12 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
+""")
}
else -> {
if(arg1.isSimple()) {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD)
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out("""
cpy P8ZP_SCRATCH_W1+1
bne +
cmp P8ZP_SCRATCH_W1
+""")
} else {
asmgen.pushCpuStack(DataType.UWORD, arg1)
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.restoreRegisterStack(CpuRegister.A, false)
asmgen.out("""
cpy P8ZP_SCRATCH_W1+1
bne +
cmp P8ZP_SCRATCH_W1
+""")
}
asmgen.assignWordOperandsToAYAndVar(arg1, arg2, "P8ZP_SCRATCH_W1")
asmgen.out("""
cpy P8ZP_SCRATCH_W1+1
bne +
cmp P8ZP_SCRATCH_W1
+""")
}
}
} else
@ -753,8 +724,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
// fall through method:
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", DataType.UWORD)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY)
asmgen.assignWordOperandsToAYAndVar(fcall.args[1], fcall.args[0], "P8ZP_SCRATCH_W1")
asmgen.out(" jsr prog8_lib.func_pokew")
}

View File

@ -357,6 +357,15 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
}
if(expr.left.type in ByteDatatypes && expr.right.type in ByteDatatypes) {
if(assignOptimizedComparisonBytes(expr, assign))
return true
} else if(expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
if(assignOptimizedComparisonWords(expr, assign))
return true
}
val origTarget = assign.target.origAstTarget
if(origTarget!=null) {
assignConstantByte(assign.target, 0)
@ -381,13 +390,19 @@ internal class AssignmentAsmGen(private val program: PtProgram,
if(expr.type !in IntegerDatatypes)
return false
fun simpleLogicalBytesExpr() {
// both left and right expression operands are simple.
if (expr.right is PtNumber || expr.right is PtIdentifier)
assignLogicalWithSimpleRightOperandByte(assign.target, expr.left, expr.operator, expr.right)
else if (expr.left is PtNumber || expr.left is PtIdentifier)
assignLogicalWithSimpleRightOperandByte(assign.target, expr.right, expr.operator, expr.left)
else {
if(expr.operator in setOf("&", "|", "^", "and", "or", "xor")) {
if (expr.left.type in ByteDatatypes && expr.right.type in ByteDatatypes) {
if (expr.right.isSimple()) {
if (expr.right is PtNumber || expr.right is PtIdentifier) {
assignLogicalWithSimpleRightOperandByte(assign.target, expr.left, expr.operator, expr.right)
return true
}
else if (expr.left is PtNumber || expr.left is PtIdentifier) {
assignLogicalWithSimpleRightOperandByte(assign.target, expr.right, expr.operator, expr.left)
return true
}
}
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.saveRegisterStack(CpuRegister.A, false)
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
@ -399,42 +414,28 @@ internal class AssignmentAsmGen(private val program: PtProgram,
else -> throw AssemblyError("invalid operator")
}
assignRegisterByte(assign.target, CpuRegister.A, false)
return true
}
}
fun simpleLogicalWordsExpr() {
// both left and right expression operands are simple.
if (expr.right is PtNumber || expr.right is PtIdentifier)
assignLogicalWithSimpleRightOperandWord(assign.target, expr.left, expr.operator, expr.right)
else if (expr.left is PtNumber || expr.left is PtIdentifier)
assignLogicalWithSimpleRightOperandWord(assign.target, expr.right, expr.operator, expr.left)
else {
assignExpressionToRegister(expr.left, RegisterOrPair.AY, false)
asmgen.saveRegisterStack(CpuRegister.A, false)
asmgen.saveRegisterStack(CpuRegister.Y, false)
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_W1", DataType.UWORD)
else if (expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
if (expr.right.isSimple()) {
if (expr.right is PtNumber || expr.right is PtIdentifier) {
assignLogicalWithSimpleRightOperandWord(assign.target, expr.left, expr.operator, expr.right)
return true
}
else if (expr.left is PtNumber || expr.left is PtIdentifier) {
assignLogicalWithSimpleRightOperandWord(assign.target, expr.right, expr.operator, expr.left)
return true
}
}
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
when (expr.operator) {
"&", "and" -> asmgen.out(" pla | and P8ZP_SCRATCH_W1+1 | tay | pla | and P8ZP_SCRATCH_W1")
"|", "or" -> asmgen.out(" pla | ora P8ZP_SCRATCH_W1+1 | tay | pla | ora P8ZP_SCRATCH_W1")
"^", "xor" -> asmgen.out(" pla | eor P8ZP_SCRATCH_W1+1 | tay | pla | eor P8ZP_SCRATCH_W1")
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_W1 | pha | tya | and P8ZP_SCRATCH_W1+1 | tay | pla")
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_W1 | pha | tya | ora P8ZP_SCRATCH_W1+1 | tay | pla")
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_W1 | pha | tya | eor P8ZP_SCRATCH_W1+1 | tay | pla")
else -> throw AssemblyError("invalid operator")
}
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
}
}
if(expr.operator in setOf("&", "|", "^", "and", "or", "xor")) {
if (expr.left.type in ByteDatatypes && expr.right.type in ByteDatatypes) {
if (expr.right.isSimple()) {
simpleLogicalBytesExpr()
return true
}
}
if (expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
if (expr.right.isSimple()) {
simpleLogicalWordsExpr()
return true
}
return true
}
return false
}
@ -468,12 +469,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
return true
} else if(expr.left.type in WordDatatypes && expr.right.type in WordDatatypes &&
expr.left.isSimple() && expr.right.isSimple()) {
assignExpressionToRegister(expr.left, RegisterOrPair.AY, false)
asmgen.saveRegisterStack(CpuRegister.A, false)
asmgen.saveRegisterStack(CpuRegister.Y, false)
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_W1", DataType.UWORD)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.restoreRegisterStack(CpuRegister.A, false)
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
if(expr.operator=="==") {
asmgen.out("""
cmp P8ZP_SCRATCH_W1
@ -525,9 +521,44 @@ internal class AssignmentAsmGen(private val program: PtProgram,
assignRegisterByte(assign.target, CpuRegister.A, dt in SignedDatatypes)
return true
}
else -> return false
else -> {
assignExpressionToRegister(left, RegisterOrPair.A, left.type==DataType.BYTE)
asmgen.out(" pha")
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", right.type)
asmgen.out(" pla")
if(expr.operator=="+")
asmgen.out(" clc | adc P8ZP_SCRATCH_B1")
else
asmgen.out(" sec | sbc P8ZP_SCRATCH_B1")
assignRegisterByte(assign.target, CpuRegister.A, dt in SignedDatatypes)
return true
}
}
} else if(dt in WordDatatypes) {
fun doAddOrSubWordExpr() {
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
if(expr.operator=="+")
asmgen.out("""
clc
adc P8ZP_SCRATCH_W1
pha
tya
adc P8ZP_SCRATCH_W1+1
tay
pla""")
else
asmgen.out("""
sec
sbc P8ZP_SCRATCH_W1
pha
tya
sbc P8ZP_SCRATCH_W1+1
tay
pla""")
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
}
when (right) {
is PtAddressOf -> {
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
@ -603,30 +634,37 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
is PtTypeCast -> {
val castedValue = right.value
if(right.type in WordDatatypes && castedValue.type in ByteDatatypes) {
if(castedValue is PtIdentifier) {
val castedSymname = asmgen.asmVariableName(castedValue)
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
if(expr.operator=="+")
asmgen.out("""
clc
adc $castedSymname
bcc +
iny
+""")
else
asmgen.out("""
sec
sbc $castedSymname
bcs +
dey
+""")
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
return true
}
if(right.type in WordDatatypes && castedValue.type in ByteDatatypes && castedValue is PtIdentifier) {
val castedSymname = asmgen.asmVariableName(castedValue)
assignExpressionToRegister(left, RegisterOrPair.AY, dt == DataType.WORD)
if (expr.operator == "+")
asmgen.out(
"""
clc
adc $castedSymname
bcc +
iny
+"""
)
else
asmgen.out(
"""
sec
sbc $castedSymname
bcs +
dey
+"""
)
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
return true
}
doAddOrSubWordExpr()
return true
}
else -> {
doAddOrSubWordExpr()
return true
}
else -> return false
}
}
}
@ -682,9 +720,384 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
}
else if(expr.operator=="*") {
val value = expr.right.asConstInteger()
if(value==null) {
when(expr.type) {
in ByteDatatypes -> {
assignExpressionToRegister(expr.left, RegisterOrPair.A, expr.type in SignedDatatypes)
asmgen.out(" pha")
assignExpressionToRegister(expr.right, RegisterOrPair.Y, expr.type in SignedDatatypes)
asmgen.out(" pla | jsr math.multiply_bytes")
assignRegisterByte(assign.target, CpuRegister.A, false)
return true
}
in WordDatatypes -> {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out("""
jsr math.multiply_words
lda math.multiply_words.result
ldy math.multiply_words.result+1""")
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
return true
}
else -> return false
}
} else {
when (expr.type) {
in ByteDatatypes -> {
assignExpressionToRegister(expr.left, RegisterOrPair.A, expr.type in SignedDatatypes)
if (value in asmgen.optimizedByteMultiplications)
asmgen.out(" jsr math.mul_byte_${value}")
else
asmgen.out(" ldy #$value | jsr math.multiply_bytes")
assignRegisterByte(assign.target, CpuRegister.A, false)
return true
}
in WordDatatypes -> {
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.type in SignedDatatypes)
if (value in asmgen.optimizedWordMultiplications)
asmgen.out(" jsr math.mul_word_${value}")
else
asmgen.out("""
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.multiply_words
lda math.multiply_words.result
ldy math.multiply_words.result+1""")
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
return true
}
else -> return false
}
}
}
else if(expr.operator=="/") {
when(expr.type) {
DataType.UBYTE -> {
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.out(" pha")
assignExpressionToRegister(expr.right, RegisterOrPair.Y, false)
asmgen.out(" pla | jsr math.divmod_ub_asm")
assignRegisterByte(assign.target, CpuRegister.Y, false)
return true
}
DataType.BYTE -> {
assignExpressionToRegister(expr.left, RegisterOrPair.A, true)
asmgen.out(" pha")
assignExpressionToRegister(expr.right, RegisterOrPair.Y, true)
asmgen.out(" pla | jsr math.divmod_b_asm")
assignRegisterByte(assign.target, CpuRegister.Y, true)
return true
}
DataType.UWORD -> {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out(" jsr math.divmod_uw_asm")
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
return true
}
DataType.WORD -> {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out(" jsr math.divmod_w_asm")
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
return true
}
else -> return false
}
}
else if(expr.operator=="%") {
when(expr.type) {
DataType.UBYTE -> {
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.out(" pha")
assignExpressionToRegister(expr.right, RegisterOrPair.Y, false)
asmgen.out(" pla | jsr math.divmod_ub_asm")
if(assign.target.register==RegisterOrPair.A)
asmgen.out(" cmp #0") // fix the status register
else
assignRegisterByte(assign.target, CpuRegister.A, false)
return true
}
DataType.UWORD -> {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out(" jsr math.divmod_uw_asm")
assignVariableWord(assign.target, "P8ZP_SCRATCH_W2")
return true
}
else -> return false
}
}
return false
}
private fun assignOptimizedComparisonBytes(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
val signed = expr.left.type == DataType.BYTE || expr.right.type == DataType.BYTE
when(expr.operator) {
"==" -> {
asmgen.assignByteOperandsToAAndVar(expr.right, expr.left, "P8ZP_SCRATCH_B1")
asmgen.out("""
cmp P8ZP_SCRATCH_B1
beq +
lda #0
beq ++
+ lda #1
+""")
}
"!=" -> {
asmgen.assignByteOperandsToAAndVar(expr.right, expr.left, "P8ZP_SCRATCH_B1")
asmgen.out("""
cmp P8ZP_SCRATCH_B1
bne +
lda #0
beq ++
+ lda #1
+""")
}
"<" -> {
asmgen.assignByteOperandsToAAndVar(expr.right, expr.left, "P8ZP_SCRATCH_B1")
if(signed)
asmgen.out("""
clc
sbc P8ZP_SCRATCH_B1
bvc +
eor #$80
+ bpl +
lda #0
beq ++
+ lda #1
+""")
else
asmgen.out("""
tay
lda #0
cpy P8ZP_SCRATCH_B1
beq +
rol a
+""")
}
"<=" -> {
asmgen.assignByteOperandsToAAndVar(expr.right, expr.left, "P8ZP_SCRATCH_B1")
if(signed)
asmgen.out("""
sec
sbc P8ZP_SCRATCH_B1
bvc +
eor #$80
+ bpl +
lda #0
beq ++
+ lda #1
+""")
else
asmgen.out("""
cmp P8ZP_SCRATCH_B1
lda #0
rol a""")
}
">" -> {
asmgen.assignByteOperandsToAAndVar(expr.right, expr.left, "P8ZP_SCRATCH_B1")
if(signed)
asmgen.out("""
sec
sbc P8ZP_SCRATCH_B1
bvc +
eor #$80
+ bmi +
lda #0
beq ++
+ lda #1
+""")
else
asmgen.out("""
cmp P8ZP_SCRATCH_B1
lda #0
rol a
eor #1""")
}
">=" -> {
asmgen.assignByteOperandsToAAndVar(expr.right, expr.left, "P8ZP_SCRATCH_B1")
if(signed)
asmgen.out("""
clc
sbc P8ZP_SCRATCH_B1
bvc +
eor #$80
+ bmi +
lda #0
beq ++
+ lda #1
+""")
else
asmgen.out("""
cmp P8ZP_SCRATCH_B1
beq +
bcc +
lda #0
beq ++
+ lda #1
+""")
}
else -> return false
}
assignRegisterByte(assign.target, CpuRegister.A, signed)
return true
}
private fun assignOptimizedComparisonWords(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
val signed = expr.left.type == DataType.WORD || expr.right.type == DataType.WORD
when(expr.operator) {
"==" -> {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out("""
cmp P8ZP_SCRATCH_W1
bne +
cpy P8ZP_SCRATCH_W1+1
bne +
lda #1
bne ++
+ lda #0
+""")
}
"!=" -> {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out("""
cmp P8ZP_SCRATCH_W1
bne +
cpy P8ZP_SCRATCH_W1+1
bne +
lda #0
beq ++
+ lda #1
+""")
}
"<" -> {
if(signed) {
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
asmgen.out("""
cmp P8ZP_SCRATCH_W1
tya
sbc P8ZP_SCRATCH_W1+1
bvc +
eor #${'$'}80
+ bpl ++
+ lda #1
bne ++
+ lda #0
+""")
}
else {
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
asmgen.out("""
cpy P8ZP_SCRATCH_W1+1
bcc +
bne ++
cmp P8ZP_SCRATCH_W1
bcs ++
+ lda #1
bne ++
+ lda #0
+""")
}
}
"<=" -> {
if(signed) {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out("""
cmp P8ZP_SCRATCH_W1
tya
sbc P8ZP_SCRATCH_W1+1
bvc +
eor #${'$'}80
+ bmi +
lda #1
bne ++
+ lda #0
+""")
}
else {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out("""
cpy P8ZP_SCRATCH_W1+1
bcc ++
bne +
cmp P8ZP_SCRATCH_W1
bcc ++
+ lda #1
bne ++
+ lda #0
+""")
}
}
">" -> {
if(signed) {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out("""
cmp P8ZP_SCRATCH_W1
tya
sbc P8ZP_SCRATCH_W1+1
bvc +
eor #${'$'}80
+ bpl ++
+ lda #1
bne ++
+ lda #0
+""")
}
else {
asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
asmgen.out("""
cpy P8ZP_SCRATCH_W1+1
bcc +
bne ++
cmp P8ZP_SCRATCH_W1
bcs ++
+ lda #1
bne ++
+ lda #0
+""")
}
}
">=" -> {
if(signed) {
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
asmgen.out("""
cmp P8ZP_SCRATCH_W1
tya
sbc P8ZP_SCRATCH_W1+1
bvc +
eor #${'$'}80
+ bmi +
lda #1
bne ++
+ lda #0
+""")
}
else {
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
asmgen.out("""
cpy P8ZP_SCRATCH_W1+1
bcc ++
bne +
cmp P8ZP_SCRATCH_W1
bcc ++
+ lda #1
bne ++
+ lda #0
+""")
}
}
else -> return false
}
assignRegisterByte(assign.target, CpuRegister.A, signed)
return true
}
private fun assignLogicalWithSimpleRightOperandByte(target: AsmAssignTarget, left: PtExpression, operator: String, right: PtExpression) {
assignExpressionToRegister(left, RegisterOrPair.A, false)
val operand = when(right) {
@ -2058,14 +2471,24 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister, signed: Boolean) {
// we make an exception in the type check for assigning something to a register pair AX, AY or XY
// these will be correctly typecasted from a byte to a word value here
if(target.register !in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY))
require(target.datatype in ByteDatatypes) { "assign target must be byte type ${target.position}"}
val assignAsWord = target.datatype in WordDatatypes
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out(" st${register.name.lowercase()} ${target.asmVarname}")
if(assignAsWord) {
if(target.datatype in SignedDatatypes) {
if(register!=CpuRegister.A)
asmgen.out(" t${register.name.lowercase()}a")
asmgen.signExtendAYlsb(if(target.datatype in SignedDatatypes) DataType.BYTE else DataType.UBYTE)
asmgen.out(" sty ${target.asmVarname}+1")
} else {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${target.asmVarname}+1")
else
asmgen.out(" lda #0 | sta ${target.asmVarname}+1")
}
}
}
TargetStorageKind.MEMORY -> {
when(register) {
@ -2076,6 +2499,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
storeRegisterAInMemoryAddress(target.memory!!)
}
TargetStorageKind.ARRAY -> {
if(assignAsWord)
TODO("assign register as word into Array not yet supported")
if (target.constArrayIndexValue!=null) {
when (register) {
CpuRegister.A -> asmgen.out(" sta ${target.asmVarname}+${target.constArrayIndexValue}")
@ -2237,6 +2662,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
TargetStorageKind.STACK -> {
if(assignAsWord)
TODO("assign register as word onto Stack not yet supported")
when(register) {
CpuRegister.A -> asmgen.out(" sta P8ESTACK_LO,x | dex")
CpuRegister.X -> throw AssemblyError("can't use X here")

View File

@ -772,13 +772,12 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
"<" -> {
if(dt==DataType.UBYTE) {
asmgen.out("""
lda $name
cmp $otherName
bcc +
lda #0
beq ++
+ lda #1
+ sta $name""")
ldy $name
cpy $otherName
rol a
eor #1
sta $name""")
}
else {
// see http://www.6502.org/tutorials/compare_beyond.html
@ -798,13 +797,11 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
"<=" -> {
if(dt==DataType.UBYTE) {
asmgen.out("""
lda $otherName
cmp $name
bcs +
lda #0
beq ++
+ lda #1
+ sta $name""")
ldy $otherName
cpy $name
rol a
sta $name""")
} else {
// see http://www.6502.org/tutorials/compare_beyond.html
asmgen.out("""
@ -823,13 +820,11 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
">" -> {
if(dt==DataType.UBYTE) {
asmgen.out("""
lda $name
cmp $otherName
lda #0
ldy $name
cpy $otherName
beq +
bcs ++
+ lda #0
beq ++
+ lda #1
rol a
+ sta $name""")
} else {
// see http://www.6502.org/tutorials/compare_beyond.html
@ -849,13 +844,11 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
">=" -> {
if(dt==DataType.UBYTE) {
asmgen.out("""
lda $name
cmp $otherName
bcs +
lda #0
beq ++
+ lda #1
+ sta $name""")
ldy $name
cpy $otherName
rol a
sta $name""")
} else {
// see http://www.6502.org/tutorials/compare_beyond.html
asmgen.out("""
@ -963,13 +956,12 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
"<" -> {
if(dt==DataType.UBYTE) {
asmgen.out("""
lda $name
cmp #$value
bcc +
lda #0
beq ++
+ lda #1
+ sta $name""")
ldy $name
cpy #$value
rol a
eor #1
sta $name""")
}
else {
// see http://www.6502.org/tutorials/compare_beyond.html
@ -989,13 +981,11 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
"<=" -> {
if(dt==DataType.UBYTE) {
asmgen.out("""
lda #$value
cmp $name
bcs +
lda #0
beq ++
+ lda #1
+ sta $name""")
ldy #$value
cpy $name
rol a
sta $name""")
} else {
// see http://www.6502.org/tutorials/compare_beyond.html
asmgen.out("""
@ -1014,13 +1004,11 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
">" -> {
if(dt==DataType.UBYTE) {
asmgen.out("""
lda $name
cmp #$value
lda #0
ldy $name
cpy #$value
beq +
bcs ++
+ lda #0
beq ++
+ lda #1
rol a
+ sta $name""")
} else {
// see http://www.6502.org/tutorials/compare_beyond.html
@ -1040,13 +1028,11 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
">=" -> {
if(dt==DataType.UBYTE) {
asmgen.out("""
lda $name
cmp #$value
bcs +
lda #0
beq ++
+ lda #1
+ sta $name""")
ldy $name
cpy #$value
rol a
sta $name""")
} else {
// see http://www.6502.org/tutorials/compare_beyond.html
asmgen.out("""

View File

@ -1,5 +1,6 @@
package prog8tests.codegencpu6502
import io.kotest.assertions.throwables.shouldNotThrowAny
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import prog8.code.SymbolTableMaker
@ -102,5 +103,14 @@ class TestCodegen: FunSpec({
result.name shouldBe "test"
Files.deleteIfExists(Path("${result.name}.asm"))
}
test("64tass assembler available? - if this fails you need to install 64tass in the path") {
val command = mutableListOf("64tass", "--version")
shouldNotThrowAny {
val proc = ProcessBuilder(command).inheritIO().start()
val result = proc.waitFor()
result.shouldBe(0)
}
}
})

View File

@ -46,7 +46,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
assignment: PtAugmentedAssign
): IRCodeChunks {
val value = assignment.value
val vmDt = codeGen.irType(value.type)
val vmDt = irType(value.type)
return when(assignment.operator) {
"+" -> expressionEval.operatorPlusInplace(address, null, vmDt, value)
"-" -> expressionEval.operatorMinusInplace(address, null, vmDt, value)
@ -72,7 +72,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
private fun assignVarAugmented(symbol: String, assignment: PtAugmentedAssign): IRCodeChunks {
val value = assignment.value
val targetDt = codeGen.irType(assignment.target.type)
val targetDt = irType(assignment.target.type)
return when (assignment.operator) {
"+=" -> expressionEval.operatorPlusInplace(null, symbol, targetDt, value)
"-=" -> expressionEval.operatorMinusInplace(null, symbol, targetDt, value)
@ -161,7 +161,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val targetIdent = assignment.target.identifier
val targetMemory = assignment.target.memory
val targetArray = assignment.target.array
val vmDt = codeGen.irType(assignment.value.type)
val valueDt = irType(assignment.value.type)
val targetDt = irType(assignment.target.type)
val result = mutableListOf<IRCodeChunkBase>()
var valueRegister = -1
@ -169,30 +170,42 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val zero = codeGen.isZero(assignment.value)
if(!zero) {
// calculate the assignment value
if (vmDt == IRDataType.FLOAT) {
if (valueDt == IRDataType.FLOAT) {
val tr = expressionEval.translateExpression(assignment.value)
valueFpRegister = tr.resultFpReg
addToResult(result, tr, -1, valueFpRegister)
} else {
val extendByteToWord = if(targetDt != valueDt) {
// usually an error EXCEPT when a byte is assigned to a word.
if(targetDt==IRDataType.WORD && valueDt==IRDataType.BYTE)
true
else
throw AssemblyError("assignment value and target dt mismatch")
} else false
if (assignment.value is PtMachineRegister) {
valueRegister = (assignment.value as PtMachineRegister).register
if(extendByteToWord)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=valueRegister), null)
} else {
val tr = expressionEval.translateExpression(assignment.value)
valueRegister = tr.resultReg
addToResult(result, tr, valueRegister, -1)
if(extendByteToWord) {
val opcode = if(assignment.value.type in SignedDatatypes) Opcode.EXTS else Opcode.EXT
addInstr(result, IRInstruction(opcode, IRDataType.BYTE, reg1 = valueRegister), null)
}
}
}
}
if(targetIdent!=null) {
val instruction = if(zero) {
IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = targetIdent.name)
IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = targetIdent.name)
} else {
if (vmDt == IRDataType.FLOAT) {
IRInstruction(Opcode.STOREM, vmDt, fpReg1 = valueFpRegister, labelSymbol = targetIdent.name)
}
if (targetDt == IRDataType.FLOAT)
IRInstruction(Opcode.STOREM, targetDt, fpReg1 = valueFpRegister, labelSymbol = targetIdent.name)
else
IRInstruction(Opcode.STOREM, vmDt, reg1 = valueRegister, labelSymbol = targetIdent.name)
IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = targetIdent.name)
}
result += IRCodeChunk(null, null).also { it += instruction }
return result
@ -214,9 +227,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
if(zero) {
// there's no STOREZIX instruction
valueRegister = codeGen.registers.nextFree()
code += IRInstruction(Opcode.LOAD, vmDt, reg1=valueRegister, immediate = 0)
code += IRInstruction(Opcode.LOAD, targetDt, reg1=valueRegister, immediate = 0)
}
code += IRInstruction(Opcode.STOREIX, vmDt, reg1=valueRegister, reg2=idxReg, labelSymbol = variable)
code += IRInstruction(Opcode.STOREIX, targetDt, reg1=valueRegister, reg2=idxReg, labelSymbol = variable)
result += code
return result
}
@ -225,59 +238,59 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
if(zero) {
if(fixedIndex!=null) {
val offset = fixedIndex*itemsize
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = "$variable+$offset") }
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, targetDt, labelSymbol = "$variable+$offset") }
result += chunk
} else {
val (code, indexReg) = loadIndexReg(targetArray, itemsize)
result += code
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZX, vmDt, reg1=indexReg, labelSymbol = variable) }
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZX, targetDt, reg1=indexReg, labelSymbol = variable) }
}
} else {
if(vmDt== IRDataType.FLOAT) {
if(targetDt== IRDataType.FLOAT) {
if(fixedIndex!=null) {
val offset = fixedIndex*itemsize
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, fpReg1 = valueFpRegister, labelSymbol = "$variable+$offset") }
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, targetDt, fpReg1 = valueFpRegister, labelSymbol = "$variable+$offset") }
result += chunk
} else {
val (code, indexReg) = loadIndexReg(targetArray, itemsize)
result += code
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, vmDt, reg1 = indexReg, fpReg1 = valueFpRegister, labelSymbol = variable) }
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, targetDt, reg1 = indexReg, fpReg1 = valueFpRegister, labelSymbol = variable) }
}
} else {
if(fixedIndex!=null) {
val offset = fixedIndex*itemsize
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, reg1 = valueRegister, labelSymbol = "$variable+$offset") }
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, targetDt, reg1 = valueRegister, labelSymbol = "$variable+$offset") }
result += chunk
} else {
val (code, indexReg) = loadIndexReg(targetArray, itemsize)
result += code
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, vmDt, reg1 = valueRegister, reg2=indexReg, labelSymbol = variable) }
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREX, targetDt, reg1 = valueRegister, reg2=indexReg, labelSymbol = variable) }
}
}
}
return result
}
else if(targetMemory!=null) {
require(vmDt== IRDataType.BYTE) { "must be byte type ${targetMemory.position}"}
require(targetDt == IRDataType.BYTE) { "must be byte type ${targetMemory.position}"}
if(zero) {
if(targetMemory.address is PtNumber) {
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, vmDt, address = (targetMemory.address as PtNumber).number.toInt()) }
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZM, targetDt, address = (targetMemory.address as PtNumber).number.toInt()) }
result += chunk
} else {
val tr = expressionEval.translateExpression(targetMemory.address)
val addressReg = tr.resultReg
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZI, vmDt, reg1=addressReg) }
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREZI, targetDt, reg1=addressReg) }
}
} else {
if(targetMemory.address is PtNumber) {
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, vmDt, reg1=valueRegister, address=(targetMemory.address as PtNumber).number.toInt()) }
val chunk = IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREM, targetDt, reg1=valueRegister, address=(targetMemory.address as PtNumber).number.toInt()) }
result += chunk
} else {
val tr = expressionEval.translateExpression(targetMemory.address)
val addressReg = tr.resultReg
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREI, vmDt, reg1=valueRegister, reg2=addressReg) }
result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.STOREI, targetDt, reg1=valueRegister, reg2=addressReg) }
}
}

View File

@ -82,11 +82,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val left = exprGen.translateExpression(call.args[0])
val right = exprGen.translateExpression(call.args[1])
addToResult(result, left, left.resultReg, -1)
addInstr(result, IRInstruction(Opcode.SETPARAM, IRDataType.WORD, reg1=left.resultReg, immediate = 0), null)
addToResult(result, right, right.resultReg, -1)
addInstr(result, IRInstruction(Opcode.SETPARAM, IRDataType.WORD, reg1=right.resultReg, immediate = 1), null)
addInstr(result, IRInstruction(Opcode.SYSCALL, immediate = IMSyscall.COMPARE_STRINGS.number), null)
addInstr(result, IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=left.resultReg), null)
result += codeGen.makeSyscall(IMSyscall.COMPARE_STRINGS, listOf(IRDataType.WORD to left.resultReg, IRDataType.WORD to right.resultReg), IRDataType.BYTE to left.resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, left.resultReg, -1)
}
@ -96,7 +93,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, leftTr, leftTr.resultReg, -1)
val rightTr = exprGen.translateExpression(call.args[1])
addToResult(result, rightTr, rightTr.resultReg, -1)
val dt = codeGen.irType(call.args[0].type)
val dt = irType(call.args[0].type)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMP, dt, reg1=leftTr.resultReg, reg2=rightTr.resultReg)
}
@ -118,13 +115,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SETPARAM, IRDataType.WORD, reg1 = tr.resultReg, immediate = 0)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = tr.resultReg, immediate = array.length)
it += IRInstruction(Opcode.SETPARAM, IRDataType.BYTE, reg1 = tr.resultReg, immediate = 1)
it += IRInstruction(Opcode.SYSCALL, immediate = syscall.number)
it += IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=tr.resultReg)
}
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = array.length), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to tr.resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
}
@ -143,13 +136,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SETPARAM, IRDataType.WORD, reg1 = tr.resultReg, immediate = 0)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = tr.resultReg, immediate = array.length)
it += IRInstruction(Opcode.SETPARAM, IRDataType.BYTE, reg1 = tr.resultReg, immediate = 1)
it += IRInstruction(Opcode.SYSCALL, immediate = syscall.number)
it += IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=tr.resultReg)
}
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = array.length), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to tr.resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
}
@ -199,7 +188,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcSgn(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val vmDt = codeGen.irType(call.type)
val vmDt = irType(call.type)
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.nextFree()
@ -271,12 +260,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SETPARAM, IRDataType.WORD, reg1 = tr.resultReg, immediate = 0)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = tr.resultReg, immediate = if(array.dt==DataType.STR) array.length!!-1 else array.length)
it += IRInstruction(Opcode.SETPARAM, IRDataType.BYTE, reg1 = tr.resultReg, immediate = 1)
it += IRInstruction(Opcode.SYSCALL, immediate = syscall.number)
}
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(array.dt==DataType.STR) array.length!!-1 else array.length), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
@ -296,12 +282,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SETPARAM, IRDataType.WORD, reg1 = tr.resultReg, immediate = 0)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = tr.resultReg, immediate = if(array.dt==DataType.STR) array.length!!-1 else array.length)
it += IRInstruction(Opcode.SETPARAM, IRDataType.BYTE, reg1 = tr.resultReg, immediate = 1)
it += IRInstruction(Opcode.SYSCALL, immediate = syscall.number)
}
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(array.dt==DataType.STR) array.length!!-1 else array.length), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
@ -455,7 +438,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
private fun funcRolRor(opcode: Opcode, call: PtBuiltinFunctionCall): ExpressionCodeResult {
val vmDt = codeGen.irType(call.args[0].type)
val vmDt = irType(call.args[0].type)
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)

View File

@ -28,10 +28,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
fun translateExpression(expr: PtExpression): ExpressionCodeResult {
return when (expr) {
is PtMachineRegister -> {
ExpressionCodeResult(emptyList(), codeGen.irType(expr.type), expr.register, -1)
ExpressionCodeResult(emptyList(), irType(expr.type), expr.register, -1)
}
is PtNumber -> {
val vmDt = codeGen.irType(expr.type)
val vmDt = irType(expr.type)
val code = IRCodeChunk(null, null)
if(vmDt==IRDataType.FLOAT) {
val resultFpRegister = codeGen.registers.nextFreeFloat()
@ -45,7 +45,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
is PtIdentifier -> {
val vmDt = codeGen.irType(expr.type)
val vmDt = irType(expr.type)
val code = IRCodeChunk(null, null)
if (expr.type in PassByValueDatatypes) {
if(vmDt==IRDataType.FLOAT) {
@ -66,7 +66,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
is PtAddressOf -> {
val vmDt = codeGen.irType(expr.type)
val vmDt = irType(expr.type)
val symbol = expr.identifier.name
// note: LOAD <symbol> gets you the address of the symbol, whereas LOADM <symbol> would get you the value stored at that location
val code = IRCodeChunk(null, null)
@ -105,53 +105,35 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
private fun translate(check: PtContainmentCheck): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
var tr = translateExpression(check.element)
addToResult(result, tr, tr.resultReg, -1)
val iterable = codeGen.symbolTable.flat.getValue(check.iterable.name) as StStaticVariable
when(iterable.dt) {
DataType.STR -> {
tr = translateExpression(check.element)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.SETPARAM, IRDataType.BYTE, tr.resultReg, immediate = 0), null)
tr = translateExpression(check.iterable)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.SETPARAM, IRDataType.WORD, tr.resultReg, immediate = 1), null)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SYSCALL, immediate = IMSyscall.STRING_CONTAINS.number)
it += IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=tr.resultReg)
}
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
val elementTr = translateExpression(check.element)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(check.iterable)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
result += codeGen.makeSyscall(IMSyscall.STRING_CONTAINS, listOf(IRDataType.BYTE to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg), IRDataType.BYTE to elementTr.resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
}
DataType.ARRAY_UB, DataType.ARRAY_B -> {
tr = translateExpression(check.element)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.SETPARAM, IRDataType.BYTE, tr.resultReg, immediate = 0), null)
tr = translateExpression(check.iterable)
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SETPARAM, IRDataType.WORD, tr.resultReg, immediate = 1)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=tr.resultReg, immediate = iterable.length!!)
it += IRInstruction(Opcode.SETPARAM, IRDataType.BYTE, tr.resultReg, immediate = 2)
it += IRInstruction(Opcode.SYSCALL, immediate = IMSyscall.BYTEARRAY_CONTAINS.number)
it += IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=tr.resultReg)
}
// SysCall call convention: return value in register r0
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
val elementTr = translateExpression(check.element)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(check.iterable)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterable.length!!), null)
result += codeGen.makeSyscall(IMSyscall.BYTEARRAY_CONTAINS, listOf(IRDataType.BYTE to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to elementTr.resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
tr = translateExpression(check.element)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.SETPARAM, IRDataType.WORD, tr.resultReg, immediate = 0), null)
tr = translateExpression(check.iterable)
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SETPARAM, IRDataType.WORD, tr.resultReg, immediate = 1)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=tr.resultReg, immediate = iterable.length!!)
it += IRInstruction(Opcode.SETPARAM, IRDataType.BYTE, tr.resultReg, immediate = 2)
it += IRInstruction(Opcode.SYSCALL, immediate = IMSyscall.WORDARRAY_CONTAINS.number)
it += IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=tr.resultReg)
}
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
val elementTr = translateExpression(check.element)
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(check.iterable)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterable.length!!), null)
result += codeGen.makeSyscall(IMSyscall.WORDARRAY_CONTAINS, listOf(IRDataType.WORD to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to elementTr.resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, elementTr.resultReg, -1)
}
DataType.ARRAY_F -> throw AssemblyError("containment check in float-array not supported")
else -> throw AssemblyError("weird iterable dt ${iterable.dt} for ${check.iterable.name}")
@ -160,7 +142,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
private fun translate(arrayIx: PtArrayIndexer): ExpressionCodeResult {
val eltSize = codeGen.program.memsizer.memorySize(arrayIx.type)
val vmDt = codeGen.irType(arrayIx.type)
val vmDt = irType(arrayIx.type)
val result = mutableListOf<IRCodeChunkBase>()
val arrayVarSymbol = arrayIx.variable.name
@ -210,7 +192,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val result = mutableListOf<IRCodeChunkBase>()
val tr = translateExpression(expr.value)
addToResult(result, tr, tr.resultReg, tr.resultFpReg)
val vmDt = codeGen.irType(expr.type)
val vmDt = irType(expr.type)
when(expr.operator) {
"+" -> { }
"-" -> {
@ -326,12 +308,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else -> throw AssemblyError("weird cast type")
}
return ExpressionCodeResult(result, codeGen.irType(cast.type), actualResultReg2, actualResultFpReg2)
return ExpressionCodeResult(result, irType(cast.type), actualResultReg2, actualResultFpReg2)
}
private fun translate(binExpr: PtBinaryExpression): ExpressionCodeResult {
require(!codeGen.options.useNewExprCode)
val vmDt = codeGen.irType(binExpr.left.type)
val vmDt = irType(binExpr.left.type)
val signed = binExpr.left.type in SignedDatatypes
return when(binExpr.operator) {
"+" -> operatorPlus(binExpr, vmDt)
@ -358,97 +340,82 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
when (val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)) {
is StSub -> {
val result = mutableListOf<IRCodeChunkBase>()
for ((index, argspec) in fcall.args.zip(callTarget.parameters).withIndex()) {
val (arg, param) = argspec
val paramDt = codeGen.irType(param.type)
// assign the arguments
val argRegisters = mutableListOf<FunctionCallArgs.ArgumentSpec>()
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
val paramDt = irType(parameter.type)
val tr = translateExpression(arg)
result += tr.chunks
if(paramDt==IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.SETPARAM, paramDt, fpReg1 = tr.resultFpReg, immediate = index), null)
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, tr.resultFpReg, null)))
else
addInstr(result, IRInstruction(Opcode.SETPARAM, paramDt, reg1 = tr.resultReg, immediate = index), null)
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(paramDt, tr.resultReg, null)))
result += tr.chunks
}
// for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
// val paramDt = codeGen.irType(parameter.type)
// val symbol = "${fcall.name}.${parameter.name}"
// if(codeGen.isZero(arg)) {
// addInstr(result, IRInstruction(Opcode.STOREZM, paramDt, labelSymbol = symbol), null)
// } else {
// if (paramDt == IRDataType.FLOAT) {
// val tr = translateExpression(arg)
// addToResult(result, tr, -1, tr.resultFpReg)
// addInstr(result, IRInstruction(Opcode.STOREM, paramDt, fpReg1 = tr.resultFpReg, labelSymbol = symbol), null)
// } else {
// val tr = translateExpression(arg)
// addToResult(result, tr, tr.resultReg, -1)
// addInstr(result, IRInstruction(Opcode.STOREM, paramDt, reg1 = tr.resultReg, labelSymbol = symbol), null)
// }
// }
// }
return if(fcall.void) {
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name), null)
// return value
val returnRegSpec = if(fcall.void) null else {
val returnIrType = irType(callTarget.returnType!!)
if(returnIrType==IRDataType.FLOAT)
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFreeFloat(), null)
else
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFree(), null)
}
// create the call
val call = IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec))
addInstr(result, call, null)
return if(fcall.void)
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
} else {
var resultReg = -1
var resultFpReg = -1
if(fcall.type==DataType.FLOAT) {
resultFpReg = codeGen.registers.nextFreeFloat()
addInstr(result, IRInstruction(Opcode.CALLR, IRDataType.FLOAT, fpReg1=resultFpReg, labelSymbol=fcall.name), null)
} else {
resultReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.CALLR, codeGen.irType(fcall.type), reg1=resultReg, labelSymbol=fcall.name), null)
}
ExpressionCodeResult(result, codeGen.irType(fcall.type), resultReg, resultFpReg)
}
else if(fcall.type==DataType.FLOAT)
ExpressionCodeResult(result, returnRegSpec!!.dt, -1, returnRegSpec.registerNum)
else
ExpressionCodeResult(result, returnRegSpec!!.dt, returnRegSpec.registerNum, -1)
}
is StRomSub -> {
val result = mutableListOf<IRCodeChunkBase>()
// assign the arguments
val argRegisters = mutableListOf<FunctionCallArgs.ArgumentSpec>()
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
val paramDt = codeGen.irType(parameter.type)
val paramRegStr = if(parameter.register.registerOrPair!=null) parameter.register.registerOrPair.toString() else parameter.register.statusflag.toString()
if(codeGen.isZero(arg)) {
addInstr(result, IRInstruction(Opcode.STOREZCPU, paramDt, labelSymbol = paramRegStr), null)
val paramDt = irType(parameter.type)
val tr = translateExpression(arg)
if(paramDt==IRDataType.FLOAT)
argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, tr.resultFpReg, parameter.register)))
else
argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(paramDt, tr.resultReg, parameter.register)))
result += tr.chunks
}
// return value
val returnRegSpec = if(fcall.void) null else {
if(callTarget.returns.isEmpty())
null
else if(callTarget.returns.size==1) {
val returns = callTarget.returns[0]
val returnIrType = irType(returns.type)
if(returnIrType==IRDataType.FLOAT)
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFreeFloat(), returns.register)
else
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFree(), returns.register)
} else {
if (paramDt == IRDataType.FLOAT)
throw AssemblyError("doesn't support float register argument in asm romsub")
val tr = translateExpression(arg)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.STORECPU, paramDt, reg1 = tr.resultReg, labelSymbol = paramRegStr), null)
// multiple return values: take the first *register* (not status flag) return value and ignore the rest.
val returns = callTarget.returns.first { it.register.registerOrPair!=null }
val returnIrType = irType(returns.type)
if(returnIrType==IRDataType.FLOAT)
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFreeFloat(), returns.register)
else
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFree(), returns.register)
}
}
// just a regular call without using Vm register call convention: the value is returned in CPU registers!
addInstr(result, IRInstruction(Opcode.CALL, address = callTarget.address.toInt()), null)
val resultReg = codeGen.registers.nextFree()
if(!fcall.void) {
when(callTarget.returns.size) {
0 -> throw AssemblyError("expect a return value")
1 -> {
if(fcall.type==DataType.FLOAT)
throw AssemblyError("doesn't support float register result in asm romsub")
val returns = callTarget.returns.single()
val regStr = if(returns.register.registerOrPair!=null) returns.register.registerOrPair.toString() else returns.register.statusflag.toString()
addInstr(result, IRInstruction(Opcode.LOADCPU, codeGen.irType(fcall.type), reg1=resultReg, labelSymbol = regStr), null)
}
else -> {
val returnRegister = callTarget.returns.singleOrNull{ it.register.registerOrPair!=null }
if(returnRegister!=null) {
// we skip the other values returned in the status flags.
val regStr = returnRegister.register.registerOrPair.toString()
addInstr(result, IRInstruction(Opcode.LOADCPU, codeGen.irType(fcall.type), reg1=resultReg, labelSymbol = regStr), null)
} else {
val firstReturnRegister = callTarget.returns.firstOrNull{ it.register.registerOrPair!=null }
if(firstReturnRegister!=null) {
// we just take the first register return value and ignore the rest.
val regStr = firstReturnRegister.register.registerOrPair.toString()
addInstr(result, IRInstruction(Opcode.LOADCPU, codeGen.irType(fcall.type), reg1=resultReg, labelSymbol = regStr), null)
} else {
throw AssemblyError("invalid number of return values from call")
}
}
}
}
}
return ExpressionCodeResult(result, if(fcall.void) IRDataType.BYTE else codeGen.irType(fcall.type), resultReg, -1)
// create the call
val call =
if(callTarget.address==null)
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec))
else
IRInstruction(Opcode.CALL, address = callTarget.address!!.toInt(), fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec))
addInstr(result, call, null)
return if(fcall.void)
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
else if(fcall.type==DataType.FLOAT)
ExpressionCodeResult(result, returnRegSpec!!.dt, -1, returnRegSpec.registerNum)
else
ExpressionCodeResult(result, returnRegSpec!!.dt, returnRegSpec.registerNum, -1)
}
else -> throw AssemblyError("invalid node type")
}

View File

@ -53,24 +53,10 @@ class IRCodeGen(
replaceMemoryMappedVars(irProg)
ensureFirstChunkLabels(irProg)
irProg.linkChunks()
irProg.convertAsmChunks()
if(options.optimize) {
val optimizer = IRPeepholeOptimizer(irProg)
optimizer.optimize()
val remover = IRUnusedCodeRemover(irProg, irSymbolTable, errors)
do {
val numRemoved = remover.optimize()
} while(numRemoved>0 && errors.noErrors())
errors.report()
irProg.linkChunks() // re-link
} else {
val optimizer = IRPeepholeOptimizer(irProg)
optimizer.optimizeOnlyJoinChunks()
}
val optimizer = IRPeepholeOptimizer(irProg)
optimizer.optimize(options.optimize, errors)
irProg.validate()
return irProg
}
@ -535,16 +521,36 @@ class IRCodeGen(
addToResult(result, fromTr, fromTr.resultReg, -1)
val labelAfterFor = createLabelName()
val greaterOpcode = if(loopvarDt in SignedDatatypes) Opcode.BGTSR else Opcode.BGTR
addInstr(result, IRInstruction(greaterOpcode, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor), null)
val precheckInstruction = if(loopvarDt in SignedDatatypes) {
if(step>0)
IRInstruction(Opcode.BGTSR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor)
else
IRInstruction(Opcode.BGTSR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor)
} else {
if(step>0)
IRInstruction(Opcode.BGTR, loopvarDtIr, fromTr.resultReg, toTr.resultReg, labelSymbol=labelAfterFor)
else
IRInstruction(Opcode.BGTR, loopvarDtIr, toTr.resultReg, fromTr.resultReg, labelSymbol=labelAfterFor)
}
addInstr(result, precheckInstruction, null)
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=fromTr.resultReg, labelSymbol=loopvarSymbol), null)
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol), null)
// if endvalue >= index, iterate loop
val branchOpcode = if(loopvarDt in SignedDatatypes) Opcode.BGESR else Opcode.BGER
addInstr(result, IRInstruction(branchOpcode, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg, labelSymbol=loopLabel), null)
val branchInstr = if(loopvarDt in SignedDatatypes) {
if(step>0)
IRInstruction(Opcode.BGESR, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg, labelSymbol=loopLabel)
else
IRInstruction(Opcode.BGESR, loopvarDtIr, reg1=fromTr.resultReg, reg2=toTr.resultReg, labelSymbol=loopLabel)
} else {
if(step>0)
IRInstruction(Opcode.BGER, loopvarDtIr, reg1=toTr.resultReg, reg2=fromTr.resultReg, labelSymbol=loopLabel)
else
IRInstruction(Opcode.BGER, loopvarDtIr, reg1=fromTr.resultReg, reg2=toTr.resultReg, labelSymbol=loopLabel)
}
addInstr(result, branchInstr, null)
result += IRCodeChunk(labelAfterFor, null)
return result
@ -1470,7 +1476,7 @@ class IRCodeGen(
}
private fun translate(block: PtBlock): IRBlock {
val irBlock = IRBlock(block.name, block.address, translate(block.alignment), block.position) // no use for other attributes yet?
val irBlock = IRBlock(block.name, block.address, block.library, block.forceOutput, translate(block.alignment), block.position) // no use for other attributes yet?
for (child in block.children) {
when(child) {
is PtNop -> { /* nothing */ }
@ -1539,20 +1545,6 @@ class IRCodeGen(
}
}
internal fun irType(type: DataType): IRDataType {
return when(type) {
DataType.BOOL,
DataType.UBYTE,
DataType.BYTE -> IRDataType.BYTE
DataType.UWORD,
DataType.WORD -> IRDataType.WORD
DataType.FLOAT -> IRDataType.FLOAT
in PassByReferenceDatatypes -> IRDataType.WORD
else -> throw AssemblyError("no IR datatype for $type")
}
}
private var labelSequenceNumber = 0
internal fun createLabelName(): String {
labelSequenceNumber++
@ -1583,4 +1575,14 @@ class IRCodeGen(
irSymbolTable.add(staticVar)
return tempvar
}
fun makeSyscall(syscall: IMSyscall, params: List<Pair<IRDataType, Int>>, returns: Pair<IRDataType, Int>?, label: String?=null): IRCodeChunk {
return IRCodeChunk(label, null).also {
val args = params.map { (dt, reg)->
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(dt, reg, null))
}
val returnSpec = if(returns==null) null else FunctionCallArgs.RegSpec(returns.first, returns.second, null)
it += IRInstruction(Opcode.SYSCALL, immediate = syscall.number, fcallArgs = FunctionCallArgs(args, returnSpec))
}
}
}

View File

@ -1,18 +1,39 @@
package prog8.codegen.intermediate
import prog8.code.core.IErrorReporter
import prog8.intermediate.*
internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
fun optimizeOnlyJoinChunks() {
class IRPeepholeOptimizer(private val irprog: IRProgram) {
fun optimize(optimizationsEnabled: Boolean, errors: IErrorReporter) {
if(!optimizationsEnabled)
return optimizeOnlyJoinChunks()
peepholeOptimize()
val remover = IRUnusedCodeRemover(irprog, errors)
var totalRemovals = 0
do {
val numRemoved = remover.optimize()
totalRemovals += numRemoved
} while(numRemoved>0 && errors.noErrors())
errors.report()
if(totalRemovals>0) {
irprog.linkChunks() // re-link again.
}
}
private fun optimizeOnlyJoinChunks() {
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
joinChunks(sub)
removeEmptyChunks(sub)
joinChunks(sub)
}
irprog.linkChunks() // re-link
}
fun optimize() {
private fun peepholeOptimize() {
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
joinChunks(sub)
removeEmptyChunks(sub)
joinChunks(sub)
sub.chunks.withIndex().forEach { (index, chunk1) ->
@ -36,7 +57,7 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
removeEmptyChunks(sub)
}
irprog.linkChunks() // re-link
irprog.linkChunks() // re-link
}
private fun removeEmptyChunks(sub: IRSubroutine) {
@ -93,7 +114,7 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
if(sub.chunks.isEmpty())
return
fun mayJoin(previous: IRCodeChunkBase, chunk: IRCodeChunkBase): Boolean {
fun mayJoinCodeChunks(previous: IRCodeChunkBase, chunk: IRCodeChunkBase): Boolean {
if(chunk.label!=null)
return false
if(previous is IRCodeChunk && chunk is IRCodeChunk) {
@ -110,12 +131,39 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
chunks += sub.chunks[0]
for(ix in 1 until sub.chunks.size) {
val lastChunk = chunks.last()
if(mayJoin(lastChunk, sub.chunks[ix])) {
lastChunk.instructions += sub.chunks[ix].instructions
lastChunk.next = sub.chunks[ix].next
val candidate = sub.chunks[ix]
when(candidate) {
is IRCodeChunk -> {
if(mayJoinCodeChunks(lastChunk, candidate)) {
lastChunk.instructions += candidate.instructions
lastChunk.next = candidate.next
}
else
chunks += candidate
}
is IRInlineAsmChunk -> {
if(candidate.label!=null)
chunks += candidate
else if(lastChunk.isEmpty()) {
val label = lastChunk.label
if(label!=null)
chunks += IRInlineAsmChunk(label, candidate.assembly, candidate.isIR, candidate.next)
else
chunks += candidate
}
}
is IRInlineBinaryChunk -> {
if(candidate.label!=null)
chunks += candidate
else if(lastChunk.isEmpty()) {
val label = lastChunk.label
if(label!=null)
chunks += IRInlineBinaryChunk(label, candidate.data, candidate.next)
else
chunks += candidate
}
}
}
else
chunks += sub.chunks[ix]
}
sub.chunks.clear()
sub.chunks += chunks
@ -202,14 +250,16 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
}
// replace call + return --> jump
if(idx>0 && ins.opcode==Opcode.RETURN) {
val previous = chunk.instructions[idx-1]
if(previous.opcode==Opcode.CALL || previous.opcode==Opcode.CALLR) {
chunk.instructions[idx-1] = IRInstruction(Opcode.JUMP, address = previous.address, labelSymbol = previous.labelSymbol, branchTarget = previous.branchTarget)
chunk.instructions.removeAt(idx)
changed = true
}
}
// This can no longer be done here on the IR level, with the current CALL opcode that encodes the full subroutine call setup.
// If machine code is ever generated from this IR, *that* should possibly optimize the JSR + RTS into a JMP.
// if(idx>0 && ins.opcode==Opcode.RETURN) {
// val previous = chunk.instructions[idx-1]
// if(previous.opcode==Opcode.CALL) {
// chunk.instructions[idx-1] = IRInstruction(Opcode.JUMP, address = previous.address, labelSymbol = previous.labelSymbol, branchTarget = previous.branchTarget)
// chunk.instructions.removeAt(idx)
// changed = true
// }
// }
}
return changed
}

View File

@ -5,14 +5,27 @@ import prog8.code.core.SourceCode.Companion.libraryFilePrefix
import prog8.intermediate.*
internal class IRUnusedCodeRemover(
class IRUnusedCodeRemover(
private val irprog: IRProgram,
private val symbolTable: IRSymbolTable,
private val errors: IErrorReporter
) {
fun optimize(): Int {
val allLabeledChunks = mutableMapOf<String, IRCodeChunkBase>()
var numRemoved = removeUnusedSubroutines() + removeUnusedAsmSubroutines()
// remove empty blocks
irprog.blocks.reversed().forEach { block ->
if(block.isEmpty()) {
irprog.blocks.remove(block)
irprog.st.removeTree(block.label)
numRemoved++
}
}
return numRemoved
}
private fun removeUnusedSubroutines(): Int {
val allLabeledChunks = mutableMapOf<String, IRCodeChunkBase>()
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
sub.chunks.forEach { chunk ->
chunk.label?.let { allLabeledChunks[it] = chunk }
@ -20,8 +33,6 @@ internal class IRUnusedCodeRemover(
}
var numRemoved = removeSimpleUnlinked(allLabeledChunks) + removeUnreachable(allLabeledChunks)
// remove empty subs
irprog.blocks.forEach { block ->
block.children.filterIsInstance<IRSubroutine>().reversed().forEach { sub ->
if(sub.isEmpty()) {
@ -29,24 +40,88 @@ internal class IRUnusedCodeRemover(
errors.warn("unused subroutine ${sub.label}", sub.position)
}
block.children.remove(sub)
symbolTable.removeTree(sub.label)
irprog.st.removeTree(sub.label)
numRemoved++
}
}
}
// remove empty blocks
irprog.blocks.reversed().forEach { block ->
if(block.isEmpty()) {
irprog.blocks.remove(block)
symbolTable.removeTree(block.label)
numRemoved++
return numRemoved
}
private fun removeUnusedAsmSubroutines(): Int {
val allLabeledAsmsubs = irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRAsmSubroutine>() }
.associateBy { it.label }
var numRemoved = removeSimpleUnlinkedAsmsubs(allLabeledAsmsubs)
irprog.blocks.forEach { block ->
block.children.filterIsInstance<IRAsmSubroutine>().reversed().forEach { sub ->
if(sub.isEmpty()) {
if(!sub.position.file.startsWith(libraryFilePrefix)) {
errors.warn("unused asmsubroutine ${sub.label}", sub.position)
}
block.children.remove(sub)
irprog.st.removeTree(sub.label)
numRemoved++
}
}
}
return numRemoved
}
private fun removeSimpleUnlinkedAsmsubs(allSubs: Map<String, IRAsmSubroutine>): Int {
val linkedAsmSubs = mutableSetOf<IRAsmSubroutine>()
// TODO: asmsubs in library modules are never removed, we can't really tell here if they're actually being called or not...
// check if asmsub is called from another asmsub
irprog.blocks.asSequence().forEach { block ->
block.children.filterIsInstance<IRAsmSubroutine>().forEach { sub ->
if (block.forceOutput || block.library)
linkedAsmSubs += sub
if (sub.asmChunk.isNotEmpty()) {
allSubs.forEach { (label, asmsub) ->
if (sub.asmChunk.assembly.contains(label))
linkedAsmSubs += asmsub
}
}
val inlineAsm = sub.asmChunk.next as? IRInlineAsmChunk
if(inlineAsm!=null) {
allSubs.forEach { (label, asmsub) ->
if (inlineAsm.assembly.contains(label))
linkedAsmSubs += asmsub
}
}
}
}
// check if asmsub is linked or called from another regular subroutine
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
sub.chunks.forEach { chunk ->
chunk.instructions.forEach {
it.labelSymbol?.let { label -> allSubs[label]?.let { cc -> linkedAsmSubs += cc } }
// note: branchTarget can't yet point to another IRAsmSubroutine, so do nothing when it's set
}
}
}
return removeUnlinkedAsmsubs(linkedAsmSubs)
}
private fun removeUnlinkedAsmsubs(linkedAsmSubs: Set<IRAsmSubroutine>): Int {
var numRemoved = 0
irprog.blocks.asSequence().forEach { block ->
block.children.withIndex().reversed().forEach { (index, child) ->
if(child is IRAsmSubroutine && child !in linkedAsmSubs) {
block.children.removeAt(index)
numRemoved++
}
}
}
return numRemoved
}
private fun removeUnreachable(allLabeledChunks: MutableMap<String, IRCodeChunkBase>): Int {
val entrypointSub = irprog.blocks.single { it.label=="main" }.children.single { it is IRSubroutine && it.label=="main.start" }
val reachable = mutableSetOf((entrypointSub as IRSubroutine).chunks.first())
@ -98,7 +173,7 @@ internal class IRUnusedCodeRemover(
}
private fun removeUnlinkedChunks(
linkedChunks: MutableSet<IRCodeChunkBase>
linkedChunks: Set<IRCodeChunkBase>
): Int {
var numRemoved = 0
irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->

View File

@ -8,7 +8,7 @@ import prog8.intermediate.*
class TestIRPeepholeOpt: FunSpec({
fun makeIRProgram(chunks: List<IRCodeChunkBase>): IRProgram {
require(chunks.first().label=="main.start")
val block = IRBlock("main", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
val block = IRBlock("main", null, false, false, IRBlock.BlockAlignment.NONE, Position.DUMMY)
val sub = IRSubroutine("main.start", emptyList(), null, Position.DUMMY)
chunks.forEach { sub += it }
block += sub
@ -46,7 +46,7 @@ class TestIRPeepholeOpt: FunSpec({
))
irProg.chunks().single().instructions.size shouldBe 3
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
opt.optimize(true, ErrorReporterForTests())
irProg.chunks().single().instructions.size shouldBe 1
}
@ -65,7 +65,7 @@ class TestIRPeepholeOpt: FunSpec({
irProg.chunks().size shouldBe 4
irProg.chunks().flatMap { it.instructions }.size shouldBe 5
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
opt.optimize(true, ErrorReporterForTests())
irProg.chunks().size shouldBe 4
irProg.chunks()[0].label shouldBe "main.start"
irProg.chunks()[1].label shouldBe "label"
@ -92,7 +92,7 @@ class TestIRPeepholeOpt: FunSpec({
))
irProg.chunks().single().instructions.size shouldBe 6
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.size shouldBe 1
instr[0].opcode shouldBe Opcode.CLC
@ -107,7 +107,7 @@ class TestIRPeepholeOpt: FunSpec({
))
irProg.chunks().single().instructions.size shouldBe 4
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.size shouldBe 1
instr[0].opcode shouldBe Opcode.LOADR
@ -130,7 +130,7 @@ class TestIRPeepholeOpt: FunSpec({
))
irProg.chunks().single().instructions.size shouldBe 10
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
opt.optimize(true, ErrorReporterForTests())
irProg.chunks().single().instructions.size shouldBe 4
}
@ -141,7 +141,7 @@ class TestIRPeepholeOpt: FunSpec({
))
irProg.chunks().single().instructions.size shouldBe 2
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.size shouldBe 2
instr[0].opcode shouldBe Opcode.INC
@ -161,7 +161,7 @@ class TestIRPeepholeOpt: FunSpec({
))
irProg.chunks().single().instructions.size shouldBe 8
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
opt.optimize(true, ErrorReporterForTests())
irProg.chunks().single().instructions.size shouldBe 4
}
@ -174,7 +174,7 @@ class TestIRPeepholeOpt: FunSpec({
))
irProg.chunks().single().instructions.size shouldBe 4
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.size shouldBe 4
instr[0].opcode shouldBe Opcode.LOAD

View File

@ -441,7 +441,7 @@ class TestVmCodeGen: FunSpec({
irChunks.size shouldBe 1
}
test("romsub allowed in codegen") {
test("romsub allowed in ir-codegen") {
//main {
// romsub $5000 = routine()
//
@ -452,7 +452,7 @@ class TestVmCodeGen: FunSpec({
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)
val romsub = PtAsmSub("routine", 0x5000u, setOf(CpuRegister.Y), 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)

View File

@ -157,6 +157,12 @@ class UnusedCodeRemover(private val program: Program,
return noModifications
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.target isSameAs assignment.value)
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
return noModifications
}
private fun deduplicateAssignments(statements: List<Statement>, scope: IStatementContainer): List<IAstModification> {
// removes 'duplicate' assignments that assign the same target directly after another, unless it is a function call
val linesToRemove = mutableListOf<Assignment>()

View File

@ -310,7 +310,7 @@ asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
sta _modified+1
sty _modified+2
lda #0
adc #0
rol a
sta _use_kernal
sei
lda #<_irq_handler
@ -404,7 +404,7 @@ asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, ubyte useKernal @P
sta _modified+1
sty _modified+2
lda #0
adc #0
rol a
sta set_irq._use_kernal
lda cx16.r0
ldy cx16.r0+1

View File

@ -356,7 +356,7 @@ asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
sta _modified+1
sty _modified+2
lda #0
adc #0
rol a
sta _use_kernal
sei
lda #<_irq_handler
@ -450,7 +450,7 @@ asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, ubyte useKernal @P
sta _modified+1
sty _modified+2
lda #0
adc #0
rol a
sta set_irq._use_kernal
lda cx16.r0
ldy cx16.r0+1

View File

@ -22,7 +22,10 @@ psg {
; waveform = one of PULSE,SAWTOOTH,TRIANGLE,NOISE.
; pulsewidth = 0-63. Specifies the pulse width for waveform=PULSE.
envelope_states[voice_num] = 255
sys.set_irqd()
%asm {{
php
sei
}}
cx16.r0 = $f9c2 + voice_num * 4
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_L = lsb(cx16.r0)
@ -33,7 +36,9 @@ psg {
cx16.VERA_DATA0 = waveform | pulsewidth
envelope_volumes[voice_num] = mkword(volume, 0)
envelope_maxvolumes[voice_num] = volume
sys.clear_irqd()
%asm {{
plp
}}
}
; sub freq_hz(ubyte voice_num, float hertz) {
@ -48,7 +53,10 @@ psg {
; voice_num = 0-15, vera_freq = 0-65535 calculate this via the formula given in the Vera's PSG documentation.
; (https://github.com/x16community/x16-docs/blob/master/VERA%20Programmer's%20Reference.md)
; Write freq MSB first and then LSB to reduce the chance on clicks
sys.set_irqd()
%asm {{
php
sei
}}
cx16.r0 = $f9c1 + voice_num * 4
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_L = lsb(cx16.r0)
@ -57,7 +65,9 @@ psg {
cx16.VERA_DATA0 = msb(vera_freq)
cx16.VERA_ADDR_L--
cx16.VERA_DATA0 = lsb(vera_freq)
sys.clear_irqd()
%asm {{
plp
}}
}
sub volume(ubyte voice_num, ubyte vol) {
@ -87,8 +97,9 @@ psg {
envelope_attacks[voice_num] = attack
envelope_sustains[voice_num] = sustain
envelope_releases[voice_num] = release
if maxvolume<envelope_volumes[voice_num]
envelope_volumes[voice_num] = maxvolume
cx16.r0 = mkword(maxvolume, 0)
if cx16.r0<envelope_volumes[voice_num]
envelope_volumes[voice_num] = cx16.r0
envelope_maxvolumes[voice_num] = maxvolume
envelope_states[voice_num] = 0
}
@ -106,12 +117,11 @@ psg {
; you have to call this routine every 1/60th second, for example from your vsync irq handler,
; or just install this routine as the only irq handler if you don't have to do other things there.
; Example: cx16.set_irq(&psg.envelopes_irq, true)
; NOTE: this routine calls save/restore_vera_context() for you, don't nest this!
; NOTE: this routine calls save/restore_vera_context() for you, don't nest this or call it yourself!
; cx16.r0 = the volume word (volume scaled by 256)
; cx16.r1L = the voice number
; cx16.r2L = attack value
pushw(cx16.r0)
push(cx16.r1L)
push(cx16.r2L)

View File

@ -70,7 +70,10 @@ asmsub RDTIM16() -> uword @AY {
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience
%asm {{
phx
php
sei
jsr c64.RDTIM
plp
pha
txa
tay
@ -697,7 +700,7 @@ asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
sta _modified+1
sty _modified+2
lda #0
adc #0
rol a
sta _use_kernal
sei
lda #<_irq_handler
@ -784,6 +787,7 @@ asmsub save_vera_context() clobbers(A) {
lda cx16.VERA_CTRL
sta _vera_storage+3
eor #1
sta _vera_storage+7
sta cx16.VERA_CTRL
lda cx16.VERA_ADDR_L
sta _vera_storage+4
@ -791,8 +795,6 @@ asmsub save_vera_context() clobbers(A) {
sta _vera_storage+5
lda cx16.VERA_ADDR_H
sta _vera_storage+6
lda cx16.VERA_CTRL
sta _vera_storage+7
rts
_vera_storage: .byte 0,0,0,0,0,0,0,0
}}
@ -926,6 +928,7 @@ sys {
asmsub wait(uword jiffies @AY) {
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
; note: this routine cannot be used from inside a irq handler
%asm {{
phx
sta P8ZP_SCRATCH_W1
@ -937,9 +940,13 @@ _loop lda P8ZP_SCRATCH_W1
plx
rts
+ jsr c64.RDTIM
+ sei
jsr c64.RDTIM
cli
sta P8ZP_SCRATCH_B1
- jsr c64.RDTIM
- sei
jsr c64.RDTIM
cli
cmp P8ZP_SCRATCH_B1
beq -

View File

@ -292,7 +292,7 @@ str = P8ZP_SCRATCH_W1
sta modify_pattern2+2
jsr _match
lda #0
adc #0
rol a
ldx P8ZP_SCRATCH_REG
rts

View File

@ -197,9 +197,7 @@ sub str2uword(str string) -> uword {
; (any non-digit character will terminate the number string that is parsed)
%ir {{
loadm.w r65535,conv.str2uword.string
setparam.w r65535,0
syscall 11
pop.w r0
syscall 11 (r65535.w) : r0.w
returnr.w r0
}}
}
@ -210,9 +208,7 @@ sub str2word(str string) -> word {
; (any non-digit character will terminate the number string that is parsed)
%ir {{
loadm.w r65535,conv.str2word.string
setparam.w r65535,0
syscall 12
pop.w r0
syscall 12 (r65535.w) : r0.w
returnr.w r0
}}
}

View File

@ -11,8 +11,7 @@ sub print_f(float value) {
; ---- prints the floating point value (without a newline).
%ir {{
loadm.f fr65535,floats.print_f.value
setparam.f fr65535,0
syscall 25
syscall 25 (fr65535.f)
return
}}
}
@ -127,8 +126,7 @@ sub ceil(float value) -> float {
sub rndf() -> float {
%ir {{
syscall 35
pop.f fr0
syscall 35 () : fr0.f
returnr.f fr0
}}
}
@ -136,8 +134,8 @@ sub rndf() -> float {
sub rndseedf(float seed) {
%ir {{
loadm.f fr65535,floats.rndseedf.seed
setparam.f fr65535,0
syscall 32
syscall 32 (fr65535.f)
return
}}
}

View File

@ -161,16 +161,14 @@ math {
sub rnd() -> ubyte {
%ir {{
syscall 33
pop.b r0
syscall 33 (): r0.b
returnr.b r0
}}
}
sub rndw() -> uword {
%ir {{
syscall 34
pop.w r0
syscall 34 (): r0.w
returnr.w r0
}}
}
@ -178,11 +176,9 @@ math {
sub rndseed(uword seed1, uword seed2) {
; -- reset the pseudo RNG's seed values. Defaults are: $a55a, $7653.
%ir {{
loadm.w r65535,math.rndseed.seed1
setparam.w r65535,0
loadm.w r65534,math.rndseed.seed1
loadm.w r65535,math.rndseed.seed2
setparam.w r65535,1
syscall 31
syscall 31 (r65534.w, r65535.w)
return
}}
}

View File

@ -84,12 +84,9 @@ string {
; Note that you can also directly compare strings and string values with eachother using
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
%ir {{
loadm.w r65535,string.compare.st1
setparam.w r65535,0
loadm.w r65534,string.compare.st1
loadm.w r65535,string.compare.st2
setparam.w r65535,1
syscall 29
pop.b r0
syscall 29 (r65534.w, r65535.w) : r0.b
returnr.b r0
}}
}

View File

@ -8,7 +8,7 @@ sys {
sub reset_system() {
; Soft-reset the system back to initial power-on Basic prompt.
%ir {{
syscall 0
syscall 0 ()
}}
}
@ -16,15 +16,14 @@ sys {
; --- wait approximately the given number of jiffies (1/60th seconds)
%ir {{
loadm.w r65535,sys.wait.jiffies
setparam.w r65535,0
syscall 13
syscall 13 (r65535.w)
}}
}
sub waitvsync() {
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
%ir {{
syscall 14
syscall 14()
}}
}
@ -64,8 +63,7 @@ sys {
; -- immediately exit the program with a return code in the A register
%ir {{
loadm.b r65535,sys.exit.returnvalue
setparam.b r65535,0
syscall 1
syscall 1 (r65535.b)
}}
}
@ -85,39 +83,31 @@ sys {
sub gfx_enable(ubyte mode) {
%ir {{
loadm.b r65535,sys.gfx_enable.mode
setparam.b r65535,0
syscall 8
syscall 8 (r65535.b)
}}
}
sub gfx_clear(ubyte color) {
%ir {{
loadm.b r65535,sys.gfx_clear.color
setparam.b r65535,0
syscall 9
syscall 9 (r65535.b)
}}
}
sub gfx_plot(uword xx, uword yy, ubyte color) {
%ir {{
loadm.w r65535,sys.gfx_plot.xx
setparam.w r65535,0
loadm.w r65535,sys.gfx_plot.yy
setparam.w r65535,1
loadm.w r65533,sys.gfx_plot.xx
loadm.w r65534,sys.gfx_plot.yy
loadm.b r65535,sys.gfx_plot.color
setparam.b r65535,2
syscall 10
syscall 10 (r65533.w, r65534.w, r65535.b)
}}
}
sub gfx_getpixel(uword xx, uword yy) -> ubyte {
%ir {{
loadm.w r65535,sys.gfx_getpixel.xx
setparam.w r65535,0
loadm.w r65534,sys.gfx_getpixel.xx
loadm.w r65535,sys.gfx_getpixel.yy
setparam.w r65535,1
syscall 30
pop.b r0
syscall 30 (r65534.w, r65535.w): r0.b
returnr.b r0
}}
}

View File

@ -16,8 +16,7 @@ sub clear_screen() {
str @shared sequence = "\x1b[2J\x1B[H"
%ir {{
load.w r65535,txt.clear_screen.sequence
setparam.w r65535,0
syscall 3
syscall 3 (r65535.w)
}}
}
@ -40,16 +39,14 @@ sub uppercase() {
sub chrout(ubyte char) {
%ir {{
loadm.b r65535,txt.chrout.char
setparam.b r65535,0
syscall 2
syscall 2 (r65535.b)
}}
}
sub print (str text) {
%ir {{
loadm.w r65535,txt.print.text
setparam.w r65535,0
syscall 3
syscall 3 (r65535.w)
}}
}
@ -125,12 +122,9 @@ sub input_chars (uword buffer) -> ubyte {
; ---- Input a string (max. 80 chars) from the keyboard. Returns length of input. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
%ir {{
loadm.w r65535,txt.input_chars.buffer
setparam.w r65535,0
loadm.w r65534,txt.input_chars.buffer
load.b r65535,80
setparam.b r65535,1
syscall 6
pop.b r0
syscall 6 (r65534.w, r65535.b): r0.b
returnr.b r0
}}
}

View File

@ -1 +1 @@
8.12
8.14

View File

@ -480,7 +480,9 @@ internal class AstChecker(private val program: Program,
errors.err("target datatype is unknown", assignment.target.position)
// otherwise, another error about missing symbol is already reported.
} else {
errors.err("type of value $valueDt doesn't match target $targetDt", assignment.value.position)
// allow bitwise operations on different types as long as the size is the same
if (!((assignment.value as? BinaryExpression)?.operator in BitwiseOperators && targetDt.isBytes && valueDt.isBytes || targetDt.isWords && valueDt.isWords))
errors.err("type of value $valueDt doesn't match target $targetDt", assignment.value.position)
}
}
}
@ -958,11 +960,7 @@ internal class AstChecker(private val program: Program,
}
}
if(expr.operator in ComparisonOperators) {
if(leftDt!=rightDt && !(leftDt in ByteDatatypes && rightDt in ByteDatatypes)) {
throw FatalAstException("got comparison with different operand types: $leftDt ${expr.operator} $rightDt ${expr.position}")
}
} else {
if(expr.operator !in ComparisonOperators) {
if (leftDt == DataType.STR && rightDt == DataType.STR || leftDt in ArrayDatatypes && rightDt in ArrayDatatypes) {
// str+str and str*number have already been const evaluated before we get here.
errors.err("no computational or logical expressions with strings or arrays are possible", expr.position)
@ -1611,8 +1609,11 @@ internal class AstChecker(private val program: Program,
else if(sourceDatatype== DataType.FLOAT && targetDatatype in IntegerDatatypes)
errors.err("cannot assign float to ${targetDatatype.name.lowercase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position)
else {
if(targetDatatype!=DataType.UWORD && sourceDatatype !in PassByReferenceDatatypes)
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
if(targetDatatype!=DataType.UWORD && sourceDatatype !in PassByReferenceDatatypes) {
// allow bitwise operations on different types as long as the size is the same
if (!((sourceValue as? BinaryExpression)?.operator in BitwiseOperators && targetDatatype.equalsSize(sourceDatatype)))
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
}
}
return false

View File

@ -93,6 +93,11 @@ internal class BeforeAsmAstChanger(val program: Program,
)
}
} else {
if(binExpr.left isSameAs assignment.target)
return noModifications
val typeCast = binExpr.left as? TypecastExpression
if(typeCast!=null && typeCast.expression isSameAs assignment.target)
return noModifications
val sourceDt = binExpr.left.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
val (_, left) = binExpr.left.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError(
"unknown dt"
@ -229,13 +234,13 @@ internal class BeforeAsmAstChanger(val program: Program,
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
val containingStatement = getContainingStatement(arrayIndexedExpression)
if(getComplexArrayIndexedExpressions(containingStatement).size > 1) {
errors.err("it's not possible to use more than one complex array indexing expression in a single statement; break it up via a temporary variable for instance", containingStatement.position)
return noModifications
}
if(options.compTarget.name!=VMTarget.NAME) { // don't apply this optimization/check for Vm target
val containingStatement = getContainingStatement(arrayIndexedExpression)
if(getComplexArrayIndexedExpressions(containingStatement).size > 1) {
errors.err("it's not possible to use more than one complex array indexing expression in a single statement; break it up via a temporary variable for instance", containingStatement.position)
return noModifications
}
if(options.compTarget.name!=VMTarget.NAME) { // don't apply this optimization for Vm target
val index = arrayIndexedExpression.indexer.indexExpr
if (index !is NumericLiteral && index !is IdentifierReference) {
// replace complex indexing expression with a temp variable to hold the computed index first

View File

@ -133,9 +133,16 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
if(leftDt istype DataType.WORD && rightDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
// cast left to unsigned
val cast = TypecastExpression(expr.left, rightDt.getOr(DataType.UNDEFINED), true, expr.left.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
// cast left to unsigned word. Cast right to unsigned word if it is ubyte
val mods = mutableListOf<IAstModification>()
val cast = TypecastExpression(expr.left, DataType.UWORD, true, expr.left.position)
mods += IAstModification.ReplaceNode(expr.left, cast, expr)
if(rightDt istype DataType.UBYTE) {
mods += IAstModification.ReplaceNode(expr.right,
TypecastExpression(expr.right, DataType.UWORD, true, expr.right.position),
expr)
}
return mods
}
if(rightDt istype DataType.BYTE && leftDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
// cast right to unsigned
@ -143,9 +150,16 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
return listOf(IAstModification.ReplaceNode(expr.right, cast, expr))
}
if(rightDt istype DataType.WORD && leftDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
// cast right to unsigned
val cast = TypecastExpression(expr.right, leftDt.getOr(DataType.UNDEFINED), true, expr.right.position)
return listOf(IAstModification.ReplaceNode(expr.right, cast, expr))
// cast right to unsigned word. Cast left to unsigned word if it is ubyte
val mods = mutableListOf<IAstModification>()
val cast = TypecastExpression(expr.right, DataType.UWORD, true, expr.right.position)
mods += IAstModification.ReplaceNode(expr.right, cast, expr)
if(leftDt istype DataType.UBYTE) {
mods += IAstModification.ReplaceNode(expr.left,
TypecastExpression(expr.left, DataType.UWORD, true, expr.left.position),
expr)
}
return mods
}
}

View File

@ -33,7 +33,7 @@ class TestLaunchEmu: FunSpec({
<INITGLOBALS>
</INITGLOBALS>
<BLOCK NAME="main" ADDRESS="" ALIGN="NONE" POS="[unittest: line 42 col 1-9]">
<BLOCK NAME="main" ADDRESS="" LIBRARY="false" FORCEOUTPUT="false" ALIGN="NONE" POS="[unittest: line 42 col 1-9]">
</BLOCK>
</PROGRAM>
""")

View File

@ -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") {

View File

@ -1,117 +1,9 @@
TODO
====
For next minor release
^^^^^^^^^^^^^^^^^^^^^^
- try to optimize newexpr a bit more
This is the maintenance branch for Prog8 version 8, only important bugfixes will be made here.
New development for version 9 and up is taking place in the master branch.
TODO V8 maintenance fixes
^^^^^^^^^^^^^^^^^^^^^^^^^
...
For 9.0 major changes
^^^^^^^^^^^^^^^^^^^^^
- rename builtin function sqrt16 to just sqrt
- copy (not move) the CBM kernal romsubs to a new 'cbm' block so programs on c128 and cx16 can also
simply refer to cbm.CHROUT rather than c64.CHROUT which looks a bit silly on the non-c64 cbm systems.
we keep the old definitions intact because of backwards compatibility reasons.
- try to reintroduce builtin functions max/maxw/min/minw that take 2 args and return the largest/smallest of them.
This is a major change because it will likely break existing code that is now using min and max as variable names.
Also add optimization that changes the word variant to byte variant if the operands are bytes.
- 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.
It is documented behavior to now loop 'around' $00 but it's too easy to forget about!
Lot of work because of so many special cases in ForLoopsAsmgen.....
(vm codegen already behaves like this!)
- ?? get rid of the disknumber parameter everywhere in diskio, make it a configurable variable that defaults to 8.
the large majority of users will only deal with a single disk drive so why not make it easier for them.
But see complaint on github https://github.com/irmen/prog8/issues/106
- duplicate diskio for cx16 (get rid of cx16diskio, just copy diskio and tweak everything) + documentation
- get f_seek_w working like in the BASIC program - this needs the changes to diskio.f_open to use suffixes ,p,m
- add special (u)word array type (or modifier such as @fast? ) that puts the array into memory as 2 separate byte-arrays 1 for LSB 1 for MSB -> allows for word arrays of length 256 and faster indexing
this is an enormous amout of work, if this type is to be treated equally as existing (u)word , because all expression / lookup / assignment routines need to know about the distinction....
So maybe only allow the bare essentials? (store, get, bitwise operations?)
- Some more support for (64tass) SEGMENTS ?
- (What, how, isn't current BSS support enough?)
- Add a mechanism to allocate variables into golden ram (or segments really) (see GoldenRam class)
- maybe treat block "golden" in a special way: can only contain vars, every var will be allocated in the Golden ram area?
- maybe or may not needed: the variables can NOT have initialization values, they will all be set to zero on startup (simple memset)
just initialize them yourself in start() if you need a non-zero value .
- OR.... do all this automatically if 'golden' is enabled as a compiler option? So compiler allocates in ZP first, then Golden Ram, then regular ram
- OR.... make all this more generic and use some %segment option to create real segments for 64tass?
- (need separate step in codegen and IR to write the "golden" variables)
Need help with
^^^^^^^^^^^^^^
- atari target: more details details about the machine, fixing library routines. I have no clue whatsoever.
- see the :ref:`portingguide` for details on what information is needed.
Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
Compiler:
- ir: idea: (but LLVM IR simply keeps the variables, so not a good idea then?...): replace all scalar variables by an allocated register. Keep a table of the variable to register mapping (including the datatype)
global initialization values are simply a list of LOAD instructions.
Variables replaced include all subroutine parameters! So the only variables that remain as variables are arrays and strings.
- ir: mechanism to determine for chunks which registers are getting input values from "outside"
- ir: mechanism to determine for chunks which registers are passing values out? (i.e. are used again in another chunk)
- ir: peephole opt: (maybe just integrate this in the variable/register allocator though?) renumber registers in chunks to start with 1 again every time (but keep entry values in mind!)
- ir: peephole opt: (maybe just integrate this in the variable/register allocator though?) 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)
- PtAst/IR: more complex common subexpression eliminations
- generate WASM to eventually run prog8 on a browser canvas? Use binaryen toolkit or my binaryen kotlin library?
- can we get rid of pieces of asmgen.AssignmentAsmGen by just reusing the AugmentableAssignment ? generated code should not suffer
- [problematic due to using 64tass:] better support for building library programs, where unused .proc shouldn't be deleted from the assembly?
Perhaps replace all uses of .proc/.pend/.endproc by .block/.bend will fix that with a compiler flag?
But all library code written in asm uses .proc already..... (textual search/replace when writing the actual asm?)
Once new codegen is written that is based on the IR, this point is mostly moot anyway as that will have its own dead code removal on the IR level.
- Zig-like try-based error handling where the V flag could indicate error condition? and/or BRK to jump into monitor on failure? (has to set BRK vector for that) But the V flag is also set on certain normal instructions
- For c128 target; put floating point variables in bank 1 to make the FP routines work (is this even worth it? very few people will use fp)
Libraries:
- fix the problems in atari target, and flesh out its libraries.
- c128 target: make syslib more complete (missing kernal routines)?
- c64: make the graphics.BITMAP_ADDRESS configurable (VIC banking)
- optimize several inner loops in gfx2 even further?
- add modes 3 and perhaps even 2 to gfx2 (lores 16 color and 4 color)?
- add a flood fill (span fill/scanline fill) routine to gfx2?
Expressions:
- Once the evalstack-free expression codegen is in place, the Eval Stack can be removed from the compiler.
Machinedefinition, .p8 and .asm library files, all routines operationg on estack, and everything saving/restoring the X register related to this stack.
- Or rewrite expression tree evaluation such that it doesn't use an eval stack but flatten the tree into linear code
that, for instance, uses a fixed number of predetermined value 'variables'?
The VM IL solves this already (by using unlimited registers) but that still lacks a translation to 6502.
- this removes the need for the BinExprSplitter? (which is problematic and very limited now)
and perhaps the assignment splitting in BeforeAsmAstChanger too
Optimizations:
- VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served?
for instance, vars used inside loops first, then loopvars, then uwords used as pointers, then the rest
- various optimizers skip stuff if compTarget.name==VMTarget.NAME. Once 6502-codegen is done from IR code,
those checks should probably be removed, or be made permanent
STRUCTS again?
--------------
What if we were to re-introduce Structs in prog8? Some thoughts:
- can contain only numeric types (byte,word,float) - no nested structs, no reference types (strings, arrays) inside structs
- is just some syntactic sugar for a scoped set of variables -> ast transform to do exactly this before codegen. Codegen doesn't know about struct.
- no arrays of struct -- because too slow on 6502 to access those, rather use struct of arrays instead.
can we make this a compiler/codegen only issue? i.e. syntax is just as if it was an array of structs?
or make it explicit in the syntax so that it is clear what the memory layout of it is.
- ability to assign struct variable to another? this is slow but can be quite handy sometimes.
however how to handle this in a function that gets the struct passed as reference? Don't allow it there? (there's no pointer dereferencing concept in prog8)
- ability to be passed as argument to a function (by reference)?
however there is no typed pointer in prog8 at the moment so this can't be implemented in a meaningful way yet,
because there is no way to reference it as the struct type again. (current ast gets the by-reference parameter
type replaced by uword)
So-- maybe don't replace the parameter type in the ast? Should fix that for str and array types as well then

View File

@ -15,14 +15,14 @@ main {
palette.set_rgb(amigacolors, len(amigacolors))
cx16.VERA_DC_VSCALE = 64 ; have the vertical resolution so it is 640*240 - more or less Amiga's default non interlaced mode
gfx2.text_charset(3)
gfx2.text_charset(1)
screen_titlebar()
window_workbench()
window_system()
window_shell()
gfx2.text(280, 210, 1, sc:"640x480(240) 4 colors")
gfx2.text(280, 220, 1, sc:"Mockup drawn using Prog8 gfx2 library")
gfx2.text(280, 210, 1, iso:"640x480(240) 4 colors")
gfx2.text(280, 220, 1, iso:"Mockup drawn using Prog8 gfx2 library")
repeat {
}
@ -30,7 +30,7 @@ main {
sub screen_titlebar() {
gfx2.fillrect(0, 0, gfx2.width, 10, 2)
gfx2.text(8,1, 1, sc:"AmigaOS 3.1 2,002,448 graphics mem 16,504,384 other mem")
gfx2.text(8,1, 1, iso:"AmigaOS 3.1 2,002,448 graphics mem 16,504,384 other mem")
gfx2.horizontal_line(0, 10, gfx2.width, 1)
widget.window_order_icon(gfx2.width-widget.window_order_icon.width, 0, false)
}
@ -42,7 +42,7 @@ main {
const uword width = 600
const uword height = 220
widget.window_titlebar(win_x, win_y, width, sc:"Workbench", false)
widget.window_titlebar(win_x, win_y, width, iso:"Workbench", false)
; gfx2.fillrect(win_x+3, win_y+11, width-4, height-11-2,0) ; clear window pane
widget.window_leftborder(win_x, win_y, height, false)
widget.window_bottomborder(win_x, win_y, width, height)
@ -51,8 +51,8 @@ main {
vector_v(win_x+width - 390, win_y+height-20)
vector_v(win_x+width - 390 -14, win_y+height-20)
widget.icon(45,40, sc:"Ram Disk")
widget.icon(45,90, sc:"Workbench3.1")
widget.icon(45,40, iso:"Ram Disk")
widget.icon(45,90, iso:"Workbench3.1")
}
sub vector_v(uword x, uword y) {
@ -70,17 +70,17 @@ main {
const uword win_x = 320
const uword win_y = 40
widget.window_titlebar(win_x, win_y, width, sc:"System", false)
widget.window_titlebar(win_x, win_y, width, iso:"System", false)
gfx2.fillrect(win_x+3, win_y+11, width-4, height-11-2, 0) ; clear window pane
widget.window_leftborder(win_x, win_y, height, false)
widget.window_bottomborder(win_x, win_y, width, height)
widget.window_rightborder(win_x, win_y, width, height, false)
widget.icon(win_x+16, win_y+14, sc:"FixFonts")
widget.icon(win_x+16+80, win_y+14, sc:"NoFastMem")
widget.icon(win_x+16, win_y+56, sc:"Format")
widget.icon(win_x+16+80, win_y+56, sc:"RexxMast")
widget.icon(win_x+16+160, win_y+56, sc:"Shell")
widget.icon(win_x+16, win_y+14, iso:"FixFonts")
widget.icon(win_x+16+80, win_y+14, iso:"NoFastMem")
widget.icon(win_x+16, win_y+56, iso:"Format")
widget.icon(win_x+16+80, win_y+56, iso:"RexxMast")
widget.icon(win_x+16+160, win_y+56, iso:"Shell")
}
sub window_shell() {
@ -89,14 +89,14 @@ main {
const uword width = 500
const uword height = 65
widget.window_titlebar(win_x, win_y, width, sc:"AmigaShell", true)
widget.window_titlebar(win_x, win_y, width, iso:"AmigaShell", true)
gfx2.fillrect(win_x+3, win_y+11, width-4, height-11-2,0) ; clear window pane
widget.window_leftborder(win_x, win_y, height, true)
widget.window_bottomborder(win_x, win_y, width, height)
widget.window_rightborder(win_x, win_y, width, height, true)
gfx2.text(win_x+5, win_y+12, 1, sc:"New Shell process 3")
gfx2.text(win_x+5, win_y+12+8, 1, sc:"3.Workbench3.1:>")
gfx2.text(win_x+5, win_y+12, 1, iso:"New Shell process 3")
gfx2.text(win_x+5, win_y+12+8, 1, iso:"3.Workbench3.1:>")
gfx2.fillrect(win_x+5+17*8, win_y+12+8, 8, 8, 1) ; cursor
}
}

View File

@ -34,7 +34,7 @@ main {
irq {
const ubyte BAR_Y_OFFSET = 6
const ubyte BAR_Y_OFFSET = 5
uword next_irq_line = 0
ubyte anim1 = 0
ubyte av1 = 0

View File

@ -9,17 +9,16 @@ main {
sub start() {
txt.print("ps2 custom key handler test - press keys! esc to quit!\n")
txt.print("custom key handler test - press keys! esc to quit!\n")
sys.set_irqd()
uword old_keyhdl = cx16.KEYHDL
cx16.KEYHDL = &keyboard_scancode_handler
sys.clear_irqd()
bool escape_pressed
while not escape_pressed {
handle_keyboard_event()
while handle_keyboard_event() {
}
sys.set_irqd()
cx16.KEYHDL = old_keyhdl
sys.clear_irqd()
@ -30,70 +29,49 @@ main {
; so that they won't get overwritten with initialization values every time.
; The assembly keyboard handler will set these, prog8 will read them.
bool @shared keyhdl_event ; is there a keyboard event to handle?
ubyte @shared keyhdl_prefix
ubyte @shared keyhdl_scancode
ubyte @shared keyhdl_updown
sub handle_keyboard_event() {
sub handle_keyboard_event() -> bool {
; Potentially handle keyboard event.
; Note that we do this from the program's main loop instead of
; the actual keyboard handler routine itself.
; The reason for this is documented below in the handler assembly routine.
if not keyhdl_event
return
return true
keyhdl_event = false
txt.print_ubhex(keyhdl_prefix, true)
txt.chrout(':')
txt.print_ubhex(keyhdl_scancode, true)
txt.spc()
if keyhdl_updown
if keyhdl_scancode & $80
txt.chrout('u')
else
txt.chrout('d')
txt.nl()
if keyhdl_prefix==0 and keyhdl_scancode==119 and keyhdl_updown {
; escape was pressed! exit back to basic
main.start.escape_pressed = true
}
return keyhdl_scancode!=$6e ; escape breaks the loop
}
asmsub keyboard_scancode_handler() {
; NOTE that the keyboard handler is an asm subroutine.
; Unfortunately is it not possible to use prog8 code or calls here,
; because the X register gets overwritten here (to store the prefix byte)
; and prog8 uses the X register internally (for the evaluation stack).
; because the X register gets overwritten here by the kernal.
; Pog8 uses the X register internally (for the software eval stack).
; So it is unsafe to call prog8 code from here because the evaluation stack pointer
; will be invalid which produces undefined results.
; So, instead, we store the various keyboard event bytes and signal
; the main prog8 program that a keyboard event has occurred.
; It then processes it independently from the assembly code here.
;
; Unfortunately this also means you cannot decide from that prog8 code
; Unfortunately this also means you cannot decide easily from that prog8 code
; if the keyboard press should be consumed/ignored or put into the keyboard queue
; (this is controlled by returning 0 or 1 in register A here)
;
; see:
; https://github.com/X16Community/x16-docs/blob/master/X16%20Reference%20-%2002%20-%20Editor.md#custom-keyboard-scancode-handler
%asm {{
php
pha
phx
stz keyhdl_updown
bcc +
inc keyhdl_updown
+ stx keyhdl_prefix
sta keyhdl_scancode
lda #1
sta keyhdl_event
; we can do additional stuff here and decide if we want to
; consume the key event or not (A=0 or A!=0)
plx
pla
lda #0 ;By setting A=0 we will remove this key event for now
tax
plp
lda #0 ; By setting A=0 we will eat this key event. leave A unchanged to pass it through.
rts
}}
}

View File

@ -1,28 +1,35 @@
%import textio
%option no_sysinit
%zeropage basicsafe
main {
sub start() {
byte v1s = 22
byte v2s = -99
word ww
ubyte ub = 123
byte bb = -100
uword uw = 12345
word ww = -12345
txt.print_w(minsb()) ; TODO WRONG RESULT!
ub |= 63 ; vm/c64 ok (127)
bb |= 63 ; vm/c64 ok (-65)
uw |= 63 ; vm/c64 ok (12351)
ww |= 63 ; vm/c64 ok (-12289)
txt.print_ub(ub)
txt.spc()
txt.print_b(bb)
txt.spc()
txt.print_uw(uw)
txt.spc()
ww = minsb()
txt.print_w(ww)
txt.spc()
txt.print_b(minsb())
txt.spc()
v2s = minsb()
txt.print_w(v2s)
txt.nl()
sub minsb() -> byte {
cx16.r0++
return v2s
}
uw |= 16384 ; vm/c64 ok (28735)
ww |= 8192 ; vm/c64 ok (-4097)
txt.print_uw(uw)
txt.spc()
txt.print_w(ww)
txt.nl()
}
}

View File

@ -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
@ -336,6 +337,8 @@ class IRFileReader {
val block = IRBlock(
attrs.getValue("NAME"),
if(attrs.getValue("ADDRESS")=="") null else parseIRValue(attrs.getValue("ADDRESS")).toUInt(),
if(attrs.getValue("LIBRARY")=="") false else attrs.getValue("LIBRARY").toBoolean(),
if(attrs.getValue("FORCEOUTPUT")=="") false else attrs.getValue("FORCEOUTPUT").toBoolean(),
IRBlock.BlockAlignment.valueOf(attrs.getValue("ALIGN")),
parsePosition(attrs.getValue("POS")))
skipText(reader)
@ -501,17 +504,6 @@ class IRFileReader {
}
}
private fun parseRegisterOrStatusflag(regs: String): RegisterOrStatusflag {
var reg: RegisterOrPair? = null
var sf: Statusflag? = null
try {
reg = RegisterOrPair.valueOf(regs)
} catch (x: IllegalArgumentException) {
sf = Statusflag.valueOf(regs)
}
return RegisterOrStatusflag(reg, sf)
}
private val posPattern = Regex("\\[(.+): line (.+) col (.+)-(.+)\\]")
private fun parsePosition(strpos: String): Position {

View File

@ -57,6 +57,8 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
xml.writeStartElement("BLOCK")
xml.writeAttribute("NAME", block.label)
xml.writeAttribute("ADDRESS", block.address?.toHex() ?: "")
xml.writeAttribute("LIBRARY", block.library.toString())
xml.writeAttribute("FORCEOUTPUT", block.forceOutput.toString())
xml.writeAttribute("ALIGN", block.alignment.toString())
xml.writeAttribute("POS", block.position.toString())
xml.writeCharacters("\n")
@ -138,7 +140,6 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
private fun writeInlineBytes(chunk: IRInlineBinaryChunk) {
xml.writeStartElement("BYTES")
chunk.label?.let { xml.writeAttribute("LABEL", chunk.label) }
xml.writeCharacters("\n")
chunk.data.withIndex().forEach {(index, byte) ->
xml.writeCharacters(byte.toString(16).padStart(2,'0'))
if(index and 63 == 63 && index < chunk.data.size-1)

View File

@ -1,5 +1,6 @@
package prog8.intermediate
import prog8.code.core.RegisterOrStatusflag
import prog8.code.core.toHex
/*
@ -23,7 +24,8 @@ Currently ther is NO support for 24 or 32 bits integers.
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.
Instructions taking more than 1 register cannot take the same register multiple times! (to avoid confusing different datatypes)
NOTE: Labels in source text should always start with an underscore.
LOAD/STORE
@ -36,15 +38,11 @@ loadi reg1, reg2 - load reg1 with value at memory indirect,
loadx reg1, reg2, address - load reg1 with value at memory address indexed by value in reg2
loadix reg1, reg2, pointeraddr - load reg1 with value at memory indirect, pointed to by pointeraddr indexed by value in reg2
loadr reg1, reg2 - load reg1 with value in register reg2
loadcpu reg1, cpureg - load reg1 with value from cpu register (register/registerpair/statusflag)
storem reg1, address - store reg1 at memory address
storecpu reg1, cpureg - store reg1 in cpu register (register/registerpair/statusflag)
storei reg1, reg2 - store reg1 at memory indirect, memory pointed to by reg2
storex reg1, reg2, address - store reg1 at memory address, indexed by value in reg2
storeix reg1, reg2, pointeraddr - store reg1 at memory indirect, pointed to by pointeraddr indexed by value in reg2
storezm address - store zero at memory address
storezcpu cpureg - store zero in cpu register (register/registerpair/statusflag)
storezi reg1 - store zero at memory pointed to by reg1
storezx reg1, address - store zero at memory address, indexed by value in reg
@ -53,63 +51,70 @@ CONTROL FLOW
------------
jump location - continue running at instruction number given by location
jumpa address - continue running at memory address (note: only used to encode a physical cpu jump to fixed address instruction)
setparam reg1, argpos - sets reg1 as the value for the parameter in the given position for an upcoming function call (call, callr, syscall, or even jump opcode).
call location - save current instruction location+1, continue execution at instruction nr given by location. No return value is expected.
callr reg1, location - like call but expects the routine to return a value with a returnr instruction, it then puts that in reg1
syscall value - do a systemcall identified by call number, result value(s) are pushed on value stack so need to be POPped off (depends on syscall)
call label(argument register list) [: resultreg.type]
- calls a subroutine with the given arguments and return value (optional).
save current instruction location+1, continue execution at instruction nr of the label.
the argument register list is positional and includes the datatype, ex.: r4.b,r5.w,fp1.f
If the call is to a rom-routine, 'label' will be a hexadecimal address instead such as $ffd2
If the arguments should be passed in CPU registers, they'll have a @REGISTER postfix.
For example: call $ffd2(r5.b@A)
syscall number (argument register list) [: resultreg.type]
- do a systemcall identified by number, result value(s) are pushed on value stack by the syscall code so
will be POPped off into the given resultregister if any.
return - restore last saved instruction location and continue at that instruction. No return value.
returnr reg1 - like return, but also returns the value in reg1 to the caller
BRANCHING and CONDITIONALS
--------------------------
All have type b or w except the branches that only check status bits.
bstcc address - branch to location if Status bit Carry is Clear
bstcs address - branch to location if Status bit Carry is Set
bstcc address - branch to location if Status bit Carry is clear
bstcs address - branch to location if Status bit Carry is set
bstne address - branch to location if Status bit Zero is clear
bsteq address - branch to location if Status bit Zero is set
bstne address - branch to location if Status bit Zero is not set
bstneg address - branch to location if Status bit Negative is not set
bstpos address - branch to location if Status bit Negative is not set
bstvc address - branch to location if Status bit Overflow is not set
bstvs address - branch to location if Status bit Overflow is not set
bstpos address - branch to location if Status bit Negative is clear
bstneg address - branch to location if Status bit Negative is set
bstvc address - branch to location if Status bit Overflow is clear
bstvs address - branch to location if Status bit Overflow is set
beqr reg1, reg2, address - jump to location in program given by location, if reg1 == reg2
beq reg1, value, address - jump to location in program given by location, if reg1 == immediate value
bner reg1, reg2, address - jump to location in program given by location, if reg1 != reg2
bne reg1, value, address - jump to location in program given by location, if reg1 != immediate value
bgtr reg1, reg2, address - jump to location in program given by location, if reg1 > reg2 (unsigned)
bgt reg1, value, address - jump to location in program given by location, if reg1 > immediate value (unsigned)
blt reg1, value, address - jump to location in program given by location, if reg1 < immediate value (unsigned)
bgtsr reg1, reg2, address - jump to location in program given by location, if reg1 > reg2 (signed)
bgts reg1, value, address - jump to location in program given by location, if reg1 > immediate value (signed)
bgtr reg1, reg2, address - jump to location in program given by location, if reg1 > reg2 (unsigned)
bgtsr reg1, reg2, address - jump to location in program given by location, if reg1 > reg2 (signed)
blt reg1, value, address - jump to location in program given by location, if reg1 < immediate value (unsigned)
blts reg1, value, address - jump to location in program given by location, if reg1 < immediate value (signed)
bger reg1, reg2, address - jump to location in program given by location, if reg1 >= reg2 (unsigned)
bge reg1, value, address - jump to location in program given by location, if reg1 >= immediate value (unsigned)
ble reg1, value, address - jump to location in program given by location, if reg1 <= immediate value (unsigned)
bgesr reg1, reg2, address - jump to location in program given by location, if reg1 >= reg2 (signed)
bges reg1, value, address - jump to location in program given by location, if reg1 >= immediate value (signed)
bger reg1, reg2, address - jump to location in program given by location, if reg1 >= reg2 (unsigned)
bgesr reg1, reg2, address - jump to location in program given by location, if reg1 >= reg2 (signed)
ble reg1, value, address - jump to location in program given by location, if reg1 <= immediate value (unsigned)
bles reg1, value, address - jump to location in program given by location, if reg1 <= immediate value (signed)
( NOTE: there are no bltr/bler instructions because these are equivalent to bgtr/bger with the register operands swapped around.)
sz reg1, reg2 - set reg1=1 if reg2==0, otherwise set reg1=0
snz reg1, reg2 - set reg1=1 if reg2!=0, otherwise set reg1=0
seq reg1, reg2 - set reg1=1 if reg1 == reg2, otherwise set reg1=0
sne reg1, reg2 - set reg1=1 if reg1 != reg2, otherwise set reg1=0
slt reg1, reg2 - set reg1=1 if reg1 < reg2 (unsigned), otherwise set reg1=0
slts reg1, reg2 - set reg1=1 if reg1 < reg2 (signed), otherwise set reg1=0
sle reg1, reg2 - set reg1=1 if reg1 <= reg2 (unsigned), otherwise set reg1=0
sles reg1, reg2 - set reg1=1 if reg1 <= reg2 (signed), otherwise set reg1=0
sgt reg1, reg2 - set reg1=1 if reg1 > reg2 (unsigned), otherwise set reg1=0
sgts reg1, reg2 - set reg1=1 if reg1 > reg2 (signed), otherwise set reg1=0
sge reg1, reg2 - set reg1=1 if reg1 >= reg2 (unsigned), otherwise set reg1=0
sges reg1, reg2 - set reg1=1 if reg1 >= reg2 (signed), otherwise set reg1=0
sz reg1, reg2 - set reg1=1.b if reg2==0, otherwise set reg1=0.b
snz reg1, reg2 - set reg1=1.b if reg2!=0, otherwise set reg1=0.b
seq reg1, reg2 - set reg1=1.b if reg1 == reg2, otherwise set reg1=0.b
sne reg1, reg2 - set reg1=1.b if reg1 != reg2, otherwise set reg1=0.b
slt reg1, reg2 - set reg1=1.b if reg1 < reg2 (unsigned), otherwise set reg1=0.b
slts reg1, reg2 - set reg1=1.b if reg1 < reg2 (signed), otherwise set reg1=0.b
sle reg1, reg2 - set reg1=1.b if reg1 <= reg2 (unsigned), otherwise set reg1=0.b
sles reg1, reg2 - set reg1=1.b if reg1 <= reg2 (signed), otherwise set reg1=0.b
sgt reg1, reg2 - set reg1=1.b if reg1 > reg2 (unsigned), otherwise set reg1=0.b
sgts reg1, reg2 - set reg1=1.b if reg1 > reg2 (signed), otherwise set reg1=0.b
sge reg1, reg2 - set reg1=1.b if reg1 >= reg2 (unsigned), otherwise set reg1=0.b
sges reg1, reg2 - set reg1=1.b if reg1 >= reg2 (signed), otherwise set reg1=0.b
ARITHMETIC
----------
All have type b or w or f. Note: result types are the same as operand types! E.g. byte*byte->byte.
ext reg1 - reg1 = unsigned extension of reg1 (which in practice just means clearing the MSB / MSW) (ext.w not yet implemented as we don't have longs yet)
exts reg1 - reg1 = signed extension of reg1 (byte to word, or word to long) (note: ext.w is not yet implemented as we don't have longs yet)
ext reg1 - reg1 = unsigned extension of reg1 (which in practice just means clearing the MSB / MSW) (ext.w not yet implemented as we don't have longs yet)
inc reg1 - reg1 = reg1+1
incm address - memory at address += 1
dec reg1 - reg1 = reg1-1
@ -214,7 +219,6 @@ msig [b, w] reg1, reg2 - reg1 becomes the most significant by
concat [b, w] reg1, reg2 - reg1 = concatenated lsb/lsw of reg1 (as lsb) and lsb/lsw of reg2 (as msb) into word or int (int not yet implemented; requires 32bits regs)
push [b, w, f] reg1 - push value in reg1 on the stack
pop [b, w, f] reg1 - pop value from stack into reg1
binarydata - 'instruction' to hold inlined binary data bytes
*/
enum class Opcode {
@ -225,22 +229,17 @@ enum class Opcode {
LOADX,
LOADIX,
LOADR,
LOADCPU,
STOREM,
STORECPU,
STOREI,
STOREX,
STOREIX,
STOREZM,
STOREZCPU,
STOREZI,
STOREZX,
JUMP,
JUMPA,
SETPARAM,
CALL,
CALLR,
SYSCALL,
RETURN,
RETURNR,
@ -372,8 +371,7 @@ enum class Opcode {
POP,
MSIG,
CONCAT,
BREAKPOINT,
BINARYDATA
BREAKPOINT
}
val OpcodesThatJump = setOf(
@ -389,7 +387,6 @@ val OpcodesThatBranch = setOf(
Opcode.RETURN,
Opcode.RETURNR,
Opcode.CALL,
Opcode.CALLR,
Opcode.SYSCALL,
Opcode.BSTCC,
Opcode.BSTCS,
@ -417,12 +414,6 @@ val OpcodesThatBranch = setOf(
Opcode.BLES
)
val OpcodesForCpuRegisters = setOf(
Opcode.LOADCPU,
Opcode.STORECPU,
Opcode.STOREZCPU
)
enum class IRDataType {
BYTE,
WORD,
@ -443,7 +434,9 @@ data class InstructionFormat(val datatype: IRDataType?,
val fpReg1: OperandDirection,
val fpReg2: OperandDirection,
val address: OperandDirection,
val immediate: Boolean) {
val immediate: Boolean,
val funcCall: Boolean,
val sysCall: Boolean) {
companion object {
fun from(spec: String): Map<IRDataType?, InstructionFormat> {
val result = mutableMapOf<IRDataType?, InstructionFormat>()
@ -456,33 +449,37 @@ data class InstructionFormat(val datatype: IRDataType?,
var immediate = false
val splits = part.splitToSequence(',').iterator()
val typespec = splits.next()
var funcCall = false
var sysCall = false
while(splits.hasNext()) {
when(splits.next()) {
"<r1" -> { reg1=OperandDirection.READ }
">r1" -> { reg1=OperandDirection.WRITE }
"<>r1" -> { reg1=OperandDirection.READWRITE }
"<r1" -> reg1 = OperandDirection.READ
">r1" -> reg1 = OperandDirection.WRITE
"<>r1" -> reg1 = OperandDirection.READWRITE
"<r2" -> reg2 = OperandDirection.READ
"<fr1" -> { fpreg1=OperandDirection.READ }
">fr1" -> { fpreg1=OperandDirection.WRITE }
"<>fr1" -> { fpreg1=OperandDirection.READWRITE }
"<fr1" -> fpreg1 = OperandDirection.READ
">fr1" -> fpreg1 = OperandDirection.WRITE
"<>fr1" -> fpreg1 = OperandDirection.READWRITE
"<fr2" -> fpreg2 = OperandDirection.READ
">i", "<>i" -> throw IllegalArgumentException("can't write into an immediate value")
"<i" -> immediate = true
"<a" -> address = OperandDirection.READ
">a" -> address = OperandDirection.WRITE
"<>a" -> address = OperandDirection.READWRITE
"call" -> funcCall = true
"syscall" -> sysCall = true
else -> throw IllegalArgumentException(spec)
}
}
if(typespec=="N")
result[null] = InstructionFormat(null, reg1, reg2, fpreg1, fpreg2, address, immediate)
result[null] = InstructionFormat(null, reg1, reg2, fpreg1, fpreg2, address, immediate, funcCall, sysCall)
if('B' in typespec)
result[IRDataType.BYTE] = InstructionFormat(IRDataType.BYTE, reg1, reg2, fpreg1, fpreg2, address, immediate)
result[IRDataType.BYTE] = InstructionFormat(IRDataType.BYTE, reg1, reg2, fpreg1, fpreg2, address, immediate, funcCall, sysCall)
if('W' in typespec)
result[IRDataType.WORD] = InstructionFormat(IRDataType.WORD, reg1, reg2, fpreg1, fpreg2, address, immediate)
result[IRDataType.WORD] = InstructionFormat(IRDataType.WORD, reg1, reg2, fpreg1, fpreg2, address, immediate, funcCall, sysCall)
if('F' in typespec)
result[IRDataType.FLOAT] = InstructionFormat(IRDataType.FLOAT, reg1, reg2, fpreg1, fpreg2, address, immediate)
result[IRDataType.FLOAT] = InstructionFormat(IRDataType.FLOAT, reg1, reg2, fpreg1, fpreg2, address, immediate, funcCall, sysCall)
}
return result
}
@ -507,22 +504,17 @@ val instructionFormats = mutableMapOf(
Opcode.LOADX to InstructionFormat.from("BW,>r1,<r2,<a | F,>fr1,<r1,<a"),
Opcode.LOADIX to InstructionFormat.from("BW,>r1,<r2,<a | F,>fr1,<r1,<a"),
Opcode.LOADR to InstructionFormat.from("BW,>r1,<r2 | F,>fr1,<fr2"),
Opcode.LOADCPU to InstructionFormat.from("BW,>r1"),
Opcode.STOREM to InstructionFormat.from("BW,<r1,>a | F,<fr1,>a"),
Opcode.STORECPU to InstructionFormat.from("BW,<r1"),
Opcode.STOREI to InstructionFormat.from("BW,<r1,<r2 | F,<fr1,<r1"),
Opcode.STOREX to InstructionFormat.from("BW,<r1,<r2,>a | F,<fr1,<r1,>a"),
Opcode.STOREIX to InstructionFormat.from("BW,<r1,<r2,>a | F,<fr1,<r1,>a"),
Opcode.STOREZM to InstructionFormat.from("BW,>a | F,>a"),
Opcode.STOREZCPU to InstructionFormat.from("BW"),
Opcode.STOREZI to InstructionFormat.from("BW,<r1 | F,<r1"),
Opcode.STOREZX to InstructionFormat.from("BW,<r1,>a | F,<r1,>a"),
Opcode.JUMP to InstructionFormat.from("N,<a"),
Opcode.JUMPA to InstructionFormat.from("N,<a"),
Opcode.SETPARAM to InstructionFormat.from("BW,<r1,<i | F,<fr1,<i"),
Opcode.CALL to InstructionFormat.from("N,<a"),
Opcode.CALLR to InstructionFormat.from("BW,>r1,<a | F,>fr1,<a"),
Opcode.SYSCALL to InstructionFormat.from("N,<i"),
Opcode.CALL to InstructionFormat.from("N,call"),
Opcode.SYSCALL to InstructionFormat.from("N,syscall"),
Opcode.RETURN to InstructionFormat.from("N"),
Opcode.RETURNR to InstructionFormat.from("BW,>r1 | F,>fr1"),
Opcode.BSTCC to InstructionFormat.from("N,<a"),
@ -651,10 +643,17 @@ val instructionFormats = mutableMapOf(
Opcode.CLC to InstructionFormat.from("N"),
Opcode.SEC to InstructionFormat.from("N"),
Opcode.BREAKPOINT to InstructionFormat.from("N"),
Opcode.BINARYDATA to InstructionFormat.from("N"),
)
class FunctionCallArgs(
var arguments: List<ArgumentSpec>,
val returns: RegSpec?
) {
class RegSpec(val dt: IRDataType, val registerNum: Int, val cpuRegister: RegisterOrStatusflag?)
class ArgumentSpec(val name: String, val address: Int?, val reg: RegSpec)
}
data class IRInstruction(
val opcode: Opcode,
val type: IRDataType?=null,
@ -665,9 +664,9 @@ data class IRInstruction(
val immediate: Int?=null, // 0-$ff or $ffff if word
val immediateFp: Float?=null,
val address: Int?=null, // 0-$ffff
val labelSymbol: String?=null, // symbolic label name as alternative to value (so only for Branch/jump/call Instructions!)
val binaryData: Collection<UByte>?=null,
var branchTarget: IRCodeChunkBase? = null // will be linked after loading
val labelSymbol: String?=null, // symbolic label name as alternative to address (so only for Branch/jump/call Instructions!)
var branchTarget: IRCodeChunkBase? = null, // Will be linked after loading in IRProgram.linkChunks()! This is the chunk that the branch labelSymbol points to.
val fcallArgs: FunctionCallArgs? = null // will be set for the CALL and SYSCALL instructions.
) {
// reg1 and fpreg1 can be IN/OUT/INOUT (all others are readonly INPUT)
// This knowledge is useful in IL assembly optimizers to see how registers are used.
@ -684,10 +683,6 @@ data class IRInstruction(
require(fpReg2==null || fpReg2 in 0..65536) {"fpReg2 out of bounds"}
if(reg1!=null && reg2!=null) require(reg1!=reg2) {"reg1 must not be same as reg2"} // note: this is ok for fpRegs as these are always the same type
require((opcode==Opcode.BINARYDATA && binaryData!=null) || (opcode!=Opcode.BINARYDATA && binaryData==null)) {
"binarydata inconsistency"
}
val formats = instructionFormats.getValue(opcode)
require (type != null || formats.containsKey(null)) { "missing type" }
@ -701,13 +696,10 @@ data class IRInstruction(
if(format.fpReg1==OperandDirection.UNUSED) require(fpReg1==null) { "invalid fpReg1" }
if(format.fpReg2==OperandDirection.UNUSED) require(fpReg2==null) { "invalid fpReg2" }
if(format.immediate) {
if(type==IRDataType.FLOAT) {
if(opcode!=Opcode.SETPARAM)
require(immediateFp !=null) {"missing immediate fp value"}
else
require(immediateFp==null) {"setparam never has immediateFp only immediate"}
}
else require(immediate!=null || labelSymbol!=null) {"missing immediate value or labelsymbol"}
if(type==IRDataType.FLOAT)
require(immediateFp !=null) {"missing immediate fp value"}
else
require(immediate!=null || labelSymbol!=null) {"missing immediate value or labelsymbol"}
}
if(type!=IRDataType.FLOAT)
require(fpReg1==null && fpReg2==null) {"int instruction can't use fp reg"}
@ -727,18 +719,6 @@ data class IRInstruction(
fpReg1direction = format.fpReg1
fpReg2direction = format.fpReg2
if(opcode in setOf(Opcode.BEQR, Opcode.BNER,
Opcode.BGTR, Opcode.BGTSR,
Opcode.BGER, Opcode.BGESR,
Opcode.SEQ, Opcode.SNE, Opcode.SLT, Opcode.SLTS,
Opcode.SGT, Opcode.SGTS, Opcode.SLE, Opcode.SLES,
Opcode.SGE, Opcode.SGES)) {
if(type==IRDataType.FLOAT)
require(fpReg1!=fpReg2) {"$opcode: fpReg1 and fpReg2 should be different"}
else
require(reg1!=reg2) {"$opcode: reg1 and reg2 should be different"}
}
if(opcode==Opcode.SYSCALL) {
require(immediate!=null) {
"syscall needs immediate integer for the syscall number"
@ -830,36 +810,84 @@ data class IRInstruction(
IRDataType.FLOAT -> result.add(".f ")
else -> result.add(" ")
}
reg1?.let {
result.add("r$it")
result.add(",")
}
reg2?.let {
result.add("r$it")
result.add(",")
}
fpReg1?.let {
result.add("fr$it")
result.add(",")
}
fpReg2?.let {
result.add("fr$it")
result.add(",")
}
immediate?.let {
result.add(it.toHex())
result.add(",")
}
immediateFp?.let {
result.add(it.toString())
result.add(",")
}
address?.let {
result.add(it.toHex())
result.add(",")
}
labelSymbol?.let {
result.add(it)
if(this.fcallArgs!=null) {
immediate?.let { result.add(it.toHex()) } // syscall
labelSymbol?.let { result.add(it) } // regular subroutine call
address?.let { result.add(address.toHex()) } // romcall
result.add("(")
fcallArgs.arguments.forEach {
val location = if(it.address==null) {
if(it.name.isBlank()) "" else it.name+"="
} else "${it.address}="
val cpuReg = if(it.reg.cpuRegister==null) "" else {
if(it.reg.cpuRegister.registerOrPair!=null)
"@"+it.reg.cpuRegister.registerOrPair.toString()
else
"@"+it.reg.cpuRegister.statusflag.toString()
}
when(it.reg.dt) {
IRDataType.BYTE -> result.add("${location}r${it.reg.registerNum}.b$cpuReg,")
IRDataType.WORD -> result.add("${location}r${it.reg.registerNum}.w$cpuReg,")
IRDataType.FLOAT -> result.add("${location}fr${it.reg.registerNum}.f$cpuReg,")
}
}
if(result.last().endsWith(',')) {
result.add(result.removeLast().trimEnd(','))
}
result.add(")")
val returns = fcallArgs.returns
if(returns!=null) {
result.add(":")
when (returns.dt) {
IRDataType.BYTE -> result.add("r${returns.registerNum}.b")
IRDataType.WORD -> result.add("r${returns.registerNum}.w")
IRDataType.FLOAT -> result.add("fr${returns.registerNum}.f")
}
if(returns.cpuRegister!=null) {
val cpuReg =
if(returns.cpuRegister.registerOrPair!=null)
returns.cpuRegister.registerOrPair.toString()
else
returns.cpuRegister.statusflag.toString()
result.add("@"+cpuReg)
}
}
} else {
reg1?.let {
result.add("r$it")
result.add(",")
}
reg2?.let {
result.add("r$it")
result.add(",")
}
fpReg1?.let {
result.add("fr$it")
result.add(",")
}
fpReg2?.let {
result.add("fr$it")
result.add(",")
}
immediate?.let {
result.add(it.toHex())
result.add(",")
}
immediateFp?.let {
result.add(it.toString())
result.add(",")
}
address?.let {
result.add(it.toHex())
result.add(",")
}
labelSymbol?.let {
result.add(it)
}
}
if(result.last() == ",")
result.removeLast()

View File

@ -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,11 +241,72 @@ 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(
val label: String,
val address: UInt?,
val library: Boolean,
val forceOutput: Boolean,
val alignment: BlockAlignment,
val position: Position
) {
@ -416,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 */ }

View File

@ -1,8 +1,7 @@
package prog8.intermediate
import prog8.code.*
import prog8.code.core.DataType
import prog8.code.core.InternalCompilerException
import prog8.code.core.*
fun getTypeString(dt : DataType): String = when(dt) {
@ -78,9 +77,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,48 +114,54 @@ 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)
}
}
operands.forEach { oper ->
if(oper[0] == '&')
throw IRParseException("address-of should be done with normal LOAD <symbol>")
else if(oper[0] in "rR") {
if(reg1==null) reg1 = oper.substring(1).toInt()
else if(reg2==null) reg2 = oper.substring(1).toInt()
else throw IRParseException("too many register operands")
} else if (oper[0] in "fF" && oper[1] in "rR") {
if(fpReg1==null) fpReg1 = oper.substring(2).toInt()
else if(fpReg2==null) fpReg2 = oper.substring(2).toInt()
else throw IRParseException("too many fp register operands")
} else if (oper[0].isDigit() || oper[0] == '$' || oper[0]=='%' || oper[0]=='-' || oper.startsWith("0x")) {
val value = parseIRValue(oper)
if(format.immediate) {
if(immediateInt==null && immediateFp==null) {
if (type == IRDataType.FLOAT)
immediateFp = value
else
immediateInt = value.toInt()
if(format.sysCall) {
val call = parseCall(rest)
val syscallNum = parseIRValue(call.target).toInt()
return left(IRInstruction(Opcode.SYSCALL, immediate = syscallNum, fcallArgs = FunctionCallArgs(call.args, call.returns)))
} else if (format.funcCall) {
val call = parseCall(rest)
return left(IRInstruction(Opcode.CALL, labelSymbol = call.target, fcallArgs = FunctionCallArgs(call.args, call.returns)))
} else {
operands.forEach { oper ->
if (oper[0] == '&')
throw IRParseException("address-of should be done with normal LOAD <symbol>")
else if (oper[0] in "rR") {
if (reg1 == null) reg1 = oper.substring(1).toInt()
else if (reg2 == null) reg2 = oper.substring(1).toInt()
else throw IRParseException("too many register operands")
} else if (oper[0] in "fF" && oper[1] in "rR") {
if (fpReg1 == null) fpReg1 = oper.substring(2).toInt()
else if (fpReg2 == null) fpReg2 = oper.substring(2).toInt()
else throw IRParseException("too many fp register operands")
} else if (oper[0].isDigit() || oper[0] == '$' || oper[0] == '%' || oper[0] == '-' || oper.startsWith("0x")) {
val value = parseIRValue(oper)
if (format.immediate) {
if (immediateInt == null && immediateFp == null) {
if (type == IRDataType.FLOAT)
immediateFp = value
else
immediateInt = value.toInt()
} else {
address = value.toInt()
}
} else {
address = value.toInt()
}
} else {
address = value.toInt()
if (!oper[0].isLetter())
throw IRParseException("expected symbol name: $oper")
labelSymbol = oper
val value = parseValueOrPlaceholder(oper)
if (value != null)
address = value.toInt()
}
} else {
if(!oper[0].isLetter())
throw IRParseException("expected symbol name: $oper")
labelSymbol = oper
val value = parseValueOrPlaceholder(oper, location)
if(value!=null)
address = value.toInt()
}
}
@ -198,10 +201,6 @@ fun parseIRCodeLine(line: String, location: Pair<IRCodeChunk, Int>?, placeholder
null -> {}
}
}
if(format.immediate && opcode==Opcode.SETPARAM && immediateInt==null) {
immediateInt = immediateFp!!.toInt()
immediateFp = null
}
if(format.address!=OperandDirection.UNUSED && address==null && labelSymbol==null)
throw IRParseException("requires address or symbol for $line")
@ -211,20 +210,83 @@ fun parseIRCodeLine(line: String, location: Pair<IRCodeChunk, Int>?, placeholder
throw IRParseException("labelsymbol confused with register?: $labelSymbol")
}
if(opcode in OpcodesForCpuRegisters) {
val reg = operands.last().lowercase()
if(reg !in setOf(
"a", "x", "y",
"ax", "ay", "xy",
"r0", "r1", "r2", "r3",
"r4", "r5", "r6", "r7",
"r8", "r9", "r10","r11",
"r12", "r13", "r14", "r15",
"pc", "pz", "pv","pn"))
throw IRParseException("invalid cpu reg: $reg")
return left(IRInstruction(opcode, type, reg1, labelSymbol = reg))
}
return left(IRInstruction(opcode, type, reg1, reg2, fpReg1, fpReg2, immediateInt, immediateFp, address, labelSymbol = labelSymbol))
}
private class ParsedCall(
val target: String,
val args: List<FunctionCallArgs.ArgumentSpec>,
val returns: FunctionCallArgs.RegSpec?
)
private fun parseCall(rest: String): ParsedCall {
fun parseRegspec(reg: String): FunctionCallArgs.RegSpec {
val pattern = Regex("f?r([0-9]+)\\.(.)(@.{1,2})?$")
val match = pattern.matchEntire(reg) ?: throw IRParseException("invalid regspec $reg")
val num = match.groups[1]!!.value.toInt()
val type = when(match.groups[2]!!.value) {
"b" -> IRDataType.BYTE
"w" -> IRDataType.WORD
"f" -> IRDataType.FLOAT
else -> throw IRParseException("invalid type spec in $reg")
}
val cpuRegister: RegisterOrStatusflag? =
if(match.groups[3]!=null) {
val cpuRegStr = match.groups[3]!!.value.drop(1)
parseRegisterOrStatusflag(cpuRegStr)
} else null
return FunctionCallArgs.RegSpec(type, num, cpuRegister)
}
fun parseArgs(args: String): List<FunctionCallArgs.ArgumentSpec> {
if(args.isBlank())
return emptyList()
return args.split(',').map {
if(it.contains('=')) {
val (argVar, argReg) = it.split('=')
FunctionCallArgs.ArgumentSpec(argVar, null, parseRegspec(argReg)) // address will be set later
} else {
FunctionCallArgs.ArgumentSpec("", null, parseRegspec(it)) // address will be set later
}
}
}
val pattern = Regex("(?<target>.+?)\\((?<arglist>.*?)\\)(:(?<returns>.+?))?")
val match = pattern.matchEntire(rest.replace(" ","")) ?: throw IRParseException("invalid call spec $rest")
val target = match.groups["target"]!!.value
val args = match.groups["arglist"]!!.value
val arguments = parseArgs(args)
val returns = match.groups["returns"]?.value
return ParsedCall(
target,
arguments,
if(returns==null) null else parseRegspec(returns)
)
}
internal fun parseRegisterOrStatusflag(regs: String): RegisterOrStatusflag {
var reg: RegisterOrPair? = null
var sf: Statusflag? = null
try {
reg = RegisterOrPair.valueOf(regs)
} catch (x: IllegalArgumentException) {
sf = Statusflag.valueOf(regs)
}
return RegisterOrStatusflag(reg, sf)
}
fun irType(type: DataType): IRDataType {
return when(type) {
DataType.BOOL,
DataType.UBYTE,
DataType.BYTE -> IRDataType.BYTE
DataType.UWORD,
DataType.WORD -> IRDataType.WORD
DataType.FLOAT -> IRDataType.FLOAT
in PassByReferenceDatatypes -> IRDataType.WORD
else -> throw AssemblyError("no IR datatype for $type")
}
}

View File

@ -75,7 +75,7 @@ load.b r1,42
</CODE>
</INITGLOBALS>
<BLOCK NAME="main" ADDRESS="" ALIGN="NONE" POS="[examples/test.p8: line 2 col 2-5]">
<BLOCK NAME="main" ADDRESS="" LIBRARY="false" FORCEOUTPUT="false" ALIGN="NONE" POS="[examples/test.p8: line 2 col 2-5]">
<SUB NAME="main.start" RETURNTYPE="" POS="[examples/test.p8: line 4 col 6-8]">
<PARAMS>
</PARAMS>
@ -85,15 +85,13 @@ return
</SUB>
</BLOCK>
<BLOCK NAME="sys" ADDRESS="" ALIGN="NONE" POS="[library:/prog8lib/virtual/syslib.p8: line 3 col 2-4]">
<BLOCK NAME="sys" ADDRESS="" LIBRARY="false" FORCEOUTPUT="false" ALIGN="NONE" POS="[library:/prog8lib/virtual/syslib.p8: line 3 col 2-4]">
<SUB NAME="sys.wait" RETURNTYPE="" POS="[library:/prog8lib/virtual/syslib.p8: line 15 col 6-8]">
<PARAMS>
uword sys.wait.jiffies
</PARAMS>
<INLINEASM LABEL="sys.wait" IR="true" POS="[library:/prog8lib/virtual/syslib.p8: line 17 col 10-13]">
loadm.w r0,sys.wait.jiffies
setparam.w r0,0
syscall 13
</INLINEASM>
<CODE>
return

View File

@ -1,6 +1,8 @@
package prog8.vm
import prog8.code.core.AssemblyError
import prog8.intermediate.FunctionCallArgs
import prog8.intermediate.IRDataType
import kotlin.math.min
/*
@ -89,21 +91,50 @@ enum class Syscall {
}
object SysCalls {
fun call(call: Syscall, vm: VirtualMachine) {
private fun getArgValues(argspec: List<FunctionCallArgs.ArgumentSpec>, vm: VirtualMachine): List<Comparable<Nothing>> {
return argspec.map {
when(it.reg.dt) {
IRDataType.BYTE -> vm.registers.getUB(it.reg.registerNum)
IRDataType.WORD -> vm.registers.getUW(it.reg.registerNum)
IRDataType.FLOAT -> vm.registers.getFloat(it.reg.registerNum)
}
}
}
private fun returnValue(returns: FunctionCallArgs.RegSpec, value: Comparable<Nothing>, vm: VirtualMachine) {
val vv: Float = when(value) {
is UByte -> value.toFloat()
is UShort -> value.toFloat()
is UInt -> value.toFloat()
is Byte -> value.toFloat()
is Short -> value.toFloat()
is Int -> value.toFloat()
is Float -> value
else -> (value as Number).toFloat()
}
when(returns.dt) {
IRDataType.BYTE -> vm.registers.setUB(returns.registerNum, vv.toInt().toUByte())
IRDataType.WORD -> vm.registers.setUW(returns.registerNum, vv.toInt().toUShort())
IRDataType.FLOAT -> vm.registers.setFloat(returns.registerNum, vv)
}
}
fun call(call: Syscall, callspec: FunctionCallArgs, vm: VirtualMachine) {
when(call) {
Syscall.RESET -> {
vm.reset(false)
}
Syscall.EXIT ->{
vm.exit(vm.valueStack.pop().toInt())
val exitValue = getArgValues(callspec.arguments, vm).single() as UByte
vm.exit(exitValue.toInt())
}
Syscall.PRINT_C -> {
val char = vm.valueStack.pop().toInt()
print(Char(char))
val char = getArgValues(callspec.arguments, vm).single() as UByte
print(Char(char.toInt()))
}
Syscall.PRINT_S -> {
var addr = vm.valueStack.popw().toInt()
var addr = (getArgValues(callspec.arguments, vm).single() as UShort).toInt()
while(true) {
val char = vm.memory.getUB(addr).toInt()
if(char==0)
@ -113,35 +144,52 @@ object SysCalls {
}
}
Syscall.PRINT_U8 -> {
print(vm.valueStack.pop())
val value = getArgValues(callspec.arguments, vm).single()
print(value)
}
Syscall.PRINT_U16 -> {
print(vm.valueStack.popw())
val value = getArgValues(callspec.arguments, vm).single()
print(value)
}
Syscall.INPUT -> {
val (address, maxlen) = getArgValues(callspec.arguments, vm)
var input = readln()
val maxlen = vm.valueStack.pop().toInt()
if(maxlen>0)
input = input.substring(0, min(input.length, maxlen))
vm.memory.setString(vm.valueStack.popw().toInt(), input, true)
vm.valueStack.push(input.length.toUByte())
val maxlenvalue = (maxlen as UByte).toInt()
if(maxlenvalue>0)
input = input.substring(0, min(input.length, maxlenvalue))
vm.memory.setString((address as UShort).toInt(), input, true)
returnValue(callspec.returns!!, input.length, vm)
}
Syscall.SLEEP -> {
val duration = vm.valueStack.popw().toLong()
Thread.sleep(duration)
val duration = getArgValues(callspec.arguments, vm).single() as UShort
Thread.sleep(duration.toLong())
}
Syscall.GFX_ENABLE -> {
val mode = getArgValues(callspec.arguments, vm).single() as UByte
vm.gfx_enable(mode)
}
Syscall.GFX_CLEAR -> {
val color = getArgValues(callspec.arguments, vm).single() as UByte
vm.gfx_clear(color)
}
Syscall.GFX_PLOT -> {
val (x,y,color) = getArgValues(callspec.arguments, vm)
vm.gfx_plot(x as UShort, y as UShort, color as UByte)
}
Syscall.GFX_GETPIXEL -> {
val (x,y) = getArgValues(callspec.arguments, vm)
val color = vm.gfx_getpixel(x as UShort, y as UShort)
returnValue(callspec.returns!!, color, vm)
}
Syscall.GFX_ENABLE -> vm.gfx_enable()
Syscall.GFX_CLEAR -> vm.gfx_clear()
Syscall.GFX_PLOT -> vm.gfx_plot()
Syscall.GFX_GETPIXEL ->vm.gfx_getpixel()
Syscall.WAIT -> {
val millis = vm.valueStack.popw().toLong() * 1000/60
Thread.sleep(millis)
val time = getArgValues(callspec.arguments, vm).single() as UShort
Thread.sleep(time.toLong() * 1000/60)
}
Syscall.WAITVSYNC -> vm.waitvsync()
Syscall.SORT_UBYTE -> {
val length = vm.valueStack.pop().toInt()
val address = vm.valueStack.popw().toInt()
val (addressV, lengthV) = getArgValues(callspec.arguments, vm)
val address = (addressV as UShort).toInt()
val length = (lengthV as UByte).toInt()
val array = IntProgression.fromClosedRange(address, address+length-1, 1).map {
vm.memory.getUB(it)
}.sorted()
@ -150,8 +198,9 @@ object SysCalls {
}
}
Syscall.SORT_BYTE -> {
val length = vm.valueStack.pop().toInt()
val address = vm.valueStack.popw().toInt()
val (addressV, lengthV) = getArgValues(callspec.arguments, vm)
val address = (addressV as UShort).toInt()
val length = (lengthV as UByte).toInt()
val array = IntProgression.fromClosedRange(address, address+length-1, 1).map {
vm.memory.getSB(it)
}.sorted()
@ -160,8 +209,9 @@ object SysCalls {
}
}
Syscall.SORT_UWORD -> {
val length = vm.valueStack.pop().toInt()
val address = vm.valueStack.popw().toInt()
val (addressV, lengthV) = getArgValues(callspec.arguments, vm)
val address = (addressV as UShort).toInt()
val length = (lengthV as UByte).toInt()
val array = IntProgression.fromClosedRange(address, address+length*2-2, 2).map {
vm.memory.getUW(it)
}.sorted()
@ -170,8 +220,9 @@ object SysCalls {
}
}
Syscall.SORT_WORD -> {
val length = vm.valueStack.pop().toInt()
val address = vm.valueStack.popw().toInt()
val (addressV, lengthV) = getArgValues(callspec.arguments, vm)
val address = (addressV as UShort).toInt()
val length = (lengthV as UByte).toInt()
val array = IntProgression.fromClosedRange(address, address+length*2-2, 2).map {
vm.memory.getSW(it)
}.sorted()
@ -180,8 +231,9 @@ object SysCalls {
}
}
Syscall.REVERSE_BYTES -> {
val length = vm.valueStack.pop().toInt()
val address = vm.valueStack.popw().toInt()
val (addressV, lengthV) = getArgValues(callspec.arguments, vm)
val address = (addressV as UShort).toInt()
val length = (lengthV as UByte).toInt()
val array = IntProgression.fromClosedRange(address, address+length-1, 1).map {
vm.memory.getUB(it)
}.reversed()
@ -190,8 +242,9 @@ object SysCalls {
}
}
Syscall.REVERSE_WORDS -> {
val length = vm.valueStack.pop().toInt()
val address = vm.valueStack.popw().toInt()
val (addressV, lengthV) = getArgValues(callspec.arguments, vm)
val address = (addressV as UShort).toInt()
val length = (lengthV as UByte).toInt()
val array = IntProgression.fromClosedRange(address, address+length*2-2, 2).map {
vm.memory.getUW(it)
}.reversed()
@ -200,8 +253,9 @@ object SysCalls {
}
}
Syscall.REVERSE_FLOATS -> {
val length = vm.valueStack.pop().toInt()
val address = vm.valueStack.popw().toInt()
val (addressV, lengthV) = getArgValues(callspec.arguments, vm)
val address = (addressV as UShort).toInt()
val length = (lengthV as UByte).toInt()
val array = IntProgression.fromClosedRange(address, address+length*4-2, 4).map {
vm.memory.getFloat(it)
}.reversed()
@ -210,154 +264,154 @@ object SysCalls {
}
}
Syscall.ANY_BYTE -> {
val length = vm.valueStack.pop().toInt()
val address = vm.valueStack.popw().toInt()
val (addressV, lengthV) = getArgValues(callspec.arguments, vm)
val address = (addressV as UShort).toInt()
val length = (lengthV as UByte).toInt()
val addresses = IntProgression.fromClosedRange(address, address+length*2-2, 2)
if(addresses.any { vm.memory.getUB(it).toInt()!=0 })
vm.valueStack.push(1u)
returnValue(callspec.returns!!, 1, vm)
else
vm.valueStack.push(0u)
returnValue(callspec.returns!!, 0, vm)
}
Syscall.ANY_WORD -> {
val length = vm.valueStack.pop().toInt()
val address = vm.valueStack.popw().toInt()
val (addressV, lengthV) = getArgValues(callspec.arguments, vm)
val address = (addressV as UShort).toInt()
val length = (lengthV as UByte).toInt()
val addresses = IntProgression.fromClosedRange(address, address+length*2-2, 2)
if(addresses.any { vm.memory.getUW(it).toInt()!=0 })
vm.valueStack.push(1u)
returnValue(callspec.returns!!, 1, vm)
else
vm.valueStack.push(0u)
returnValue(callspec.returns!!, 0, vm)
}
Syscall.ANY_FLOAT -> {
val length = vm.valueStack.pop().toInt()
val address = vm.valueStack.popw().toInt()
val (addressV, lengthV) = getArgValues(callspec.arguments, vm)
val address = (addressV as UShort).toInt()
val length = (lengthV as UByte).toInt()
val addresses = IntProgression.fromClosedRange(address, address+length*4-2, 4)
if(addresses.any { vm.memory.getFloat(it).toInt()!=0 })
vm.valueStack.push(1u)
returnValue(callspec.returns!!, 1, vm)
else
vm.valueStack.push(0u)
returnValue(callspec.returns!!, 0, vm)
}
Syscall.ALL_BYTE -> {
val length = vm.valueStack.pop().toInt()
val address = vm.valueStack.popw().toInt()
val (addressV, lengthV) = getArgValues(callspec.arguments, vm)
val address = (addressV as UShort).toInt()
val length = (lengthV as UByte).toInt()
val addresses = IntProgression.fromClosedRange(address, address+length*2-2, 2)
if(addresses.all { vm.memory.getUB(it).toInt()!=0 })
vm.valueStack.push(1u)
returnValue(callspec.returns!!, 1, vm)
else
vm.valueStack.push(0u)
returnValue(callspec.returns!!, 0, vm)
}
Syscall.ALL_WORD -> {
val length = vm.valueStack.pop().toInt()
val address = vm.valueStack.popw().toInt()
val (addressV, lengthV) = getArgValues(callspec.arguments, vm)
val address = (addressV as UShort).toInt()
val length = (lengthV as UByte).toInt()
val addresses = IntProgression.fromClosedRange(address, address+length*2-2, 2)
if(addresses.all { vm.memory.getUW(it).toInt()!=0 })
vm.valueStack.push(1u)
returnValue(callspec.returns!!, 1, vm)
else
vm.valueStack.push(0u)
returnValue(callspec.returns!!, 0, vm)
}
Syscall.ALL_FLOAT -> {
val length = vm.valueStack.pop().toInt()
val address = vm.valueStack.popw().toInt()
val (addressV, lengthV) = getArgValues(callspec.arguments, vm)
val address = (addressV as UShort).toInt()
val length = (lengthV as UByte).toInt()
val addresses = IntProgression.fromClosedRange(address, address+length*4-2, 4)
if(addresses.all { vm.memory.getFloat(it).toInt()!=0 })
vm.valueStack.push(1u)
returnValue(callspec.returns!!, 1, vm)
else
vm.valueStack.push(0u)
returnValue(callspec.returns!!, 0, vm)
}
Syscall.PRINT_F -> {
print(vm.valueStack.popf())
val value = getArgValues(callspec.arguments, vm).single() as Float
print(value)
}
Syscall.STR_TO_UWORD -> {
val stringAddr = vm.valueStack.popw()
val stringAddr = getArgValues(callspec.arguments, vm).single() as UShort
val string = vm.memory.getString(stringAddr.toInt()).takeWhile { it.isDigit() }
val value = try {
string.toUShort()
} catch(_: NumberFormatException) {
0u
}
vm.valueStack.pushw(value)
returnValue(callspec.returns!!, value, vm)
}
Syscall.STR_TO_WORD -> {
val stringAddr = vm.valueStack.popw()
val stringAddr = getArgValues(callspec.arguments, vm).single() as UShort
val memstring = vm.memory.getString(stringAddr.toInt())
val match = Regex("^[+-]?\\d+").find(memstring)
if(match==null) {
vm.valueStack.pushw(0u)
return
}
val match = Regex("^[+-]?\\d+").find(memstring) ?: return returnValue(callspec.returns!!, 0, vm)
val value = try {
match.value.toShort()
} catch(_: NumberFormatException) {
0
}
vm.valueStack.pushw(value.toUShort())
return returnValue(callspec.returns!!, value, vm)
}
Syscall.COMPARE_STRINGS -> {
val secondAddr = vm.valueStack.popw()
val firstAddr = vm.valueStack.popw()
val (firstV, secondV) = getArgValues(callspec.arguments, vm)
val firstAddr = firstV as UShort
val secondAddr = secondV as UShort
val first = vm.memory.getString(firstAddr.toInt())
val second = vm.memory.getString(secondAddr.toInt())
val comparison = first.compareTo(second)
if(comparison==0)
vm.valueStack.push(0u)
returnValue(callspec.returns!!, 0, vm)
else if(comparison<0)
vm.valueStack.push((-1).toUByte())
returnValue(callspec.returns!!, -1, vm)
else
vm.valueStack.push(1u)
returnValue(callspec.returns!!, 1, vm)
}
Syscall.RNDFSEED -> {
val seed = vm.valueStack.popf()
val seed = getArgValues(callspec.arguments, vm).single() as Float
if(seed>0) // always use negative seed, this mimics the behavior on CBM machines
vm.randomSeedFloat(-seed)
else
vm.randomSeedFloat(seed)
}
Syscall.RNDSEED -> {
val seed2 = vm.valueStack.popw()
val seed1 = vm.valueStack.popw()
vm.randomSeed(seed1, seed2)
val (seed1, seed2) = getArgValues(callspec.arguments, vm)
vm.randomSeed(seed1 as UShort, seed2 as UShort)
}
Syscall.RND -> {
vm.valueStack.push(vm.randomGenerator.nextInt().toUByte())
returnValue(callspec.returns!!, vm.randomGenerator.nextInt().toUByte(), vm)
}
Syscall.RNDW -> {
vm.valueStack.pushw(vm.randomGenerator.nextInt().toUShort())
returnValue(callspec.returns!!, vm.randomGenerator.nextInt().toUShort(), vm)
}
Syscall.RNDF -> {
vm.valueStack.pushf(vm.randomGeneratorFloats.nextFloat())
returnValue(callspec.returns!!, vm.randomGeneratorFloats.nextFloat(), vm)
}
Syscall.STRING_CONTAINS -> {
val stringAddr = vm.valueStack.popw()
val char = vm.valueStack.pop().toInt().toChar()
val (charV, addr) = getArgValues(callspec.arguments, vm)
val stringAddr = addr as UShort
val char = (charV as UByte).toInt().toChar()
val string = vm.memory.getString(stringAddr.toInt())
vm.valueStack.push(if(char in string) 1u else 0u)
returnValue(callspec.returns!!, if(char in string) 1u else 0u, vm)
}
Syscall.BYTEARRAY_CONTAINS -> {
var length = vm.valueStack.pop()
var array = vm.valueStack.popw().toInt()
val value = vm.valueStack.pop()
val (value, arrayV, lengthV) = getArgValues(callspec.arguments, vm)
var length = lengthV as UByte
var array = (arrayV as UShort).toInt()
while(length>0u) {
if(vm.memory.getUB(array)==value) {
vm.valueStack.push(1u)
return
}
if(vm.memory.getUB(array)==value)
return returnValue(callspec.returns!!, 1u, vm)
array++
length--
}
vm.valueStack.push(0u)
returnValue(callspec.returns!!, 0u, vm)
}
Syscall.WORDARRAY_CONTAINS -> {
var length = vm.valueStack.pop()
var array = vm.valueStack.popw().toInt()
val value = vm.valueStack.popw()
val (value, arrayV, lengthV) = getArgValues(callspec.arguments, vm)
var length = lengthV as UByte
var array = (arrayV as UShort).toInt()
while(length>0u) {
if(vm.memory.getUW(array)==value) {
vm.valueStack.push(1u)
return
}
if(vm.memory.getUW(array)==value)
return returnValue(callspec.returns!!, 1u, vm)
array += 2
length--
}
vm.valueStack.push(0u)
returnValue(callspec.returns!!, 0u, vm)
}
else -> throw AssemblyError("missing syscall ${call.name}")
}

View File

@ -32,7 +32,7 @@ class BreakpointException(val pcChunk: IRCodeChunk, val pcIndex: Int): Exception
@Suppress("FunctionName")
class VirtualMachine(irProgram: IRProgram) {
class CallSiteContext(val returnChunk: IRCodeChunk, val returnIndex: Int, val returnValueReg: Int?, val returnValueFpReg: Int?)
class CallSiteContext(val returnChunk: IRCodeChunk, val returnIndex: Int, val fcallSpec: FunctionCallArgs)
val memory = Memory()
val program: List<IRCodeChunk>
val registers = Registers()
@ -176,8 +176,7 @@ class VirtualMachine(irProgram: IRProgram) {
Opcode.STOREZI -> InsSTOREZI(ins)
Opcode.JUMP -> InsJUMP(ins)
Opcode.JUMPA -> throw IllegalArgumentException("vm program can't jump to system memory address (JUMPA)")
Opcode.SETPARAM -> InsSETPARAM(ins)
Opcode.CALL, Opcode.CALLR -> InsCALL(ins)
Opcode.CALL -> InsCALL(ins)
Opcode.SYSCALL -> InsSYSCALL(ins)
Opcode.RETURN -> InsRETURN()
Opcode.RETURNR -> InsRETURNR(ins)
@ -284,10 +283,6 @@ class VirtualMachine(irProgram: IRProgram) {
Opcode.BREAKPOINT -> InsBREAKPOINT()
Opcode.CLC -> { statusCarry = false; nextPc() }
Opcode.SEC -> { statusCarry = true; nextPc() }
Opcode.BINARYDATA -> TODO("BINARYDATA not yet supported in VM")
Opcode.LOADCPU -> InsLOADCPU(ins)
Opcode.STORECPU -> InsSTORECPU(ins)
Opcode.STOREZCPU -> InsSTOREZCPU(ins)
Opcode.FFROMUB -> InsFFROMUB(ins)
Opcode.FFROMSB -> InsFFROMSB(ins)
@ -364,7 +359,7 @@ class VirtualMachine(irProgram: IRProgram) {
value.dt=null
}
val call = Syscall.fromInt(i.immediate!!)
SysCalls.call(call, this) // note: any result value(s) are pushed back on the value stack
SysCalls.call(call, i.fcallArgs!!, this) // note: any result value(s) are pushed back on the value stack
nextPc()
}
@ -376,86 +371,6 @@ class VirtualMachine(irProgram: IRProgram) {
throw BreakpointException(pcChunk, pcIndex)
}
private fun InsLOADCPU(i: IRInstruction) {
val reg = i.labelSymbol!!
val value: UInt
if(reg.startsWith('r')) {
val regnum = reg.substring(1).toInt()
val regAddr = cx16virtualregsBaseAddress + regnum*2
value = memory.getUW(regAddr).toUInt()
} else {
value = when(reg) {
"a" -> registers.cpuA.toUInt()
"x" -> registers.cpuX.toUInt()
"y" -> registers.cpuY.toUInt()
"ax" -> (registers.cpuA.toUInt() shl 8) or registers.cpuX.toUInt()
"ay" -> (registers.cpuA.toUInt() shl 8) or registers.cpuY.toUInt()
"xy" -> (registers.cpuX.toUInt() shl 8) or registers.cpuY.toUInt()
"pc" -> if(statusCarry) 1u else 0u
"pz" -> if(statusZero) 1u else 0u
"pn" -> if(statusNegative) 1u else 0u
"pv" -> throw IllegalArgumentException("overflow status register not supported in VM")
else -> throw IllegalArgumentException("invalid cpu reg")
}
}
when(i.type!!) {
IRDataType.BYTE -> registers.setUB(i.reg1!!, value.toUByte())
IRDataType.WORD -> registers.setUW(i.reg1!!, value.toUShort())
else -> throw java.lang.IllegalArgumentException("invalid cpu reg type")
}
nextPc()
}
private fun InsSTORECPU(i: IRInstruction) {
val value: UInt = when(i.type!!) {
IRDataType.BYTE -> registers.getUB(i.reg1!!).toUInt()
IRDataType.WORD -> registers.getUW(i.reg1!!).toUInt()
IRDataType.FLOAT -> throw IllegalArgumentException("there are no float cpu registers")
}
StoreCPU(value, i.type!!, i.labelSymbol!!)
nextPc()
}
private fun InsSTOREZCPU(i: IRInstruction) {
StoreCPU(0u, i.type!!, i.labelSymbol!!)
nextPc()
}
private fun StoreCPU(value: UInt, dt: IRDataType, regStr: String) {
if(regStr.startsWith('r')) {
val regnum = regStr.substring(1).toInt()
val regAddr = cx16virtualregsBaseAddress + regnum*2
when(dt) {
IRDataType.BYTE -> memory.setUB(regAddr, value.toUByte())
IRDataType.WORD -> memory.setUW(regAddr, value.toUShort())
else -> throw IllegalArgumentException("invalid reg dt")
}
} else {
when (regStr) {
"a" -> registers.cpuA = value.toUByte()
"x" -> registers.cpuX = value.toUByte()
"y" -> registers.cpuY = value.toUByte()
"ax" -> {
registers.cpuA = (value and 255u).toUByte()
registers.cpuX = (value shr 8).toUByte()
}
"ay" -> {
registers.cpuA = (value and 255u).toUByte()
registers.cpuY = (value shr 8).toUByte()
}
"xy" -> {
registers.cpuX = (value and 255u).toUByte()
registers.cpuY = (value shr 8).toUByte()
}
"pc" -> statusCarry = value == 1u
"pz" -> statusZero = value == 1u
"pn" -> statusNegative = value == 1u
"pv" -> throw IllegalArgumentException("overflow status register not supported in VM")
else -> throw IllegalArgumentException("invalid cpu reg")
}
}
}
private fun InsLOAD(i: IRInstruction) {
if(i.type==IRDataType.FLOAT)
registers.setFloat(i.fpReg1!!, i.immediateFp!!)
@ -604,30 +519,17 @@ class VirtualMachine(irProgram: IRProgram) {
private class SyscallParamValue(var dt: IRDataType?, var value: Comparable<*>?)
private val syscallParams = Array(100) { SyscallParamValue(null, null) }
private fun InsSETPARAM(i: IRInstruction) {
// store the argument value into the retrieved subroutine's parameter variable (already cached in the instruction's address)
// the reason this is a special instruction is to be flexible in implementing the call convention
val address = i.address
if(address==null) {
// this param is for a SYSCALL (that has no param variable address, instead it goes via the stack)
syscallParams[i.immediate!!].dt = i.type!!
syscallParams[i.immediate!!].value = when(i.type!!) {
IRDataType.BYTE -> registers.getUB(i.reg1!!)
IRDataType.WORD -> registers.getUW(i.reg1!!)
IRDataType.FLOAT -> registers.getFloat(i.fpReg1!!)
}
} else {
when (i.type!!) {
IRDataType.BYTE -> memory.setUB(address, registers.getUB(i.reg1!!))
IRDataType.WORD -> memory.setUW(address, registers.getUW(i.reg1!!))
IRDataType.FLOAT -> memory.setFloat(address, registers.getFloat(i.fpReg1!!))
private fun InsCALL(i: IRInstruction) {
i.fcallArgs!!.arguments.forEach { arg ->
require(arg.address!=null) {"argument variable should have been given its memory address as well"}
when(arg.reg.dt) {
IRDataType.BYTE -> memory.setUB(arg.address!!, registers.getUB(arg.reg.registerNum))
IRDataType.WORD -> memory.setUW(arg.address!!, registers.getUW(arg.reg.registerNum))
IRDataType.FLOAT -> memory.setFloat(arg.address!!, registers.getFloat(arg.reg.registerNum))
}
}
nextPc()
}
private fun InsCALL(i: IRInstruction) {
callStack.push(CallSiteContext(pcChunk, pcIndex+1, i.reg1, i.fpReg1))
// store the call site and jump
callStack.push(CallSiteContext(pcChunk, pcIndex+1, i.fcallArgs!!))
branchTo(i)
}
@ -647,10 +549,11 @@ class VirtualMachine(irProgram: IRProgram) {
exit(0)
else {
val context = callStack.pop()
val returns = context.fcallSpec.returns
when (i.type!!) {
IRDataType.BYTE -> {
if(context.returnValueReg!=null)
registers.setUB(context.returnValueReg, registers.getUB(i.reg1!!))
if(returns!=null)
registers.setUB(returns.registerNum, registers.getUB(i.reg1!!))
else {
val callInstr = context.returnChunk.instructions[context.returnIndex-1]
if(callInstr.opcode!=Opcode.CALL)
@ -658,8 +561,8 @@ class VirtualMachine(irProgram: IRProgram) {
}
}
IRDataType.WORD -> {
if(context.returnValueReg!=null)
registers.setUW(context.returnValueReg, registers.getUW(i.reg1!!))
if(returns!=null)
registers.setUW(returns.registerNum, registers.getUW(i.reg1!!))
else {
val callInstr = context.returnChunk.instructions[context.returnIndex-1]
if(callInstr.opcode!=Opcode.CALL)
@ -667,8 +570,8 @@ class VirtualMachine(irProgram: IRProgram) {
}
}
IRDataType.FLOAT -> {
if(context.returnValueFpReg!=null)
registers.setFloat(context.returnValueFpReg, registers.getFloat(i.fpReg1!!))
if(returns!=null)
registers.setFloat(returns.registerNum, registers.getFloat(i.fpReg1!!))
else {
val callInstr = context.returnChunk.instructions[context.returnIndex-1]
if(callInstr.opcode!=Opcode.CALL)
@ -1367,7 +1270,7 @@ class VirtualMachine(irProgram: IRProgram) {
else left / right
}
"%" -> {
if(right==0.toUByte()) 0xffu
if(right==0.toUByte()) 0u
else left % right
}
else -> throw IllegalArgumentException("operator byte $operator")
@ -1383,7 +1286,7 @@ class VirtualMachine(irProgram: IRProgram) {
else left / value
}
"%" -> {
if(value==0.toUByte()) 0xffu
if(value==0.toUByte()) 0u
else left % value
}
else -> throw IllegalArgumentException("operator byte $operator")
@ -1395,7 +1298,7 @@ class VirtualMachine(irProgram: IRProgram) {
val left = registers.getUB(reg1)
val right = registers.getUB(reg2)
val division = if(right==0.toUByte()) 0xffu else left / right
val remainder = if(right==0.toUByte()) 0xffu else left % right
val remainder = if(right==0.toUByte()) 0u else left % right
valueStack.push(division.toUByte())
valueStack.push(remainder.toUByte())
}
@ -1403,7 +1306,7 @@ class VirtualMachine(irProgram: IRProgram) {
private fun divAndModConstUByte(reg1: Int, value: UByte) {
val left = registers.getUB(reg1)
val division = if(value==0.toUByte()) 0xffu else left / value
val remainder = if(value==0.toUByte()) 0xffu else left % value
val remainder = if(value==0.toUByte()) 0u else left % value
valueStack.push(division.toUByte())
valueStack.push(remainder.toUByte())
}
@ -1412,7 +1315,7 @@ class VirtualMachine(irProgram: IRProgram) {
val left = registers.getUW(reg1)
val right = registers.getUW(reg2)
val division = if(right==0.toUShort()) 0xffffu else left / right
val remainder = if(right==0.toUShort()) 0xffffu else left % right
val remainder = if(right==0.toUShort()) 0u else left % right
valueStack.pushw(division.toUShort())
valueStack.pushw(remainder.toUShort())
}
@ -1420,7 +1323,7 @@ class VirtualMachine(irProgram: IRProgram) {
private fun divAndModConstUWord(reg1: Int, value: UShort) {
val left = registers.getUW(reg1)
val division = if(value==0.toUShort()) 0xffffu else left / value
val remainder = if(value==0.toUShort()) 0xffffu else left % value
val remainder = if(value==0.toUShort()) 0u else left % value
valueStack.pushw(division.toUShort())
valueStack.pushw(remainder.toUShort())
}
@ -1434,7 +1337,7 @@ class VirtualMachine(irProgram: IRProgram) {
else left / right
}
"%" -> {
if(right==0.toUByte()) 0xffu
if(right==0.toUByte()) 0u
else left % right
}
else -> throw IllegalArgumentException("operator byte $operator")
@ -1486,7 +1389,7 @@ class VirtualMachine(irProgram: IRProgram) {
else left / right
}
"%" -> {
if(right==0.toUShort()) 0xffffu
if(right==0.toUShort()) 0u
else left % right
}
else -> throw IllegalArgumentException("operator word $operator")
@ -1502,7 +1405,7 @@ class VirtualMachine(irProgram: IRProgram) {
else left / value
}
"%" -> {
if(value==0.toUShort()) 0xffffu
if(value==0.toUShort()) 0u
else left % value
}
else -> throw IllegalArgumentException("operator word $operator")
@ -1519,7 +1422,7 @@ class VirtualMachine(irProgram: IRProgram) {
else left / right
}
"%" -> {
if(right==0.toUShort()) 0xffffu
if(right==0.toUShort()) 0u
else left % right
}
else -> throw IllegalArgumentException("operator word $operator")
@ -2303,8 +2206,8 @@ class VirtualMachine(irProgram: IRProgram) {
private var window: GraphicsWindow? = null
fun gfx_enable() {
window = when(valueStack.pop().toInt()) {
fun gfx_enable(mode: UByte) {
window = when(mode.toInt()) {
0 -> GraphicsWindow(320, 240, 3)
1 -> GraphicsWindow(640, 480, 2)
else -> throw IllegalArgumentException("invalid screen mode")
@ -2312,25 +2215,20 @@ class VirtualMachine(irProgram: IRProgram) {
window!!.start()
}
fun gfx_clear() {
window?.clear(valueStack.pop().toInt())
fun gfx_clear(color: UByte) {
window?.clear(color.toInt())
}
fun gfx_plot() {
val color = valueStack.pop()
val y = valueStack.popw()
val x = valueStack.popw()
fun gfx_plot(x: UShort, y: UShort, color: UByte) {
window?.plot(x.toInt(), y.toInt(), color.toInt())
}
fun gfx_getpixel() {
val y = valueStack.popw()
val x = valueStack.popw()
if(window==null)
valueStack.push(0u)
fun gfx_getpixel(x: UShort, y: UShort): UByte {
return if(window==null)
0u
else {
val color = Color(window!!.getpixel(x.toInt(), y.toInt()))
valueStack.push(color.green.toUByte()) // gets called from a syscall, return value via stack.
color.green.toUByte()
}
}

View File

@ -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 += 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 += 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")
}
@ -73,7 +67,7 @@ class VmProgramLoader {
programChunks.forEach {
it.instructions.forEach { ins ->
if (ins.labelSymbol != null && ins.opcode !in OpcodesThatBranch && ins.opcode !in OpcodesForCpuRegisters)
if (ins.labelSymbol != null && ins.opcode !in OpcodesThatBranch)
require(ins.address != null) { "instruction with labelSymbol for a var should have value set to the memory address" }
}
}
@ -158,43 +152,39 @@ 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) {
// 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) {
if(target==null)
throw IRParseException("placeholder not found in variables nor labels: $label")
else if(opcode in OpcodesThatBranch)
chunk.instructions[line] = chunk.instructions[line].copy(branchTarget = target, address = null)
} else {
else
throw IRParseException("vm cannot yet load a label address as a value: ${chunk.instructions[line]}") // TODO
}
}
} else {
chunk.instructions[line] = chunk.instructions[line].copy(address = replacement)
}
}
chunks.forEach {
it.instructions.withIndex().forEach { (index, ins) ->
if(ins.opcode==Opcode.SETPARAM && ins.address==null) {
val call = findCall(it, index)
if(call.opcode==Opcode.SYSCALL) {
// there is no variable to set, SYSCALLs get their args from the stack.
} else if(call.labelSymbol!=null) {
// set the address in the instruction to the subroutine's parameter variable's address
// this avoids having to look it up every time the SETPARAM instruction is encountered during execution
val target = subroutines.getValue(call.labelSymbol!!)
val paramVar = target.parameters[ins.immediate!!]
val address = variableAddresses.getValue(paramVar.name)
it.instructions[index] = ins.copy(address = address)
} else
throw IRParseException("weird call $call")
subroutines.forEach {
it.value.chunks.forEach { chunk ->
chunk.instructions.withIndex().forEach { (index, ins) ->
if(ins.opcode==Opcode.CALL) {
val fcallspec = ins.fcallArgs!!
val argsWithAddresses = fcallspec.arguments.map { arg ->
if(arg.address!=null)
arg
else {
val address = variableAddresses.getValue(ins.labelSymbol + "." + arg.name)
FunctionCallArgs.ArgumentSpec(arg.name, address, arg.reg)
}
}
fcallspec.arguments = argsWithAddresses
}
}
}
}
}
private val functionCallOpcodes = setOf(Opcode.CALL, Opcode.CALLR, Opcode.SYSCALL, Opcode.JUMP, Opcode.JUMPA)
private val functionCallOpcodes = setOf(Opcode.CALL, Opcode.SYSCALL, Opcode.JUMP, Opcode.JUMPA)
private fun findCall(it: IRCodeChunk, startIndex: Int): IRInstruction {
var idx = startIndex
while(it.instructions[idx].opcode !in functionCallOpcodes)
@ -333,26 +323,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>,
): Pair<IRCodeChunkBase, 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 Pair(asmChunk, chunk)
} else {
throw IRParseException("vm currently does not support real inlined assembly (only IR): ${asmChunk.label}")
}
}
}

View File

@ -43,7 +43,7 @@ class TestVm: FunSpec( {
test("vm execution: modify memory") {
val program = IRProgram("test", IRSymbolTable(null), getTestOptions(), VMTarget())
val block = IRBlock("testmain", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
val block = IRBlock("testmain", null, false, false, IRBlock.BlockAlignment.NONE, Position.DUMMY)
val startSub = IRSubroutine("testmain.testsub", emptyList(), null, Position.DUMMY)
val code = IRCodeChunk(startSub.label, null)
code += IRInstruction(Opcode.NOP)
@ -70,25 +70,9 @@ class TestVm: FunSpec( {
vm.stepCount shouldBe code.instructions.size
}
test("vm asmbinary not supported") {
val program = IRProgram("test", IRSymbolTable(null), getTestOptions(), VMTarget())
val block = IRBlock("testmain", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
val startSub = IRSubroutine("testmain.testsub", emptyList(), null, Position.DUMMY)
val code = IRCodeChunk(startSub.label, null)
code += IRInstruction(Opcode.BINARYDATA, binaryData = listOf(1u,2u,3u))
code += IRInstruction(Opcode.RETURN)
startSub += code
block += startSub
program.addBlock(block)
val vm = VirtualMachine(program)
shouldThrowWithMessage<NotImplementedError>("An operation is not implemented: BINARYDATA not yet supported in VM") {
vm.run()
}
}
test("asmsub not supported in vm even with IR") {
val program = IRProgram("test", IRSymbolTable(null), getTestOptions(), VMTarget())
val block = IRBlock("main", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
val block = IRBlock("main", null, false, false, IRBlock.BlockAlignment.NONE, Position.DUMMY)
val startSub = IRAsmSubroutine(
"main.asmstart",
0x2000u,
@ -129,7 +113,7 @@ class TestVm: FunSpec( {
<INITGLOBALS>
</INITGLOBALS>
<BLOCK NAME="main" ADDRESS="" ALIGN="NONE" POS="[unittest: line 42 col 1-9]">
<BLOCK NAME="main" ADDRESS="" LIBRARY="false" FORCEOUTPUT="false" ALIGN="NONE" POS="[unittest: line 42 col 1-9]">
</BLOCK>
</PROGRAM>
"""