I'm not sure why the first two cases aren't aligned:
type
MyArr = ref array[4, int]
MyObj = ref object
MyArrObj = ref object
arr {.align(256).}: array[4, int]
var arr {.align(256).}: MyArr
var obj {.align(256).}: MyObj
var arrObj: MyArrObj
arr = new MyArr # not aligned
obj = new MyObj # not aligned
arrObj = new MyArrObj # arrObj.arr is aligned
Why aren't the ref types not aligned as expected? Only the field arr in the third type is aligned. It's a workaround I'm using, but it's forcing me to define a field that I don't need. My use case is the first type, i.e. the ref array, so I'm forced to do arrObj.arr[i] instead of just arrObj[i].
var arr {.align(256).}: MyArr
var obj {.align(256).}: MyObj
I think this means references (or pointers) arr and obj are aligned, not objects these references point are aligned.
type
MyArr = ref array[4, int]
MyObj = ref object
MyArrObj = ref object
arr {.align(256).}: array[4, int]
var arr {.align(256).}: MyArr
var obj {.align(256).}: MyObj
var arrObj: MyArrObj
arr = new MyArr # not aligned
obj = new MyObj # not aligned
arrObj = new MyArrObj # arrObj.arr is aligned
echo cast[uint](arr.addr).toHex # aligned
echo cast[uint](obj.addr).toHex # aligned
echo cast[uint](arr).toHex # not aligned
echo cast[uint](obj).toHex # not aligned
As adding align pragma to a type is compile error, it seems defining an object type and adding the pragma to the field is the only way to align referenced object or array.
type
MyObj2 {.align(256).} = object # invalid pragma: align(256)
MyObj2Ref = ref MyObj2
MyArray {.align(256).} = array[4, int] # invalid pragma: align(256)
MyArrayRef = ref MyArray
@demotomohiro That's what I realized as well, that the references (i.e. pointers) themselves are aligned, but not what they point to. IMHO, this is counter-intuitive.
The alignment is not passed over to the heap allocator.
@Araq is this a bug, or by design?
I don't see a bug? The code is doing exactly what you asked.
type
MyArr = ref array[4, int]
MyObj = ref object
MyArrObj = ref object
arr {.align(256).}: array[4, int]
There is no constraint on MyArr and MyObj type at all.
var arr {.align(256).}: MyArr
var obj {.align(256).}: MyObj
var arrObj: MyArrObj
Now you add a constraint on the very specific instantantiations arr and obj which are of type ref array and ref object
If you do the same in C or C++, you'll have the exact same behavior.
If that's not possible, then maybe the align pragma should also apply to types (currently it doesn't)? e.g.
You can open a RFC for this, this should be possible as alignas can also work at the type level: https://en.cppreference.com/w/cpp/language/alignas
BTW, I found another thread from several years ago about the same issue: https://forum.nim-lang.org/t/2976#18732
This thread is mine and is not the same issue. It predates the align pragma, during that time the only way to specify alignment was to use codegendecl you could only specify alignment at the variable (on stack) level. I.e. with:
{.pragma: align16, codegenDecl: "$# $# __attribute__((aligned(16)))".}
let foo = cint(1234)
the following was generated
int foo __attribute__((aligned(16))) = 1234
There was no way to specify alignment at the field level like today.
C and C++ have aligned_alloc for a while now, which should work everywhere as long as the compiler is somewhat up to date (C11/C++17).
Just be aware of the different argument order on Windows. These are my aligned_alloc primitives: https://github.com/mratsim/weave-io/blob/8672f1cc4182633a9e31a2eb6641e941926b869d/weave_io/primitives/allocs.nim#L42-L55
when defined(windows):
proc aligned_alloc_windows(size, alignment: int): pointer {.tags:[HeapAlloc],importc:"_aligned_malloc", header:"<malloc.h>".}
# Beware of the arg order!
proc aligned_alloc(alignment, size: int): pointer {.inline.} =
aligned_alloc_windows(size, alignment)
proc aligned_free(p: pointer){.tags:[HeapAlloc],importc:"_aligned_free", header:"<malloc.h>".}
elif defined(osx):
proc posix_memalign(mem: var pointer, alignment, size: int){.tags:[HeapAlloc],importc, header:"<stdlib.h>".}
proc aligned_alloc(alignment, size: int): pointer {.inline.} =
posix_memalign(result, alignment, size)
proc aligned_free(p: pointer) {.tags:[HeapAlloc], importc: "free", header: "<stdlib.h>".}
else:
proc aligned_alloc(alignment, size: int): pointer {.tags:[HeapAlloc],importc, header:"<stdlib.h>".}
proc aligned_free(p: pointer) {.tags:[HeapAlloc], importc: "free", header: "<stdlib.h>".}
and then I build these high-level wrappers on top: https://github.com/mratsim/weave-io/blob/8672f1cc4182633a9e31a2eb6641e941926b869d/weave_io/primitives/allocs.nim#L100-L133
proc allocHeapAligned*(T: typedesc, alignment: static Natural): ptr T {.inline.} =
# aligned_alloc requires allocating in multiple of the alignment.
let # Cannot be static with bitfields. Workaround https://github.com/nim-lang/Nim/issues/19040
size = sizeof(T)
requiredMem = size.roundNextMultipleOf(alignment)
cast[ptr T](aligned_alloc(alignment, requiredMem))
proc allocHeapUncheckedAligned*(T: typedesc, size: int, alignment: static Natural): ptr T {.inline.} =
## Aligned heap allocation for types containing a variable-sized UncheckedArray field
## or an importc type with missing size information
# aligned_alloc requires allocating in multiple of the alignment.
let requiredMem = size.roundNextMultipleOf(alignment)
cast[ptr T](aligned_alloc(alignment, requiredMem))
proc allocHeapArrayAligned*(T: typedesc, len: int, alignment: static Natural): ptr UncheckedArray[T] {.inline.} =
# aligned_alloc requires allocating in multiple of the alignment.
let
size = sizeof(T) * len
requiredMem = size.roundNextMultipleOf(alignment)
cast[ptr UncheckedArray[T]](aligned_alloc(alignment, requiredMem))
proc allocHeapAlignedPtr*(T: typedesc[ptr], alignment: static Natural): T {.inline.} =
allocHeapAligned(typeof(default(T)[]), alignment)
proc allocHeapUncheckedAlignedPtr*(T: typedesc[ptr], size: int, alignment: static Natural): T {.inline.} =
## Aligned heap allocation for types containing a variable-sized UncheckedArray field
## or an importc type with missing size information
allocHeapUncheckedAligned(typeof(default(T)[]), size, alignment)
proc freeHeapAligned*(p: pointer) {.inline.} =
aligned_free(p)