@Araq A section pragma sounds like a comprehensive solution, but also it sounds
like it would be really hard to get right. Would it be like codegenDecl but
with an awareness of what the sections actually mean? Different toolchains
might have different section names with different restrictions. Would it just
recognise common ones like .text, .bss, .rodata?
For read-only sections, you would have to ensure that the variable is fully
initialised at the top level. This initialisation might contain pointers to
valid things such as procedures or other read-only variables, but I guess it
should disallow (or at least not be expected to work with) regular runtime
variables.
But looking at generated C code, it seems like this goes against how top-level
var/let in Nim generally works (where initialisations are mostly done at
runtime in the module's 'Init' function)
Ideally it should be possible to make a read-only linked list of pointers to
procedures:
type Action = object
body: proc () {.nimcall.}
next: ptr Action
proc fn1() = echo "first"
proc fn2() = echo "second"
let a2 {.section:rodata.} = Action(body: fn2, next: nil)
let a1 {.section:rodata.} = Action(body: fn1, next: unsafeAddr a2)
var current = unsafeAddr a1
while current != nil:
current.body()
current = current.next
Run
Which would be equivalent to the following C code:
typedef struct Action {
void (*body)(void);
const struct Action *next;
} Action;
void fn1(void) {
printf("first\n");
}
void fn2(void) {
printf("second\n");
}
static const Action a2 __attribute__((section("rodata"))) = { .body = fn2,
.next = NULL };
static const Action a1 __attribute__((section("rodata"))) = { .body = fn1,
.next = &a2 };
int main() {
const Action *current = &a1;
while (current) {
current->body();
current = current->next;
}
}
Run
Do you think this kind of output would be possible? Would an {.addressable.}
pragma for consts be easier? And if so would it still be possible to make it
that work with pointers to procs?
-
@mratsim Unfortunately that doesn't do what I need. Yes, a C const is
generated, but it gets copied into a mutable container. So it's still taking up
RAM. For example, this code currently works when it shouldn't, if arr was truly
constant:
let arr = [1,2,3,4,5]
let a = unsafeAddr(arr)
a[2] = 100
echo arr # outputs [1, 2, 100, 4, 5]
Run
Besides that, I often need more than just simple arrays. Here are some more
concrete examples of things I want to be constant in the underlying C code:
Animation data:
type AnimData = object
loop: bool
frames: ptr UncheckedArray[int]
len: int
let walkFrames = [4,5,6,7,8,9]
let walkAnimation = AnimData(
loop: true,
frames: cast[ptr UncheckedArray[int]](unsafeAddr walkFrames),
len: walkFrames.len
)
# pointer to the currently playing animation
var currentAnim = unsafeAddr(walkAnimation)
Run
Dispatch tables for scenes, entity behaviors, etc. :
type Scene = object
show: proc () {.nimcall.}
hide: proc () {.nimcall.}
update: proc () {.nimcall.}
draw: proc () {.nimcall.}
proc onShow() =
echo "enter main menu"
proc onHide() =
echo "leave main menu"
proc onUpdate() =
echo "updating main menu"
proc onDraw() =
echo "drawing main menu"
let mainMenu = Scene(
show: onShow,
hide: onHide,
update: onUpdate,
draw: onDraw,
)
# pointer to the current scene
var currentScene = unsafeAddr(mainMenu)
Run