# How to Use Tagged Pointers with the MulleObjCTaggedPointer Protocol in MulleObjC

> Learn how to use tagged pointers with the MulleObjCTaggedPointer protocol. Store small scalar values directly in pointers, eliminating heap allocation and overhead.

- Repository: [mulle-objc/mulleobjc](https://github.com/mulle-objc/mulleobjc)
- Tags: deep-dive
- Published: 2026-03-07

---

**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`](https://github.com/mulle-objc/mulleobjc/blob/main/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`](https://github.com/mulle-objc/mulleobjc/blob/main/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.

```objc
#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:

```objc
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`:

```objc
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:

```objc
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:

```objc
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 `MulleObjCTaggedPointerRegisterClassAtIndex` to reserve an index between 0 and 255 before creating tagged instances.
- **Validation required**: Always validate values with `MulleObjCTaggedPointerIs*Value` functions 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*Value` and `MulleObjCTaggedPointerGetIndex` to 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`](https://github.com/mulle-objc/mulleobjc/blob/main/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.