revert & to untyped pointer, added && for typed pointer address-of

This commit is contained in:
Irmen de Jong
2025-07-07 15:53:33 +02:00
parent f9fbfe30e3
commit 9f6106452e
12 changed files with 81 additions and 64 deletions

View File

@@ -39,14 +39,14 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
} else {
listOf(IAstModification.ReplaceNode(typecast,
AddressOf(identifier, null, null, false, typecast.position), parent))
AddressOf(identifier, null, null, false, false,typecast.position), parent))
}
} else if (typecast.expression is IFunctionCall) {
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
}
} else if(sourceDt.isString && typecast.type.isPointer && typecast.type.sub==BaseDataType.UBYTE) {
// casting a string to a ^^ubyte is just taking the address of the string.
val addr = AddressOf(typecast.expression as IdentifierReference, null, null, false, typecast.position)
val addr = AddressOf(typecast.expression as IdentifierReference, null, null, false, true, typecast.position)
return listOf(IAstModification.ReplaceNode(typecast, addr, parent))
} else {
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)

View File

@@ -317,7 +317,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(!argDt.isString || it.second is IdentifierReference) {
modifications += IAstModification.ReplaceNode(
identifier,
AddressOf(identifier, null, null, false, it.second.position),
AddressOf(identifier, null, null, false, true, it.second.position),
call as Node
)
}
@@ -337,7 +337,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
// take the address of the identifier
modifications += IAstModification.ReplaceNode(
identifier,
AddressOf(identifier, null, null, false, it.second.position),
AddressOf(identifier, null, null, false, false,it.second.position),
call as Node
)
} else if(dt.isUnknown) {
@@ -346,7 +346,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
// take the address of the subroutine or label
modifications += IAstModification.ReplaceNode(
identifier,
AddressOf(identifier, null, null, false, it.second.position),
AddressOf(identifier, null, null, false, false, it.second.position),
call as Node
)
}
@@ -481,7 +481,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
val eltType = elt.inferType(program)
val tgt = elt.targetStatement()
if(eltType.isIterable || tgt is Subroutine || tgt is Label || tgt is Block) {
val addressof = AddressOf(elt, null, null, false, elt.position)
val addressof = AddressOf(elt, null, null, false, false, elt.position)
addressof.linkParents(array)
array.value[index] = addressof
}

View File

