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.isPointer get() = this == BaseDataType.POINTER
val BaseDataType.isPointerArray get() = this == BaseDataType.ARRAY_POINTER val BaseDataType.isPointerArray get() = this == BaseDataType.ARRAY_POINTER
val BaseDataType.isSplitWordArray get() = this == BaseDataType.ARRAY_SPLITW 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.isIterable get() = this in arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER, BaseDataType.POINTER)
val BaseDataType.isPassByRef get() = this.isIterable val BaseDataType.isPassByRef get() = this.isIterable && !this.isPointer
val BaseDataType.isPassByValue get() = !this.isIterable val BaseDataType.isPassByValue get() = !this.isIterable || this.isPointer
interface ISubType { interface ISubType {

View File

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

View File

@@ -423,6 +423,17 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
return noModifications 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? { private fun applyAbsorptionLaws(expr: BinaryExpression): Expression? {
// NOTE: only when the terms are not function calls!!! // NOTE: only when the terms are not function calls!!!
if(expr.left is IFunctionCall || expr.right is IFunctionCall) 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. ; 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) ; 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. ; Verafx doesn't support unsigned values like this for full 32 bit result.
; Note: clobbers VRAM $1f9bc - $1f9bf (inclusive)
%asm {{ %asm {{
lda cx16.r0 lda cx16.r0
sta P8ZP_SCRATCH_W1 sta P8ZP_SCRATCH_W1
@@ -136,6 +137,7 @@ verafx {
asmsub muls(word value1 @R0, word value2 @R1) clobbers(X) -> word @AY, word @R0 { 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). ; Returns the 32 bits signed result in AY and R0 (lower word, upper word).
; Vera Fx multiplication support only works on signed values! ; Vera Fx multiplication support only works on signed values!
; Note: clobbers VRAM $1f9bc - $1f9bf (inclusive)
%asm {{ %asm {{
lda #(2 << 1) lda #(2 << 1)
sta cx16.VERA_CTRL ; $9F25 sta cx16.VERA_CTRL ; $9F25

View File

@@ -1341,7 +1341,7 @@ internal class AstChecker(private val program: Program,
override fun visit(typecast: TypecastExpression) { override fun visit(typecast: TypecastExpression) {
checkLongType(typecast) checkLongType(typecast)
if(typecast.type.isIterable) if(typecast.type.isPassByRef)
errors.err("cannot type cast to string or array type", typecast.position) errors.err("cannot type cast to string or array type", typecast.position)
if(!typecast.expression.inferType(program).isKnown) if(!typecast.expression.inferType(program).isKnown)
@@ -1796,6 +1796,9 @@ internal class AstChecker(private val program: Program,
val memsize = struct.memsize(program.memsizer) val memsize = struct.memsize(program.memsizer)
if(memsize>256) if(memsize>256)
errors.err("struct contains too many fields, max struct size is 256 bytes (actual: $memsize)", struct.position) 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) { 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 { private fun transform(srcArr: ArrayIndexedExpression): PtArrayIndexer {
val dt = srcArr.arrayvar.targetVarDecl()!!.datatype val dt = srcArr.arrayvar.targetVarDecl()!!.datatype
if(!dt.isArray && !dt.isString) if(!dt.isArray && !dt.isString && !dt.isPointer)
throw FatalAstException("array indexing can only be used on array or string variables ${srcArr.position}") 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 eltType = srcArr.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
val array = PtArrayIndexer(eltType, srcArr.position) val array = PtArrayIndexer(eltType, srcArr.position)
array.add(transform(srcArr.arrayvar)) array.add(transform(srcArr.arrayvar))

View File

@@ -356,6 +356,12 @@ class ArrayIndexedExpression(var arrayvar: IdentifierReference,
return when { return when {
target.datatype.isString || target.datatype.isUnsignedWord -> InferredTypes.knownFor(BaseDataType.UBYTE) target.datatype.isString || target.datatype.isUnsignedWord -> InferredTypes.knownFor(BaseDataType.UBYTE)
target.datatype.isArray -> InferredTypes.knownFor(target.datatype.elementType()) 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) 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 bool, also in loop conditions (while ptr {...})
- DONE: implicit cast of pointer to uword in conditional expressions - 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: 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 - 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. - 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 - 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 - 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 - 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..?) - 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? - what about pointers to subroutines? should these be typed as well now?
- asm symbol name prefixing should work for dereferences too. - 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 Future Things and Ideas

View File

@@ -3,6 +3,22 @@
%zeropage basicsafe %zeropage basicsafe
%option no_sysinit %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 { main {
struct Enemy { struct Enemy {
@@ -109,3 +125,4 @@ main {
} }
*/