How MulleObjC Handles C Pointers and Integer Datatypes: Atomic Storage and Tagged Pointers
MulleObjC treats C pointers and Objective-C integer primitives as atomic, first-class values using lock-free compare-and-swap operations and tagged-pointer encoding to store scalars directly within pointer addresses.
The mulle-objc/mulleobjc runtime diverges from traditional Objective-C implementations by treating NSInteger, NSUInteger, and C pointers as fundamental value types that support atomic operations and pointer-embedding optimizations. This design eliminates heap allocations for small scalars while guaranteeing thread-safe access to integer instance variables without locks.
Atomic Storage for Integer Datatypes
MulleObjC wraps integer primitives in atomic pointer containers, enabling lock-free concurrent access to numeric state.
NSIntegerAtomic and NSUIntegerAtomic Unions
In src/MulleObjCIntegralType.h, the runtime defines NSIntegerAtomic and NSUIntegerAtomic as unions containing a mulle_atomic_pointer_t. These unions allow the runtime to store integer values within atomic pointer storage cells. The definition appears at lines 33-44, where the union overlays an integer value with the atomic pointer type, ensuring that integer instance variables occupy the same memory layout as object pointers.
Atomic accessor helpers are implemented at lines 49-88 of the same file. The NSIntegerAtomicSet and NSIntegerAtomicGet functions translate integer values to and from the atomic pointer container, while NSIntegerAtomicUpdate and NSUIntegerAtomicUpdate perform compare-and-swap loops to modify values atomically and return the previous state.
Lock-Free Thread Safety with mulle_atomic_pointer_t
The runtime guarantees thread-safe integer updates by leveraging _mulle_atomic_pointer_read for atomic loads and _mulle_atomic_pointer_cas (compare-and-swap) for atomic stores. When updating an atomic integer property, NSIntegerAtomicUpdate executes a CAS loop that retries until the operation succeeds, eliminating the need for mutex locks or @synchronize blocks.
This mechanism is utilized throughout the class hierarchy. For example, src/class/NSThread.h and src/class/NSRecursiveLock-Private.h employ these atomic pointer reads for thread-related state management, while src/function/MulleObjCProperty.h implements _MulleObjCAcquirePointerAtomically using the same primitives.
Tagged Pointers for Scalar Values
MulleObjC implements tagged pointers to encode small scalar values—signed integers, unsigned integers, floats, and doubles—directly into the lower bits of a pointer address, avoiding heap allocation.
Validity Checks for Small Integer Encoding
Before encoding a value as a tagged pointer, the runtime validates whether the scalar fits within the architecture-specific bit limits reserved for data. In src/protocol/MulleObjCTaggedPointer.h (lines 81-92), the functions MulleObjCTaggedPointerIsIntegerValue, MulleObjCTaggedPointerIsUnsignedIntegerValue, MulleObjCTaggedPointerIsFloatValue, and MulleObjCTaggedPointerIsDoubleValue perform these bounds checks. If the value exceeds the storable range, the runtime falls back to standard heap allocation.
Creating and Extracting Tagged Pointers
Creation functions at lines 111-144 of src/protocol/MulleObjCTaggedPointer.h implement the encoding logic. MulleObjCCreateTaggedPointerWithIntegerValueAndIndex and its variants call runtime helpers such as mulle_objc_create_integer_taggedpointer to embed both the scalar value and a class index into the pointer address.
Extraction functions at lines 148-176 reverse this process. MulleObjCTaggedPointerGetIntegerValue, MulleObjCTaggedPointerGetUnsignedIntegerValue, and their float/double counterparts invoke mulle_objc_taggedpointer_get_integer_value to retrieve the original scalar from the pointer's payload bits.
Class Index Routing
At lines 178-183 of src/protocol/MulleObjCTaggedPointer.h, MulleObjCTaggedPointerGetIndex extracts the class index stored within the tagged pointer. The runtime uses this index to route messages to the correct class methods, allowing tagged pointers to behave polymorphically like full objects while maintaining the performance characteristics of immediate values.
Practical Implementation Examples
The following patterns demonstrate concrete usage of atomic integers and tagged pointers in MulleObjC code:
/* Atomic integer storage and retrieval */
#include "MulleObjCIntegralType.h"
NSIntegerAtomic myInt;
NSIntegerAtomicSet(&myInt, 42); // Atomic store via _mulle_atomic_pointer_cas
NSInteger v = NSIntegerAtomicGet(&myInt); // Atomic load via _mulle_atomic_pointer_read
/* Creating a tagged pointer for a small signed integer */
#include "MulleObjCTaggedPointer.h"
if (MulleObjCTaggedPointerIsIntegerValue(7))
{
void *tp = MulleObjCCreateTaggedPointerWithIntegerValueAndIndex(
7,
MulleObjCTaggedPointerClassGetIndex([MyClass class]));
/* tp can be passed as an id or void* without heap allocation */
}
/* Retrieving the original integer from a tagged pointer */
NSInteger original = MulleObjCTaggedPointerGetIntegerValue(tp);
/* Validates pointer tagging bits before extraction */
Summary
- Atomic integer types use
mulle_atomic_pointer_tunions defined insrc/MulleObjCIntegralType.hto provide lock-free thread safety forNSIntegerandNSUIntegerivars. - Tagged pointers store small scalars directly in pointer addresses via creation functions in
src/protocol/MulleObjCTaggedPointer.h, avoiding heap allocation for values that fit within architecture-specific bit limits. - Validity checks (
MulleObjCTaggedPointerIsIntegerValue, etc.) ensure only representable values are encoded as tagged pointers. - Class index embedding allows the runtime to treat tagged pointers as first-class objects, routing messages based on the encoded index extracted by
MulleObjCTaggedPointerGetIndex. - Atomic updates use compare-and-swap loops (
_mulle_atomic_pointer_cas) rather than locks, providing wait-free progress guarantees for concurrent integer modifications.
Frequently Asked Questions
How does MulleObjC ensure thread-safe integer updates without locks?
MulleObjC wraps integers in NSIntegerAtomic or NSUIntegerAtomic unions containing a mulle_atomic_pointer_t. Update operations use _mulle_atomic_pointer_cas in retry loops to atomically compare and swap values, providing lock-free thread safety for ivar access.
What is a tagged pointer in the MulleObjC runtime?
A tagged pointer is a pointer-sized value that encodes a small scalar (integer, float, or double) directly into the lower bits of the address, combined with a class index. The runtime treats these as immediate objects, eliminating heap allocation and reducing cache pressure for numeric values.
Which scalar types support tagged pointer encoding?
According to src/protocol/MulleObjCTaggedPointer.h, MulleObjC supports tagged pointer encoding for signed integers (NSInteger), unsigned integers (NSUInteger), float, and double. Each type has specific validity checks (MulleObjCTaggedPointerIsFloatValue, etc.) to determine if the value fits within the available payload bits.
Where are the atomic integer type definitions located?
The atomic integer union definitions and accessor helpers reside in src/MulleObjCIntegralType.h, specifically between lines 33-44 for the type structures and lines 49-88 for the atomic getter and setter implementations.
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 →