Merge pull request #2311 from bbbradsmith/ca65-struct-doc-clarify

ca65 struct and union documentation improvement and feature testing.
This commit is contained in:
Bob Andrews 2023-12-31 18:51:09 +01:00 committed by GitHub
commit d0903ba225
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 293 additions and 11 deletions

View File

@ -4086,7 +4086,9 @@ See: <tt><ref id=".ASCIIZ" name=".ASCIIZ"></tt>,<tt><ref id=".BYTE" name=".BYTE"
<sect1><tt>.TAG</tt><label id=".TAG"><p>
Allocate space for a struct or union.
Allocate space for a struct or union. This is equivalent to
<tt><ref id=".RES" name=".RES"></tt> with the
<tt><ref id=".SIZEOF" name=".SIZEOF"></tt> of a struct.
Example:
@ -4100,6 +4102,7 @@ See: <tt><ref id=".ASCIIZ" name=".ASCIIZ"></tt>,<tt><ref id=".BYTE" name=".BYTE"
.tag Point ; Allocate 4 bytes
</verb></tscreen>
See: <ref id="structs" name="&quot;Structs and unions&quot;">
<sect1><tt>.UNDEF, .UNDEFINE</tt><label id=".UNDEFINE"><p>
@ -4865,10 +4868,15 @@ compiler, depending on the target system selected:
Structs and unions are special forms of <ref id="scopes" name="scopes">. They
are, to some degree, comparable to their C counterparts. Both have a list of
members. Each member allocates storage, and optionally may have a name whose
value, in the case of a struct, usually is the storage offset from the
beginning, and in the case of a union, doesn't change, and usually is zero.
members. Each member allocates storage, and optionally may have a name.
Each named member has a constant value equal to the storage offset from the
beginning of the structure. In the case of a union, all members are placed at
the same offset, typically 0.
Each named member also has a storage size which can be accessed with the
<tt><ref id=".SIZEOF" name=".SIZEOF"></tt> operator. The struct or union itself
also has a <tt/.SIZEOF/ indicating its total storage size.
<sect1>Declaration<p>
@ -4895,8 +4903,9 @@ A struct or union may not necessarily have a name. If it is anonymous, no
local scope is opened; the identifiers used to name the members are placed
into the current scope instead.
A struct may contain unnamed members and definitions of local structs/unions.
The storage allocators may contain a multiplier, as in the example below:
Storage allocators may contain a multiplier. A struct may also contain members
and definitions of local structs/unions. Example:
<tscreen><verb>
.struct Circle
.struct Point
@ -4905,7 +4914,8 @@ The storage allocators may contain a multiplier, as in the example below:
Radius .word
.endstruct
</verb></tscreen>
The size of the Circle struct is 6 (three words).
In this example the size of the Circle struct is 6 (three words).
<sect1>The storage allocator keywords<p>
@ -4915,7 +4925,7 @@ The size of the Circle struct is 6 (three words).
<tag/.BYTE, .RES/
Allocates multiples of 1 byte. <tt/.RES/ requires an operand.
<tag/.DBYTE, .WORD, .ADDR/
<tag/.DBYT, .WORD, .ADDR/
Allocates multiples of 2 bytes.
<tag/.FARADDR/
@ -4924,6 +4934,15 @@ The size of the Circle struct is 6 (three words).
<tag/.DWORD/
Allocates multiples of 4 bytes.
<tag/.TAG/
Allocates a previously defined struct.
<tag/.STRUCT, .UNION/
Begins a nested .struct or .union definition, and allocates it.
Note that its member offset values will begin at 0, unless this nested
structure is anonymous, in which case they will instead become members of
the enclosing scope.
</descrip>
@ -4968,13 +4987,54 @@ name=".TAG"> directive.
C: .tag Circle
</verb></tscreen>
Currently, members are just offsets from the start of the struct or union. To
Members are just offsets from the start of the struct or union. To
access a field of a struct, the member offset must be added to the address of
the struct variable itself:
<tscreen><verb>
lda C+Circle::Radius ; Load circle radius into A
lda C + Circle::Radius ; Load circle radius
lda C + Circle::Origin + Point::ycoord ; Load circle origin.ycoord
</verb></tscreen>
That may change in a future version of the assembler.
Nested structures or unions are treated differently depending on whether they
are anonymous. If named, a new structure definition is created within the
enclosing scope, with its offsets beginning at 0. If anonymous, the members of
the new structure are added to the enclosing scope instead, with offsets
continuing through that scope. Example:
<tscreen><verb>
.struct Object
id .byte ; Object::id = 0
target .struct Point ; Object::target = 1
xcoord .word ; Object::Point::xcoord = 0
ycoord .word ; Object::Point::ycoord = 2
.endstruct
cost .struct ; Object::cost = 5
price .word ; Object::price = 5
tax .word ; Object::tax = 7
.endstruct
.struct
radius .word ; Object::radius = 9
.endstruct
.endstruct
O: .tag Object
lda O + Object::target + Object::Point::ycoord ; Named struct
lda O + Object::tax ; Anonymous
lda O + Object::radius ; Anonymous
; Be careful not to use a named nested structure without also adding the
; offset to the nested structure itself.
lda O + Object::Point::ycoord ; Incorrect!
lda O + Object::target + Object::Point::ycoord ; Correct
</verb></tscreen>
In this example, the first nested structure is named "Point", and its member
offsets begin at 0. On the other hand, the two anonymous structures simply
continue to add members to the enclosing "Object" structure.
Note that an anonymous structure does not need a member name, since all of its
members become part of the enclosing structure. The "cost" member in the
example is redundantly the same offset as its first member "price".
<sect1>Limitations<p>

