always put all struct types as .struct in asm code to make them all accessible for size and offsets

This commit is contained in:
Irmen de Jong
2025-12-10 21:39:44 +01:00
parent 745cf3d958
commit 014a82a1ee
5 changed files with 109 additions and 27 deletions

View File

@@ -412,10 +412,9 @@ internal class ProgramAndVarsGen(
asmgen.out("; struct types") asmgen.out("; struct types")
symboltable.allStructInstances.distinctBy { it.structName }.forEach { symboltable.allStructTypes().distinctBy { it.name }.forEach { structtype ->
val structtype: StStruct = symboltable.lookup(it.structName) as StStruct
val structargs = structtype.fields.withIndex().joinToString(",") { field -> "f${field.index}" } val structargs = structtype.fields.withIndex().joinToString(",") { field -> "f${field.index}" }
asmgen.out("${it.structName} .struct $structargs\n") asmgen.out("${structtype.scopedNameString} .struct $structargs\n")
structtype.fields.withIndex().forEach { (index, field) -> structtype.fields.withIndex().forEach { (index, field) ->
val dt = field.first val dt = field.first
val varname = "f${index}" val varname = "f${index}"
@@ -425,7 +424,7 @@ internal class ProgramAndVarsGen(
asmgen.out(" .endstruct\n") asmgen.out(" .endstruct\n")
} }
val (instancesNoInit, instances) = symboltable.allStructInstances.partition { it.initialValues.isEmpty() } val (instancesNoInit, instances) = symboltable.allStructInstances().partition { it.initialValues.isEmpty() }
asmgen.out("; struct instances without initialization values, as BSS zeroed at startup\n") asmgen.out("; struct instances without initialization values, as BSS zeroed at startup\n")
asmgen.out(" .section BSS\n") asmgen.out(" .section BSS\n")
asmgen.out("${StStructInstanceBlockName}_bss .block\n") asmgen.out("${StStructInstanceBlockName}_bss .block\n")

View File

@@ -514,5 +514,37 @@ main {
compileText(Cx16Target(), false, src, outputDir, writeAssembly = false) shouldNotBe null compileText(Cx16Target(), false, src, outputDir, writeAssembly = false) shouldNotBe null
} }
test("const struct sizes and offsets, also available in assembly code") {
val src = """
main {
struct Node {
ubyte type
uword value
bool flag
}
sub start() {
const ubyte size = sizeof(Node)
const ubyte offset1 = offsetof(Node.type)
const ubyte offset2 = offsetof(Node.value)
const ubyte offset3 = offsetof(Node.flag)
%asm {{
lda p8c_size
lda p8c_offset1
lda p8c_offset2
lda p8c_offset3
lda #size(p8t_Node)
lda #p8t_Node.p8v_type
lda #p8t_Node.p8v_value
lda #p8t_Node.p8v_flag
}}
}
}"""
compileText(Cx16Target(), false, src, outputDir, writeAssembly = true) shouldNotBe null
}
}) })

View File

@@ -261,3 +261,40 @@ Address-Of: untyped vs typed
``&`` still returns an untyped (uword) pointer, as it did in older Prog8 versions. This is for backward compatibility reasons so existing programs don't break. ``&`` still returns an untyped (uword) pointer, as it did in older Prog8 versions. This is for backward compatibility reasons so existing programs don't break.
The new *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 The new *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. a number from a typed pointer uses *pointer arithmetic* that takes the size of the value that it points to into account.
Accessing struct definitions in Assembly code
---------------------------------------------
Prog8 lets you query the size of a struct type, and the offsets of a field.
You can do the same in assembly code if needed: the struct definition gets written as `.struct` into the assembly file::
; prog8 struct declaration:
struct Node {
ubyte type
uword value
bool flag
}
; generates this assembly code:
; (the symbol prefixes are explained in the 'Technical details' chapter)
p8b_main.p8t_Node .struct f0,f1,f2
p8v_type .byte \f0
p8v_value .word \f1
p8v_flag .byte \f2
.endstruct
64tass then lets you query that information::
; prog8 code:
ubyte size = sizeof(Node)
ubyte offset1 = offsetof(Node.type)
ubyte offset2 = offsetof(Node.value)
ubyte offset3 = offsetof(Node.flag)
; assembly equivalents (64tass syntax):
lda #size(p8t_Node)
lda #p8t_Node.p8v_type
lda #p8t_Node.p8v_value
lda #p8t_Node.p8v_flag

View File

@@ -4,28 +4,28 @@
main { main {
struct Node {
ubyte type
uword value
bool flag
}
sub start() { sub start() {
uword @shared string = 20000 const ubyte size = sizeof(Node)
ubyte[200] barray const ubyte offset1 = offsetof(Node.type)
uword[100] @nosplit warray const ubyte offset2 = offsetof(Node.value)
ubyte @shared length const ubyte offset3 = offsetof(Node.flag)
uword @shared lengthw
cx16.r0L = string[length] %asm {{
cx16.r1L = string[lengthw] lda p8c_size
cx16.r0bL = string[length]!=0 lda p8c_offset1
cx16.r1bL = string[lengthw]!=0 lda p8c_offset2
while string[length]!=0 lda p8c_offset3
break lda #size(p8t_Node)
while string[lengthw]!=0 lda #p8t_Node.p8v_type
break lda #p8t_Node.p8v_value
lda #p8t_Node.p8v_flag
}}
string[length] = 42
string[lengthw] = 42
;cx16.r1L = barray[length]
;cx16.r2 = warray[length]
} }
} }

View File

@@ -81,7 +81,7 @@ class SymbolTable(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GL
vars vars
} }
val allStructInstances: Collection<StStructInstance> by lazy { fun allStructInstances(): Collection<StStructInstance> {
val vars = mutableListOf<StStructInstance>() val vars = mutableListOf<StStructInstance>()
fun collect(node: StNode) { fun collect(node: StNode) {
for(child in node.children) { for(child in node.children) {
@@ -92,7 +92,21 @@ class SymbolTable(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GL
} }
} }
collect(this) collect(this)
vars return vars
}
fun allStructTypes(): Collection<StStruct> {
val vars = mutableListOf<StStruct>()
fun collect(node: StNode) {
for(child in node.children) {
if(child.value.type == StNodeType.STRUCT)
vars.add(child.value as StStruct)
else
collect(child.value)
}
}
collect(this)
return vars
} }
override fun lookup(scopedName: String) = flat[scopedName] override fun lookup(scopedName: String) = flat[scopedName]