allow array syntax on pointers

This commit is contained in:
Irmen de Jong
2025-04-27 22:00:54 +02:00
parent b89bbb9281
commit 2661d3c489
9 changed files with 122 additions and 31 deletions

View File

@@ -56,9 +56,9 @@ val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataT
val BaseDataType.isPointer get() = this == BaseDataType.POINTER
val BaseDataType.isPointerArray get() = this == BaseDataType.ARRAY_POINTER
val BaseDataType.isSplitWordArray get() = this == BaseDataType.ARRAY_SPLITW
val BaseDataType.isIterable get() = this in arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER)
val BaseDataType.isPassByRef get() = this.isIterable
val BaseDataType.isPassByValue get() = !this.isIterable
val BaseDataType.isIterable get() = this in arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER, BaseDataType.POINTER)
val BaseDataType.isPassByRef get() = this.isIterable && !this.isPointer
val BaseDataType.isPassByValue get() = !this.isIterable || this.isPointer
interface ISubType {

View File

@@ -382,6 +382,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val result = mutableListOf<IRCodeChunkBase>()
val arrayVarSymbol = arrayIx.variable.name
var resultRegister = -1
val isPointer = arrayIx.variable.type.isPointer
if(arrayIx.splitWords) {
require(vmDt==IRDataType.WORD)
@@ -409,8 +410,27 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
var resultFpRegister = -1
if(arrayIx.index is PtNumber) {
val memOffset = ((arrayIx.index as PtNumber).number.toInt() * eltSize)
fun getByNumber(index: Int) {
val memOffset = index * eltSize
if(isPointer) {
// indexing a pointer
val pointerTr = translateExpression(arrayIx.variable)
result += pointerTr.chunks
val pointerReg = pointerTr.resultReg
if(memOffset>0)
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1=pointerReg, immediate = memOffset), null)
if(vmDt==IRDataType.FLOAT) {
resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1=resultFpRegister, reg1=pointerReg), null)
}
else {
resultRegister = codeGen.registers.next(vmDt)
addInstr(result, IRInstruction(Opcode.LOADI, vmDt, reg1=resultRegister, reg2=pointerReg), null)
}
} else {
// indexing an array or string type
if(vmDt==IRDataType.FLOAT) {
resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1=resultFpRegister, labelSymbol = arrayVarSymbol, symbolOffset = memOffset), null)
@@ -419,23 +439,54 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
resultRegister = codeGen.registers.next(vmDt)
addInstr(result, IRInstruction(Opcode.LOADM, vmDt, reg1=resultRegister, labelSymbol = arrayVarSymbol, symbolOffset = memOffset), null)
}
} else {
val tr = translateExpression(arrayIx.index)
addToResult(result, tr, tr.resultReg, -1)
}
}
fun getByExpression(index: PtExpression) {
val indexByteTr = translateExpression(index)
addToResult(result, indexByteTr, indexByteTr.resultReg, -1)
if(isPointer) {
// indexing on a pointer
val indexWordReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=indexWordReg, reg2=indexByteTr.resultReg), null)
if(eltSize>1)
result += codeGen.multiplyByConst(IRDataType.BYTE, tr.resultReg, eltSize)
result += codeGen.multiplyByConst(IRDataType.WORD, indexWordReg, eltSize)
val pointerTr = translateExpression(arrayIx.variable)
result += pointerTr.chunks
val pointerReg = pointerTr.resultReg
addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1=pointerReg, reg2=indexWordReg), null)
if(vmDt==IRDataType.FLOAT) {
resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADX, IRDataType.FLOAT, fpReg1 = resultFpRegister, reg1=tr.resultReg, labelSymbol = arrayVarSymbol), null)
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1=resultFpRegister, reg1=pointerReg), null)
}
else {
resultRegister = codeGen.registers.next(vmDt)
addInstr(result, IRInstruction(Opcode.LOADX, vmDt, reg1=resultRegister, reg2=tr.resultReg, labelSymbol = arrayVarSymbol), null)
addInstr(result, IRInstruction(Opcode.LOADI, vmDt, reg1=resultRegister, reg2=pointerReg), null)
}
} else {
// indexing an array or string type
if(eltSize>1)
result += codeGen.multiplyByConst(IRDataType.BYTE, indexByteTr.resultReg, eltSize)
if(vmDt==IRDataType.FLOAT) {
resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADX, IRDataType.FLOAT, fpReg1 = resultFpRegister, reg1=indexByteTr.resultReg, labelSymbol = arrayVarSymbol), null)
}
else {
resultRegister = codeGen.registers.next(vmDt)
addInstr(result, IRInstruction(Opcode.LOADX, vmDt, reg1=resultRegister, reg2=indexByteTr.resultReg, labelSymbol = arrayVarSymbol), null)
}
}
}
if(arrayIx.index is PtNumber)
getByNumber((arrayIx.index as PtNumber).number.toInt())
else
getByExpression(arrayIx.index)
return ExpressionCodeResult(result, vmDt, resultRegister, resultFpRegister)
}
private fun translate(expr: PtPrefix): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val tr = translateExpression(expr.value)

View File

