# How to Use Vararg Extensions for Method Calls in Mulle-ObjC Runtime

> Learn to use vararg extensions for dynamic method calls in Mulle-ObjC. Discover macros to build argument lists with objects selectors and primitives for efficient invocation via mulle_objc_object_call.

- Repository: [mulle-objc/mulle-objc-runtime](https://github.com/mulle-objc/mulle-objc-runtime)
- Tags: how-to-guide
- Published: 2026-03-07

---

**Mulle-ObjC runtime provides specialized macros in [`src/mulle-objc-runtime.h`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/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`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-runtime.h), the system operates through a five-phase pipeline:

1. **Buffer Allocation** – Stack-allocate a `mulle_vararg_builderbuffer_t` array sized with `mulle_vararg_builderbuffer_n(total_size)`, where total_size accounts for all fixed and variable arguments.

2. **List Creation** – Convert the raw buffer into a write cursor using `mulle_vararg_list_make(buf)`, producing a `mulle_vararg_list` pointer that tracks the current insertion position.

3. **Argument Pushing** – Populate the list using type-specific macros that understand Objective-C semantics:
   - `mulle_vararg_push_object(list, obj)` pushes an `id` (object pointer)
   - `mulle_vararg_push_selector(list, sel)` pushes an `SEL` (encoded as `int32_t`)
   - `mulle_vararg_push_double(list, value)` handles primitive types

4. **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.

5. **Argument Consumption** – Inside the method implementation, retrieve values using `mulle_vararg_next_id(list)`, `mulle_vararg_next_selector(list)`, or `mulle_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`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-runtime.h):

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

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

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

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

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

```c
// 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:

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

```c
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 allocate `mulle_vararg_builderbuffer_t` arrays on the stack.
- **Type-safe pushing**: Use `mulle_vararg_push_object`, `mulle_vararg_push_selector`, and `mulle_vararg_push_double` from [`src/mulle-objc-runtime.h`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-runtime.h) to 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`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-runtime.h), with working examples in `test-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`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/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.