Ointers: A library for representing pointers where bits have been stolen (2021)

Mechanics of “stolen-bit” pointers and this library

  • Some readers initially thought it only masked high bits, conflicting with typical low-bit tagging from alignment.
  • Clarification: the library packs “stealable” bits contiguously by shifting pointers by their alignment to free low bits, then re-expands on load.
  • This is seen by some as clever but complex; one guideline raised: if you’re doing more than simple masking, you risk fragility.
  • Several commenters recommend strong testing infrastructure for this kind of low-level trick.

Available bits and architecture details

  • On common 64-bit CPUs, virtual addresses are usually only 48 bits, leaving many high bits free; low bits from alignment add a few more.
  • Extensions (e.g., 5-level paging, various “ignore upper bits” and “top-byte ignore” mechanisms) expand usable virtual space and can reduce available tag bits.
  • These extensions are opt-in and kernel-mediated; on Linux, typical behavior keeps mmap in 48-bit space until a higher address is requested.
  • There’s uncertainty about practical use cases for huge (57-bit+) virtual spaces; one example given: reserving very large disjoint segments per thread.

Prevalence, benefits, and drawbacks

  • Many language runtimes and VMs use tagged pointers or NaN-boxing (Lisps, Lua-like systems, JS engines, Objective-C, OCaml, Smalltalk, game consoles).
  • Reported experience: used tactically, tagging works well and ships in major software; problems arise when it pervades whole systems without clear abstraction.
  • Tagged pointers can store types, integer vs pointer flags, refcounts, and “dirty” bits directly in the pointer.
  • Debugging becomes harder because raw pointers in memory aren’t directly dereferenceable; mitigations include wrapper types and debugger pretty-printers.

Performance and Lisp discussion

  • Debate on whether tagged pointers harmed Lisp performance:
    • One side: performance issues stem mainly from pervasive linked lists (poor cache locality), GC behavior, and low register counts on early x86.
    • Others note compacting GCs can improve locality but lists still pay overhead in links and branch/pipeline costs versus arrays/vectors.
  • Consensus: pointer tagging cost is minor compared to cache misses and indirection; data structures and overall architecture dominate.

Alternatives and related techniques

  • Relative pointers or indices plus a base pointer/arena can save more space than shaving a few bits, at the cost of allocator and architecture complexity.
  • In Rust-like settings, “NonMax” index types were proposed to get niche-optimization without sacrificing zero; workarounds use NonZero + offset/xor.
  • Unused bits can also hold pointer authentication codes; contrasted with ECC RAM, which doesn’t protect against cross-process or targeted pointer corruption.
  • OS/allocator tricks: mmap a large aligned region, constrain allocators to a range, or use special mmap flags (e.g., 32-bit-limited regions on x86-64).