mirror of
https://github.com/irmen/prog8.git
synced 2025-06-14 11:23:37 +00:00
Compare commits
40 Commits
Author | SHA1 | Date | |
---|---|---|---|
e55a675d2e | |||
6e07602d77 | |||
0eb2d437e2 | |||
d9e13201dd | |||
48864ad6cf | |||
b5255444cd | |||
8e5c67b4b2 | |||
c69c17de42 | |||
125ce3240f | |||
7215efe167 | |||
7ceb76cff5 | |||
7e734214dc | |||
dea7f37553 | |||
415c599310 | |||
70cd4fedbe | |||
1e6d7673bc | |||
0371ffa4ce | |||
88ce9300bc | |||
0e3d75cfeb | |||
630c8a5faa | |||
905921a684 | |||
bff3c4f95c | |||
4c8898a639 | |||
97df33ab1a | |||
ef46fb2685 | |||
d5d6dd3614 | |||
6c233c6a0a | |||
6db715d879 | |||
ab02e8a546 | |||
8cbfe64f19 | |||
68336a76c5 | |||
393e914a86 | |||
ffb54110e9 | |||
533d825f1a | |||
c65279b672 | |||
f9926beeef | |||
add8a777d8 | |||
3fc49c001e | |||
24f37e2062 | |||
f465b2e2a0 |
2
.idea/kotlinc.xml
generated
2
.idea/kotlinc.xml
generated
@ -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>
|
@ -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) :
|
||||
|
@ -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)
|
||||
|
@ -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?
|
||||
}
|
||||
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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("""
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 ->
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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>()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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 -
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}}
|
||||
}
|
||||
|
@ -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
|
||||
}}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}}
|
||||
}
|
||||
|
@ -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
|
||||
}}
|
||||
}
|
||||
|
@ -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
|
||||
}}
|
||||
}
|
||||
|
@ -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
|
||||
}}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
8.12
|
||||
8.14
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
""")
|
||||
|
@ -5,6 +5,7 @@ import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.string.shouldNotContain
|
||||
import prog8.ast.expressions.BuiltinFunctionCall
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.code.target.C64Target
|
||||
@ -244,25 +245,26 @@ main {
|
||||
val exc = shouldThrow<Exception> {
|
||||
VmRunner().runProgram(virtfile.readText())
|
||||
}
|
||||
exc.message shouldContain("does not support real inlined assembly")
|
||||
exc.message shouldContain("encountered unconverted inline assembly chunk")
|
||||
}
|
||||
|
||||
test("inline asm for virtual target with IR is accepted") {
|
||||
test("inline asm for virtual target with IR is accepted and converted to regular instructions") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
%ir {{
|
||||
loadr.b r1,r2
|
||||
return
|
||||
}}
|
||||
}
|
||||
}"""
|
||||
val othertarget = Cx16Target()
|
||||
compileText(othertarget, true, src, writeAssembly = true) shouldNotBe null
|
||||
|
||||
val target = VMTarget()
|
||||
val result = compileText(target, false, src, writeAssembly = true)!!
|
||||
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
|
||||
VmRunner().runProgram(virtfile.readText())
|
||||
val irSrc = virtfile.readText()
|
||||
irSrc.shouldContain("loadr.b r1,r2")
|
||||
irSrc.shouldNotContain("INLINEASM")
|
||||
VmRunner().runProgram(irSrc)
|
||||
}
|
||||
|
||||
test("addresses from labels/subroutines not yet supported in VM") {
|
||||
|
@ -1,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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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 */ }
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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}")
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
"""
|
||||
|
Reference in New Issue
Block a user