222
test/asm/val/struct.s Normal file
View File

@ -0,0 +1,222 @@
; test .struct and .union features
.code
; exit with 0
.export _main
_main:
lda #0
tax
rts
; test storage allocator sizes and offsets
.struct Storage
mb1 .byte
mb5 .byte 5
mr1 .res 1
mr5 .res 5
mdb1 .dbyt
mdb5 .dbyt 5
mw1 .word
mw5 .word 5
ma1 .addr
ma5 .addr 5
mf1 .faraddr
mf5 .faraddr 5
mdw1 .dword
mdw5 .dword 5
.endstruct
.assert .sizeof(Storage::mb1) = 1, error, ".struct .byte member has unexpected .sizeof"
.assert .sizeof(Storage::mb5) = 5, error, ".struct .byte 5 member has unexpected .sizeof"
.assert .sizeof(Storage::mr1) = 1, error, ".struct .res 1 member has unexpected .sizeof"
.assert .sizeof(Storage::mr5) = 5, error, ".struct .res 5 member has unexpected .sizeof"
.assert .sizeof(Storage::mdb1) = 2, error, ".struct .dbyt member has unexpected .sizeof"
.assert .sizeof(Storage::mdb5) = 10, error, ".struct .dbyt 5 member has unexpected .sizeof"
.assert .sizeof(Storage::mw1) = 2, error, ".struct .word member has unexpected .sizeof"
.assert .sizeof(Storage::mw5) = 10, error, ".struct .word 5 member has unexpected .sizeof"
.assert .sizeof(Storage::ma1) = 2, error, ".struct .addr member has unexpected .sizeof"
.assert .sizeof(Storage::ma5) = 10, error, ".struct .addr 5 member has unexpected .sizeof"
.assert .sizeof(Storage::mf1) = 3, error, ".struct .faraddr member has unexpected .sizeof"
.assert .sizeof(Storage::mf5) = 15, error, ".struct .faraddr 5 member has unexpected .sizeof"
.assert .sizeof(Storage::mdw1) = 4, error, ".struct .dword member has unexpected .sizeof"
.assert .sizeof(Storage::mdw5) = 20, error, ".struct .dword 5 member has unexpected .sizeof"
.assert Storage::mb1 = 0, error, ".struct storage offset is incorrect"
.assert Storage::mb5 = Storage::mb1 + .sizeof(Storage::mb1), error, ".struct storage offset is incorrect"
.assert Storage::mr1 = Storage::mb5 + .sizeof(Storage::mb5), error, ".struct storage offset is incorrect"
.assert Storage::mr5 = Storage::mr1 + .sizeof(Storage::mr1), error, ".struct storage offset is incorrect"
.assert Storage::mdb1 = Storage::mr5 + .sizeof(Storage::mr5), error, ".struct storage offset is incorrect"
.assert Storage::mdb5 = Storage::mdb1 + .sizeof(Storage::mdb1), error, ".struct storage offset is incorrect"
.assert Storage::mw1 = Storage::mdb5 + .sizeof(Storage::mdb5), error, ".struct storage offset is incorrect"
.assert Storage::mw5 = Storage::mw1 + .sizeof(Storage::mw1), error, ".struct storage offset is incorrect"
.assert Storage::ma1 = Storage::mw5 + .sizeof(Storage::mw5), error, ".struct storage offset is incorrect"
.assert Storage::ma5 = Storage::ma1 + .sizeof(Storage::ma1), error, ".struct storage offset is incorrect"
.assert Storage::mf1 = Storage::ma5 + .sizeof(Storage::ma5), error, ".struct storage offset is incorrect"
.assert Storage::mf5 = Storage::mf1 + .sizeof(Storage::mf1), error, ".struct storage offset is incorrect"
.assert Storage::mdw1 = Storage::mf5 + .sizeof(Storage::mf5), error, ".struct storage offset is incorrect"
.assert Storage::mdw5 = Storage::mdw1 + .sizeof(Storage::mdw1), error, ".struct storage offset is incorrect"
.assert .sizeof(Storage) = Storage::mdw5 + .sizeof(Storage::mdw5), error, ".struct has unexpected .sizeof"
; test union offset and size
.union UStorage
mb1 .byte
mb5 .byte 5
mr1 .res 1
mr5 .res 5
mdb1 .dbyt
mdb5 .dbyt 5
mw1 .word
mw5 .word 5
ma1 .addr
ma5 .addr 5
mf1 .faraddr
mf5 .faraddr 5
mdw1 .dword
mdw5 .dword 5
.endunion
.assert .sizeof(UStorage::mb1) = 1, error, ".union .byte member has unexpected .sizeof"
.assert .sizeof(UStorage::mb5) = 5, error, ".union .byte 5 member has unexpected .sizeof"
.assert .sizeof(UStorage::mr1) = 1, error, ".union .res 1 member has unexpected .sizeof"
.assert .sizeof(UStorage::mr5) = 5, error, ".union .res 5 member has unexpected .sizeof"
.assert .sizeof(UStorage::mdb1) = 2, error, ".union .dbyt member has unexpected .sizeof"
.assert .sizeof(UStorage::mdb5) = 10, error, ".union .dbyt 5 member has unexpected .sizeof"
.assert .sizeof(UStorage::mw1) = 2, error, ".union .word member has unexpected .sizeof"
.assert .sizeof(UStorage::mw5) = 10, error, ".union .word 5 member has unexpected .sizeof"
.assert .sizeof(UStorage::ma1) = 2, error, ".union .addr member has unexpected .sizeof"
.assert .sizeof(UStorage::ma5) = 10, error, ".union .addr 5 member has unexpected .sizeof"
.assert .sizeof(UStorage::mf1) = 3, error, ".union .faraddr member has unexpected .sizeof"
.assert .sizeof(UStorage::mf5) = 15, error, ".union .faraddr 5 member has unexpected .sizeof"
.assert .sizeof(UStorage::mdw1) = 4, error, ".union .dword member has unexpected .sizeof"
.assert .sizeof(UStorage::mdw5) = 20, error, ".union .dword 5 member has unexpected .sizeof"
.assert .sizeof(UStorage) = 20, error, ".union has unexpected .sizeof"
.assert UStorage::mb1 = 0, error, ".union storage offset is incorrect"
.assert UStorage::mb5 = 0, error, ".union storage offset is incorrect"
.assert UStorage::mr1 = 0, error, ".union storage offset is incorrect"
.assert UStorage::mr5 = 0, error, ".union storage offset is incorrect"
.assert UStorage::mdb1 = 0, error, ".union storage offset is incorrect"
.assert UStorage::mdb5 = 0, error, ".union storage offset is incorrect"
.assert UStorage::mw1 = 0, error, ".union storage offset is incorrect"
.assert UStorage::mw5 = 0, error, ".union storage offset is incorrect"
.assert UStorage::ma1 = 0, error, ".union storage offset is incorrect"
.assert UStorage::ma5 = 0, error, ".union storage offset is incorrect"
.assert UStorage::mf1 = 0, error, ".union storage offset is incorrect"
.assert UStorage::mf5 = 0, error, ".union storage offset is incorrect"
.assert UStorage::mdw1 = 0, error, ".union storage offset is incorrect"
.assert UStorage::mdw5 = 0, error, ".union storage offset is incorrect"
; test tag
storage: .tag Storage
.assert (*-storage)=.sizeof(Storage), error, ".tag reserved size incorrect"
; test nested structures
.struct Point
xc .word
yc .word
.endstruct
.struct Nested
pad .res 13
tag .tag Point
ch .struct Child
ca .word ; offset = 0
gch .struct Grandchild
gca .word ; offset = 0
gcb .byte
.endstruct
cb .byte
.endstruct
anon .struct
aa .dword ; offset = Nested::anon (anonymous .struct)
ab .dword
.endstruct
chu .union Chunion
ua .byte ; offset = 0
ub .dword
.endunion
chanon .union
uc .byte ; offset = Nested::chanon
ud .dword
.endunion
last .byte
.endstruct
.assert Nested::pad = 0, error, "Nested .struct has unexpected starting offset"
.assert Nested::Child::ca = 0, error, "Nested .struct has unexpected starting offset"
.assert Nested::Child::Grandchild::gca = 0, error, "Nested .struct has unexpected starting offset"
.assert .sizeof(Nested::tag) = .sizeof(Point), error, ".tag in .struct has unexpected .sizeof"
.assert .sizeof(Nested::Child::Grandchild) = 2 + 1, error, "Nested .struct has unexpected .sizeof"
.assert .sizeof(Nested::Child) = 2 + 1 + .sizeof(Nested::Child::Grandchild), error, "Nested .struct has unpexpected .sizeof"
.assert .sizeof(Nested::ch) = .sizeof(Nested::Child), error, "Nested .struct has unexpected member .sizeof"
.assert .sizeof(Nested::Child::gch) = .sizeof(Nested::Child::Grandchild), error, "Nested .struct has unexpected member .sizeof"
.assert .sizeof(Nested::anon) = 8, error, "Nested anonymous member .struct has unexpected .sizeof"
.assert .sizeof(Nested::aa) = 4, error, "Nested anonymous .struct member has unexpected .sizeof"
.assert .sizeof(Nested::ab) = 4, error, "Nested anonymous .struct member has unexpected .sizeof"
.assert .sizeof(Nested::Chunion) = 4, error, "Nested .union has unexpected .sizeof"
.assert .sizeof(Nested::chu) = .sizeof(Nested::Chunion), error, "Nested member .union has unexpected .sizeof"
.assert .sizeof(Nested::chanon) = 4, error, "Nested anonymous member .union as unexpected .sizeof"
.assert Nested::tag = Nested::pad + .sizeof(Nested::pad), error, ".tag within .struct has unexpected offset"
.assert Nested::ch = Nested::tag + .sizeof(Nested::tag), error, "Nested .struct has unexpected offset"
.assert Nested::anon = Nested::ch + .sizeof(Nested::ch), error, "Nested anonymous member .struct has unexpected offset"
.assert Nested::aa = Nested::anon, error, "Nested anonymous .struct member has unexpected offset"
.assert Nested::ab = Nested::aa + .sizeof(Nested::aa), error, "Nested anonymous .struct member has unexpected offset"
.assert Nested::chu = Nested::ab + .sizeof(Nested::ab), error, "Nested member .union has unexpected offset"
.assert Nested::chanon = Nested::chu + .sizeof(Nested::Chunion), error, "Nested anonymous member .union has unexpected offset"
.assert Nested::uc = Nested::chanon, error, "Nested anonymous .union member has unexpected offset"
.assert Nested::ud = Nested::chanon, error, "Nested anonymous .union member has unexpected offset"
.assert Nested::last = Nested::ud + .sizeof(Nested::ud), error, ".struct member has unexpected offset after anonymous nested .struct"
; test .org
start:
.struct OrgStruct
ma .byte
mb .byte
.org $1234
mc .byte
md .byte
.struct Nested
me .byte
.org $5678
mf .byte
mg .byte
.endstruct
mh .byte
.endstruct
.assert start <> (OrgStruct::mh+1), error, "Fatal test error: accidental code position conflict, move OrgStruct .org to another arbitrary address."
.assert * = start, error, ".org within .struct does not return to previous location at .endstruct"
.assert OrgStruct::ma = 0, error, ".struct with .org has unexpected offset"
.assert OrgStruct::mb = 1, error, ".struct with .org has unexpected offset"
.assert OrgStruct::mc = $1234, error, ".struct with .org has unexpected offset"
.assert OrgStruct::md = $1235, error, ".struct with .org has unexpected offset"
.assert OrgStruct::Nested::me = 0, error, "Nested .struct with .org has unexpected offset"
.assert OrgStruct::Nested::mf = $5678, error, "Nested .struct with .org has unexpected offset"
.assert OrgStruct::Nested::mg = $5679, error, "Nested .struct with .org has unexpected offset"
.assert OrgStruct::mh = $1239, error, ".struct with .org has unexpected offset"
.assert .sizeof(OrgStruct) = 8, error, ".struct with .org has unexpected .sizeof"
.union OrgUnion
ma .byte
mb .word
.org $1234
mc .byte
md .word
.endunion
.assert start <> OrgUnion::md, error, "Fatal test error: accidental code position conflict, move OrgUnion .org to another arbitrary address."
.assert * = start, error, ".org within .union does not return to previous location at .endunion"
.assert OrgUnion::ma = 0, error, ".union with .org has unexpected offset"
.assert OrgUnion::mb = 0, error, ".union with .org has unexpected offset"
.assert OrgUnion::mc = $1234, error, ".union with .org has unexpected offset"
.assert OrgUnion::md = $1234, error, ".union with .org has unexpected offset"
.assert .sizeof(OrgUnion) = 2, error, ".union with .org has unexpected .sizeof"