@@ -423,6 +423,17 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
if(arrayIndexedExpression.indexer.constIndex()==0) {
if(arrayIndexedExpression.arrayvar.inferType(program).isPointer) {
// pointer[0] --> pointer^^
val deref = PtrDereference(arrayIndexedExpression.arrayvar, emptyList(), null, arrayIndexedExpression.position)
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, deref, parent))
}
}
return noModifications
}
private fun applyAbsorptionLaws(expr: BinaryExpression): Expression? {
// NOTE: only when the terms are not function calls!!!
if(expr.left is IFunctionCall || expr.right is IFunctionCall)

View File

@@ -119,6 +119,7 @@ verafx {
; Returns the 16 bits unsigned result of R0*R1 in AY.
; Note: only the lower 16 bits! (the upper 16 bits are not valid for unsigned word multiplications, only for signed)
; Verafx doesn't support unsigned values like this for full 32 bit result.
; Note: clobbers VRAM $1f9bc - $1f9bf (inclusive)
%asm {{
lda cx16.r0
sta P8ZP_SCRATCH_W1
@@ -136,6 +137,7 @@ verafx {
asmsub muls(word value1 @R0, word value2 @R1) clobbers(X) -> word @AY, word @R0 {
; Returns the 32 bits signed result in AY and R0 (lower word, upper word).
; Vera Fx multiplication support only works on signed values!
; Note: clobbers VRAM $1f9bc - $1f9bf (inclusive)
%asm {{
lda #(2 << 1)
sta cx16.VERA_CTRL ; $9F25

View File

@@ -1341,7 +1341,7 @@ internal class AstChecker(private val program: Program,
override fun visit(typecast: TypecastExpression) {
checkLongType(typecast)
if(typecast.type.isIterable)
if(typecast.type.isPassByRef)
errors.err("cannot type cast to string or array type", typecast.position)
if(!typecast.expression.inferType(program).isKnown)
@@ -1796,6 +1796,9 @@ internal class AstChecker(private val program: Program,
val memsize = struct.memsize(program.memsizer)
if(memsize>256)
errors.err("struct contains too many fields, max struct size is 256 bytes (actual: $memsize)", struct.position)
if(uniqueFields.isEmpty())
errors.err("struct must contain at least one field", struct.position)
}
private fun checkLongType(expression: Expression) {

View File

@@ -644,8 +644,8 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
private fun transform(srcArr: ArrayIndexedExpression): PtArrayIndexer {
val dt = srcArr.arrayvar.targetVarDecl()!!.datatype
if(!dt.isArray && !dt.isString)
throw FatalAstException("array indexing can only be used on array or string variables ${srcArr.position}")
if(!dt.isArray && !dt.isString && !dt.isPointer)
throw FatalAstException("array indexing can only be used on array, string or pointer variables ${srcArr.position}")
val eltType = srcArr.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
val array = PtArrayIndexer(eltType, srcArr.position)
array.add(transform(srcArr.arrayvar))

View File

@@ -356,6 +356,12 @@ class ArrayIndexedExpression(var arrayvar: IdentifierReference,
return when {
target.datatype.isString || target.datatype.isUnsignedWord -> InferredTypes.knownFor(BaseDataType.UBYTE)
target.datatype.isArray -> InferredTypes.knownFor(target.datatype.elementType())
target.datatype.isPointer -> {
if(target.datatype.subType!=null)
TODO("indexing on pointer to struct would yield the struct type itself, this is not yet supported (only pointers) at $position")
else
InferredTypes.knownFor(target.datatype.sub!!)
}
else -> InferredTypes.knownFor(target.datatype)
}
}

View File

@@ -25,17 +25,18 @@ STRUCTS and TYPED POINTERS
- DONE: implicit cast of pointer to bool, also in loop conditions (while ptr {...})
- DONE: implicit cast of pointer to uword in conditional expressions
- DONE: subroutine parameters and return values should be able to accept pointers as well now
- DONE (for basic types only): allow array syntax on pointers too: ptr[2] means ptr+sizeof()*2, ptr[0] just means ptr^^ .
- allow array syntax on pointers to structs too, but what type will ptr[2] have? And it will require ptr[2].field to work as well now. Actually that will be the only thing to work for now.
- pointer arithmetic should follow C: ptr=ptr+10 adds 10*sizeof() instead of just 10.
- add unit tests for all changes
- arrays of structs? No -> Just an array of uword pointers to said structs. Can even be @split as the only representation form because that's the default for word arrays.
- static initialization of structs may be allowed only at block scope and then behaves like arrays; it won't reset to the original value when program is restarted, so beware. Syntax = TBD
- allow memory-mapped structs? Something like &Sprite sprite0 = $9000 basically behaves identically to a typed pointer, but the address is immutable as usual
- existing STR and ARRAY remain unchanged (don't become typed pointers) so we can keep doing register-indexed addressing directly on them
- rather than str or uword parameter types for routines with a string argument, use ^^str (or ^^ubyte maybe? these are more or less identical..?)
- same for arrays? pointer-to-array syntax = TBD
- pointer-to-array syntax = TBD
- what about pointers to subroutines? should these be typed as well now?
- asm symbol name prefixing should work for dereferences too.
- pointer arithmetic is a pain, but need to follow C? ptr=ptr+10 adds 10*sizeof() instead of just 10.
- allow array syntax on pointers too: ptr[2] means ptr+sizeof()*2, ptr[0] just means ptr^^ .
Future Things and Ideas

View File

@@ -3,6 +3,22 @@
%zeropage basicsafe
%option no_sysinit
main {
sub start() {
struct Node {
bool flag
}
^^Node ptr = 2000
txt.print_uw(ptr)
txt.nl()
bool derp = ptr[10].flag
}
}
/*
main {
struct Enemy {
@@ -109,3 +125,4 @@ main {
}
*/