How to Use Tagged Pointers with the MulleObjCTaggedPointer Protocol in MulleObjC
The MulleObjCTaggedPointer protocol enables classes to store small scalar values—integers, floats, or custom data—directly inside 64-bit pointer values, eliminating heap allocation and reference counting overhead.
The mulle-objc runtime implements tagged pointers as a zero-allocation optimization for immutable small values. By conforming to the MulleObjCTaggedPointer protocol defined in src/protocol/MulleObjCTaggedPointer.h, classes register for a unique tag index and pack raw data into unused high-order bits of the pointer itself. This bypasses the allocator, improves CPU cache locality, and renders retain/release cycles unnecessary for these encoded values.
How Tagged Pointers Work in MulleObjC
On 64-bit architectures, the upper bits of a pointer often remain unused by the hardware. The MulleObjC runtime exploits this space by reserving a tag index (a value from 0 to 255) for a specific class and embedding that index alongside a compressed scalar value into the pointer address.
The runtime distinguishes tagged pointers from regular object pointers through bit-pattern validation. When the class method +isTaggedPointerEnabled returns YES, the system permits these encodings. Low-level functions in src/runtime/mulle_objc-taggedpointer.c handle the actual bit-packing via mulle_objc_create_*_taggedpointer helpers, shifting values into the lower bits while placing the tag index into the metadata region.
Registering a Class for Tagged Pointer Support
Before creating tagged pointers, a class must reserve a tag index using MulleObjCTaggedPointerRegisterClassAtIndex. The runtime stores this index in the class metadata via _mulle_objc_infraclass_get_taggedpointerindex and rejects duplicate registrations.
#import "MulleObjCTaggedPointer.h"
@interface CompactNumber : NSObject <MulleObjCTaggedPointer>
@end
@implementation CompactNumber
@end
__attribute__((constructor))
static void registerCompactNumber(void)
{
int result = MulleObjCTaggedPointerRegisterClassAtIndex([CompactNumber class], 5);
if (result != 0) {
abort(); // Index already taken or other error (errno set)
}
}
To verify registration later, use MulleObjCTaggedPointerClassGetIndex, which returns the registered index or -1 if the class lacks a tag:
int idx = MulleObjCTaggedPointerClassGetIndex([CompactNumber class]);
NSLog(@"Tag index: %d", idx); // Output: 5
Creating Tagged Pointers from Scalar Values
The protocol provides inline validation helpers to ensure a value fits within the tagged pointer bit constraints before creation. These delegate to mulle_objc_taggedpointer_is_valid_* runtime functions.
Integer and Unsigned Values
Validate and pack integer values using MulleObjCTaggedPointerIsIntegerValue and MulleObjCCreateTaggedPointerWithIntegerValueAndIndex:
NSInteger rawValue = 42;
if (MulleObjCTaggedPointerIsIntegerValue(rawValue)) {
void *tagged = MulleObjCCreateTaggedPointerWithIntegerValueAndIndex(rawValue, 5);
id obj = (id)tagged; // Usable as object reference
}
For unsigned integers, use MulleObjCTaggedPointerIsUnsignedIntegerValue and MulleObjCCreateTaggedPointerWithUnsignedIntegerValueAndIndex.
Floating-Point Values
Floats and doubles require specific validation and creation paths. The helpers MulleObjCTaggedPointerIsFloatValue and MulleObjCCreateTaggedPointerWithFloatValueAndIndex handle the bit-reinterpretation:
float f = 3.14f;
if (MulleObjCTaggedPointerIsFloatValue(f)) {
void *tagged = MulleObjCCreateTaggedPointerWithFloatValueAndIndex(f, 5);
// tagged now holds the float value without heap allocation
}
For double-precision values, use MulleObjCTaggedPointerIsDoubleValue with MulleObjCCreateTaggedPointerWithDoubleValueAndIndex.
Retrieving Values and Index Information
Extract the original scalar and metadata from a tagged pointer using the getter functions. These decode the bit-packed representation created by the low-level runtime:
void *tp = (void *)obj;
if (MulleObjCTaggedPointerIsIntegerValue((NSInteger)tp)) {
NSInteger value = MulleObjCTaggedPointerGetIntegerValue(tp);
int tagIndex = MulleObjCTaggedPointerGetIndex(tp);
NSLog(@"Value: %ld, Index: %d", (long)value, tagIndex);
}
Available getters include MulleObjCTaggedPointerGetUnsignedIntegerValue, MulleObjCTaggedPointerGetFloatValue, and MulleObjCTaggedPointerGetDoubleValue. The function MulleObjCTaggedPointerGetIndex retrieves the class tag index from any tagged pointer.
Memory Management and Protocol Conformance
Classes conforming to MulleObjCTaggedPointer declare immutable, non-heap values. Consequently, the protocol defines optional retain and release methods as no-ops; the runtime never allocates or deallocates tagged pointers. This design eliminates the overhead of retain, release, and autorelease calls for small scalar objects.
The conformance marker also enables the runtime to identify which classes participate in the tagged pointer system, ensuring that message sends to tagged pointers route correctly through the class registered at the embedded tag index.
Summary
- Zero-allocation storage: Tagged pointers embed integers, floats, or doubles directly into 64-bit pointer values, avoiding heap allocation.
- Tag index registration: Classes must call
MulleObjCTaggedPointerRegisterClassAtIndexto reserve an index between 0 and 255 before creating tagged instances. - Validation required: Always validate values with
MulleObjCTaggedPointerIs*Valuefunctions before packing to ensure they fit the bit constraints. - No reference counting: Tagged pointers bypass retain/release cycles; the protocol methods are implemented as no-ops.
- Bit-packed retrieval: Use
MulleObjCTaggedPointerGet*ValueandMulleObjCTaggedPointerGetIndexto decode the original data and class metadata.
Frequently Asked Questions
What is the valid range for tag indices in MulleObjC?
Tag indices range from 0 to 255 on most platforms. The runtime reserves these values in the class metadata structure and refuses duplicate registrations, returning a non-zero error code from MulleObjCTaggedPointerRegisterClassAtIndex if the index is already claimed.
Do tagged pointers require manual memory management?
No. Tagged pointers are immutable values encoded directly into the pointer bits; they are never heap-allocated. The MulleObjCTaggedPointer protocol defines retain and release methods as no-ops, so the runtime performs no reference counting on these values.
How can I verify if a pointer contains a tagged value?
Use the validation inline functions provided in src/protocol/MulleObjCTaggedPointer.h, such as MulleObjCTaggedPointerIsIntegerValue or MulleObjCTaggedPointerIsFloatValue. These delegate to the low-level runtime helpers to test whether the pointer follows the tagged-pointer bit encoding.
Can I store any integer value in a tagged pointer?
No. Only values that fit within the remaining bit width after the tag index encoding are valid. Use MulleObjCTaggedPointerIsIntegerValue or MulleObjCTaggedPointerIsUnsignedIntegerValue to verify a specific value can be represented before calling the creation functions.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →