How to Use Vararg Extensions for Method Calls in Mulle-ObjC Runtime
Mulle-ObjC runtime provides specialized macros in src/mulle-objc-runtime.h that extend the mulle-vararg library to build variable-argument lists containing Objective-C objects, selectors, and primitives, enabling dynamic method invocation through mulle_objc_object_call.
The mulle-objc/mulle-objc-runtime repository extends the generic mulle-vararg library with Objective-C specific extensions that simplify constructing variable-argument method calls at runtime. These vararg extensions handle the alignment and type promotion rules required for Objective-C method dispatch, allowing developers to programmatically assemble argument lists for dynamic invocation through low-level runtime functions.
Architecture of the Vararg Extension System
The vararg extension system bridges the generic mulle-vararg library with Objective-C runtime semantics. According to the source code in src/mulle-objc-runtime.h, the system operates through a five-phase pipeline:
-
Buffer Allocation – Stack-allocate a
mulle_vararg_builderbuffer_tarray sized withmulle_vararg_builderbuffer_n(total_size), where total_size accounts for all fixed and variable arguments. -
List Creation – Convert the raw buffer into a write cursor using
mulle_vararg_list_make(buf), producing amulle_vararg_listpointer that tracks the current insertion position. -
Argument Pushing – Populate the list using type-specific macros that understand Objective-C semantics:
mulle_vararg_push_object(list, obj)pushes anid(object pointer)mulle_vararg_push_selector(list, sel)pushes anSEL(encoded asint32_t)mulle_vararg_push_double(list, value)handles primitive types
-
Method Invocation – Pass the raw buffer (not the list cursor) to
mulle_objc_object_call(receiver, selector, buf), which extracts fixed arguments (self and _cmd) then consumes the vararg list. -
Argument Consumption – Inside the method implementation, retrieve values using
mulle_vararg_next_id(list),mulle_vararg_next_selector(list), ormulle_vararg_next_double(list)in the same order they were pushed.
The macro definitions that enable this type safety are found at lines 148–153 of src/mulle-objc-runtime.h:
#define mulle_vararg_push_selector( args, value) \
mulle_vararg_push_int32( args, (int32_t) value)
#define mulle_vararg_push_id( args, value) \
_mulle_vararg_push( args, id, value)
#define mulle_vararg_push_object( args, value) \
mulle_vararg_push_id( args, value)
Building Argument Lists Step by Step
To construct a vararg method call, follow this sequence demonstrated in test-compiler/metaabi/metaabi-build.m (lines 53–75):
1. Allocate a properly sized buffer
Calculate the total bytes required for all arguments, then declare the stack buffer:
mulle_vararg_builderbuffer_t buf[
mulle_vararg_builderbuffer_n(sizeof(id) + sizeof(SEL) + sizeof(double))];
2. Create and optionally copy the list cursor
Initialize the write cursor from the buffer. Create a mutable copy if you need to preserve the original position:
mulle_vararg_list list = mulle_vararg_list_make(buf);
mulle_vararg_list q;
mulle_vararg_copy(q, list); // q advances while list remains unchanged
3. Push arguments in declaration order
Insert values using the type-specific macros. These handle alignment and encoding automatically:
mulle_vararg_push_object(q, foo); // pushes id
mulle_vararg_push_selector(q, sel); // pushes SEL as int32_t
mulle_vararg_push_double(q, 18.48); // pushes double
4. Invoke via the runtime
Pass the original buffer (not the cursor) to the low-level call function:
mulle_objc_object_call(foo, @selector(call:selector:doubleValue:), buf);
Handling C Vararg Promotion Rules
When passing char or short values through variable argument lists, the C standard promotes these types to int. The Mulle-ObjC runtime follows this ABI requirement, as shown in test-compiler/metaabi/metaabi-build.m (lines 99–103).
You must account for this promotion both when pushing and when reading arguments:
// Pushing (automatically handles promotion)
mulle_vararg_push_char(q, 'A'); // Actually pushes int
// Reading inside the method
int promoted = mulle_vararg_next_int(list);
char actual = (char) promoted;
Failure to use mulle_vararg_next_int for character values results in undefined behavior due to alignment mismatches.
Complete Code Examples
Example 1: Calling a Method with Object, Selector, and Double
This example from the test suite demonstrates the full pipeline for calling a method with mixed Objective-C and primitive types:
Foo *foo = [Foo new];
SEL sel = @selector(dummy);
// Allocate buffer for id + SEL + double
mulle_vararg_builderbuffer_t buf[
mulle_vararg_builderbuffer_n(sizeof(id) + sizeof(SEL) + sizeof(double))];
mulle_vararg_list list = mulle_vararg_list_make(buf);
mulle_vararg_list q;
/* Copy so we can write without losing the base pointer */
mulle_vararg_copy(q, list);
/* Push the variable arguments */
mulle_vararg_push_object(q, foo);
mulle_vararg_push_selector(q, sel);
mulle_vararg_push_double(q, 18.48);
/* Invoke the Objective-C method */
mulle_objc_object_call(foo, @selector(call:selector:doubleValue:), buf);
Example 2: Passing Character Values with Type Promotion
When passing characters, remember they are promoted to integers:
Foo *foo = [Foo new];
SEL sel = @selector(dummy);
mulle_vararg_builderbuffer_t buf[
mulle_vararg_builderbuffer_n(sizeof(id) + sizeof(SEL) + 2*sizeof(char))];
mulle_vararg_list list = mulle_vararg_list_make(buf);
mulle_vararg_list q;
mulle_vararg_copy(q, list);
mulle_vararg_push_object(q, foo);
mulle_vararg_push_selector(q, sel);
mulle_vararg_push_char(q, 'A');
mulle_vararg_push_char(q, 'B');
mulle_objc_object_call(foo,
@selector(call:selector:charValue:charValue:), buf);
Inside the called method, retrieve these values using mulle_vararg_next_int and cast back to char.
Summary
- Buffer sizing: Use
mulle_vararg_builderbuffer_n(sizeof(id) + sizeof(primitive)...)to allocatemulle_vararg_builderbuffer_tarrays on the stack. - Type-safe pushing: Use
mulle_vararg_push_object,mulle_vararg_push_selector, andmulle_vararg_push_doublefromsrc/mulle-objc-runtime.hto encode arguments. - Invocation: Pass the raw buffer to
mulle_objc_object_call(receiver, selector, buf)for low-level method dispatch. - Promotion awareness: Account for C vararg promotion rules (char → int) when pushing and reading character or short values.
- Reference implementation: The macros are defined at lines 148–153 of
src/mulle-objc-runtime.h, with working examples intest-compiler/metaabi/metaabi-build.m.
Frequently Asked Questions
How do I calculate the correct buffer size for vararg method calls?
Calculate the sum of sizeof() for each argument type you intend to pass—including id for objects, SEL for selectors, and sizes for primitives—then pass this total to the mulle_vararg_builderbuffer_n() macro. This macro computes the required stack buffer size including any necessary alignment padding for the target architecture.
Why must char arguments be retrieved as int?
The C vararg promotion rules automatically promote char and short arguments to int when passed through variadic parameters. The Mulle-ObjC runtime follows this standard ABI requirement, storing characters as integers internally. Callees must retrieve these values using mulle_vararg_next_int and cast back to char if needed, as demonstrated in test-compiler/metaabi/metaabi-build.m (lines 99–103).
What is the difference between mulle_vararg_list and the raw buffer?
The mulle_vararg_builderbuffer_t array provides the raw memory storage for arguments, while mulle_vararg_list is a cursor object that tracks the current write position. You create the list from the buffer using mulle_vararg_list_make(), and the list advances as you push arguments, while the underlying buffer remains the static data source passed to mulle_objc_object_call.
Which header file contains the Objective-C vararg macro definitions?
The macros including mulle_vararg_push_object, mulle_vararg_push_selector, and mulle_vararg_next_id are defined in src/mulle-objc-runtime.h, specifically around lines 148–153. These wrap the generic mulle-vararg functions with Objective-C type awareness and proper selector encoding.
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 →