fix various bugs around word-indexing combined with address-of: &buffer[2000]

This commit is contained in:
Irmen de Jong
2025-05-09 00:08:18 +02:00
parent d634061cd9
commit 99b9370178
9 changed files with 122 additions and 53 deletions

View File

@@ -438,6 +438,8 @@ internal class AssignmentAsmGen(
private fun assignExpression(assign: AsmAssignment, scope: IPtSubroutine?) { private fun assignExpression(assign: AsmAssignment, scope: IPtSubroutine?) {
when(val value = assign.source.expression!!) { when(val value = assign.source.expression!!) {
is PtAddressOf -> { 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 arrayDt = value.identifier.type
val sourceName = val sourceName =
if(value.isMsbForSplitArray) if(value.isMsbForSplitArray)
@@ -1567,8 +1569,8 @@ internal class AssignmentAsmGen(
if(ptrVar!=null && asmgen.isZpVar(ptrVar)) { if(ptrVar!=null && asmgen.isZpVar(ptrVar)) {
assignExpressionToRegister(value, RegisterOrPair.A, false) assignExpressionToRegister(value, RegisterOrPair.A, false)
val pointername = asmgen.asmVariableName(ptrVar) val pointername = asmgen.asmVariableName(ptrVar)
if (constOffset != null && constOffset < 256) { if (constOffset != null) {
// we have value + @(zpptr + 255), or value - @(zpptr+255) // we have value + @(zpptr + 255), or value - @(zpptr+255). the offset is always <256.
asmgen.out(" ldy #$constOffset") asmgen.out(" ldy #$constOffset")
if (operator == "+") if (operator == "+")
asmgen.out(" clc | adc ($pointername),y") asmgen.out(" clc | adc ($pointername),y")
@@ -2613,13 +2615,23 @@ $endLabel""")
if (arrayDt.isUnsignedWord) { if (arrayDt.isUnsignedWord) {
require(!msb) require(!msb)
assignVariableToRegister(sourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position) assignVariableToRegister(sourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
if(constIndex>0) if(constIndex in 1..255)
asmgen.out(""" asmgen.out("""
clc clc
adc #$constIndex adc #$constIndex
bcc + bcc +
iny iny
+""") +""")
else if(constIndex>=256) {
asmgen.out("""
clc
adc #<$constIndex
pha
tya
adc #>$constIndex
tay
pla""")
}
} }
else { else {
if(constIndex>0) { if(constIndex>0) {
@@ -2637,6 +2649,23 @@ $endLabel""")
assignVariableToRegister(sourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position) assignVariableToRegister(sourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
asmgen.saveRegisterStack(CpuRegister.A, false) asmgen.saveRegisterStack(CpuRegister.A, false)
asmgen.saveRegisterStack(CpuRegister.Y, false) asmgen.saveRegisterStack(CpuRegister.Y, false)
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) assignExpressionToVariable(arrayIndexExpr, "P8ZP_SCRATCH_REG", DataType.UBYTE)
asmgen.restoreRegisterStack(CpuRegister.Y, false) asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.restoreRegisterStack(CpuRegister.A, false) asmgen.restoreRegisterStack(CpuRegister.A, false)
@@ -2647,6 +2676,7 @@ $endLabel""")
iny iny
+""") +""")
} }
}
else { else {
assignExpressionToRegister(arrayIndexExpr, RegisterOrPair.A, false) assignExpressionToRegister(arrayIndexExpr, RegisterOrPair.A, false)
asmgen.out(""" asmgen.out("""

View File

@@ -1,9 +1,6 @@
package prog8.codegen.intermediate package prog8.codegen.intermediate
import prog8.code.StExtSub import prog8.code.*
import prog8.code.StNode
import prog8.code.StNodeType
import prog8.code.StSub
import prog8.code.ast.* import prog8.code.ast.*
import prog8.code.core.AssemblyError import prog8.code.core.AssemblyError
import prog8.code.core.BaseDataType import prog8.code.core.BaseDataType
@@ -184,12 +181,19 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
if(expr.isFromArrayElement) { if(expr.isFromArrayElement) {
val indexTr = translateExpression(expr.arrayIndexExpr!!) val indexTr = translateExpression(expr.arrayIndexExpr!!)
addToResult(result, indexTr, indexTr.resultReg, -1) addToResult(result, indexTr, indexTr.resultReg, -1)
val indexWordReg = codeGen.registers.next(IRDataType.WORD) val indexWordReg = if(indexTr.dt==IRDataType.BYTE) {
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=indexWordReg, reg2=indexTr.resultReg), null) 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) { if(expr.identifier.type.isUnsignedWord) {
require(!expr.isMsbForSplitArray) require(!expr.isMsbForSplitArray)
result += IRCodeChunk(null, null).also { 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) it += IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1=resultRegister, reg2=indexWordReg)
} }
} else { } else {

View File

@@ -429,5 +429,24 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
} }
return noModifications 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
}
} }

View File

@@ -6,7 +6,6 @@ import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.types.instanceOf import io.kotest.matchers.types.instanceOf
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.BinaryExpression import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteral import prog8.ast.expressions.NumericLiteral
@@ -261,16 +260,16 @@ main {
} }
test("const address-of memory mapped arrays") { test("const address-of memory mapped arrays") {
val src=""" val src= """
main { main {
sub start() { sub start() {
&uword[30] @nosplit wb = ${'$'}2000 &uword[30] @nosplit wb = $2000
&uword[100] @nosplit array1 = ${'$'}9e00 &uword[100] @nosplit array1 = $9e00
&uword[30] @nosplit array2 = &array1[len(wb)] &uword[30] @nosplit array2 = &array1[len(wb)]
cx16.r0 = &array1 ; ${'$'}9e00 cx16.r0 = &array1 ; $9e00
cx16.r1 = &array1[len(wb)] ; ${'$'}9e3c cx16.r1 = &array1[len(wb)] ; $9e3c
cx16.r2 = &array2 ; ${'$'}9e3c cx16.r2 = &array2 ; $9e3c
} }
}""" }"""
val result = compileText(Cx16Target(), false, src, outputDir, writeAssembly = false)!! val result = compileText(Cx16Target(), false, src, outputDir, writeAssembly = false)!!
@@ -307,10 +306,10 @@ main {
} }
test("address of a const uword pointer array expression") { test("address of a const uword pointer array expression") {
val src=""" val src= """
main { main {
sub start() { sub start() {
const uword buffer = ${'$'}2000 const uword buffer = 2000
uword @shared addr = &buffer[2] uword @shared addr = &buffer[2]
const ubyte width = 100 const ubyte width = 100
@@ -323,10 +322,9 @@ main {
val st = result.compilerAst.entrypoint.statements val st = result.compilerAst.entrypoint.statements
st.size shouldBe 11 st.size shouldBe 11
val assignAddr = (st[2] as Assignment).value val assignAddr = (st[2] as Assignment).value
(assignAddr as NumericLiteral).number shouldBe 8194.0 (assignAddr as NumericLiteral).number shouldBe 2002.0
val assignAddr2 = ((st[9] as Assignment).value as AddressOf) val assignAddr2 = (st[9] as Assignment).value as BinaryExpression
assignAddr2.identifier.nameInSource shouldBe listOf("buffer") assignAddr2.operator shouldBe "+"
assignAddr2.arrayIndex!!.indexExpr shouldBe instanceOf<BinaryExpression>()
} }
test("out of range const byte and word give correct error") { test("out of range const byte and word give correct error") {

View File

@@ -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].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" ((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
}
} }
}) })

View File

@@ -532,4 +532,22 @@ main {
instructions[10].type shouldBe IRDataType.WORD 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)
}
}) })

View File

@@ -31,6 +31,7 @@ Future Things and Ideas
IR/VM IR/VM
----- -----
- fix bug: label not found error (see unit test "typed address-of a const pointer with non-const array indexing")
- getting it in shape for code generation...: the IR file should be able to encode every detail about a prog8 program (the VM doesn't have to actually be able to run all of it though!) - getting it in shape for code generation...: the IR file should be able to encode every detail about a prog8 program (the VM doesn't have to actually be able to run all of it though!)
- fix call() return value handling (... what's wrong with it again?) - fix call() return value handling (... what's wrong with it again?)
- encode asmsub/extsub clobber info in the call , or maybe include these definitions in the p8ir file itself too. (return registers are already encoded in the CALL instruction) - encode asmsub/extsub clobber info in the call , or maybe include these definitions in the p8ir file itself too. (return registers are already encoded in the CALL instruction)

View File

@@ -2,26 +2,7 @@
main { main {
sub start() { sub start() {
word[5] xpos const uword cbuffer = $2000
cx16.r1 = &cbuffer[cx16.r0] ; ERROR
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
xpos[4] += $7000
xpos[4] += $7000 as word
xpos[4] = xpos[4] + $7000
xpos[4] = xpos[4] + $7000 as word
} }
} }

View File

@@ -149,7 +149,7 @@ class VmProgramLoader {
// placeholder is not a variable, so it must be a label of a code chunk instead // 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 target: IRCodeChunk? = chunks.firstOrNull { it.label==label }
if(target==null) 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) else if(instr.opcode in OpcodesThatBranch)
chunk.instructions[line] = instr.copy(branchTarget = target, address = null) chunk.instructions[line] = instr.copy(branchTarget = target, address = null)
else { else {