support word size indexing on typed pointers

This commit is contained in:
Irmen de Jong
2025-08-25 22:18:47 +02:00
parent 3cdb25ce8e
commit 0f79351de9
9 changed files with 199 additions and 291 deletions

View File

@@ -719,6 +719,8 @@ class AsmGen6502Internal (
val reg = register.toString().lowercase()
val indexnum = expr.index.asConstInteger()
if (indexnum != null) {
if(indexnum > 255)
throw AssemblyError("array index $indexnum is larger than a byte ${expr.position}")
val indexValue = if(expr.splitWords)
indexnum
else
@@ -727,6 +729,9 @@ class AsmGen6502Internal (
return
}
if(!expr.index.type.isByte)
throw AssemblyError("array index $indexnum is larger than a byte ${expr.position}")
if(expr.splitWords) {
assignExpressionToRegister(expr.index, RegisterOrPair.fromCpuRegister(register))
return
@@ -806,15 +811,15 @@ class AsmGen6502Internal (
when(target.kind) {
TargetStorageKind.VARIABLE -> {
if (isTargetCpu(CpuType.CPU6502))
out("lda #0 | sta ${target.asmVarname}")
out(" lda #0 | sta ${target.asmVarname}")
else
out("stz ${target.asmVarname}")
out(" stz ${target.asmVarname}")
}
TargetStorageKind.MEMORY -> {
val address = target.memory!!.address.asConstInteger()
if(address!=null) {
if (isTargetCpu(CpuType.CPU6502))
out("lda #0 | sta ${address.toHex()}")
out(" lda #0 | sta ${address.toHex()}")
else
out(" stz ${address.toHex()}")
return

View File

@@ -3606,7 +3606,9 @@ $endLabel""")
if(indexVar!=null) {
asmgen.out(" ldy ${asmgen.asmVariableName(indexVar)} | sta ${target.asmVarname},y")
} else {
require(target.array.index.type.isByteOrBool)
require(target.array.index.type.isByte) {
"wot"
}
asmgen.saveRegisterStack(register, false)
asmgen.assignExpressionToRegister(target.array.index, RegisterOrPair.Y)
asmgen.out(" pla | sta ${target.asmVarname},y")

View File

@@ -1594,8 +1594,15 @@ internal class AstChecker(private val program: Program,
}
else {
if (leftDt.isBool || rightDt.isBool) {
if(expr.operator!="==" && expr.operator!="!=")
errors.err("operator requires numeric operands", expr.right.position)
if(expr.operator!="==" && expr.operator!="!=") {
val msg = when(expr.operator) {
"^" -> "operator requires numeric operands, did you mean logical 'xor'?"
"&" -> "operator requires numeric operands, did you mean logical 'and'?"
"|" -> "operator requires numeric operands, did you mean logical 'or'?"
else -> "operator requires numeric operands"
}
errors.err(msg, expr.right.position)
}
}
}
}

View File

@@ -240,6 +240,7 @@ _after:
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
// replace pointervar[word] by @(pointervar+word) to avoid the
// "array indexing is limited to byte size 0..255" error for pointervariables.
// (uses pokew or pokef if the ointer is a word or float pointer).
if(arrayIndexedExpression.pointerderef!=null) {
return noModifications
@@ -247,7 +248,7 @@ _after:
val indexExpr = arrayIndexedExpression.indexer.indexExpr
val arrayVar = arrayIndexedExpression.plainarrayvar!!.targetVarDecl()
if(arrayVar!=null && (arrayVar.datatype.isUnsignedWord || (arrayVar.datatype.isPointer && arrayVar.datatype.sub==BaseDataType.UBYTE))) {
if(arrayVar!=null && (arrayVar.datatype.isUnsignedWord || arrayVar.datatype.isPointer)) {
val wordIndex = TypecastExpression(indexExpr, DataType.UWORD, true, indexExpr.position)
val address = BinaryExpression(
arrayIndexedExpression.plainarrayvar!!.copy(),
@@ -255,22 +256,58 @@ _after:
wordIndex,
arrayIndexedExpression.position
)
if(arrayVar.datatype.isUnsignedWord || arrayVar.datatype.sub?.isByte==true) {
return if (parent is AssignTarget) {
// assignment to array
val memwrite = DirectMemoryWrite(address, arrayIndexedExpression.position)
val newtarget = AssignTarget(
null,
null,
memwrite,
null,
false,
position = arrayIndexedExpression.position
)
val newtarget = AssignTarget(null, null, memwrite, null, false, position = arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent))
} else {
// read from array
val memread = DirectMemoryRead(address, arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memread, parent))
val replacement = if(arrayVar.datatype.sub?.isSigned==true)
TypecastExpression(memread, DataType.BYTE, true, memread.position)
else
memread
listOf(IAstModification.ReplaceNode(arrayIndexedExpression, replacement, parent))
}
} else if(arrayVar.datatype.sub?.isWord==true) {
// use peekw/pokew
if(parent is AssignTarget) {
val assignment = parent.parent as Assignment
val args = mutableListOf(address, assignment.value)
val poke = FunctionCallStatement(IdentifierReference(listOf("pokew"), arrayIndexedExpression.position), args, false, arrayIndexedExpression.position)
return listOf(IAstModification.ReplaceNode(assignment, poke, assignment.parent))
} else {
val peek = FunctionCallExpression(IdentifierReference(listOf("peekw"), arrayIndexedExpression.position), mutableListOf(address), arrayIndexedExpression.position)
val replacement = if(arrayVar.datatype.sub?.isSigned==true)
TypecastExpression(peek, DataType.WORD, true, peek.position)
else
peek
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, replacement, parent))
}
} else if(arrayVar.datatype.sub==BaseDataType.BOOL) {
// use peekbool/pokebool
if(parent is AssignTarget) {
val assignment = parent.parent as Assignment
val args = mutableListOf(address, assignment.value)
val poke = FunctionCallStatement(IdentifierReference(listOf("pokebool"), arrayIndexedExpression.position), args, false, arrayIndexedExpression.position)
return listOf(IAstModification.ReplaceNode(assignment, poke, assignment.parent))
} else {
val peek = FunctionCallExpression(IdentifierReference(listOf("peekbool"), arrayIndexedExpression.position), mutableListOf(address), arrayIndexedExpression.position)
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, peek, parent))
}
} else if(arrayVar.datatype.sub==BaseDataType.FLOAT) {
// use peekf/pokef
if(parent is AssignTarget) {
val assignment = parent.parent as Assignment
val args = mutableListOf(address, assignment.value)
val poke = FunctionCallStatement(IdentifierReference(listOf("pokef"), arrayIndexedExpression.position), args, false, arrayIndexedExpression.position)
return listOf(IAstModification.ReplaceNode(assignment, poke, assignment.parent))
} else {
val peek = FunctionCallExpression(IdentifierReference(listOf("peekf"), arrayIndexedExpression.position), mutableListOf(address), arrayIndexedExpression.position)
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, peek, parent))
}
}
} else if(arrayVar!=null && (arrayVar.type==VarDeclType.MEMORY || arrayVar.datatype.isString || arrayVar.datatype.isPointer || arrayVar.datatype.isArray)) {
return noModifications

View File

@@ -840,7 +840,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
// don't multiply simply shift
offset = PtBinaryExpression("<<", DataType.UWORD, expr.position)
offset.add(transformExpression(expr.right))
offset.add(PtNumber(BaseDataType.UWORD, log2(structSize.toDouble()), expr.position))
offset.add(PtNumber(BaseDataType.UBYTE, log2(structSize.toDouble()), expr.position))
}
else {
offset = PtBinaryExpression("*", DataType.UWORD, expr.position)

View File

@@ -332,11 +332,11 @@ main {
st.size shouldBe 27
val a_zz = (st[20] as Assignment).value
a_zz shouldBe instanceOf<ArrayIndexedExpression>()
a_zz shouldBe instanceOf<FunctionCallExpression>()
val a_fl = (st[21] as Assignment).value
a_fl shouldBe instanceOf<ArrayIndexedExpression>()
a_fl shouldBe instanceOf<FunctionCallExpression>()
val a_bb = (st[22] as Assignment).value
a_bb shouldBe instanceOf<ArrayIndexedExpression>()
a_bb shouldBe instanceOf<DirectMemoryRead>()
val a_r0 = (st[23] as Assignment).value
a_r0 shouldBe instanceOf<DirectMemoryRead>()
val a_r1 = (st[24] as Assignment).value
@@ -885,28 +885,6 @@ main {
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
}
test("uword as pointer versus pointer to uword difference") {
val src="""
main {
sub start() {
uword @shared ptr1
^^uword @shared ptr2
ptr1[2] = 1
ptr2[2] = 1
}
}"""
compileText(C64Target(), false, src, outputDir) shouldNotBe null
val result = compileText(VMTarget(), false, src, outputDir)!!
val st = result.codegenAst!!.entrypoint()!!.children
st.size shouldBe 8
val a1 = st[5] as PtAssignment
val a2 = st[6] as PtAssignment
a1.target.memory shouldNotBe null
a2.target.array shouldNotBe null
}
test("array indexing on non pointer fields give correct error messages") {
val src="""
main {
@@ -1021,15 +999,15 @@ main {
val dr7 = (st[17] as Assignment).target.pointerDereference!!
val dr8 = (st[18] as Assignment).target.pointerDereference!!
val dr9 = (st[19] as Assignment).value as PtrDereference
val dr9 = (st[19] as Assignment).value as FunctionCallExpression
val dr10 = (st[20] as Assignment).value as PtrDereference
val dr11 = (st[21] as Assignment).target.pointerDereference!!
val dr12 = (st[22] as Assignment).target.pointerDereference!!
(st[22] as FunctionCallStatement).target.nameInSource shouldBe listOf("pokew")
val dr13 = (st[23] as Assignment).value as PtrDereference
val dr14 = (st[24] as Assignment).value as PtrDereference
((st[24] as Assignment).value as FunctionCallExpression).target.nameInSource shouldBe listOf("peekf")
val dr15 = (st[25] as Assignment).target.pointerDereference!!
val dr16 = (st[26] as Assignment).target.pointerDereference!!
(st[26] as FunctionCallStatement).target.nameInSource shouldBe listOf("pokef")
dr0.chain shouldBe listOf("l1", "s")
dr0.derefLast shouldBe true
@@ -1051,23 +1029,16 @@ main {
dr8.chain shouldBe listOf("l1", "s")
dr8.derefLast shouldBe true
dr9.chain shouldBe listOf("wptr")
dr9.derefLast shouldBe true
dr9.target.nameInSource shouldBe listOf("peekw")
dr10.chain shouldBe listOf("wptr")
dr10.derefLast shouldBe true
dr11.chain shouldBe listOf("wptr")
dr11.derefLast shouldBe true
dr12.chain shouldBe listOf("wptr")
dr12.derefLast shouldBe true
dr13.chain shouldBe listOf("fptr")
dr13.derefLast shouldBe true
dr14.chain shouldBe listOf("fptr")
dr14.derefLast shouldBe true
dr15.chain shouldBe listOf("fptr")
dr15.derefLast shouldBe true
dr16.chain shouldBe listOf("fptr")
dr16.derefLast shouldBe true
}
test("global and local pointer vars") {
@@ -1721,6 +1692,84 @@ main {
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
}
test("array indexing on a pointer with a word size index works") {
val src="""
%import floats
main {
sub start() {
^^ubyte @shared ptr1 = $4000
^^uword @shared ptr2 = $4000
^^float @shared ptr3 = $4000
^^bool @shared ptr4 = $4000
uword @shared untyped = $4000
float @shared fl
bool @shared bb, bb2
untyped[$1000] = 0
ptr1[$1000] = 0
ptr2[$1000] = 0
ptr3[$1000] = 0
ptr4[$1000] = false
untyped[$1000] = 99
ptr1[$1000] = 99
ptr2[$1000] = 99
ptr3[$1000] = 99
ptr4[$1000] = true
untyped[$1000] = cx16.r0L
ptr1[$1000] = cx16.r0L
ptr2[$1000] = cx16.r0L
ptr3[$1000] = fl
ptr4[$1000] = bb
untyped[$1000] = cx16.r0L+1
ptr1[$1000] = cx16.r0L+1
ptr2[$1000] = cx16.r0L+1
ptr3[$1000] = fl+1.1
ptr4[$1000] = bb xor bb2
untyped[$1000 + cx16.r0] = 0
ptr1[$1000 + cx16.r0] = 0
ptr2[$1000 + cx16.r0] = 0
ptr3[$1000 + cx16.r0] = 0
ptr4[$1000 + cx16.r0] = false
untyped[$1000 + cx16.r0] = 99
ptr1[$1000 + cx16.r0] = 99
ptr2[$1000 + cx16.r0] = 99
ptr3[$1000 + cx16.r0] = 99
ptr4[$1000 + cx16.r0] = true
untyped[$1000 + cx16.r0] = cx16.r0L
ptr1[$1000 + cx16.r0] = cx16.r0L
ptr2[$1000 + cx16.r0] = cx16.r0L
ptr3[$1000 + cx16.r0] = fl
ptr4[$1000 + cx16.r0] = bb
untyped[$1000 + cx16.r0] = cx16.r0L+1
ptr1[$1000 + cx16.r0] = cx16.r0L+1
ptr2[$1000 + cx16.r0] = cx16.r0L+1
ptr3[$1000 + cx16.r0] = fl+1.1
ptr4[$1000 + cx16.r0] = bb xor bb2
cx16.r0L = untyped[$1000]
cx16.r1L = ptr1[$1000]
cx16.r2 = ptr2[$1000]
fl = ptr3[$1000]
bb = ptr4[$1000]
cx16.r0L = untyped[cx16.r0]
cx16.r1L = ptr1[cx16.r0]
cx16.r2 = ptr2[cx16.r0]
fl = ptr3[cx16.r0]
bb = ptr4[cx16.r0]
cx16.r0L = untyped[cx16.r0+1]
cx16.r1L = ptr1[cx16.r0+1]
cx16.r2 = ptr2[cx16.r0+1]
fl = ptr3[cx16.r0+1]
bb = ptr4[cx16.r0+1]
}
}"""
compileText(VMTarget(), false, src, outputDir) shouldNotBe null
compileText(C64Target(), false, src, outputDir) shouldNotBe null
}
test("correct type of address of split and nosplit arrays") {
val src="""
main {

View File

@@ -2,7 +2,6 @@ TODO
====
c64 (and cx16 as well) fileselector has 2 issues when using ^^ubyte (code size and compiler error about return statement)
cx16 life code size regression when using ^^ubyte
pointer arithmetic precedence issue?:
@@ -33,3 +32,5 @@ STRUCTS and TYPED POINTERS (6502 codegen specific)
- optimize the float copying in assignIndexedPointer() (also word?)
- implement some more struct instance assignments (via memcopy) in CodeDesugarer (see the TODO) (add to documentation as well, paragraph 'Structs')
- try to optimize pointer arithmetic used in peek/poke a bit more so the routines in sorting module can use typed pointers without increasing code size
- should @(wordpointer) be equivalent to wordpointer^^ (that would require a LOT of code rewrite that now knows that @() is strictly byte based) ?
or do an implicit cast @(wpointer as ubyte^^) ? And/or add a warning about that?

View File

@@ -1,4 +1,7 @@
; conway's game of life.
; Conway's game of life.
; the world is represented by a matrix of bytes (the cells) that can be 1 (alive) or 0 (dead)
; numeric byte is used because we need to count the number of neighbors and that would require casts if we used booleans.
; (but you totally could use booleans)
%import math
%import textio
@@ -7,9 +10,9 @@ main {
const ubyte WIDTH = 80
const ubyte HEIGHT = 60-4
const uword STRIDE = $0002+WIDTH
uword world1 = memory("world1", (WIDTH+2)*(HEIGHT+2), 0)
uword world2 = memory("world2", (WIDTH+2)*(HEIGHT+2), 0)
uword @requirezp active_world = world1
^^ubyte world1 = memory("world1", (WIDTH+2)*(HEIGHT+2), 0)
^^ubyte world2 = memory("world2", (WIDTH+2)*(HEIGHT+2), 0)
^^ubyte @requirezp active_world = world1
sub start() {
; cx16.set_screen_mode(3)
@@ -91,7 +94,7 @@ main {
const ubyte DYOFFSET = 2
ubyte[2] cell_chars = [sc:' ', sc:'●']
uword @requirezp new_world = world1
^^ubyte @requirezp new_world = world1
if active_world == world1
new_world = world2
@@ -102,8 +105,8 @@ main {
; It's more readable to use active_world[offset] etc, but offset is a word value, and this produces
; inefficient assembly code because we can't use a register indexed mode in this case. Costly inside a loop.
uword @requirezp new_world_ptr = new_world + STRIDE+1-DXOFFSET
uword @requirezp active_world_ptr = active_world + STRIDE+1-DXOFFSET
^^ubyte @requirezp new_world_ptr = new_world + STRIDE+1-DXOFFSET
^^ubyte @requirezp active_world_ptr = active_world + STRIDE+1-DXOFFSET
ubyte x
ubyte y
@@ -114,7 +117,7 @@ main {
for x in DXOFFSET to WIDTH+DXOFFSET-1 {
; count the living neighbors
ubyte cell = @(active_world_ptr + x)
uword @requirezp ptr = active_world_ptr + x - STRIDE - 1
^^ubyte @requirezp ptr = active_world_ptr + x - STRIDE - 1
ubyte neighbors = @(ptr) + @(ptr+1) + @(ptr+2) +
@(ptr+STRIDE) + cell + @(ptr+STRIDE+2) +
@(ptr+STRIDE*2) + @(ptr+STRIDE*2+1) + @(ptr+STRIDE*2+2)

View File

@@ -1,228 +1,32 @@
%option no_sysinit
%zeropage kernalsafe
%import textio
%import compression
%import math
%import sorting
%import strings
%import diskio
%import floats
main {
struct List {
^^uword s
ubyte n
^^List next
}
sub start() {
; test_compression()
; test_sorting1()
; test_sorting2()
; test_math()
; test_syslib()
; test_strings()
; test_conv()
test_diskio()
; test_textio()
ubyte[10] array
uword @shared wordptr
^^bool @shared boolptr
^^float @shared floatptr
^^byte @shared byteptr
^^ubyte @shared ubyteptr
^^List @shared listptr
^^List @shared listptr2
repeat {}
}
bool @shared zz
float @shared fl
byte @shared bb
sub test_diskio() {
txt.print("--diskio--\n")
sys.memset(target, len(target), 0)
diskio.delete("derp.bin")
void diskio.f_open_w("derp.bin")
repeat 12
void diskio.f_write("derpderp123", 11)
diskio.f_close_w()
void diskio.f_open("derp.bin")
diskio.f_read(target, 60)
txt.print(target)
txt.nl()
}
ubyte[100] target
sub test_conv() {
txt.print("--conv--\n")
txt.print_b(-111)
txt.spc()
txt.print_ub(222)
txt.spc()
txt.print_uw(22222)
txt.spc()
txt.print_w(-22222)
txt.nl()
txt.print_ubbin(222, true)
txt.spc()
txt.print_ubhex(222, true)
txt.spc()
txt.print_uwbin(2222, true)
txt.spc()
txt.print_uwhex(2222, true)
txt.nl()
txt.print_ub0(1)
txt.spc()
txt.print_uw0(123)
txt.nl()
}
sub test_strings() {
txt.print("--strings--\n")
ubyte idx
bool found
idx, found = strings.rfind(source, '1')
txt.print_ub(idx)
txt.nl()
}
sub test_textio() {
txt.print("--textio--\n")
txt.print("enter some input: ")
void txt.input_chars(&target)
txt.print(target)
txt.nl()
}
sub test_syslib() {
txt.print("--syslib--\n")
sys.internal_stringcopy(source, target)
txt.print(target)
txt.nl()
sys.memset(target, sizeof(target), 0)
txt.print(target)
txt.nl()
sys.memcopy(source, target, len(source))
txt.print(target)
txt.nl()
sys.memsetw(&target as ^^uword, 20, $5051)
txt.print(target)
txt.nl()
txt.print_b(sys.memcmp(source, target, len(source)))
txt.nl()
}
sub test_sorting1() {
txt.print("--sorting (shell)--\n")
ubyte[] bytes1 = [77,33,44,99,11,55]
ubyte[] bytes2 = [77,33,44,99,11,55]
uword[] @nosplit values1 = [1,2,3,4,5,6]
uword[] @nosplit words1 = [777,333,444,999,111,555]
uword[] @nosplit words2 = [777,333,444,999,111,555]
uword[] @nosplit values2 = [1,2,3,4,5,6]
sorting.shellsort_ub(&bytes1, len(bytes1))
sorting.shellsort_by_ub(&bytes2, &values1, len(bytes2))
sorting.shellsort_uw(&words1, len(words1))
sorting.shellsort_by_uw(&words2, &values2, len(words2))
for cx16.r0L in bytes1 {
txt.print_ub(cx16.r0L)
txt.spc()
}
txt.nl()
for cx16.r0L in bytes2 {
txt.print_ub(cx16.r0L)
txt.spc()
}
txt.nl()
for cx16.r0 in values1 {
txt.print_uw(cx16.r0)
txt.spc()
}
txt.nl()
for cx16.r0 in words1 {
txt.print_uw(cx16.r0)
txt.spc()
}
txt.nl()
for cx16.r0 in words2 {
txt.print_uw(cx16.r0)
txt.spc()
}
txt.nl()
for cx16.r0 in values2 {
txt.print_uw(cx16.r0)
txt.spc()
}
txt.nl()
}
sub test_sorting2() {
txt.print("--sorting (gnome)--\n")
ubyte[] bytes1 = [77,33,44,99,11,55]
ubyte[] bytes2 = [77,33,44,99,11,55]
uword[] @nosplit values1 = [1,2,3,4,5,6]
uword[] @nosplit words1 = [777,333,444,999,111,555]
uword[] @nosplit words2 = [777,333,444,999,111,555]
uword[] @nosplit values2 = [1,2,3,4,5,6]
sorting.gnomesort_ub(&bytes1, len(bytes1))
sorting.gnomesort_by_ub(&bytes2, &values1, len(bytes2))
sorting.gnomesort_uw(&words1, len(words1))
sorting.gnomesort_by_uw(&words2, &values2, len(words2))
for cx16.r0L in bytes1 {
txt.print_ub(cx16.r0L)
txt.spc()
}
txt.nl()
for cx16.r0L in bytes2 {
txt.print_ub(cx16.r0L)
txt.spc()
}
txt.nl()
for cx16.r0 in values1 {
txt.print_uw(cx16.r0)
txt.spc()
}
txt.nl()
for cx16.r0 in words1 {
txt.print_uw(cx16.r0)
txt.spc()
}
txt.nl()
for cx16.r0 in words2 {
txt.print_uw(cx16.r0)
txt.spc()
}
txt.nl()
for cx16.r0 in values2 {
txt.print_uw(cx16.r0)
txt.spc()
}
txt.nl()
}
sub test_math() {
txt.print("--math--\n")
txt.print("expected 15567: ")
txt.print_uw(math.crc16(source, len(source)))
txt.print("\nexpected 8747,54089: ")
math.crc32(source, len(source))
txt.print_uw(cx16.r14)
txt.chrout(',')
txt.print_uw(cx16.r15)
txt.nl()
}
str source = petscii:"Lorem ipsuuuuuuuuuuuum dollllllllllllllloooooooor sit ametttttttttttttttt, cccccccccccccccconsecteeeeetuuuuuur aaaaaaaaa111111222222333333444444"
sub test_compression() {
txt.print("--compression--\n")
ubyte[256] compressed
ubyte[256] decompressed
txt.print_uw(len(source))
txt.nl()
uword size = compression.encode_rle(source, len(source), compressed, true)
txt.print_uw(size)
txt.nl()
size = compression.decode_rle(compressed, decompressed, sizeof(decompressed))
txt.print_uw(size)
txt.nl()
txt.print(source)
txt.nl()
txt.print(decompressed)
txt.nl()
zz = boolptr[999]
fl = floatptr[999]
bb = byteptr[999]
cx16.r0L = ubyteptr[999]
cx16.r1L = wordptr[999]
cx16.r2L = array[9]
}
}