diff --git a/codeCore/src/prog8/code/core/Enumerations.kt b/codeCore/src/prog8/code/core/Enumerations.kt index 26fb90220..b3f83a67f 100644 --- a/codeCore/src/prog8/code/core/Enumerations.kt +++ b/codeCore/src/prog8/code/core/Enumerations.kt @@ -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 { diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt index 4e3f68d5a..c693f6643 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt @@ -382,6 +382,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val result = mutableListOf() val arrayVarSymbol = arrayIx.variable.name var resultRegister = -1 + val isPointer = arrayIx.variable.type.isPointer if(arrayIx.splitWords) { require(vmDt==IRDataType.WORD) @@ -409,33 +410,83 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { } var resultFpRegister = -1 - if(arrayIx.index is PtNumber) { - val memOffset = ((arrayIx.index as PtNumber).number.toInt() * eltSize) - 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) - } - } else { - val tr = translateExpression(arrayIx.index) - addToResult(result, tr, tr.resultReg, -1) - if(eltSize>1) - result += codeGen.multiplyByConst(IRDataType.BYTE, tr.resultReg, eltSize) - 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) - } - else { - resultRegister = codeGen.registers.next(vmDt) - addInstr(result, IRInstruction(Opcode.LOADX, vmDt, reg1=resultRegister, reg2=tr.resultReg, labelSymbol = arrayVarSymbol), null) + + 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) + } + 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) } + private fun translate(expr: PtPrefix): ExpressionCodeResult { val result = mutableListOf() val tr = translateExpression(expr.value) diff --git a/codeOptimizers/src/prog8/optimizer/ExpressionSimplifier.kt b/codeOptimizers/src/prog8/optimizer/ExpressionSimplifier.kt index 17d43575a..62d1b42d5 100644 --- a/codeOptimizers/src/prog8/optimizer/ExpressionSimplifier.kt +++ b/codeOptimizers/src/prog8/optimizer/ExpressionSimplifier.kt @@ -423,6 +423,17 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr return noModifications } + override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable { + 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) diff --git a/compiler/res/prog8lib/cx16/verafx.p8 b/compiler/res/prog8lib/cx16/verafx.p8 index c32e4323c..4ce8b2408 100644 --- a/compiler/res/prog8lib/cx16/verafx.p8 +++ b/compiler/res/prog8lib/cx16/verafx.p8 @@ -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 diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index fa1d1ba18..344183835 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -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) { diff --git a/compiler/src/prog8/compiler/astprocessing/SimplifiedAstMaker.kt b/compiler/src/prog8/compiler/astprocessing/SimplifiedAstMaker.kt index 1a464a77b..3a3d81db8 100644 --- a/compiler/src/prog8/compiler/astprocessing/SimplifiedAstMaker.kt +++ b/compiler/src/prog8/compiler/astprocessing/SimplifiedAstMaker.kt @@ -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)) diff --git a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt index 7e99a64aa..73fd5bc06 100644 --- a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt +++ b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt @@ -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) } } diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 8226a9bd8..6d0c1b002 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -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 diff --git a/examples/test.p8 b/examples/test.p8 index bf420be8f..c5d0acf20 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -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 { } +*/