Photo by Franck V. on Unsplash

Tagged Pointers: Encoding Metadata in Spare Address Bits


Modern 64-bit systems rarely use all 64 bits of a pointer. Virtual address spaces typically use 48 or 57 bits, leaving the upper bits unused by hardware. Tagged pointers exploit this gap by storing metadata directly in pointer values, eliminating separate fields and reducing memory overhead.

How Address Space Leaves Room

On x86-64, canonical addresses use only the lower 48 bits. The top 16 bits must be copies of bit 47 (sign extension). ARM64 provides 48 to 52 bits depending on configuration, with top byte ignored (TBI) features that explicitly allow software use of the high byte. This architectural headroom isn’t a quirk—it’s designed into the memory management unit.

The lower bits can also carry information. Aligned allocations guarantee trailing zero bits: an 8-byte aligned pointer always has the bottom 3 bits clear. A 16-byte alignment provides 4 bits. These bits are functionally invisible to dereferencing but available for tagging.

Packing Type and State Information

Language runtimes use tagged pointers to distinguish object types without separate metadata fields. JavaScript engines like V8 encode whether a value is a small integer, pointer, or special constant in the low bits. A simple check replaces a full type field, cutting memory use and improving cache density.

Objective-C and Swift pack reference counts, flags for weak references, and class information into spare bits. This eliminates separate metadata allocations for common objects. Garbage collectors store mark bits or forwarding pointers inline during collection phases, avoiding external bitmaps.

The trade is pointer masking. Every dereference must strip the tag bits before accessing memory. On modern CPUs, bitwise AND with a constant mask costs a single cycle and often fuses with the load instruction, making the overhead negligible.

Alignment Assumptions and Portability

Tagged pointers depend on alignment guarantees. If a memory allocator returns 8-byte aligned pointers, the low 3 bits are safe. If alignment drops, the scheme breaks. Portable code must enforce alignment explicitly, either through allocator configuration or careful structure padding.

High-bit tagging assumes the OS respects unused address bits. macOS, iOS, and Linux allow this by default on ARM64 with TBI enabled. Some x86-64 systems enforce canonical form strictly, faulting on non-canonical addresses. Five-level paging extends addressing to 57 bits, shrinking the available tag space.

Intel’s upcoming Linear Address Masking (LAM) and ARM’s Top Byte Ignore formalize high-bit tagging, letting software declare which bits are metadata. This removes guesswork and provides forward compatibility as address spaces grow.

Use Cases Beyond Runtime Tagging

Lock-free data structures use the low bits of pointers for version counters or flags in compare-and-swap loops. A single atomic operation can update both pointer and metadata, solving ABA problems without separate generation counters.

Memory allocators tag pointers to track allocation size classes or owning arenas. This avoids external lookup tables for free operations. Security-focused allocators store canary bits to detect corruption or use-after-free bugs, failing fast when tags mismatch.

Some file systems and databases use tagged pointers in-memory to distinguish node types in tree structures, avoiding virtual dispatch or type fields on every allocation.

Why It Matters for Performance

Removing an 8-byte type field from a small object is substantial. A 24-byte structure becomes 16 bytes, doubling cache line density. Reference-heavy workloads see direct gains in memory bandwidth and cache hit rates.

Avoiding pointer chasing for type checks matters in hot loops. A bit test on the pointer itself is faster and more predictable than loading a separate field, improving branch prediction and reducing memory dependencies.

The technique is invisible to most application code but foundational to high-performance runtimes. Every modern language runtime, garbage collector, and lock-free library uses some form of pointer tagging under the hood.