@@ -12,9 +12,7 @@ import prog8.ast.expressions.ArrayIndexedExpression
import prog8.ast.expressions.DirectMemoryRead
import prog8.ast.expressions.PtrDereference
import prog8.ast.statements.*
import prog8.code.ast.PtAssignment
import prog8.code.ast.PtReturn
import prog8.code.ast.PtSubSignature
import prog8.code.ast.*
import prog8.code.core.BaseDataType
import prog8.code.core.DataType
import prog8.code.core.IMemSizer
@@ -585,7 +583,7 @@ main {
}
}
test("type of & operator (address-of)") {
test("internal type for address-of") {
DataType.BYTE.typeForAddressOf(false) shouldBe DataType.pointer(BaseDataType.BYTE)
DataType.WORD.typeForAddressOf(false) shouldBe DataType.pointer(BaseDataType.WORD)
DataType.FLOAT.typeForAddressOf(false) shouldBe DataType.pointer(BaseDataType.FLOAT)
@@ -605,6 +603,31 @@ main {
DataType.pointer(BaseDataType.BOOL).typeForAddressOf(false) shouldBe DataType.UWORD
}
test("untyped and typed address-of operators") {
val src="""
%option enable_floats
main {
sub start() {
float f
cx16.r0 = &f+1
cx16.r1 = &&f+1
}
}"""
val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = true)!!
val st = result.codegenAst!!.entrypoint()!!.children
st.size shouldBe 6
val r0v = (st[3] as PtAssignment).value as PtBinaryExpression
val r1v = (st[4] as PtAssignment).value as PtBinaryExpression
r0v.left shouldBe instanceOf<PtAddressOf>()
r0v.right shouldBe instanceOf<PtNumber>()
(r0v.right as PtNumber).number shouldBe 1.0
r1v.left shouldBe instanceOf<PtAddressOf>()
r1v.right shouldBe instanceOf<PtNumber>()
(r1v.right as PtNumber).number shouldBe VMTarget.FLOAT_MEM_SIZE
}
test("address-of struct fields") {
val src="""
%option enable_floats
@@ -1375,7 +1398,7 @@ main {
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, src, outputDir, errors = errors) shouldBe null
errors.errors.size shouldBe 999
// TODO
// TODO implement this test
}
xtest("array of pointers as subroutine param") {

View File

@@ -65,13 +65,13 @@ class TestAsmGenSymbols: StringSpec({
position = Position.DUMMY
)
val assign1 = Assignment(tgt, IdentifierReference(listOf("localvar"), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign2 = Assignment(tgt, AddressOf(IdentifierReference(listOf("locallabel"), Position.DUMMY), null, null, false, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign3 = Assignment(tgt, AddressOf(IdentifierReference(listOf("var_outside"), Position.DUMMY), null, null, false, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign4 = Assignment(tgt, AddressOf(IdentifierReference(listOf("label_outside"), Position.DUMMY), null, null, false, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign5 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","localvar"), Position.DUMMY), null, null, false, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign6 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","locallabel"), Position.DUMMY), null, null, false, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign7 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","var_outside"), Position.DUMMY), null, null, false, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign8 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","label_outside"), Position.DUMMY), null, null, false, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign2 = Assignment(tgt, AddressOf(IdentifierReference(listOf("locallabel"), Position.DUMMY), null, null, false, false, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign3 = Assignment(tgt, AddressOf(IdentifierReference(listOf("var_outside"), Position.DUMMY), null, null, false, false, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign4 = Assignment(tgt, AddressOf(IdentifierReference(listOf("label_outside"), Position.DUMMY), null, null, false, false, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign5 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","localvar"), Position.DUMMY), null, null, false, false, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign6 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","locallabel"), Position.DUMMY), null, null, false, false, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign7 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","var_outside"), Position.DUMMY), null, null, false, false, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val assign8 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","label_outside"), Position.DUMMY), null, null, false, false, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val statements = mutableListOf(varInSub, var2InSub, labelInSub, assign1, assign2, assign3, assign4, assign5, assign6, assign7, assign8)
val subroutine = Subroutine("start", mutableListOf(), mutableListOf(), emptyList(), emptyList(), emptySet(), null, false, false, false, statements, Position.DUMMY)

View File

@@ -280,10 +280,17 @@ class Antlr2KotlinVisitor(val source: SourceCode): AbstractParseTreeVisitor<Node
val msb = ctx.ADDRESS_OF_MSB()!=null
// note: &< (ADDRESS_OF_LSB) is equivalent to a regular &.
val index = ctx.arrayindex()?.accept(this) as? ArrayIndex
return if(index!=null) {
AddressOf(identifier, index, null, msb, ctx.toPosition())
var typed = false
if(ctx.TYPED_ADDRESS_OF()!=null) {
// new typed AddressOf
if(msb)
throw SyntaxError("typed address of not allowed with msb", ctx.toPosition())
typed = true
}
return if (index != null) {
AddressOf(identifier, index, null, msb, typed, ctx.toPosition())
} else {
AddressOf(identifier,null, null, msb, ctx.toPosition())
AddressOf(identifier, null, null, msb, typed, ctx.toPosition())
}
}

View File

@@ -502,7 +502,8 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
}
}
data class AddressOf(var identifier: IdentifierReference?, var arrayIndex: ArrayIndex?, var dereference: PtrDereference?, val msb: Boolean, override val position: Position) : Expression() {
data class AddressOf(var identifier: IdentifierReference?, var arrayIndex: ArrayIndex?, var dereference: PtrDereference?,
val msb: Boolean, val typed: Boolean, override val position: Position) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
@@ -538,7 +539,7 @@ data class AddressOf(var identifier: IdentifierReference?, var arrayIndex: Array
replacement.parent = this
}
override fun copy() = AddressOf(identifier?.copy(), arrayIndex?.copy(), dereference?.copy(), msb, position)
override fun copy() = AddressOf(identifier?.copy(), arrayIndex?.copy(), dereference?.copy(), msb, typed, position)
override fun constValue(program: Program): NumericLiteral? {
if(msb)
return null
@@ -574,8 +575,9 @@ data class AddressOf(var identifier: IdentifierReference?, var arrayIndex: Array
return null
}
override fun referencesIdentifier(nameInSource: List<String>) = identifier?.nameInSource==nameInSource || arrayIndex?.referencesIdentifier(nameInSource)==true || dereference?.referencesIdentifier(nameInSource)==true
// override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(BaseDataType.UWORD) // TODO orignal behavior
override fun inferType(program: Program): InferredTypes.InferredType {
if(!typed)
return InferredTypes.knownFor(BaseDataType.UWORD) // orignal pre-v12 untyped AddressOf
if(identifier!=null) {
val type = identifier!!.inferType(program).getOrUndef()
val addrofDt = type.typeForAddressOf(msb)

View File

@@ -270,7 +270,7 @@ class VarDecl(
// parameter variable memory mapped to a R0-R15 virtual register
val regname = param.registerOrPair.asScopedNameVirtualReg(param.type)
decltype = VarDeclType.MEMORY
value = AddressOf(IdentifierReference(regname, param.position), null, null, false, param.position)
value = AddressOf(IdentifierReference(regname, param.position), null, null, false, false,param.position)
}
val dt = if(param.type.isArray) DataType.UWORD else param.type
return VarDecl(decltype, VarDeclOrigin.SUBROUTINEPARAM, dt, param.zp, SplitWish.DONTCARE, null, param.name, emptyList(), value,

View File

@@ -999,7 +999,7 @@ containment check: ``in``
}
address of: ``&``, ``&<``, ``&>``
address of: ``&``, ``&<``, ``&>``, ``&&``
This is a prefix operator that can be applied to a string or array variable or literal value.
It results in the memory address (UWORD) of that string or array in memory: ``uword a = &stringvar``
Sometimes the compiler silently inserts this operator to make it easier for instance
@@ -1012,6 +1012,11 @@ address of: ``&``, ``&<``, ``&>``
and MSB byte array separately, respectively. Note that ``&<`` is just the same as ``&`` in this case.
For more details on split word arrays, see :ref:`arrayvars`.
**Typed pointer version:** the single ``&`` operator still returns an untyped uword address for
backward compatibility reasons, so existing programs keep working. The *double ampersand* ``&&`` operator
however returns a *typed* pointer to the value. The semantics are slightly different because adding or subtracting
a number from a typed pointer uses *pointer arithmetic* that takes the size of the value that it points to into account.
ternary:
Prog8 doesn't have a ternary operator to choose one of two values (``x? y : z`` in many other languages)

View File

@@ -81,3 +81,11 @@ Typed pointer to Struct type
----------------------------
Work in progress.
Address-Of: untyped vs typed
----------------------------
``&`` still returns untyped (uword) pointer, as it did in older Prog8 versions. This is for backward compatibility reasons so existing programs don't break.
The *double ampersand* operator ``&&`` returns a *typed* pointer to the value. The semantics are slightly different from the old untyped address-of operator, because adding or subtracting
a number from a typed pointer uses *pointer arithmetic* that takes the size of the value that it points to into account.

View File

@@ -1,10 +1,6 @@
TODO
====
What to do with the changed adress-of behavior? &x now returns a typed pointer to &x + 10 will now be calculated differently (C pointer arithmetic semantics rather than simply byte addition)
compiler flag to select old behavior? new operator that has the new behavior? Don't want to break existing code that used &....
Old behavior can be put back by always returning UWORD as the inferred type for AddressOf nodes
STRUCTS and TYPED POINTERS
--------------------------
@@ -58,6 +54,7 @@ STRUCTS and TYPED POINTERS
- DONE: fix _msb/_lsb storage of the split-words pointer-arrays
- DONE: what about static initialization of an array of struct pointers? -> impossible right now because the pointer values are not constants.
- DONE: make typeForAddressOf() be even more specific about the typed pointers it returns for the address-of operator.
- DONE: existing '&' address-of still returns untyped uword (for backward compatibility). New '&&' operator returns typed pointer.
- DONE: allow list1^^ = list2^^ (value wise assignment of List structures) by replacing it with a sys.memcopy(list2, list1, sizeof(List)) call.
- DONE: allow a.b.ptr[i].value (equiv to a.b.ptr[i]^^.value) expressions (assignment target doesn't parse yet, see below)
- DONE: check passing arrays to typed ptr sub-parameters. NOTE: word array can only be a @nosplit array if the parameter type is ^^word, because the words need to be sequential in memory there

View File

@@ -5,48 +5,23 @@
main {
ubyte @shared thingIndex = 10
uword[20] @shared dummy
uword[10] @split curframesplit
uword[10] @nosplit curframe
uword p1, p2
float f
sub start() {
classic()
; new()
}
sub classic() {
txt.print("float pointer+1: ")
txt.print("classic float pointer+1: ")
txt.print_uwhex(&f, true)
txt.spc()
txt.print_uwhex(&f + 1, true)
txt.spc()
txt.print_uw(&f + 1 - &f)
txt.nl()
p1 = &curframesplit[thingIndex]
p2 = &curframe[thingIndex]
txt.print("&array (split): ")
txt.print_uwhex(&curframesplit, true)
txt.print("typed float pointer+1: ")
txt.print_uwhex(&&f, true)
txt.spc()
txt.print_uwhex(p1, true)
txt.print_uwhex(&&f + 1, true)
txt.spc()
txt.print_uw(p1 - &curframesplit)
txt.spc()
txt.print_uwhex(p1 + &curframesplit, true)
txt.nl()
txt.print("&array (normal): ")
txt.print_uwhex(&curframe, true)
txt.spc()
txt.print_uwhex(p2, true)
txt.spc()
txt.print_uw(p2 - &curframe)
txt.spc()
txt.print_uwhex(p2 + &curframe, true)
txt.print_uw(&&f + 1 - &&f)
txt.nl()
}
; 6502 data size: $0251
}

View File

@@ -37,9 +37,9 @@ DEC_INTEGER : DEC_DIGIT (DEC_DIGIT | '_')* ;
HEX_INTEGER : '$' HEX_DIGIT (HEX_DIGIT | '_')* ;
BIN_INTEGER : '%' BIN_DIGIT (BIN_DIGIT | '_')* ;
ADDRESS_OF: '&' ;
TYPED_ADDRESS_OF: '&&' ;
ADDRESS_OF_MSB: '&>' ;
ADDRESS_OF_LSB: '&<' ;
INVALID_AND_COMPOSITE: '&&' ;
POINTER: '^^';
fragment HEX_DIGIT: ('a'..'f') | ('A'..'F') | ('0'..'9') ;
@@ -241,7 +241,7 @@ typecast : 'as' datatype;
directmemory : '@' '(' expression ')';
addressof : <assoc=right> (ADDRESS_OF | ADDRESS_OF_LSB | ADDRESS_OF_MSB) scoped_identifier arrayindex? ;
addressof : <assoc=right> (ADDRESS_OF | TYPED_ADDRESS_OF | | ADDRESS_OF_LSB | ADDRESS_OF_MSB) scoped_identifier arrayindex? ;
functioncall : scoped_identifier '(' expression_list? ')' ;