Currently I improve the proceduer computeSize in types.nim. I worked now quite a bit on the procedure, but I don't understand, how system.sizeof eventually calls types.computeSize. I know that in some cases the call system.sizeof was not able to be evaluated at compile time. I would like to know how this decision is made.
Eventually I would like to add system.alignof analogue to system.sizeof. Then I would like to add offsetof, but here I do not know how the interface should look like. This is an idea:
proc offsetof[T](typ: typedesc[T]; member: untyped): int
For my use case it is very important, that I can get all the sizes at compile time, therefore I would like to make all this also available with NimNode arguments:
# macros.nim
proc sizeof(typ: NimNode)
proc alignof(typ: NimNode)
proc offsetof(member: NimNode)
But here again, I don't understand how I can bind these procedures to the functions I defined in types.nim.
I need it for my current pull request
Either add the handling to:
Or:
# DON'T forget to update ast.SpecialSemMagics if you add a magic here!
With (2) you can bypass the overloading resolution mechanism which introduces a builtin that cannot be overloaded (only shadowed) so (1) is the preferred way.
The macros API requires patching vmgen and vm. I'd reuse the opcNGetType opcode for this.
For my use case it is very important, that I can get all the sizes at compile time
You may not be able to make all these things available at Nim-compile-time because they are not fully determined until the C (or whatever) backend compile is reached. For example,
type foo object =
bar: char
baz: int
will induce generation by Nim of some struct in the C backend. This creates a choice for the C of how to layout the struct - is it "1 byte packed next to 4 or 8 bytes" or rather is there a 3 or 7 byte gap between bar and baz. Many back ends will put in that 3 or 7 byte padding to have the int field be aligned which can result in faster generated assembly code on some CPUs.
Almost all C compilers allow some kind of compiler directives like GCC's __attribute__((__packed__)) or an analogue to kill all padding and let users control layout. For the C backend (but maybe not others..?) it may be possible for Nim to use those directives for all its structs to workaround the ambiguities of backend struct layout and resolve these to Nim-compile-time values. It is very debatable if that is desirable, though. It is definitely more complexity than just "binding" calls. That binding is really more delegation than resolution to a value.
What the compiler currently does seems the right thing to me which is to delegate the complex/compound cases to the backend and "optimize" the cases for simple/non-composite types where it can reliably know the answer. For compound types, Nim can know these backend layout sensitive values are Nim- and backend-compile-time constants, just not what the values are to do fully resolved at compile-time calculations.
So, a higher level question is if this resolution to a value known at Nim compile time is really a strict requirement for your purposes or if you can relax that to delegated backend work so the calculation can be done at "backend compile time".
@cblake To my research Each platform (x86 arm powerpc) may have different alignment rules. But there is definitively no alignment rule distinction between compilers, otherwise it would be impossible to use structs as interfaces in C. This makes header files in C compatible between different compilers. This compatibility was never enforced or was defined, the C standard actually would allow different behaviours, but compiler manufacturers wanted compatibility and that is what we can rely on today.
I am aware of the packed attribute, I think nim should have the same.
To your last question. I think I can move the resolution of the alignment parameter to the runtime, but that would not only increase the complexity of the code a lot, but also remove the ability for compile time error checking.
Since you can use statements such as when defined(windows): in your code, the generated C code is already platform dependent. This means it would not be wrong at all to generate platform dependent constants in the C code. For other platforms the C code would be regenerated anyway.
@Araq
I just looked at magicsAfterOverloadResolution, since you said it is the preferred way. I tried to find the mSizeOf handling in there for an example, but it is not handled there. I would like to know what I am actually supposed to do in this procedure. What information do the parameters provide to me? And what am I supposed to return?
Currently the procedure itself does not have any documentation string, nor does any of the procedure argument types have any documentation.
So my question here is, what are the parameters, and what should it return? Especially the parameter c: PContext is a bit confusing to me. Since I already used PNode a bit, I guess it should eventually return an integer literal in form of a PNode with the value of the offset/alignment/size.
Fair point about the LLVM backends, though I don't know their level of C struct compatibility or if they can lever internal LLVM apis to get that. Anyway, just trying to help. Code complexity jello (squeeze one place, watch others expand) is very surely not an exact science. :-)
You've probably already considered it, and I surely don't know all the different ABIs out there for all CPUs, but I can imagine nested struct transitions being complicated for some base types at the beginnings/ends of nested structs. It also might be good for you to try get two pretty different backend CPU archs working at first to cover all your cases/tests..Maybe ARM32 and x86_64..whatever you have easy access to.
I have problems with the procedure magicsAfterOverloadResolution.
I added a proc to system.nim:
proc alignOf*[T](x: T): int {.magic: "AlignOf", noSideEffect.}
And ann entry in magicsAfterOverloadResolution:
of mAlignOf:
echo "evaluating mAlignOf:"
let typ = n[1].typ
debug(typ)
let align = typ.getAlign
result = newIntNode(nkIntLit, align)
result.info = n.info
debug(result)
The output I get is the following:
nim c compiler/nim
CC: compiler_sem
bin/nim_temp c -r testsizeof
evaluating mAlignOf:
tyObject SimpleAlignment(null, node: {
"kind": "nkRecList",
"info": ["testsizeof.nim", 12, 20],
"flags": {},
"sons": [
{
"kind": "nkSym",
"info": ["testsizeof.nim", 13, 4],
"flags": {},
"sym": a_104030,
"typ": tyInt8 int8
},
{
"kind": "nkSym",
"info": ["testsizeof.nim", 13, 6],
"flags": {},
"sym": b_104031,
"typ": tyInt8 int8
},
{
"kind": "nkSym",
"info": ["testsizeof.nim", 14, 4],
"flags": {},
"sym": c_104032,
"typ": tyInt64 int64
}
]
})
{
"kind": "nkIntLit",
"info": ["testsizeof.nim", 111, 51],
"flags": {},
"intVal": 8
}
testsizeof.nim(111, 52) Error: type mismatch: got (void)
but expected one of:
proc `$`(x: int): string
[...]
FAILURE
the line with the error in testsizeof.nim is the following:
echo a.type.name, ":\t", sizeof(a), "\t", alignof(a)
where a is of type SimpleAlignment
type
SimpleAlignment = object
a,b: int8
c: int64
Why do I get this error? I create an integer literal, and the compiler complains that it is of type void!