Skip to main content

@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 64

In 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 8

When 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 64

Stack 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]]())  # 128

Compare 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]]())  # 8

Interaction 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]())  # 8

Requirements 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: Int

Special 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 8

Real-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 128

The alignment is validated when the struct is instantiated, so invalid values like AlignedBuffer[3] will produce a compile-time error.

Was this page helpful?