I know there is the align pragma but I guess that won't help me in my case. I'm looking for a way to automatically add paddings to objects. Like C# FieldOffset
eg.:
Instead of:
type Controller* = object
pad_0000: array[0xF0, byte]
velocity*: array[3, float32] # Field at: 0xF1
I would like to use:
type Controller* = object
velocity* {.fieldOffset(0xF1).}: array[3, float32]
Any help would be greatly appreciated
0xF0 seems like a strange value but you can do this
type Controller* = object
velocity* {.align: 0xF0.}: array[3, float32]
Note: I've had issue with aligned object stored in seq on 32-bit, it seems like Nim doesn't respect alignment constraint there but if you are doing such low-level manipulation you might already be using UncheckedArrays.
For an example in an actual code base, this https://github.com/mratsim/weave/blob/1c5b3f1/weave/channels/channels_mpsc_unbounded.nim#L13-L32
type
Enqueueable = concept x, type T
x is ptr
x.next is Atomic[pointer]
ChannelMpscUnbounded*[T: Enqueueable] = object
count: Atomic[int]
dummy: typeof(default(T)[]) # Deref the pointer type
pad0: array[WV_CacheLineSize - sizeof(pointer), byte]
front: T
pad1: array[WV_CacheLineSize - sizeof(int), byte]
back: Atomic[pointer]
was replaced by https://github.com/mratsim/weave/blob/5034793/weave/cross_thread_com/channels_mpsc_unbounded_batch.nim#L27-L56
type
Enqueueable = concept x, type T
x is ptr
x.next is Atomic[pointer]
ChannelMpscUnboundedBatch*[T: Enqueueable, keepCount: static bool] = object
back{.align: MpscPadding.}: Atomic[pointer]
count: Atomic[int]
front{.align: MpscPadding.}: typeof(default(T)[])
0xF0 was just an example, but the padding size could actually be any value. I'm not sure if I understand align correctly and understand how it works but
type Controller* = object
velocity* {.align: 0xF0.}: array[3, float32]
fails with power of two expected.
Does align change the memory position of the field? I'm expecting that the example from the docs:
type sseType = object
sseData {.align(16).}: array[4, float32]
would have a size of 16 (alignment / padding) + 16 (4 x 4 float32) bytes. Which isn't the case. type Controller* = object
velocity* {.align: 0xF0.}: array[3, float32]
does not compile, align must be power of 2, and doesn't do what OP is looking for:
type Controller* = object
velocity* {.align: 0x100.}: array[3, float32]
echo Controller.offsetof(velocity) # 0
i believe @Shucks would need a macro to insert the necessary padding fields
so i gave it a try:
import macros
template fieldOffset(x: int){.pragma.}
macro attempt(x: typed) =
#echo x.treeRepr
x.expectKind(nnkStmtList)
x[0].expectKind(nnkTypeSection)
for td in x[0]:
td.expectKind(nnkTypeDef)
td[2].expectKind(nnkObjectTy)
#add {.packed.}
td[0].expectKind(nnkSym) #TODO if it's already got a pragma
td[0] = nnkPragmaExpr.newTree(
td[0],
nnkPragma.newTree(ident("packed"))
)
td[2][2].expectKind(nnkRecList)
var prevoffset = 0
var newreclist = newNimNode(nnkRecList, lineinfoFrom = td[2][2])
for id in td[2][2]:
id.expectKind(nnkIdentDefs)
id[0].expectKind(nnkPragmaExpr)
id[0][0].expectKind(nnkIdent)
let fldname = id[0][0].strVal
id[0][1].expectKind(nnkPragma)
id[0][1][0].expectKind(nnkExprColonExpr)
assert id[0][1][0][0].strVal == "fieldOffset"
id[0][1][0][1].expectKind(nnkIntLit)
let fldoffset = id[0][1][0][1].intVal
let fldsize = id[1].getSize()
let paddingamt = fldoffset.int - prevoffset
var padding = nnkIdentDefs.newTree(
ident(fldname & "_padding"),
nnkBracketExpr.newTree(
bindSym("array"),
newLit(paddingamt),
bindSym("byte")
),
newEmptyNode()
)
newreclist.add padding
prevoffset += paddingamt
newreclist.add id
prevoffset += fldsize
td[2][2] = newreclist
#echo "..."
#echo x.treeRepr
x
expandMacros:
attempt:
type
Controller = object
velocity{.fieldOffset: 0xf0.}: array[3, float32]
ambition{.fieldOffset: 0xfe.}: array[5, cint]
static:
echo default(Controller).velocity.getCustomPragmaVal(fieldOffset), " == ",
Controller.offsetof(velocity)
echo default(Controller).ambition.getCustomPragmaVal(fieldOffset), " == ",
Controller.offsetOf(ambition)
Exactly what I'm looking for! Great job!
I've played a bit with it and tried to add a expected nnkPostfix to make it work with public fields aswell but failed so far. To be honest I never wrote a macro myself or even looked into the docs about macros.
fails with power of two expected.
yes indeed, align was written for hardware use-cases:
All of those are served by power of 2 and the underlying generated C code annotation requires power of 2 on some compiler (see MSVC https://docs.microsoft.com/en-us/cpp/cpp/align-cpp?view=msvc-160)
you can use dumpTree to help you see what your goal AST should look like. for this case, though, it's important that the input be typed (so that you can use getSize on the fields), so you'll have to write your own:
macro dumpTypedTree(x:typed) =
echo x.treeRepr