mirror of
https://github.com/irmen/prog8.git
synced 2026-10-23 12:02:29 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78b1076110 | ||
|
|
0a3c748e41 | ||
|
|
ebf79ef9e2 | ||
|
|
99b9370178 | ||
|
|
d634061cd9 | ||
|
|
d59d8ff1fe | ||
|
|
53e442d509 | ||
|
|
f7cbfdff06 | ||
|
|
b28ee0819f | ||
|
|
522958e0e9 | ||
|
|
ccc6b56e35 |
No files matched your search
@@ -186,7 +186,7 @@ private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
|
||||
else -> throw AssemblyError("weird array value element $elt")
|
||||
}
|
||||
}
|
||||
val result = PtVariable(name, type, zeropage, align, newValue, arraySize, position)
|
||||
val result = PtVariable(name, type, zeropage, align, dirty, newValue, arraySize, position)
|
||||
result.parent = parent
|
||||
result
|
||||
}
|
||||
@@ -1033,6 +1033,7 @@ $repeatLabel""")
|
||||
val target = getJumpTarget(jump)
|
||||
require(!target.needsExpressionEvaluation)
|
||||
if(target.indirect) {
|
||||
require(!target.indexedX)
|
||||
val complementedInstruction = branchInstruction(stmt.condition, true)
|
||||
out("""
|
||||
$complementedInstruction +
|
||||
@@ -1087,7 +1088,7 @@ $repeatLabel""")
|
||||
if (isTargetCpu(CpuType.CPU65C02) && arrayIdx!=null) {
|
||||
if (!arrayIdx.splitWords) {
|
||||
// if the jump target is an address in a non-split array (like a jump table of only pointers),
|
||||
// on the 65c02, more optimal assembly can be generated using JMP address,X
|
||||
// on the 65c02, more optimal assembly can be generated using JMP (address,X)
|
||||
assignExpressionToRegister(arrayIdx.index, RegisterOrPair.A)
|
||||
out(" asl a | tax")
|
||||
return JumpTarget(asmSymbolName(arrayIdx.variable), true, true, false)
|
||||
|
||||
@@ -38,7 +38,6 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
// use a BIT instruction to test for bit 7 or 6 set/clear
|
||||
val (testBitSet, variable, bitmask) = useBIT
|
||||
return translateIfBIT(stmt, jumpAfterIf, testBitSet, variable, bitmask)
|
||||
return
|
||||
}
|
||||
|
||||
val rightDt = compareCond.right.type
|
||||
@@ -87,6 +86,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
if(testForBitSet) {
|
||||
if(jumpAfterIf!=null) {
|
||||
val target = asmgen.getJumpTarget(jumpAfterIf)
|
||||
require(!target.indexedX)
|
||||
branch("bmi", target)
|
||||
}
|
||||
else
|
||||
@@ -94,6 +94,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
} else {
|
||||
if(jumpAfterIf!=null) {
|
||||
val target = asmgen.getJumpTarget(jumpAfterIf)
|
||||
require(!target.indexedX)
|
||||
branch("bpl", target)
|
||||
}
|
||||
else
|
||||
@@ -107,6 +108,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
if(testForBitSet) {
|
||||
if(jumpAfterIf!=null) {
|
||||
val target = asmgen.getJumpTarget(jumpAfterIf)
|
||||
require(!target.indexedX)
|
||||
branch("bvs", target)
|
||||
}
|
||||
else
|
||||
@@ -114,6 +116,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
} else {
|
||||
if(jumpAfterIf!=null) {
|
||||
val target = asmgen.getJumpTarget(jumpAfterIf)
|
||||
require(!target.indexedX)
|
||||
branch("bvc", target)
|
||||
}
|
||||
else
|
||||
@@ -172,9 +175,8 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" $falseBranch +")
|
||||
if(target.needsExpressionEvaluation)
|
||||
target = asmgen.getJumpTarget(jump)
|
||||
asmgen.out("""
|
||||
jmp (${target.asmLabel})
|
||||
+""")
|
||||
asmgen.jmp(target.asmLabel, target.indirect, target.indexedX)
|
||||
asmgen.out("+")
|
||||
} else {
|
||||
require(!target.needsExpressionEvaluation)
|
||||
asmgen.out(" $branchInstr ${target.asmLabel}")
|
||||
@@ -287,6 +289,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" bmi + | beq +")
|
||||
if(target.needsExpressionEvaluation)
|
||||
target = asmgen.getJumpTarget(jumpAfterIf)
|
||||
require(!target.indexedX)
|
||||
asmgen.out("""
|
||||
jmp (${target.asmLabel})
|
||||
+""")
|
||||
@@ -354,6 +357,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" bmi + | bne ++")
|
||||
if(target.needsExpressionEvaluation)
|
||||
target = asmgen.getJumpTarget(jumpAfterIf)
|
||||
require(!target.indexedX)
|
||||
asmgen.out("""
|
||||
+ jmp (${target.asmLabel})
|
||||
+""")
|
||||
@@ -435,6 +439,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
asmgen.out(" bcc + | beq +")
|
||||
if(target.needsExpressionEvaluation)
|
||||
target = asmgen.getJumpTarget(jumpAfterIf)
|
||||
require(!target.indexedX)
|
||||
asmgen.out("""
|
||||
jmp (${target.asmLabel})
|
||||
+""")
|
||||
@@ -536,6 +541,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
+ bpl +""")
|
||||
if(target.needsExpressionEvaluation)
|
||||
target = asmgen.getJumpTarget(jump)
|
||||
require(!target.indexedX)
|
||||
asmgen.out("""
|
||||
jmp (${target.asmLabel})
|
||||
+""")
|
||||
@@ -591,6 +597,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
bcs +""")
|
||||
if(target.needsExpressionEvaluation)
|
||||
target = asmgen.getJumpTarget(jump)
|
||||
require(!target.indexedX)
|
||||
asmgen.out("""
|
||||
_jump jmp (${target.asmLabel})
|
||||
+""")
|
||||
@@ -668,6 +675,7 @@ _jump jmp (${target.asmLabel})
|
||||
+ bpl +""")
|
||||
if(target.needsExpressionEvaluation)
|
||||
target = asmgen.getJumpTarget(jump)
|
||||
require(!target.indexedX)
|
||||
asmgen.out("""
|
||||
jmp (${target.asmLabel})
|
||||
+""")
|
||||
@@ -722,6 +730,7 @@ _jump jmp (${target.asmLabel})
|
||||
bcc +""")
|
||||
if(target.needsExpressionEvaluation)
|
||||
target = asmgen.getJumpTarget(jump)
|
||||
require(!target.indexedX)
|
||||
asmgen.out("""
|
||||
jmp (${target.asmLabel})
|
||||
+""")
|
||||
@@ -830,6 +839,7 @@ _jump jmp (${target.asmLabel})
|
||||
bne ++""")
|
||||
if(target.needsExpressionEvaluation)
|
||||
target = asmgen.getJumpTarget(jump)
|
||||
require(!target.indexedX)
|
||||
asmgen.out("""
|
||||
+ jmp (${target.asmLabel})
|
||||
+""")
|
||||
@@ -909,6 +919,7 @@ _jump jmp (${target.asmLabel})
|
||||
bne ++""")
|
||||
if(target.needsExpressionEvaluation)
|
||||
target = asmgen.getJumpTarget(jump)
|
||||
require(!target.indexedX)
|
||||
asmgen.out("""
|
||||
+ jmp (${target.asmLabel})
|
||||
+""")
|
||||
@@ -974,6 +985,7 @@ _jump jmp (${target.asmLabel})
|
||||
beq ++""")
|
||||
if(target.needsExpressionEvaluation)
|
||||
target = asmgen.getJumpTarget(jump)
|
||||
require(!target.indexedX)
|
||||
asmgen.out("""
|
||||
+ jmp (${target.asmLabel})
|
||||
+""")
|
||||
@@ -1054,6 +1066,7 @@ _jump jmp (${target.asmLabel})
|
||||
beq ++""")
|
||||
if(target.needsExpressionEvaluation)
|
||||
target = asmgen.getJumpTarget(jump)
|
||||
require(!target.indexedX)
|
||||
asmgen.out("""
|
||||
+ jmp (${target.asmLabel})
|
||||
+""")
|
||||
@@ -1197,6 +1210,7 @@ _jump jmp (${target.asmLabel})
|
||||
beq ++""")
|
||||
if(target.needsExpressionEvaluation)
|
||||
target = asmgen.getJumpTarget(jump)
|
||||
require(!target.indexedX)
|
||||
asmgen.out("""
|
||||
+ jmp (${target.asmLabel})
|
||||
+""")
|
||||
@@ -1249,6 +1263,7 @@ _jump jmp (${target.asmLabel})
|
||||
bne +""")
|
||||
if(target.needsExpressionEvaluation)
|
||||
target = asmgen.getJumpTarget(jump)
|
||||
require(!target.indexedX)
|
||||
asmgen.out("""
|
||||
jmp (${target.asmLabel})
|
||||
+""")
|
||||
@@ -1303,6 +1318,7 @@ _jump jmp (${target.asmLabel})
|
||||
beq ++""")
|
||||
if(target.needsExpressionEvaluation)
|
||||
target = asmgen.getJumpTarget(jump)
|
||||
require(!target.indexedX)
|
||||
asmgen.out("""
|
||||
+ jmp (${target.asmLabel})
|
||||
+""")
|
||||
@@ -1362,6 +1378,7 @@ _jump jmp (${target.asmLabel})
|
||||
bne +""")
|
||||
if(target.needsExpressionEvaluation)
|
||||
target = asmgen.getJumpTarget(jump)
|
||||
require(!target.indexedX)
|
||||
asmgen.out("""
|
||||
jmp (${target.asmLabel})
|
||||
+""")
|
||||
@@ -1424,6 +1441,7 @@ _jump jmp (${target.asmLabel})
|
||||
beq ++""")
|
||||
if(target.needsExpressionEvaluation)
|
||||
target = asmgen.getJumpTarget(jump)
|
||||
require(!target.indexedX)
|
||||
asmgen.out("""
|
||||
+ jmp (${target.asmLabel})
|
||||
+""")
|
||||
@@ -1482,6 +1500,7 @@ _jump jmp (${target.asmLabel})
|
||||
bne +""")
|
||||
if(target.needsExpressionEvaluation)
|
||||
target = asmgen.getJumpTarget(jump)
|
||||
require(!target.indexedX)
|
||||
asmgen.out("""
|
||||
jmp (${target.asmLabel})
|
||||
+""")
|
||||
|
||||
@@ -220,7 +220,8 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
|
||||
private fun tempVars() {
|
||||
asmgen.out("; expression temp vars\n .section BSS")
|
||||
// these can be in the no clear section because they'll always get assigned a new value
|
||||
asmgen.out("; expression temp vars\n .section BSS_NOCLEAR")
|
||||
for((dt, count) in asmgen.tempVarsCounters) {
|
||||
if(count>0) {
|
||||
for(num in 1..count) {
|
||||
@@ -238,7 +239,7 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
}
|
||||
}
|
||||
asmgen.out(" .send BSS")
|
||||
asmgen.out(" .send BSS_NOCLEAR")
|
||||
}
|
||||
|
||||
private fun footer() {
|
||||
@@ -494,7 +495,7 @@ internal class ProgramAndVarsGen(
|
||||
sub.children.forEach { asmgen.translate(it) }
|
||||
|
||||
asmgen.out("; variables")
|
||||
asmgen.out(" .section BSS")
|
||||
asmgen.out(" .section BSS_NOCLEAR") // these extra vars are initialized before use
|
||||
val asmGenInfo = asmgen.subroutineExtra(sub)
|
||||
for((dt, name, addr) in asmGenInfo.extraVars) {
|
||||
if(addr!=null)
|
||||
@@ -510,7 +511,7 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out("$subroutineFloatEvalResultVar1 .fill ${options.compTarget.FLOAT_MEM_SIZE}")
|
||||
if(asmGenInfo.usedFloatEvalResultVar2)
|
||||
asmgen.out("$subroutineFloatEvalResultVar2 .fill ${options.compTarget.FLOAT_MEM_SIZE}")
|
||||
asmgen.out(" .send BSS")
|
||||
asmgen.out(" .send BSS_NOCLEAR")
|
||||
|
||||
// normal statically allocated variables
|
||||
val variables = varsInSubroutine
|
||||
@@ -638,15 +639,30 @@ internal class ProgramAndVarsGen(
|
||||
val (varsNoInit, varsWithInit) = variables.partition { it.uninitialized }
|
||||
if(varsNoInit.isNotEmpty()) {
|
||||
asmgen.out("; non-zeropage variables")
|
||||
asmgen.out(" .section BSS")
|
||||
val (notAligned, aligned) = varsNoInit.partition { it.align==0 }
|
||||
notAligned.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt.base }).forEach {
|
||||
uninitializedVariable2asm(it)
|
||||
val (dirty, clean) = varsNoInit.partition { it.dirty }
|
||||
|
||||
fun generate(section: String, variables: List<StStaticVariable>) {
|
||||
asmgen.out(" .section $section")
|
||||
val (notAligned, aligned) = variables.partition { it.align == 0 }
|
||||
notAligned.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt.base }).forEach {
|
||||
uninitializedVariable2asm(it)
|
||||
}
|
||||
aligned.sortedWith(compareBy<StStaticVariable> { it.align }.thenBy { it.name }.thenBy { it.dt.base })
|
||||
.forEach { uninitializedVariable2asm(it) }
|
||||
asmgen.out(" .send $section")
|
||||
}
|
||||
aligned.sortedWith(compareBy<StStaticVariable> { it.align }.thenBy { it.name }.thenBy { it.dt.base }).forEach {
|
||||
uninitializedVariable2asm(it)
|
||||
|
||||
if(clean.isNotEmpty()) {
|
||||
// clean vars end up in BSS so they're at least cleared to 0 at startup
|
||||
generate("BSS", clean)
|
||||
}
|
||||
if(dirty.isNotEmpty()) {
|
||||
// Dirty vars actually are ALSO are put into BSS so they're cleared to 0 at program startup,
|
||||
// but NOT at each entry of the subroutine they're declared in.
|
||||
// This saves the STZ's instructions in the subroutine, while still having deterministic start state.
|
||||
// So there is no actual difference here when compared to the way non-dirty variables are allocated.
|
||||
generate("BSS", dirty)
|
||||
}
|
||||
asmgen.out(" .send BSS")
|
||||
}
|
||||
|
||||
if(varsWithInit.isNotEmpty()) {
|
||||
|
||||
@@ -438,6 +438,8 @@ internal class AssignmentAsmGen(
|
||||
private fun assignExpression(assign: AsmAssignment, scope: IPtSubroutine?) {
|
||||
when(val value = assign.source.expression!!) {
|
||||
is PtAddressOf -> {
|
||||
val source = asmgen.symbolTable.lookup(value.identifier.name)
|
||||
require(source !is StConstant) { "addressOf of a constant should have been rewritten to a simple addition expression" }
|
||||
val arrayDt = value.identifier.type
|
||||
val sourceName =
|
||||
if(value.isMsbForSplitArray)
|
||||
@@ -1567,8 +1569,8 @@ internal class AssignmentAsmGen(
|
||||
if(ptrVar!=null && asmgen.isZpVar(ptrVar)) {
|
||||
assignExpressionToRegister(value, RegisterOrPair.A, false)
|
||||
val pointername = asmgen.asmVariableName(ptrVar)
|
||||
if (constOffset != null && constOffset < 256) {
|
||||
// we have value + @(zpptr + 255), or value - @(zpptr+255)
|
||||
if (constOffset != null) {
|
||||
// we have value + @(zpptr + 255), or value - @(zpptr+255). the offset is always <256.
|
||||
asmgen.out(" ldy #$constOffset")
|
||||
if (operator == "+")
|
||||
asmgen.out(" clc | adc ($pointername),y")
|
||||
@@ -2613,13 +2615,23 @@ $endLabel""")
|
||||
if (arrayDt.isUnsignedWord) {
|
||||
require(!msb)
|
||||
assignVariableToRegister(sourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
|
||||
if(constIndex>0)
|
||||
if(constIndex in 1..255)
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc #$constIndex
|
||||
bcc +
|
||||
iny
|
||||
+""")
|
||||
else if(constIndex>=256) {
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc #<$constIndex
|
||||
pha
|
||||
tya
|
||||
adc #>$constIndex
|
||||
tay
|
||||
pla""")
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(constIndex>0) {
|
||||
@@ -2637,15 +2649,33 @@ $endLabel""")
|
||||
assignVariableToRegister(sourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
|
||||
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
assignExpressionToVariable(arrayIndexExpr, "P8ZP_SCRATCH_REG", DataType.UBYTE)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc P8ZP_SCRATCH_REG
|
||||
bcc +
|
||||
iny
|
||||
if(arrayIndexExpr.type.isWord) {
|
||||
assignExpressionToRegister(arrayIndexExpr, RegisterOrPair.AY, false)
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
clc
|
||||
adc P8ZP_SCRATCH_W1
|
||||
pha
|
||||
tya
|
||||
adc P8ZP_SCRATCH_W1+1
|
||||
tay
|
||||
pla""")
|
||||
}
|
||||
else {
|
||||
assignExpressionToVariable(arrayIndexExpr, "P8ZP_SCRATCH_REG", DataType.UBYTE)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc P8ZP_SCRATCH_REG
|
||||
bcc +
|
||||
iny
|
||||
+""")
|
||||
}
|
||||
}
|
||||
else {
|
||||
assignExpressionToRegister(arrayIndexExpr, RegisterOrPair.A, false)
|
||||
|
||||
@@ -58,6 +58,7 @@ class TestCodegen: FunSpec({
|
||||
DataType.UBYTE,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
@@ -67,6 +68,7 @@ class TestCodegen: FunSpec({
|
||||
DataType.arrayFor(BaseDataType.UBYTE),
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
null,
|
||||
3u,
|
||||
Position.DUMMY
|
||||
@@ -76,6 +78,7 @@ class TestCodegen: FunSpec({
|
||||
DataType.arrayFor(BaseDataType.UBYTE),
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
null,
|
||||
3u,
|
||||
Position.DUMMY
|
||||
@@ -85,6 +88,7 @@ class TestCodegen: FunSpec({
|
||||
DataType.WORD,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.StExtSub
|
||||
import prog8.code.StNode
|
||||
import prog8.code.StNodeType
|
||||
import prog8.code.StSub
|
||||
import prog8.code.*
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.AssemblyError
|
||||
import prog8.code.core.BaseDataType
|
||||
@@ -184,12 +181,19 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
if(expr.isFromArrayElement) {
|
||||
val indexTr = translateExpression(expr.arrayIndexExpr!!)
|
||||
addToResult(result, indexTr, indexTr.resultReg, -1)
|
||||
val indexWordReg = codeGen.registers.next(IRDataType.WORD)
|
||||
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=indexWordReg, reg2=indexTr.resultReg), null)
|
||||
val indexWordReg = if(indexTr.dt==IRDataType.BYTE) {
|
||||
val ixWord = codeGen.registers.next(IRDataType.WORD)
|
||||
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=ixWord, reg2=indexTr.resultReg), null)
|
||||
ixWord
|
||||
} else indexTr.resultReg
|
||||
if(expr.identifier.type.isUnsignedWord) {
|
||||
require(!expr.isMsbForSplitArray)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = symbol)
|
||||
val ptr = codeGen.symbolTable.lookup(expr.identifier.name)
|
||||
it += if(ptr is StConstant)
|
||||
IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, immediate = ptr.value.toInt())
|
||||
else
|
||||
IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = symbol)
|
||||
it += IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1=resultRegister, reg2=indexWordReg)
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -52,7 +52,8 @@ private fun convert(variable: StStaticVariable): IRStStaticVariable {
|
||||
variable.initializationArrayValue?.map { convertArrayElt(it) },
|
||||
variable.length,
|
||||
variable.zpwish,
|
||||
variable.align)
|
||||
variable.align,
|
||||
variable.dirty)
|
||||
} else {
|
||||
fun fixupAddressOfInArray(array: List<StArrayElement>?): List<IRStArrayElement>? {
|
||||
if(array==null)
|
||||
@@ -76,7 +77,8 @@ private fun convert(variable: StStaticVariable): IRStStaticVariable {
|
||||
fixupAddressOfInArray(variable.initializationArrayValue),
|
||||
variable.length,
|
||||
variable.zpwish,
|
||||
variable.align
|
||||
variable.align,
|
||||
variable.dirty
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ class TestVmCodeGen: FunSpec({
|
||||
DataType.UBYTE,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
@@ -61,6 +62,7 @@ class TestVmCodeGen: FunSpec({
|
||||
DataType.arrayFor(BaseDataType.UBYTE),
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
null,
|
||||
3u,
|
||||
Position.DUMMY
|
||||
@@ -70,6 +72,7 @@ class TestVmCodeGen: FunSpec({
|
||||
DataType.arrayFor(BaseDataType.UBYTE),
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
null,
|
||||
3u,
|
||||
Position.DUMMY
|
||||
@@ -79,6 +82,7 @@ class TestVmCodeGen: FunSpec({
|
||||
DataType.WORD,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
@@ -167,6 +171,7 @@ class TestVmCodeGen: FunSpec({
|
||||
DataType.FLOAT,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
@@ -238,6 +243,7 @@ class TestVmCodeGen: FunSpec({
|
||||
DataType.FLOAT,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
@@ -305,6 +311,7 @@ class TestVmCodeGen: FunSpec({
|
||||
DataType.FLOAT,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
@@ -360,6 +367,7 @@ class TestVmCodeGen: FunSpec({
|
||||
DataType.BYTE,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
@@ -431,6 +439,7 @@ class TestVmCodeGen: FunSpec({
|
||||
DataType.BYTE,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
@@ -498,6 +507,7 @@ class TestVmCodeGen: FunSpec({
|
||||
DataType.BYTE,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
|
||||
@@ -681,7 +681,7 @@ internal class AstChecker(private val program: Program,
|
||||
if (assignment.value !is BinaryExpression && assignment.value !is PrefixExpression && assignment.value !is ContainmentCheck && assignment.value !is IfExpression)
|
||||
errors.err("invalid assignment value, maybe forgot '&' (address-of)", assignment.value.position)
|
||||
} else {
|
||||
checkAssignmentCompatible(targetDatatype.getOrUndef(),sourceDatatype.getOrUndef(), assignment.value.position)
|
||||
checkAssignmentCompatible(targetDatatype.getOrUndef(),sourceDatatype.getOrUndef(), assignment.value, assignment.value.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2008,6 +2008,7 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
private fun checkAssignmentCompatible(targetDatatype: DataType,
|
||||
sourceDatatype: DataType,
|
||||
sourceValue: Expression,
|
||||
position: Position) : Boolean {
|
||||
|
||||
if (targetDatatype.isArray) {
|
||||
@@ -2044,6 +2045,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(result)
|
||||
return true
|
||||
|
||||
val sourceIsBitwiseOperatorExpression = (sourceValue as? BinaryExpression)?.operator in BitwiseOperators
|
||||
if(sourceDatatype.isWord && targetDatatype.isByte)
|
||||
errors.err("cannot assign word to byte, maybe use msb() or lsb()", position)
|
||||
else if(sourceDatatype.isFloat&& targetDatatype.isInteger)
|
||||
@@ -2051,6 +2053,9 @@ internal class AstChecker(private val program: Program,
|
||||
else if(targetDatatype.isUnsignedWord && sourceDatatype.isPassByRef) {
|
||||
// this is allowed: a pass-by-reference datatype into an uword (pointer value).
|
||||
}
|
||||
else if(sourceIsBitwiseOperatorExpression && targetDatatype.equalsSize(sourceDatatype)) {
|
||||
// this is allowed: bitwise operation between different types as long as they're the same size.
|
||||
}
|
||||
else if(targetDatatype.isString && sourceDatatype.isUnsignedWord)
|
||||
errors.err("can't assign uword to str. If the source is a string pointer and you actually want to overwrite the target string, use an explicit strings.copy(src,tgt) instead.", position)
|
||||
else
|
||||
|
||||
@@ -578,6 +578,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
|
||||
srcVar.datatype,
|
||||
srcVar.zeropage,
|
||||
srcVar.alignment,
|
||||
srcVar.dirty,
|
||||
value,
|
||||
srcVar.arraysize?.constIndex()?.toUInt(),
|
||||
srcVar.position
|
||||
|
||||
@@ -46,6 +46,7 @@ private fun setDeferMasks(program: PtProgram, errors: IErrorReporter): Map<PtSub
|
||||
DataType.UBYTE,
|
||||
ZeropageWish.NOT_IN_ZEROPAGE,
|
||||
0u,
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
sub.position
|
||||
|
||||
@@ -429,5 +429,24 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(addressOf: AddressOf, parent: Node): Iterable<IAstModification> {
|
||||
if(addressOf.arrayIndex!=null) {
|
||||
val tgt = addressOf.identifier.constValue(program)
|
||||
if (tgt != null && tgt.type.isWord) {
|
||||
// &constant[idx] --> constant + idx
|
||||
val indexExpr = addressOf.arrayIndex!!.indexExpr
|
||||
val right = if(indexExpr.inferType(program) issimpletype tgt.type)
|
||||
indexExpr
|
||||
else
|
||||
TypecastExpression(indexExpr, tgt.type, true, indexExpr.position)
|
||||
val add = BinaryExpression(tgt, "+", right, addressOf.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(addressOf, add, parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ class TestLaunchEmu: FunSpec({
|
||||
|
||||
<VARIABLESNOINIT>
|
||||
</VARIABLESNOINIT>
|
||||
<VARIABLESNOINITDIRTY>
|
||||
</VARIABLESNOINITDIRTY>
|
||||
<VARIABLESWITHINIT>
|
||||
</VARIABLESWITHINIT>
|
||||
<CONSTANTS>
|
||||
|
||||
@@ -86,14 +86,14 @@ class TestSymbolTable: FunSpec({
|
||||
|
||||
test("static vars") {
|
||||
val node = PtIdentifier("dummy", DataType.UBYTE, Position.DUMMY)
|
||||
val stVar1 = StStaticVariable("initialized", DataType.UBYTE, null, null, null, ZeropageWish.DONTCARE, 0, node)
|
||||
val stVar1 = StStaticVariable("initialized", DataType.UBYTE, null, null, null, ZeropageWish.DONTCARE, 0, false,node)
|
||||
stVar1.setOnetimeInitNumeric(99.0)
|
||||
val stVar2 = StStaticVariable("uninitialized", DataType.UBYTE, null, null, null, ZeropageWish.DONTCARE, 0, node)
|
||||
val stVar2 = StStaticVariable("uninitialized", DataType.UBYTE, null, null, null, ZeropageWish.DONTCARE, 0, false, node)
|
||||
val arrayInitNonzero = listOf(StArrayElement(1.1, null, null), StArrayElement(2.2, null, null), StArrayElement(3.3, null, null))
|
||||
val arrayInitAllzero = listOf(StArrayElement(0.0, null, null), StArrayElement(0.0, null, null), StArrayElement(0.0, null, null))
|
||||
val stVar3 = StStaticVariable("initialized", DataType.arrayFor(BaseDataType.UWORD), null, arrayInitNonzero, 3, ZeropageWish.DONTCARE, 0, node)
|
||||
val stVar4 = StStaticVariable("initialized", DataType.arrayFor(BaseDataType.UWORD), null, arrayInitAllzero, 3, ZeropageWish.DONTCARE, 0, node)
|
||||
val stVar5 = StStaticVariable("uninitialized", DataType.arrayFor(BaseDataType.UWORD), null, null, 3, ZeropageWish.DONTCARE, 0, node)
|
||||
val stVar3 = StStaticVariable("initialized", DataType.arrayFor(BaseDataType.UWORD), null, arrayInitNonzero, 3, ZeropageWish.DONTCARE, 0, false, node)
|
||||
val stVar4 = StStaticVariable("initialized", DataType.arrayFor(BaseDataType.UWORD), null, arrayInitAllzero, 3, ZeropageWish.DONTCARE, 0, false, node)
|
||||
val stVar5 = StStaticVariable("uninitialized", DataType.arrayFor(BaseDataType.UWORD), null, null, 3, ZeropageWish.DONTCARE, 0, false, node)
|
||||
|
||||
stVar1.uninitialized shouldBe false
|
||||
stVar1.length shouldBe null
|
||||
@@ -125,6 +125,7 @@ private fun makeSt(): SymbolTable {
|
||||
DataType.BYTE,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
@@ -134,6 +135,7 @@ private fun makeSt(): SymbolTable {
|
||||
DataType.BYTE,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
@@ -143,6 +145,7 @@ private fun makeSt(): SymbolTable {
|
||||
DataType.FLOAT,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
@@ -152,6 +155,7 @@ private fun makeSt(): SymbolTable {
|
||||
DataType.UWORD,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
@@ -161,6 +165,7 @@ private fun makeSt(): SymbolTable {
|
||||
DataType.BYTE,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
@@ -170,6 +175,7 @@ private fun makeSt(): SymbolTable {
|
||||
DataType.BYTE,
|
||||
ZeropageWish.DONTCARE,
|
||||
0u,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
Position.DUMMY
|
||||
@@ -205,12 +211,12 @@ private fun makeSt(): SymbolTable {
|
||||
block1.add(sub12)
|
||||
block1.add(StConstant("c1", BaseDataType.UWORD, 12345.0, astConstant1))
|
||||
block1.add(StConstant("blockc", BaseDataType.UWORD, 999.0, astConstant2))
|
||||
sub11.add(StStaticVariable("v1", DataType.BYTE, null, null, null, ZeropageWish.DONTCARE, 0, astSub1v1))
|
||||
sub11.add(StStaticVariable("v2", DataType.BYTE, null, null, null, ZeropageWish.DONTCARE, 0, astSub1v2))
|
||||
sub11.add(StStaticVariable("v1", DataType.BYTE, null, null, null, ZeropageWish.DONTCARE, 0, false, astSub1v1))
|
||||
sub11.add(StStaticVariable("v2", DataType.BYTE, null, null, null, ZeropageWish.DONTCARE, 0, false, astSub1v2))
|
||||
sub11.add(StMemVar("v3", DataType.FLOAT, 12345u, null, astSub1v3))
|
||||
sub11.add(StMemorySlab("slab1", 200u, 64u, astSub1v4))
|
||||
sub12.add(StStaticVariable("v1", DataType.BYTE, null, null, null, ZeropageWish.DONTCARE, 0, astSub2v1))
|
||||
sub12.add(StStaticVariable("v2", DataType.BYTE, null, null, null, ZeropageWish.DONTCARE, 0, astSub2v2))
|
||||
sub12.add(StStaticVariable("v1", DataType.BYTE, null, null, null, ZeropageWish.DONTCARE, 0, false, astSub2v1))
|
||||
sub12.add(StStaticVariable("v2", DataType.BYTE, null, null, null, ZeropageWish.DONTCARE, 0, false, astSub2v2))
|
||||
val block2 = StNode("block2", StNodeType.BLOCK, astBlock2)
|
||||
val sub21 = StNode("sub1", StNodeType.SUBROUTINE, astSub21)
|
||||
val sub22 = StNode("sub2", StNodeType.SUBROUTINE, astSub22)
|
||||
|
||||
@@ -970,4 +970,36 @@ main {
|
||||
}"""
|
||||
compileText(C64Target(), false, src, outputDir, writeAssembly = false) shouldNotBe null
|
||||
}
|
||||
|
||||
test("bitwise operator on signed values") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
word[5] xpos
|
||||
|
||||
xpos[4] &= ${'$'}fff8
|
||||
xpos[4] &= ${'$'}fff8 as word
|
||||
xpos[4] = xpos[4] & ${'$'}fff8
|
||||
xpos[4] = xpos[4] & ${'$'}fff8 as word
|
||||
|
||||
xpos[4] &= $7000
|
||||
xpos[4] &= $7000 as word
|
||||
xpos[4] = xpos[4] & $7000
|
||||
xpos[4] = xpos[4] & $7000 as word
|
||||
|
||||
xpos[4] |= $7000
|
||||
xpos[4] |= $7000 as word
|
||||
xpos[4] = xpos[4] | $7000
|
||||
xpos[4] = xpos[4] | $7000 as word
|
||||
|
||||
; the error doesn't occur with other operators:
|
||||
xpos[4] += $7000
|
||||
xpos[4] += $7000 as word
|
||||
xpos[4] = xpos[4] + $7000
|
||||
xpos[4] = xpos[4] + $7000 as word
|
||||
}
|
||||
}"""
|
||||
|
||||
compileText(VMTarget(), false, src, outputDir, writeAssembly = false) shouldNotBe null
|
||||
}
|
||||
})
|
||||
@@ -6,7 +6,6 @@ import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
@@ -261,16 +260,16 @@ main {
|
||||
}
|
||||
|
||||
test("const address-of memory mapped arrays") {
|
||||
val src="""
|
||||
val src= """
|
||||
main {
|
||||
sub start() {
|
||||
&uword[30] @nosplit wb = ${'$'}2000
|
||||
&uword[100] @nosplit array1 = ${'$'}9e00
|
||||
&uword[30] @nosplit wb = $2000
|
||||
&uword[100] @nosplit array1 = $9e00
|
||||
&uword[30] @nosplit array2 = &array1[len(wb)]
|
||||
|
||||
cx16.r0 = &array1 ; ${'$'}9e00
|
||||
cx16.r1 = &array1[len(wb)] ; ${'$'}9e3c
|
||||
cx16.r2 = &array2 ; ${'$'}9e3c
|
||||
cx16.r0 = &array1 ; $9e00
|
||||
cx16.r1 = &array1[len(wb)] ; $9e3c
|
||||
cx16.r2 = &array2 ; $9e3c
|
||||
}
|
||||
}"""
|
||||
val result = compileText(Cx16Target(), false, src, outputDir, writeAssembly = false)!!
|
||||
@@ -307,10 +306,10 @@ main {
|
||||
}
|
||||
|
||||
test("address of a const uword pointer array expression") {
|
||||
val src="""
|
||||
val src= """
|
||||
main {
|
||||
sub start() {
|
||||
const uword buffer = ${'$'}2000
|
||||
const uword buffer = 2000
|
||||
uword @shared addr = &buffer[2]
|
||||
|
||||
const ubyte width = 100
|
||||
@@ -323,10 +322,9 @@ main {
|
||||
val st = result.compilerAst.entrypoint.statements
|
||||
st.size shouldBe 11
|
||||
val assignAddr = (st[2] as Assignment).value
|
||||
(assignAddr as NumericLiteral).number shouldBe 8194.0
|
||||
val assignAddr2 = ((st[9] as Assignment).value as AddressOf)
|
||||
assignAddr2.identifier.nameInSource shouldBe listOf("buffer")
|
||||
assignAddr2.arrayIndex!!.indexExpr shouldBe instanceOf<BinaryExpression>()
|
||||
(assignAddr as NumericLiteral).number shouldBe 2002.0
|
||||
val assignAddr2 = (st[9] as Assignment).value as BinaryExpression
|
||||
assignAddr2.operator shouldBe "+"
|
||||
}
|
||||
|
||||
test("out of range const byte and word give correct error") {
|
||||
|
||||
@@ -1060,6 +1060,24 @@ main {
|
||||
st2[3].children.dropLast(1).map { (it as PtAssignTarget).identifier!!.name } shouldBe listOf("p8b_main.p8s_start.p8v_x", "p8b_main.p8s_start.p8v_y", "p8b_main.p8s_start.p8v_z")
|
||||
((st2[3] as PtAssignment).value as PtFunctionCall).name shouldBe "p8b_main.p8s_multi"
|
||||
}
|
||||
|
||||
test("address-of a uword pointer with word index should not overflow") {
|
||||
val src= """
|
||||
main {
|
||||
sub start() {
|
||||
const uword cbuffer = $2000
|
||||
uword @shared buffer = $2000
|
||||
|
||||
cx16.r1 = &cbuffer[2000]
|
||||
cx16.r5 = &buffer[2000]
|
||||
|
||||
cx16.r3 = &cbuffer[cx16.r0]
|
||||
cx16.r4 = &buffer[cx16.r0]
|
||||
}
|
||||
}"""
|
||||
compileText(Cx16Target(), optimize=false, src, outputDir, writeAssembly=true) shouldNotBe null
|
||||
compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=true) shouldNotBe null
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
@@ -532,4 +532,22 @@ main {
|
||||
instructions[10].type shouldBe IRDataType.WORD
|
||||
}
|
||||
|
||||
test("typed address-of a const pointer with non-const array indexing") {
|
||||
val src= """
|
||||
main {
|
||||
sub start() {
|
||||
const uword cbuffer = $2000
|
||||
uword @shared buffer = $2000
|
||||
|
||||
cx16.r2 = @(cbuffer + cx16.r0)
|
||||
cx16.r1 = &cbuffer[cx16.r0]
|
||||
|
||||
cx16.r3 = @(buffer + cx16.r0)
|
||||
cx16.r4 = &buffer[cx16.r0]
|
||||
}
|
||||
}"""
|
||||
val result = compileText(VMTarget(), false, src, outputDir)!!
|
||||
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
|
||||
VmRunner().runProgram(virtfile.readText(), true)
|
||||
}
|
||||
})
|
||||
@@ -771,8 +771,8 @@ but perhaps the provided ones can be of service too.
|
||||
|
||||
``crc16 (uword data, uword length) -> uword``
|
||||
Returns a CRC-16 (XMODEM) checksum over the given data buffer.
|
||||
Note: on the Commander X16, there is a CRC-16 routine in the kernal: cx16.memory_crc().
|
||||
That one is faster, but yields different results. It is unclear to me what flavour of crc it is calculating.
|
||||
Note: on the Commander X16, there is a CRC-16/IBM-3740 routine in the kernal: cx16.memory_crc().
|
||||
That one is faster, but yields different results.
|
||||
|
||||
``crc16_start() / crc16_update(ubyte value) / crc16_end() -> uword``
|
||||
"streaming" crc16 calculation routines, when the data doesn't fit in a single buffer.
|
||||
|
||||
@@ -1231,7 +1231,7 @@ just before exiting of the current subroutine. That can be via a return statemen
|
||||
or just the normal ending of the subroutine. This is often useful to "not forget" to clean up stuff,
|
||||
and if the subroutine has multiple ways or places where it can exit, it saves you from repeating
|
||||
the cleanup code at every exit spot. Multiple defers can be scheduled in a single subroutine (up to a maximum of 8).
|
||||
They are handled in reversed order. Return values are evaluated before any deferred code is executed.
|
||||
The defers are handled in reversed (LIFO) order. Return values are evaluated before any deferred code is executed.
|
||||
You write defers like so::
|
||||
|
||||
sub example() -> bool {
|
||||
|
||||
@@ -50,6 +50,21 @@ Various things:
|
||||
of routines to perform tile and sprite transformations and rotation, using
|
||||
the VeraFX hardware feature. Includes examples.
|
||||
|
||||
`C64 REU Banking <https://github.com/gillham/prog8reu>`_
|
||||
A Prog8 library module that provides Commander X16 style RAM banking on a C64 with an REU.
|
||||
This module provides cx16.rambank(), x16jsrfar() and extsub @bank functionality on a C64.
|
||||
|
||||
`Library blob link example <https://github.com/FearLabsAudio/Prog8_blobLink_example/>`_
|
||||
An example of a simple utility that can link symbols in a main Prog8 program
|
||||
so that they are accessable from an externally loaded library blob.
|
||||
It pre-processes the debug symbols list file at compile time,
|
||||
and substitutes references in a template module file.
|
||||
|
||||
`XLink: an alternative library blob link example <https://github.com/gillham/X16/tree/main/xlink>`_
|
||||
This is another approach to access routines from a banked loaded library,
|
||||
and it does it at run time. In this demo a jump table is not only created in the library,
|
||||
but also in the main program and copied into the library for its use.
|
||||
|
||||
|
||||
.. image:: _static/curious.png
|
||||
:align: center
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
TODO
|
||||
====
|
||||
|
||||
|
||||
...
|
||||
|
||||
|
||||
Future Things and Ideas
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- for version 12.0 (branch pre12) : translate '\n' to char code 13 in ISO encoding as well (and all others that don't do it yet, EXCEPT cp437, because it has no control chars) so you don't have to use \r anymore as newline
|
||||
- for version 12.0 (branch pre12) : on <expr> goto label1, label2, label3 and 'call' instead of 'goto' to make it a JSR. Faster jump tables than a when, and much easier to write than a call + an array. Number starts at 0. If number too large, no jump is taken. (or add 'else failLabel' ?) else @dirty to skip all checks?
|
||||
- STRUCTS: are now being developed in their own separate branch "structs". This will be for the next major version of the compiler (v12)
|
||||
- is "checkAssignmentCompatible" redundant (gets called just 1 time!) when we also have "checkValueTypeAndRange" ?
|
||||
- enums?
|
||||
- romable: should we have a way to explicitly set the memory address for the BSS area (instead of only the highram bank number on X16, allow a memory address too for the -varshigh option?)
|
||||
- Kotlin: can we use inline value classes in certain spots?
|
||||
- add float support to the configurable compiler targets
|
||||
@@ -44,6 +46,7 @@ IR/VM
|
||||
cx16.r0sL = cx16.r1s as byte }
|
||||
- do something with the 'split' tag on split word arrays
|
||||
- add more optimizations in IRPeepholeOptimizer
|
||||
- apparently for SSA form, the IRCodeChunk is not a proper "basic block" yet because the last operation should be a branch or return, and no other branches
|
||||
- reduce register usage via linear-scan algorithm (based on live intervals) https://anoopsarkar.github.io/compilers-class/assets/lectures/opt3-regalloc-linearscan.pdf
|
||||
don't forget to take into account the data type of the register when it's going to be reused!
|
||||
- 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)
|
||||
@@ -67,7 +70,9 @@ Libraries
|
||||
Optimizations
|
||||
-------------
|
||||
|
||||
- when choices that end with a goto create a redundant bra, get rid of it. Also some redundant RTS somewhere?
|
||||
- Sorting module gnomesort_uw could be optimized more by fully rewriting it in asm? Shellshort seems consistently faster even if most of the words are already sorted.
|
||||
- can the for loop temp var be replaced by the same logic that createRepeatCounterVar() uses for repeat loops? Take care of nested for/repeat loops to not use the same var
|
||||
- Compare output of some Oscar64 samples to what prog8 does for the equivalent code (see https://github.com/drmortalwombat/OscarTutorials/tree/main and https://github.com/drmortalwombat/oscar64/tree/main/samples)
|
||||
- Optimize the IfExpression code generation to be more like regular if-else code. (both 6502 and IR) search for "TODO don't store condition as expression"
|
||||
- VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served?
|
||||
|
||||
@@ -65,7 +65,7 @@ Tag Effect
|
||||
@alignword aligns string or array variable on an even memory address
|
||||
@align64 aligns string or array variable on a 64 byte address interval (example: for C64 sprite data)
|
||||
@alignpage aligns string or array variable on a 256 byte address interval (example: to avoid page boundaries)
|
||||
@dirty the variable won't be initialized by Prog8 which means that its value is undefined. You'll have to set it yourself before using the variable. Used to reduce overhead in certain scenarios. 🦶🔫 Footgun warning.
|
||||
@dirty the variable won't be set to zero when entering the subroutine (note: it will still be set to zero once on program startup, like all other uninitialized variables). You'll usually have to make sure to assign a value yourself before using the variable! This is used to reduce overhead in certain scenarios. 🦶🔫 Footgun warning.
|
||||
========== ======
|
||||
|
||||
|
||||
@@ -122,9 +122,9 @@ it's trivial that it is not being read between the variable's declaration and th
|
||||
you declare a variable immediately before a for loop where it is the loop variable. However Prog8 is not yet very smart
|
||||
at detecting these redundant initializations. If you want to be sure, check the generated assembly output.
|
||||
|
||||
In any case, you can use the ``@dirty`` tag on the variable declaration to make the variable *not* being (re)initialized by Prog8.
|
||||
This means its value will be undefined (it can be anything) until you assign a value yourself! Don't use such
|
||||
a variable before you have done so. 🦶🔫 Footgun warning.
|
||||
In any case, you can use the ``@dirty`` tag on the variable declaration to make the variable *not* being reinitialized
|
||||
when entering the subroutine (it will still be set to 0 once at program startup).
|
||||
This means you usually have to make sure to assign a value yourself, before using the variable. 🦶🔫 Footgun warning.
|
||||
|
||||
|
||||
**memory alignment:**
|
||||
@@ -412,7 +412,7 @@ change it for the whole file at once. Here are examples of the possible encoding
|
||||
- ``iso5:"Хозяин и Работник"`` string in iso-8859-5 encoding (Cyrillic)
|
||||
- ``iso16:"zażółć gęślą jaźń"`` string in iso-8859-16 encoding (Eastern Europe)
|
||||
- ``atascii:"I am Atari!"`` string in "atascii" encoding (Atari 8-bit)
|
||||
- ``cp437:"≈ IBM Pc ≈ ♂♀♪☺¶"`` string in "cp437" encoding (IBM PC codepage 437)
|
||||
- ``cp437:"≈ IBM Pc ≈ ♂♀♪☺¶"`` string in "cp437" encoding (IBM PC codepage 437) See note below!
|
||||
- ``kata:"アノ ニホンジン ワ ガイコクジン。 # が # ガ"`` string in "kata" encoding (Katakana)
|
||||
- ``c64os:"^Hello_World! \\ ~{_}~"`` string in "c64os" encoding (C64 OS)
|
||||
|
||||
@@ -490,6 +490,13 @@ for a character in such strings (one that stops at the first 0 byte)
|
||||
of such a string without destroying other occurrences (as long as you stay within
|
||||
the size of the allocated string!)
|
||||
|
||||
.. note:: printing **cp437** encoded strings
|
||||
|
||||
To print strings in the **cp437** encoding, you will probably need ``txt.print_lit(message)`` to properly print
|
||||
them to the screen. This is because this encoding has symbols in place of where normally ASCII
|
||||
control characters such as Line feed would be. A regular ``txt.print(message)`` will likely get confused
|
||||
by such symbols and print them as control characters, messing up the output.
|
||||
|
||||
|
||||
.. _range-expression:
|
||||
|
||||
|
||||
+26
-8
@@ -1,15 +1,33 @@
|
||||
%zeropage basicsafe
|
||||
%import textio
|
||||
%import strings
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
str name1 = sc:"irmen de jong 123456789 the quick brown fox"
|
||||
str name2 = sc:"jumps over the lazy frog"
|
||||
cx16.r9L = 2
|
||||
|
||||
txt.print_ub(strings.hash(name1))
|
||||
txt.spc()
|
||||
txt.print_ub(strings.hash(name2))
|
||||
txt.nl()
|
||||
if cx16.r9L < len(jumplist)
|
||||
goto jumplist[cx16.r9L]
|
||||
|
||||
uword[] @nosplit jumplist = [thing.func1, thing.func2, thing.func3]
|
||||
}
|
||||
}
|
||||
|
||||
thing {
|
||||
sub func1() {
|
||||
cx16.r10++
|
||||
txt.print("one\n")
|
||||
}
|
||||
sub func2() {
|
||||
cx16.r10++
|
||||
txt.print("two\n")
|
||||
}
|
||||
sub func3() {
|
||||
cx16.r10++
|
||||
txt.print("three\n")
|
||||
}
|
||||
sub other() {
|
||||
cx16.r10++
|
||||
txt.print("other\n")
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -3,4 +3,4 @@ org.gradle.console=rich
|
||||
org.gradle.parallel=true
|
||||
org.gradle.daemon=true
|
||||
kotlin.code.style=official
|
||||
version=11.3.1
|
||||
version=11.3.2
|
||||
@@ -48,7 +48,8 @@ class IRFileReader {
|
||||
val programName = start.attributes.asSequence().single { it.name.localPart == "NAME" }.value
|
||||
val options = parseOptions(reader)
|
||||
val asmsymbols = parseAsmSymbols(reader)
|
||||
val varsWithoutInit = parseVarsWithoutInit(reader)
|
||||
val varsWithoutInitClean = parseVarsWithoutInit("VARIABLESNOINIT", false, reader)
|
||||
val varsWithoutInitDirty = parseVarsWithoutInit("VARIABLESNOINITDIRTY", true, reader)
|
||||
val variables = parseVariables(reader)
|
||||
val constants = parseConstants(reader)
|
||||
val memorymapped = parseMemMapped(reader)
|
||||
@@ -58,7 +59,8 @@ class IRFileReader {
|
||||
|
||||
val st = IRSymbolTable()
|
||||
asmsymbols.forEach { (name, value) -> st.addAsmSymbol(name, value)}
|
||||
varsWithoutInit.forEach { st.add(it) }
|
||||
varsWithoutInitClean.forEach { st.add(it) }
|
||||
varsWithoutInitDirty.forEach { st.add(it) }
|
||||
variables.forEach { st.add(it) }
|
||||
constants.forEach { st.add(it) }
|
||||
memorymapped.forEach { st.add(it) }
|
||||
@@ -153,10 +155,10 @@ class IRFileReader {
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseVarsWithoutInit(reader: XMLEventReader): List<IRStStaticVariable> {
|
||||
private fun parseVarsWithoutInit(segmentname: String, dirty: Boolean, reader: XMLEventReader): List<IRStStaticVariable> {
|
||||
skipText(reader)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="VARIABLESNOINIT") { "missing VARIABLESNOINIT" }
|
||||
require(start.name.localPart==segmentname) { "missing $segmentname" }
|
||||
val text = readText(reader).trim()
|
||||
require(reader.nextEvent().isEndElement)
|
||||
|
||||
@@ -167,7 +169,7 @@ class IRFileReader {
|
||||
val variables = mutableListOf<IRStStaticVariable>()
|
||||
text.lineSequence().forEach { line ->
|
||||
// example: uword main.start.qq2 zp=DONTCARE
|
||||
val match = varPattern.matchEntire(line) ?: throw IRParseException("invalid VARIABLESNOINIT $line")
|
||||
val match = varPattern.matchEntire(line) ?: throw IRParseException("invalid $segmentname $line")
|
||||
val type = match.groups["type"]!!.value
|
||||
val arrayspec = match.groups["arrayspec"]?.value ?: ""
|
||||
val name = match.groups["name"]!!.value
|
||||
@@ -181,7 +183,7 @@ class IRFileReader {
|
||||
val zp = if(zpwish.isBlank()) ZeropageWish.DONTCARE else ZeropageWish.valueOf(zpwish)
|
||||
// val isSplit = if(split.isBlank()) false else split.toBoolean()
|
||||
val align = if(alignment.isBlank()) 0u else alignment.toUInt()
|
||||
val newVar = IRStStaticVariable(name, dt, null, null, null, arraysize, zp, align.toInt())
|
||||
val newVar = IRStStaticVariable(name, dt, null, null, null, arraysize, zp, align.toInt(), dirty)
|
||||
variables.add(newVar)
|
||||
}
|
||||
return variables
|
||||
@@ -246,6 +248,7 @@ class IRFileReader {
|
||||
val zp = if(zpwish.isBlank()) ZeropageWish.DONTCARE else ZeropageWish.valueOf(zpwish)
|
||||
if(split.isBlank()) false else split.toBoolean()
|
||||
val align = if(alignment.isBlank()) 0u else alignment.toUInt()
|
||||
val dirty = false // these variables have initialization values.
|
||||
var initNumeric: Double? = null
|
||||
var initArray: IRStArray? = null
|
||||
when {
|
||||
@@ -270,7 +273,7 @@ class IRFileReader {
|
||||
if(arraysize!=null && initArray!=null && initArray.all { it.number==0.0 }) {
|
||||
initArray=null // arrays with just zeros can be left uninitialized
|
||||
}
|
||||
val stVar = IRStStaticVariable(name, dt, initNumeric, null, initArray, arraysize, zp, align.toInt())
|
||||
val stVar = IRStStaticVariable(name, dt, initNumeric, null, initArray, arraysize, zp, align.toInt(), dirty)
|
||||
variables.add(stVar)
|
||||
}
|
||||
return variables
|
||||
|
||||
@@ -328,19 +328,25 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
||||
}
|
||||
}
|
||||
|
||||
fun writeNoInitVars(segmentname: String, variables: List<IRStStaticVariable>) {
|
||||
xml.writeStartElement(segmentname)
|
||||
xml.writeCharacters("\n")
|
||||
val (noinitNotAligned, noinitAligned) = variables.partition { it.align==0 || it.align==1 }
|
||||
for (variable in noinitNotAligned) {
|
||||
writeNoInitVar(variable)
|
||||
}
|
||||
for (variable in noinitAligned.sortedBy { it.align }) {
|
||||
writeNoInitVar(variable)
|
||||
}
|
||||
xml.writeEndElement()
|
||||
xml.writeCharacters("\n")
|
||||
}
|
||||
|
||||
val (variablesNoInit, variablesWithInit) = irProgram.st.allVariables().partition { it.uninitialized }
|
||||
|
||||
xml.writeStartElement("VARIABLESNOINIT")
|
||||
xml.writeCharacters("\n")
|
||||
val (noinitNotAligned, noinitAligned) = variablesNoInit.partition { it.align==0 || it.align==1 }
|
||||
for (variable in noinitNotAligned) {
|
||||
writeNoInitVar(variable)
|
||||
}
|
||||
for (variable in noinitAligned.sortedBy { it.align }) {
|
||||
writeNoInitVar(variable)
|
||||
}
|
||||
xml.writeEndElement()
|
||||
xml.writeCharacters("\n")
|
||||
val (dirtyvars, cleanvars) = variablesNoInit.partition { it.dirty }
|
||||
writeNoInitVars("VARIABLESNOINIT", cleanvars)
|
||||
writeNoInitVars("VARIABLESNOINITDIRTY", dirtyvars)
|
||||
|
||||
xml.writeStartElement("VARIABLESWITHINIT")
|
||||
xml.writeCharacters("\n")
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package prog8.intermediate
|
||||
|
||||
import prog8.code.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.INTERNED_STRINGS_MODULENAME
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Encoding
|
||||
import prog8.code.core.ZeropageWish
|
||||
|
||||
|
||||
// In the Intermediate Representation, all nesting has been removed.
|
||||
@@ -93,7 +95,8 @@ class IRStStaticVariable(name: String,
|
||||
val onetimeInitializationArrayValue: IRStArray?,
|
||||
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
|
||||
val zpwish: ZeropageWish, // used in the variable allocator
|
||||
val align: Int
|
||||
val align: Int,
|
||||
val dirty: Boolean
|
||||
) : IRStNode(name, IRStNodeType.STATICVAR) {
|
||||
init {
|
||||
if(align > 0) {
|
||||
|
||||
@@ -55,6 +55,8 @@ loadAddress=$0000
|
||||
<VARIABLESNOINIT>
|
||||
uword sys.bssvar zp=DONTCARE align=0
|
||||
</VARIABLESNOINIT>
|
||||
<VARIABLESNOINITDIRTY>
|
||||
</VARIABLESNOINITDIRTY>
|
||||
<VARIABLESWITHINIT>
|
||||
uword sys.wait.jiffies=10 zp=DONTCARE align=0
|
||||
ubyte[3] sys.emptystring=0,0,0 zp=DONTCARE align=0
|
||||
|
||||
@@ -187,6 +187,7 @@ class StStaticVariable(name: String,
|
||||
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
|
||||
val zpwish: ZeropageWish, // used in the variable allocator
|
||||
val align: Int,
|
||||
val dirty: Boolean,
|
||||
astNode: PtNode?) : StNode(name, StNodeType.STATICVAR, astNode) {
|
||||
|
||||
var initializationNumericValue: Double? = null
|
||||
|
||||
@@ -99,7 +99,7 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
|
||||
// if(node.type in SplitWordArrayTypes) {
|
||||
// ... split array also add _lsb and _msb to symboltable?
|
||||
// }
|
||||
val stVar = StStaticVariable(node.name, node.type, initialString, initialArray, numElements, node.zeropage, node.align.toInt(), node)
|
||||
val stVar = StStaticVariable(node.name, node.type, initialString, initialArray, numElements, node.zeropage, node.align.toInt(), node.dirty,node)
|
||||
if(initialNumeric!=null)
|
||||
stVar.setOnetimeInitNumeric(initialNumeric)
|
||||
stVar
|
||||
|
||||
@@ -189,6 +189,7 @@ class PtVariable(
|
||||
override val type: DataType,
|
||||
val zeropage: ZeropageWish,
|
||||
val align: UInt,
|
||||
val dirty: Boolean,
|
||||
val value: PtExpression?,
|
||||
val arraySize: UInt?,
|
||||
position: Position
|
||||
@@ -204,6 +205,8 @@ class PtVariable(
|
||||
// The IR codegen however is different it has a special section <VARIABLESWITHINIT> for all variables
|
||||
// that have a non-zero initialization value, regardless of the datatype. It removes the initialization
|
||||
// assignment and puts the value back into the variable (but only in the symboltable).
|
||||
|
||||
require(!dirty) { "dirty var cannot have init value" }
|
||||
}
|
||||
|
||||
value?.let {it.parent=this}
|
||||
|
||||
@@ -149,7 +149,7 @@ 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 }
|
||||
if(target==null)
|
||||
throw IRParseException("label '$label' not found in variables nor labels. VM cannot reference other things such as blocks")
|
||||
throw IRParseException("label '$label' not found in variables nor labels. VM cannot reference other things such as blocks, and constants should have been replaced by their value")
|
||||
else if(instr.opcode in OpcodesThatBranch)
|
||||
chunk.instructions[line] = instr.copy(branchTarget = target, address = null)
|
||||
else {
|
||||
@@ -196,7 +196,10 @@ class VmProgramLoader {
|
||||
program.st.allVariables().forEach { variable ->
|
||||
var addr = allocations.allocations.getValue(variable.name)
|
||||
|
||||
// zero out uninitialized ('bss') variables.
|
||||
// Zero out uninitialized both clean AND dirty variables.
|
||||
// Dirty vars actually are ALSO are zero'd out here at program startup,
|
||||
// but NOT at each entry of the subroutine they're declared in.
|
||||
// This saves the clear to zero overhead in the subroutine, while still having deterministic start state.
|
||||
if(variable.uninitialized) {
|
||||
if(variable.dt.isArray) {
|
||||
val dt = variable.dt
|
||||
|
||||
@@ -103,6 +103,8 @@ class TestVm: FunSpec( {
|
||||
|
||||
<VARIABLESNOINIT>
|
||||
</VARIABLESNOINIT>
|
||||
<VARIABLESNOINITDIRTY>
|
||||
</VARIABLESNOINITDIRTY>
|
||||
<VARIABLESWITHINIT>
|
||||
</VARIABLESWITHINIT>
|
||||
<CONSTANTS>
|
||||
|
||||
Reference in new issue
Block a user