mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
codegen: fix pointer-typed struct field reads on 32-bit targets to use peekl and long address arithmetic
This commit is contained in:
1 parent
234468690e
commit
894a3e90db
9 files changed
+382
-14
No files matched your search
@@ -1,3 +1,104 @@
|
||||
;; Exec doubly linked lists - usage summary
|
||||
;;
|
||||
;; Purpose: this module is a super thin wrapper over the exec list
|
||||
;; headers and library routines. It exists for source code
|
||||
;; compatibility with other targets that supply their own lists.p8
|
||||
;; implementation. On amiga500 everything simply forwards to exec.library.
|
||||
;;
|
||||
;; Data structures (from Include_H/exec/nodes.h and exec/lists.h):
|
||||
;;
|
||||
;; A full Node is 14 bytes: Succ and Pred pointers at offsets 0 and 4,
|
||||
;; an ubyte Type at 8, a signed priority byte Pri at 9 and a Name
|
||||
;; string pointer at 10. A MinNode holds just the two link pointers
|
||||
;; (8 bytes). The list headers mirror this split: List consists of
|
||||
;; Head, Tail and TailPred pointers plus Type and Pad bytes (14 bytes),
|
||||
;; MinList has only the three pointers (12 bytes). This module aliases
|
||||
;; List/Node to the minimal variants; use FullNode when you need the
|
||||
;; Type/Pri/Name fields.
|
||||
;;
|
||||
;; Layout: an Exec list is a circular doubly linked list in which the
|
||||
;; header itself acts as the sentinel node - there are no separate
|
||||
;; head/tail marker pointers. The header's Tail field stays zero
|
||||
;; forever and serves as terminator: forward traversal via Succ ends
|
||||
;; when it reaches &Header.Tail, backward traversal via Pred ends when
|
||||
;; it reaches the header itself. The first real node's Pred points at
|
||||
;; the header, the last real node's Succ points at &Header.Tail.
|
||||
;;
|
||||
;; Creation and initialization:
|
||||
;; Place the list header in static memory or allocate it, then call
|
||||
;; init(listptr) once before any other use. init() is a thin wrapper
|
||||
;; around exec.NewList(), which sets Head = &Tail, Tail = 0 and
|
||||
;; TailPred = &Head. Nodes need no explicit setup beyond being
|
||||
;; cleared; fill in Type/Pri/Name as needed before using the routines
|
||||
;; below that depend on them. For MinList headers on very new
|
||||
;; Kickstarts there is also NewMinList (LVO -828), but NewList covers
|
||||
;; both variants because their layouts coincide.
|
||||
;;
|
||||
;; Empty test: the list is empty iff TailPred points back at the
|
||||
;; header itself (equivalently Head == &Tail); see is_empty().
|
||||
;;
|
||||
;; Operations (all resolve to exec.library LVO calls):
|
||||
;; - add_head / add_tail: link node at front / back of list
|
||||
;; (AddHead LVO -240, AddTail LVO -246).
|
||||
;; - insert(list, node, pred): links node after the given predecessor
|
||||
;; node anywhere in the list; pred = 0 means insert at head
|
||||
;; (Insert LVO -234).
|
||||
;; - enqueue(list, node): priority-sorted insertion using Pri, placed
|
||||
;; before the first lower-priority node so equal priorities come out
|
||||
;; FIFO. Requires full Nodes (Enqueue LVO -270).
|
||||
;; - remove(node): unlinks a node from whichever list it currently
|
||||
;; sits in; it must actually be linked into one (Remove LVO -252).
|
||||
;; - remove_head / remove_tail: unlink and return the first / last
|
||||
;; node, or null if empty; these give you queue and stack behaviour
|
||||
;; (RemHead LVO -258, RemTail LVO -264).
|
||||
;; - find_name(list, name): linear search over Name strings, returns
|
||||
;; matching node or null; requires full Nodes (FindName LVO -276).
|
||||
;;
|
||||
;; Concurrency warning: none of these routines arbitrate access to
|
||||
;; the list. If tasks or interrupts share a list you must serialize
|
||||
;; access yourself (Forbid/Permit, Disable/Enable, or semaphores).
|
||||
;;
|
||||
;; Allocator agnostic: the routines never allocate or free memory
|
||||
;; themselves, they only link the `pointer` values you pass in. Nodes
|
||||
;; and headers can come from static `[]` variables, `memory()` slabs,
|
||||
;; or any arena/bump allocator such as `arena_alloc(sizeof(MyNode))`
|
||||
;; as `^^MyNode` or `exec.AllocMem` on amiga. Just ensure the
|
||||
;; allocation is suitably aligned (long-aligned on m68k).
|
||||
;;
|
||||
;; Custom nodes: you can define your own node struct for use with the
|
||||
;; exec/lists routines and with `for node in mylist` loops. The only
|
||||
;; requirement is that the link pointers are the first fields at offset
|
||||
;; 0 (Succ at 0, Pred at 4 on 32-bit amiga). Minimal operations only
|
||||
;; need Succ/Pred; enqueue() and find_name() require the full
|
||||
;; Type/Pri/Name layout of exec.Node / lists.FullNode. Define the list
|
||||
;; header with your node type so `for`-iteration infers the correct
|
||||
;; type. Example:
|
||||
;;
|
||||
;; main {
|
||||
;; struct MyNode {
|
||||
;; ^^MyNode Succ ; must be first (offset 0)
|
||||
;; ^^MyNode Pred ; must be second (offset 4)
|
||||
;; ubyte Type ; include for FullNode compatibility if needed
|
||||
;; byte Pri
|
||||
;; str Name
|
||||
;; ubyte value ; your payload follows
|
||||
;; }
|
||||
;; struct MyList {
|
||||
;; ^^MyNode Head
|
||||
;; pointer Tail
|
||||
;; ^^MyNode TailPred
|
||||
;; }
|
||||
;; sub start() {
|
||||
;; ^^MyList mylist = [] ; zero-initialized header
|
||||
;; ^^MyNode n1 = [0, 0, 0, 0, 0, 11] ; Succ, Pred, Type, Pri, Name, value
|
||||
;; ^^MyNode n2 = [0, 0, 0, 0, 0, 22]
|
||||
;; exec.NewList(mylist as ^^exec.List) ; or lists.init(mylist as pointer)
|
||||
;; lists.add_tail(mylist as pointer, n1 as pointer)
|
||||
;; lists.add_tail(mylist as pointer, n2 as pointer)
|
||||
;; ; or directly: exec.AddTail(mylist as pointer, n1 as pointer)
|
||||
;; }
|
||||
;; }
|
||||
|
||||
%import exec
|
||||
|
||||
lists {
|
||||
|
||||
@@ -1,3 +1,90 @@
|
||||
;; Doubly linked lists - portable implementation
|
||||
;;
|
||||
;; Purpose: this module implements the classic Amiga Exec doubly
|
||||
;; linked list scheme natively for all non-amiga targets. It is
|
||||
;; source code compatible with the amiga500 lists.p8 module, which
|
||||
;; simply forwards to the exec.library routines; everywhere else the
|
||||
;; Prog8 code below does the work directly.
|
||||
;;
|
||||
;; Data structures:
|
||||
;;
|
||||
;; Node holds just the two link pointers Succ and Pred. List consists
|
||||
;; of Head, Tail and TailPred pointers. FullNode adds an ubyte Type,
|
||||
;; a signed priority byte Pri and a Name string pointer to the two
|
||||
;; links; only the priority and name aware routines (enqueue,
|
||||
;; find_name) require nodes to follow that extended layout.
|
||||
;;
|
||||
;; Layout: a list is a circular doubly linked list in which the
|
||||
;; header itself acts as the sentinel node - there are no separate
|
||||
;; head/tail marker pointers. The header's Tail field stays zero
|
||||
;; forever and serves as terminator: forward traversal via Succ ends
|
||||
;; when it reaches &List.Tail, backward traversal via Pred ends at
|
||||
;; the header itself. The first real node's Pred points at the
|
||||
;; header, the last real node's Succ points at &List.Tail.
|
||||
;;
|
||||
;; Creation and initialization:
|
||||
;; Define the list header as a variable (or inside a memory() area),
|
||||
;; then call init(listptr) once before any other use. It sets
|
||||
;; Head = &Tail, Tail = 0 and TailPred = &Head. Nodes need no
|
||||
;; explicit setup beyond being cleared; fill in Pri/Name as needed
|
||||
;; before using enqueue() or find_name().
|
||||
;;
|
||||
;; Empty test: the list is empty iff TailPred points back at the
|
||||
;; header itself (equivalently Head == &Tail); see is_empty().
|
||||
;;
|
||||
;; Operations (plain Prog8 subroutines in this module):
|
||||
;; - add_head / add_tail: link node at front / back of the list.
|
||||
;; - insert(list, node, pred): links node after the given predecessor
|
||||
;; node anywhere in the list; pred = 0 means insert at head.
|
||||
;; - enqueue(list, node): priority-sorted insertion using Pri,
|
||||
;; placed before the first lower-priority node so equal priorities
|
||||
;; come out FIFO. Requires the FullNode layout.
|
||||
;; - remove(node): unlinks a node from whichever list it currently
|
||||
;; sits in; it must actually be linked into one.
|
||||
;; - remove_head / remove_tail: unlink and return the first / last
|
||||
;; node, or null if empty; these give you queue and stack behaviour.
|
||||
;; - find_name(list, name): linear search over Name strings, returns
|
||||
;; matching node or null. Requires the FullNode layout.
|
||||
;;
|
||||
;; Notes: none of these routines provide any form of locking; keep
|
||||
;; all manipulation of a shared list within one execution context or
|
||||
;; add your own protection around it. Never call remove() on a node
|
||||
;; that is not currently linked into a list.
|
||||
;;
|
||||
;; Allocator agnostic: the routines never allocate or free memory
|
||||
;; themselves, they only link the `pointer` values you pass in. Nodes
|
||||
;; and headers can come from static `[]` variables, `memory()` slabs,
|
||||
;; or any arena/bump allocator such as `arena_alloc(sizeof(MyNode))`
|
||||
;; as `^^MyNode`. Just ensure the allocation is suitably aligned.
|
||||
;;
|
||||
;; Custom nodes: you can define your own node struct for use with these
|
||||
;; routines and with `for node in mylist` loops. The only requirement
|
||||
;; is that the link pointers are the first fields at offset 0. The
|
||||
;; minimal operations (add_head/add_tail/insert/remove) only need
|
||||
;; Succ/Pred; enqueue() and find_name() require the full Type/Pri/Name
|
||||
;; layout of FullNode. Example:
|
||||
;;
|
||||
;; main {
|
||||
;; struct MyNode {
|
||||
;; ^^MyNode Succ ; must be first (offset 0)
|
||||
;; ^^MyNode Pred ; must be second
|
||||
;; ubyte value ; payload follows
|
||||
;; }
|
||||
;; struct MyList {
|
||||
;; ^^MyNode Head
|
||||
;; pointer Tail
|
||||
;; ^^MyNode TailPred
|
||||
;; }
|
||||
;; sub start() {
|
||||
;; ^^MyList mylist = [] ; zero-initialized header
|
||||
;; ^^MyNode n1 = [0, 0, 11] ; Succ, Pred, value
|
||||
;; ^^MyNode n2 = [0, 0, 22]
|
||||
;; lists.init(mylist)
|
||||
;; lists.add_tail(mylist, n1)
|
||||
;; lists.add_tail(mylist, n2)
|
||||
;; }
|
||||
;; }
|
||||
|
||||
lists {
|
||||
%option ignore_unused
|
||||
|
||||
|
||||
@@ -705,11 +705,15 @@ _after:
|
||||
val fieldName = fieldIdent.nameInSource.first()
|
||||
val fieldDt = struct?.getFieldType(fieldName)
|
||||
if(struct!=null && fieldDt!=null) {
|
||||
// (ptr).field --> peekXXX(ptr as uword + offsetof(Struct.field))
|
||||
// (ptr).field --> peekXXX(ptr as uword/long + offsetof(Struct.field))
|
||||
val offset = struct.offsetof(fieldName, program.target)!!.toInt()
|
||||
val ptrAsUword = TypecastExpression(expr.left.copy(), DataType.UWORD, true, expr.left.position)
|
||||
val address: Expression = if(offset==0) ptrAsUword
|
||||
else BinaryExpression(ptrAsUword, "+", NumericLiteral.optimalInteger(offset, expr.position), expr.position)
|
||||
// cast to an integer type of the target's pointer size: keeps the '+' unscaled
|
||||
// if ptr is already a typecast (e.g. t.port as ^^Thing), unwrap to avoid
|
||||
// a WORD->POINTER->LONG chain that confuses the IR register allocator on 32-bit targets
|
||||
val ptrSrc = (expr.left as? TypecastExpression)?.expression ?: expr.left
|
||||
val ptrAsInt = TypecastExpression(ptrSrc.copy(), target.pointerType, true, ptrSrc.position)
|
||||
val address: Expression = if(offset==0) ptrAsInt
|
||||
else BinaryExpression(ptrAsInt, "+", NumericLiteral.optimalInteger(offset, expr.position), expr.position)
|
||||
fun peekCall(func: String) =
|
||||
FunctionCallExpression(IdentifierReference(listOf(func), expr.position), mutableListOf(address), expr.position)
|
||||
val readExpr: Expression = when {
|
||||
@@ -720,7 +724,11 @@ _after:
|
||||
fieldDt.isSignedWord -> TypecastExpression(peekCall("peekw"), DataType.WORD, true, expr.position)
|
||||
fieldDt.isLong -> peekCall("peekl")
|
||||
fieldDt.isFloat -> peekCall("peekf")
|
||||
fieldDt.isPointer -> TypecastExpression(peekCall("peekw"), fieldDt, true, expr.position)
|
||||
fieldDt.isPointer -> {
|
||||
// pointer fields must be read with the target's pointer size
|
||||
val peekName = if(target.POINTER_MEM_SIZE > 2u) "peekl" else "peekw"
|
||||
TypecastExpression(peekCall(peekName), fieldDt, true, expr.position)
|
||||
}
|
||||
else -> {
|
||||
errors.err("unsupported field type for pointer dereference", expr.position)
|
||||
return noModifications
|
||||
|
||||
@@ -258,6 +258,24 @@ class TestListIteration: FunSpec({
|
||||
result shouldNotBe null
|
||||
}
|
||||
|
||||
test("amiga lists module struct aliases resolve in for loop") {
|
||||
val result = compileText(Amiga500Target(), false, """
|
||||
%import lists
|
||||
main {
|
||||
sub start() {
|
||||
^^lists.List lst = memory("lst", sizeof(lists.List), 0)
|
||||
^^lists.Node node = memory("node", sizeof(lists.Node), 0)
|
||||
lists.init(lst)
|
||||
lists.add_tail(lst, node)
|
||||
for ^^lists.Node current in lst {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
""", outputDir)
|
||||
result shouldNotBe null
|
||||
}
|
||||
|
||||
test("list raw pointer Head should fail - not iterable") {
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), false, """
|
||||
|
||||
@@ -3267,26 +3267,26 @@ main {
|
||||
d1.target.nameInSource shouldBe listOf("peekbool")
|
||||
val d1addr = d1.args.single() as BinaryExpression
|
||||
d1addr.operator shouldBe "+"
|
||||
(d1addr.right as NumericLiteral).number shouldBe 4.0 // offsetof(Thing.flag)
|
||||
(d1addr.right as NumericLiteral).number shouldBe 6.0 // offsetof(Thing.flag) on VM (32-bit pointers: port 0, next 2..5, flag 6)
|
||||
|
||||
val d2 = assigns.getValue("d2").value as TypecastExpression
|
||||
d2.type shouldBe DataType.UBYTE
|
||||
(d2.expression as FunctionCallExpression).target.nameInSource shouldBe listOf("peekbool")
|
||||
|
||||
// pointer-typed field read: peekw + cast back to the pointer type
|
||||
// pointer-typed field read: peekl (+ cast) on VM (32-bit pointers)
|
||||
val d3 = assigns.getValue("d3").value as TypecastExpression
|
||||
d3.type.isPointer shouldBe true
|
||||
val d3peek = d3.expression as FunctionCallExpression
|
||||
d3peek.target.nameInSource shouldBe listOf("peekw")
|
||||
d3peek.target.nameInSource shouldBe listOf("peekl")
|
||||
((d3peek.args.single() as BinaryExpression).right as NumericLiteral).number shouldBe 2.0 // offsetof(Thing.next)
|
||||
|
||||
// chained: inner pointer-typed field is read via peekw first, then .flag via peekbool
|
||||
// chained: inner pointer-typed field is read via peekl first, then .flag via peekbool
|
||||
val d4 = assigns.getValue("d4").value as FunctionCallExpression
|
||||
d4.target.nameInSource shouldBe listOf("peekbool")
|
||||
val d4addr = d4.args.single() as BinaryExpression
|
||||
(d4addr.right as NumericLiteral).number shouldBe 4.0
|
||||
val d4inner = (d4addr.left as TypecastExpression).expression as TypecastExpression
|
||||
(d4inner.expression as FunctionCallExpression).target.nameInSource shouldBe listOf("peekw")
|
||||
(d4addr.right as NumericLiteral).number shouldBe 6.0
|
||||
val d4innerPeek = d4addr.left as FunctionCallExpression
|
||||
d4innerPeek.target.nameInSource shouldBe listOf("peekl")
|
||||
|
||||
// explicit parenthesized chain desugars the same
|
||||
(assigns.getValue("d5").value as FunctionCallExpression).target.nameInSource shouldBe listOf("peekbool")
|
||||
|
||||
@@ -299,6 +299,66 @@ testblock {
|
||||
compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=false, errors=errors) shouldNotBe null
|
||||
errors.errors.size shouldBe 0
|
||||
}
|
||||
|
||||
test("struct alias resolves for vardecl") {
|
||||
val src="""
|
||||
%import exec
|
||||
main {
|
||||
alias Node = exec.MinNode
|
||||
sub start() {
|
||||
^^Node n = memory("n", sizeof(Node), 0)
|
||||
n.Succ = 0
|
||||
}
|
||||
}
|
||||
"""
|
||||
compileText(Amiga500Target(), false, src, outputDir) shouldNotBe null
|
||||
}
|
||||
|
||||
test("struct alias resolves for typecast") {
|
||||
val src="""
|
||||
%import exec
|
||||
main {
|
||||
alias Node = exec.MinNode
|
||||
sub start() {
|
||||
^^Node n = memory("n", sizeof(Node), 0)
|
||||
uword w = n as uword
|
||||
^^Node n2 = w as ^^Node
|
||||
n2.Succ = 0
|
||||
}
|
||||
}
|
||||
"""
|
||||
compileText(Amiga500Target(), false, src, outputDir) shouldNotBe null
|
||||
}
|
||||
|
||||
test("struct alias chain resolves") {
|
||||
val src="""
|
||||
%import exec
|
||||
main {
|
||||
alias Node = exec.MinNode
|
||||
alias A = Node
|
||||
sub start() {
|
||||
^^A n = memory("n", sizeof(A), 0)
|
||||
n.Succ = 0
|
||||
}
|
||||
}
|
||||
"""
|
||||
compileText(Amiga500Target(), false, src, outputDir) shouldNotBe null
|
||||
}
|
||||
|
||||
test("struct alias cycle is rejected") {
|
||||
val src="""
|
||||
main {
|
||||
alias A = B
|
||||
alias B = A
|
||||
sub start() {
|
||||
^^A n = memory("n", sizeof(A), 0)
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(Amiga500Target(), false, src, outputDir, errors=errors) shouldBe null
|
||||
errors.errors.any { it.contains("alias loop") } shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
context("strings") {
|
||||
|
||||
@@ -906,6 +906,100 @@ fully outside, or partially crossing the clipping rectangle. With ``inside`` you
|
||||
Returns ``visible`` (boolean), plus the clipped coordinates (or (0,0)-(0,0) if not visible).
|
||||
|
||||
|
||||
lists
|
||||
^^^^^
|
||||
.. index:: pair: Libraries; lists
|
||||
|
||||
Portable doubly linked lists modeled after Amiga Exec lists (``MinList``/``MinNode``).
|
||||
Available on all targets; on ``amiga500`` the types and routines simply forward to ``exec.library``.
|
||||
|
||||
Three struct types are provided:
|
||||
|
||||
- ``lists.Node`` - minimal node with ``Succ`` and ``Pred`` link pointers (``^^Node``)
|
||||
- ``lists.List`` - list header with ``Head``, ``Tail`` and ``TailPred`` pointers
|
||||
- ``lists.FullNode`` - extended node with ``Type``, ``Pri`` and ``Name`` fields for priority/name operations
|
||||
|
||||
On ``amiga500`` these are aliases: ``Node = exec.MinNode``, ``List = exec.MinList``, ``FullNode = exec.Node``.
|
||||
|
||||
A list header must be initialized once with ``init(listptr)`` (sets ``Head = &Tail``, ``Tail = 0``, ``TailPred = &Head``).
|
||||
The list can then be manipulated with:
|
||||
|
||||
- ``init(listptr)``, ``is_empty(listptr) -> bool``
|
||||
- ``add_head(listptr, node)``, ``add_tail(listptr, node)``, ``insert(listptr, node, pred)`` (``pred=0`` inserts at head)
|
||||
- ``remove(node)``, ``remove_head(listptr) -> pointer``, ``remove_tail(listptr) -> pointer``
|
||||
- ``enqueue(listptr, node)`` - priority-sorted insertion by ``Pri`` (requires ``FullNode`` layout, FIFO for equal priorities)
|
||||
- ``find_name(listptr, name) -> pointer`` - linear search by ``Name`` (requires ``FullNode`` layout)
|
||||
|
||||
Lists are directly iterable with ``for`` loops: ``for node in mylist`` iterates forward via ``Succ``,
|
||||
``for node in mylist step -1`` iterates backward via ``Pred``. The loop variable type (``^^Node`` or ``^^FullNode``) can be inferred.
|
||||
|
||||
Custom node types are supported by making sure the link pointers are the first fields at offset 0.
|
||||
For the minimal list operations (``add_head``, ``add_tail``, ``insert``, ``remove``) only ``Succ``/``Pred`` are required;
|
||||
``enqueue`` and ``find_name`` require the full ``Type``/``Pri``/``Name`` layout of ``FullNode``.
|
||||
Example::
|
||||
|
||||
main {
|
||||
struct MyNode {
|
||||
^^MyNode Succ ; must be first field (offset 0)
|
||||
^^MyNode Pred ; must be second field
|
||||
ubyte value ; your own payload follows
|
||||
}
|
||||
struct MyList {
|
||||
^^MyNode Head
|
||||
pointer Tail
|
||||
^^MyNode TailPred
|
||||
}
|
||||
|
||||
sub start() {
|
||||
^^MyList mylist = [] ; zero-initialized header
|
||||
^^MyNode n1 = [0, 0, 42] ; Succ, Pred, value
|
||||
^^MyNode n2 = [0, 0, 99]
|
||||
lists.init(mylist)
|
||||
lists.add_tail(mylist, n1)
|
||||
lists.add_tail(mylist, n2)
|
||||
for n in mylist {
|
||||
txt.print_ub(n.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
On ``amiga500`` the same holds. Define the list header with your node type so
|
||||
``for``-iteration infers the correct type, and include ``Type``/``Pri``/``Name``
|
||||
if you need the full ``exec.Node`` layout. The header can still be
|
||||
initialized with ``exec.NewList``::
|
||||
|
||||
main {
|
||||
struct MyNode {
|
||||
^^MyNode Succ ; offset 0
|
||||
^^MyNode Pred ; offset 4
|
||||
ubyte Type ; include for FullNode compatibility if needed
|
||||
byte Pri
|
||||
str Name
|
||||
ubyte value
|
||||
}
|
||||
struct MyList {
|
||||
^^MyNode Head
|
||||
pointer Tail
|
||||
^^MyNode TailPred
|
||||
}
|
||||
sub start() {
|
||||
^^MyList mylist = []
|
||||
^^MyNode n1 = [0, 0, 0, 0, 0, 11]
|
||||
exec.NewList(mylist as ^^exec.List)
|
||||
lists.add_tail(mylist as pointer, n1 as pointer)
|
||||
}
|
||||
}
|
||||
|
||||
Allocator agnostic: the routines never allocate or free memory themselves,
|
||||
they only link the ``pointer`` values you pass in. Nodes and headers can
|
||||
come from static ``[]`` variables as above, from ``memory()`` slabs, or
|
||||
from any arena/bump allocator - e.g. ``arena_alloc(sizeof(MyNode)) as ^^MyNode``
|
||||
or ``exec.AllocMem`` on ``amiga500``. Just ensure the allocation is
|
||||
suitably aligned (even address on 6502, long-aligned on m68k).
|
||||
|
||||
Read the :source:`lists source code <compiler/res/prog8lib/lists.p8>` and
|
||||
:source:`amiga lists source code <compiler/res/prog8lib/amiga500/lists.p8>` for details.
|
||||
|
||||
math
|
||||
^^^^
|
||||
.. index:: pair: Libraries; math
|
||||
|
||||
@@ -148,7 +148,8 @@ You can use boolean fields, numeric fields (byte, word, long, float), and pointe
|
||||
You cannot nest struct types, but inline arrays are supported as fields. 2D arrays are not allowed as struct fields.
|
||||
Fields in a struct are 'packed' (meaning the values are placed back-to-back in memory), and placed in memory in order of declaration. This guarantees exact size and place of the fields.
|
||||
``sizeof()`` knows how to calculate the combined size of a struct, and ``offsetof()`` can be used to get the byte offset of a given field in the struct.
|
||||
The size of a struct cannot exceed 1 memory page (256 bytes).
|
||||
The size of a struct cannot exceed 256 bytes on 6502-based targets (C64, CX16, C128, PET32 - one memory page).
|
||||
On 32-bit targets the limit is much larger: 32768 bytes on Amiga/Qemu68k and 65535 bytes on the virtual target.
|
||||
|
||||
You can copy the whole contents of a struct to another one by assigning the dereferenced pointers::
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ TODO
|
||||
|
||||
Future Things and Ideas
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
- allow aliasing of struct definitions? that could then also solve the duplicate struct List and struct Node in list.p8, those should be identical to the leading exec.List and exec.Node (only on amiga500 target!)
|
||||
- DataType.ARRAY_POINTER depends on the compilation target to be either a split word array or not. This is horrible because now we have to check with the compilation target everywhere to see if a DataType enumeration value is split word array, and PtVariable and PtArrayIndexer need an explicit boolean to tell us if this is the case. See ideas/remove_array_pointer_plan.md for the plan.
|
||||
- m68k codegen: make use of scaling factors in the indexed instructions on 68020+ ? see ideas/scaled-indexing-IR.md
|
||||
- support arrays-of-structs, see ideas/arrays-of-structs.md
|
||||
|
||||
Reference in new issue
Block a user