Skip to content

Commit 2c7256a

Browse files
committed
support assignment to indexed pointer targets
1 parent 97420b2 commit 2c7256a

File tree

12 files changed

+93
-96
lines changed

12 files changed

+93
-96
lines changed

codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AsmAssignment.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ internal enum class TargetStorageKind {
1010
ARRAY,
1111
MEMORY,
1212
REGISTER,
13-
POINTER, // wherever the pointer variable points to
14-
VOID // assign nothing - used in multi-value assigns for void placeholders
13+
POINTER,
14+
VOID // assign nothing - used in multi-value assigns for void placeholders
1515
}
1616

1717
internal enum class SourceStorageKind {
@@ -91,7 +91,6 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
9191
array != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, type, definingSub, target.position, array = array, origAstTarget = this)
9292
memory != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, type, definingSub, target.position, memory = memory, origAstTarget = this)
9393
pointerDeref != null -> return AsmAssignTarget(TargetStorageKind.POINTER, asmgen, type, definingSub, target.position, pointer = pointerDeref, origAstTarget = this)
94-
indexedPointerDeref != null -> TODO("assign to indexed pointer ${target.position} - for now, split up the assignment target using a temporary pointer variable")
9594
else -> throw AssemblyError("weird target")
9695
}
9796
}

codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -596,9 +596,6 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
596596
return result
597597

598598
}
599-
indexedPointerDeref != null -> {
600-
TODO("assign to indexed pointer ${assignment.target.position} - for now, split up the assignment target using a temporary pointer variable")
601-
}
602599
else -> {
603600
throw AssemblyError("weird assigntarget")
604601
}

codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
8888
is PtFunctionCall -> translate(expr)
8989
is PtContainmentCheck -> translate(expr)
9090
is PtPointerDeref -> translate(expr)
91-
is PtArrayIndexedPointerDeref -> TODO("translate array indexed pointer deref expression $expr")
9291
is PtRange,
9392
is PtArray,
9493
is PtString -> throw AssemblyError("range/arrayliteral/string should no longer occur as expression")

compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,61 @@ _after:
662662
override fun after(deref: ArrayIndexedPtrDereference, parent: Node): Iterable<IAstModification> {
663663
// get rid of the ArrayIndexedPtrDereference AST node, replace it with other AST nodes that are equivalent
664664

665+
fun pokeFunc(dt: DataType): Pair<String, DataType?> {
666+
return when {
667+
dt.isBool -> "pokebool" to null
668+
dt.isUnsignedByte -> "poke" to null
669+
dt.isSignedByte -> "poke" to DataType.UBYTE
670+
dt.isUnsignedWord -> "pokew" to null
671+
dt.isSignedWord -> "pokew" to DataType.UWORD
672+
dt.isLong -> "pokel" to null
673+
dt.isFloat -> "pokef" to null
674+
else -> throw FatalAstException("can only deref a numeric or boolean pointer here")
675+
}
676+
}
677+
678+
if(parent is AssignTarget) {
679+
if(!deref.derefLast) {
680+
val assignment = parent.parent as Assignment
681+
val field = deref.chain.last()
682+
val ptr = deref.chain.dropLast(1)
683+
if(field.second==null && ptr.last().second!=null) {
684+
val ptrName = ptr.map { it.first }
685+
val ptrVar = deref.definingScope.lookup(ptrName) as? VarDecl
686+
if(ptrVar!=null && (ptrVar.datatype.isPointer || ptrVar.datatype.isPointerArray)) {
687+
val struct = ptrVar.datatype.subType!! as StructDecl
688+
val offsetNumber = NumericLiteral.optimalInteger(struct.offsetof(field.first, program.memsizer)!!.toInt(), deref.position)
689+
val pointerIdentifier = IdentifierReference(ptrName, deref.position)
690+
val address: Expression
691+
if(ptrVar.datatype.isPointer) {
692+
// pointer[idx].field = value --> pokeXXX(pointer as uword + idx*sizeof(Struct) + offsetof(Struct.field), value)
693+
val structSize = ptrVar.datatype.dereference().size(program.memsizer)
694+
val pointerAsUword = TypecastExpression(pointerIdentifier, DataType.UWORD, true, deref.position)
695+
val idx = ptr.last().second!!.indexExpr
696+
val scaledIndex = BinaryExpression(idx, "*", NumericLiteral(BaseDataType.UWORD, structSize.toDouble(), deref.position), deref.position)
697+
val structAddr = BinaryExpression(pointerAsUword, "+", scaledIndex, deref.position)
698+
address = BinaryExpression(structAddr, "+", offsetNumber, deref.position)
699+
}
700+
else {
701+
// pointerarray[idx].field = value --> pokeXXX(pointerarray[idx] as uword + offsetof(Struct.field), value)
702+
val index = ArrayIndexedExpression(pointerIdentifier, null, ptr.last().second!!, deref.position)
703+
val pointerAsUword = TypecastExpression(index, DataType.UWORD, true, deref.position)
704+
address = BinaryExpression(pointerAsUword, "+", offsetNumber, deref.position)
705+
}
706+
val (pokeFunc, valueCast) = pokeFunc(parent.inferType(program).getOrUndef())
707+
val value = if(valueCast==null) assignment.value else TypecastExpression(assignment.value, valueCast, true, assignment.value.position)
708+
val pokeCall = FunctionCallStatement(IdentifierReference(listOf(pokeFunc), assignment.position),
709+
mutableListOf(address, value), false, assignment.position)
710+
711+
if(assignment.isAugmentable)
712+
errors.warn("in-place assignment of indexed pointer variable currently is very inefficient, maybe use a temporary pointer variable", assignment.position)
713+
return listOf(IAstModification.ReplaceNode(assignment, pokeCall, assignment.parent))
714+
}
715+
}
716+
}
717+
}
718+
719+
665720
if(deref.chain.last().second!=null && deref.derefLast && deref.chain.dropLast(1).all { it.second==null } ) {
666721

667722
// parent could be Assigment directly, or a binexpr chained pointer expression (with '.' operator)
@@ -714,15 +769,7 @@ _after:
714769
val dt = deref.inferType(program).getOrUndef()
715770
if(dt.isNumericOrBool) {
716771
// if it's something else beside number (like, a struct instance) we don't support rewriting that...
717-
val (pokeFunc, cast) =
718-
if (dt.isBool) "pokebool" to null
719-
else if (dt.isUnsignedByte) "poke" to null
720-
else if (dt.isSignedByte) "poke" to DataType.UBYTE
721-
else if (dt.isUnsignedWord) "pokew" to null
722-
else if (dt.isSignedWord) "pokew" to DataType.UWORD
723-
else if (dt.isLong) "pokel" to null
724-
else if (dt.isFloat) "pokef" to null
725-
else throw FatalAstException("can only deref a numeric or boolean pointer here")
772+
val (pokeFunc, cast) = pokeFunc(dt)
726773
val indexer = deref.chain.last().second!!
727774
val identifier = IdentifierReference(deref.chain.map { it.first }, deref.position)
728775
val indexed = ArrayIndexedExpression(identifier, null, indexer, deref.position)

compiler/src/prog8/compiler/astprocessing/SimplifiedAstMaker.kt

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -107,20 +107,6 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
107107
}
108108
}
109109

110-
private fun transform(arrayIndexedDereference: ArrayIndexedPtrDereference): PtArrayIndexedPointerDeref {
111-
val type = arrayIndexedDereference.inferType(program).getOrElse {
112-
throw FatalAstException("unknown dt")
113-
}
114-
val chain = arrayIndexedDereference.chain.map {
115-
if(it.second==null)
116-
it.first to null
117-
else
118-
it.first to transformExpression(it.second!!.indexExpr)
119-
}
120-
return PtArrayIndexedPointerDeref(type, chain, arrayIndexedDereference.derefLast, arrayIndexedDereference.position)
121-
}
122-
123-
124110
private fun transform(deref: PtrDereference): PtPointerDeref {
125111
val type = deref.inferType(program).getOrElse {
126112
throw FatalAstException("unknown dt")
@@ -284,7 +270,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
284270
srcTarget.arrayindexed!=null -> target.add(transform(srcTarget.arrayindexed!!))
285271
srcTarget.memoryAddress!=null -> target.add(transform(srcTarget.memoryAddress!!))
286272
srcTarget.pointerDereference!=null -> target.add(transform(srcTarget.pointerDereference!!))
287-
srcTarget.arrayIndexedDereference!=null -> target.add(transform(srcTarget.arrayIndexedDereference!!))
273+
srcTarget.arrayIndexedDereference!=null -> throw FatalAstException("this should have been converted to some other ast nodes ${srcTarget.position}")
288274
!srcTarget.void -> throw FatalAstException("invalid AssignTarget")
289275
}
290276
return target

compiler/test/TestPointers.kt

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package prog8tests.compiler
33
import io.kotest.assertions.withClue
44
import io.kotest.core.spec.style.FunSpec
55
import io.kotest.engine.spec.tempdir
6-
import io.kotest.matchers.comparables.shouldBeGreaterThanOrEqualTo
76
import io.kotest.matchers.shouldBe
87
import io.kotest.matchers.shouldNotBe
98
import io.kotest.matchers.string.shouldContain
@@ -1346,31 +1345,32 @@ other {
13461345
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
13471346
}
13481347

1349-
test("a.b.c[i]^^.value = X where pointer is struct gives good error message") {
1348+
test("support for assigning to indexed pointers") {
13501349
val src="""
13511350
main {
1351+
13521352
sub start() {
1353-
other.foo.listarray[3]^^.value = cx16.r0
1354-
other.foo()
1355-
}
1356-
}
1353+
sprptr[2]^^.y = 99
1354+
pokew(sprptr as uword + (sizeof(Sprite) as uword)*2 + offsetof(Sprite.y), 99)
1355+
sprptr[cx16.r0L]^^.y = 99
1356+
pokew(sprptr as uword + (sizeof(Sprite) as uword)*cx16.r0L + offsetof(Sprite.y), 99)
13571357
1358-
other {
1359-
sub foo() {
1360-
struct List {
1361-
bool b
1362-
uword value
1363-
}
1358+
sprites[2]^^.y = 99
1359+
pokew(sprites[2] as uword + offsetof(Sprite.y), 99)
1360+
sprites[cx16.r0L]^^.y = 99
1361+
pokew(sprites[cx16.r0L] as uword + offsetof(Sprite.y), 99)
1362+
}
13641363
1365-
^^List[10] listarray
1366-
listarray[3]^^.value = cx16.r0
1367-
listarray[3]^^ = 999 ; cannot assign word value to struct instance
1364+
struct Sprite {
1365+
ubyte x
1366+
uword y
13681367
}
1368+
1369+
^^Sprite[4] @shared sprites
1370+
^^Sprite @shared sprptr
13691371
}"""
1370-
val errors = ErrorReporterForTests()
1371-
compileText(VMTarget(), false, src, outputDir, errors=errors) shouldBe null
1372-
errors.errors.size shouldBeGreaterThanOrEqualTo 1
1373-
errors.errors[0] shouldContain "assigning this value to struct instance not supported"
1372+
compileText(VMTarget(), false, src, outputDir) shouldNotBe null
1373+
compileText(C64Target(), false, src, outputDir) shouldNotBe null
13741374
}
13751375

13761376
xtest("array indexed assignment parses with and without explicit dereference after struct pointer [IGNORED because it's a parser error right now]") {

docs/source/structpointers.rst

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,26 @@ Structs and Pointers
1515
priority over other variables to be placed into zeropage.
1616

1717
.. note::
18-
Due to some limitations in the language parser, not all pointer related syntax is currently supported
19-
if it is a pointer to a struct type.
18+
Due to a few limitations in the language parser, some pointer related syntax is currently unsupported.
2019
The compiler tries its best to give a descriptive error message but sometimes there is still a
21-
parser limitation that has to be worked around at the moment. For example, this pointer arithmetic
22-
indexing syntax is not supported right now *to assign to* and will result in a parse error (note that
23-
using it as an expression value does work correctly)::
20+
parser limitation that has to be worked around at the moment. For example, this assigment syntax doesn't parse correctly::
2421

2522
^^Node np
26-
np[2].field = 9999 ; cannot assign to this yet
27-
ubyte value = np[2].field ; this does work though.
23+
np[2].field = 9999 ; cannot use this syntax as assignment target right now
24+
ubyte value = np[2].field ; note that using it as expression value works fine
2825

29-
To work around this (and similar) cases you'll have to break up the expression in multiple steps,
30-
in this case something like::
26+
To work around this you'll have to explicitly write the pointer dereferencing operator,
27+
or break up the expression in multiple steps (which can be beneficial too when you are assigning multiple fields
28+
because it will save a pointer calculation for every assignment)::
3129

30+
^^Node np
31+
np[2]^^.field = 9999
32+
33+
; alternatively, split up:
3234
^^Node thenode = &&np[2]
3335
thenode.field = 9999
3436

3537

36-
3738
Legacy untyped pointers (uword)
3839
-------------------------------
3940

docs/source/todo.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ TODO
55
STRUCTS and TYPED POINTERS
66
--------------------------
77

8-
- make this array indexed assignment work: ^^Node np / np[2]^^.field = 9999 (same for pointer arrays!) likely needs more support in the assignment target class (remove Note in docs when fixed)
98
- implement the remaining TODO's in PointerAssignmentsGen.
109
- optimize deref in PointerAssignmentsGen: optimize 'forceTemporary' to only use a temporary when the offset is >0
1110
- optimize the float copying in assignIndexedPointer() (also word?)
11+
- optimize augmented assignments to indexed pointer targets like sprptr[2]^^.y++ (these are now not performend in-place but as a regular assignment)
1212
- implement even more struct instance assignments (via memcopy) in CodeDesugarer (see the TODO) (add to documentation as well, paragraph 'Structs')
1313
- support @nosplit pointer arrays?
1414
- support pointer to pointer?

examples/test.p8

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
11
main {
22

33
sub start() {
4-
; TODO assigning to pointer indexed is not yet supported:
5-
sprptr[2]^^.y = 99
6-
sprptr[cx16.r0L]^^.y = 99
7-
sprites[2]^^.y = 99
8-
sprites[cx16.r0L]^^.y = 99
4+
sprptr[2]^^.y++
95
}
106

117
struct Sprite {
12-
uword x
13-
ubyte y
8+
ubyte x
9+
uword y
1410
}
1511

16-
1712
^^Sprite[4] @shared sprites
1813
^^Sprite @shared sprptr
1914
}

simpleAst/src/prog8/code/ast/AstExpressions.kt

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,6 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
139139
is PtRange -> true
140140
is PtString -> true
141141
is PtPointerDeref -> false
142-
is PtArrayIndexedPointerDeref -> false
143142
is PtTypeCast -> value.isSimple()
144143
is PtIfExpression -> condition.isSimple() && truevalue.isSimple() && falsevalue.isSimple()
145144
is PtBranchCondExpression -> truevalue.isSimple() && falsevalue.isSimple()
@@ -453,15 +452,6 @@ class PtPointerDeref(type: DataType, val chain: List<String>, val derefLast: Boo
453452
}
454453
}
455454

456-
class PtArrayIndexedPointerDeref(
457-
type: DataType,
458-
val chain: List<Pair<String, PtExpression?>>,
459-
val derefLast: Boolean,
460-
position: Position
461-
) : PtExpression(type, position) {
462-
463-
}
464-
465455

466456
// special node that isn't created from compiling user code, but used internally in the Intermediate Code
467457
class PtIrRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position)

0 commit comments

Comments
 (0)