@align
The @align decorator specifies a minimum memory alignment for values of a
struct type.
If you already work with low-level memory, SIMD, or GPUs, you can think of
@align as a way to make alignment a property of the type itself, enforced
by the compiler. If not, the short version is this: alignment controls where
values are placed in memory, and some hardware requires or benefits from
specific alignments.
Most Mojo code doesn't need explicit alignment. You only need @align when
your types need placement at specific boundaries, such as when interacting with
GPU buffers, using SIMD instructions, or avoiding cache-line contention in
concurrent code.
Without @align, alignment requirements must be tracked manually and enforced
at allocation sites. With @align, the requirement becomes part of the type,
and the compiler ensures it's respected everywhere the type is used.
What alignment means
Every byte in memory has an address. An address is N-byte aligned if it's address is evenly divisible by N.
Examples:
- 8-byte aligned addresses: 0, 8, 16, 24, etc.
- 64-byte aligned addresses: 0, 64, 128, 192, etc.
Hardware often loads memory in fixed-size chunks, such as cache lines. When a value begins at an aligned address, it fits cleanly within those chunks. When it doesn't, hardware may need extra memory accesses, or may reject the access entirely.
Alignment affects where a value begins in memory, not how large the value is.
Basic usage
Add @align(N) to your struct definition, where N is a positive power of
2 and the number represents the minimum required alignment in bytes:
from std.sys import align_of
@align(64)
struct CacheAligned:
var data: Int
def main():
print(align_of[CacheAligned]()) # Prints 64In this example, CacheAligned is aligned to 64 bytes, even though Int normally requires only 8-byte alignment.
Determining alignment
The actual alignment of a struct is the maximum of:
- The value specified by @align(N), if present.
- The struct's natural alignment (the maximum alignment of its fields).
- The alignment requirements of any embedded aligned fields.
You can't reduce alignment below the natural alignment of the struct.
The @align decorator specifies a minimum, not an override:
from sys import align_of
@align(4)
struct TryToReduce:
var x: Int # Int has 8-byte natural alignment
def main():
print(align_of[TryToReduce]()) # Prints 8When a struct contains an aligned field, the outer struct inherits that alignment:
from sys import align_of
@align(64)
struct CacheAligned:
var x: Int
struct Container:
var aligned: CacheAligned
var other: Int
def main():
print(align_of[Container]()) # Prints 64Stack and heap behavior
Both stack and heap allocations respect @align:
from memory import UnsafePointer
from sys import align_of
@fieldwise_init
@align(64)
struct CacheAligned:
var data: Int
def use_aligned():
# Stack allocation
var stack_value = CacheAligned(42)
# Heap allocation
var heap_ptr = alloc[CacheAligned](1)
heap_ptr.free()You don't need to manually request alignment when allocating values of an aligned type.
Alignment and arrays
The @align decorator guarantees alignment of the base address of a value,
including the base pointer of an array. It doesn't pad the size of the struct.
Mojo lays out array elements using size_of[T]() as your stride, not
align_of[T]():
from memory import UnsafePointer
from sys import size_of, align_of
@align(64)
struct CacheAligned:
var data: Int # 8 bytes
def demonstrate_array_stride():
var arr = UnsafePointer[CacheAligned].alloc(4)
print(align_of[CacheAligned]()) # 64
print(size_of[CacheAligned]()) # 8
# Only arr[0] is guaranteed to be 64-byte aligned
# Subsequent elements follow at 8-byte intervals
arr.free()If every element in an array must be aligned, pad the struct until its size
is a multiple of its alignment. (This matches the behavior of C and C++
alignas.)
Generic structs
Alignment also works with generic structs. All instances of a generic type share the same alignment requirement. Because of this, under certain circumstances, you may find that the alignment isn't tuned by its types:
from sys import align_of
@fieldwise_init
@align(128)
struct AlignedGeneric[T: Copyable & ImplicitlyDestructible]:
var value: Self.T
def main():
print(align_of[AlignedGeneric[Int8]]()) # 128
print(align_of[AlignedGeneric[Int64]]()) # 128Compare this with an alignment of 4, where the maximum
of the decorator value (N) and the type's natural alignment
produces a different result:
from sys import align_of
@fieldwise_init
@align(4)
struct AlignedGeneric[T: Copyable & ImplicitlyDestructible]:
var value: Self.T
def main():
print(align_of[AlignedGeneric[Int8]]()) # 4
print(align_of[AlignedGeneric[Int64]]()) # 8Interaction with RegisterPassable
When @align is present, single-field register-passable structs
aren't flattened. This preserves the alignment requirement:
from sys import align_of, size_of
@align(32)
struct AlignedTrivial(RegisterPassable):
var value: Int
def main():
print(align_of[AlignedTrivial]()) # 32
print(size_of[AlignedTrivial]()) # 8Requirements and errors
Mojo's @align decorator has the following requirements:
- The alignment value must be a positive power of 2.
- The maximum supported alignment is 2^29 bytes.
- The value must be known at compile time.
- The decorator requires exactly one argument.
Invalid uses produce compile-time errors:
@align(0)
struct Bad1:
var x: Int
@align(3)
struct Bad2:
var x: Int
@align(1073741824)
struct Bad3:
var x: Int
@align
struct Bad4:
var x: Int
@align(64, 128)
struct Bad5:
var x: Int
@align("64")
struct Bad6:
var x: IntSpecial case: @align(1)
Using @align(1) is valid and produces no warning. It doesn't reduce alignment below the natural alignment of the struct. This can be useful as a fallback value in parametric code:
from sys import align_of
@align(1)
struct MinimalAlign:
var x: Int
def main():
print(align_of[MinimalAlign]()) # Prints 8Real-world example: hardware descriptors
Some hardware accelerators require aligned descriptors for correctness. For example, NVIDIA's Tensor Memory Accelerator requires 64-byte aligned descriptors.
Before @align, this required explicit allocation tricks:
# Verbose and error-prone
var tensormap = my_custom_stack_allocation[1, TensorMap, alignment=64]()[0]With @align, the type encodes your alignment requirement:
@align(64)
struct TensorMap:
# Descriptor fields
pass
var tensormap = TensorMap()
var heap_tensormap = alloc[TensorMap](1)Alignment is enforced automatically everywhere the type is used.
Both stack and heap allocations respect @align.
Parametric alignment
The alignment value can also be a struct parameter, enabling generic aligned types:
from std.sys import align_of
@align(Self.alignment)
struct AlignedBuffer[alignment: Int]:
var data: Int
def main():
print(align_of[AlignedBuffer[64]]()) # Prints 64
print(align_of[AlignedBuffer[128]]()) # Prints 128The alignment is validated when the struct is instantiated, so invalid values
like AlignedBuffer[3] will produce a compile-time error.
Was this page helpful?
Thank you! We'll create more content like this.
Thank you for helping us improve!