# How the `_param` Struct Simplifies Method Forwarding in mulle-objc

> Discover how the _param struct in mulle-objc simplifies method forwarding. Learn about zero-copy message passing and efficient parameter handling for improved performance.

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

---

**The `_param` struct in mulle-objc packages all method arguments into a single pointer, enabling zero-copy message forwarding by passing the same struct address to forward targets without marshaling individual parameters.**

Every method implementation in the mulle-objc runtime uses a canonical calling convention that replaces variadic argument lists with a unified parameter struct. This design choice, rooted in the Meta-ABI specification, eliminates the need for runtime argument reconstruction when forwarding messages to superclass implementations.

## The Canonical Method Signature and `_param`

In `mulle-objc/mulle-objc-runtime`, every method implementation adheres to a strict three-parameter prototype:

```c
void *implementation( struct Foo *self,
                      mulle_objc_methodid_t _cmd,
                      void *_params );

```

The third argument, **`_params`**, is a pointer to a compiler-generated struct containing all method arguments. Rather than pushing individual arguments onto the stack or registers, the compiler packages them into a temporary struct and passes its address. This convention applies uniformly regardless of whether the method accepts zero, two, or twelve parameters.

## Zero-Copy Forwarding via Parameter Structs

When a selector cannot be resolved, the runtime forwards the message to another implementation—typically the superclass's method. Because the forward target expects **exactly the same prototype**, the forwarding code passes the original `_params` pointer unchanged.

In [`src/mulle-objc-call.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-call.c) (lines 306-311), the dispatch logic checks for missing methods and initiates forwarding:

```c
if( ! method)  // must be forward, so check later
    return;
// forward: is not "methodid", that's what we forward to

```

The forward implementation then calls `mulle_objc_object_call_super` with the identical `_params` pointer:

```c
self = (void *) mulle_objc_object_call_super( (void *)self,
                                               _cmd,
                                               _params,      // <-- reused unchanged
                                               ___Foo_init_superid );

```

This **parameter-struct pass-through** eliminates argument marshaling, preserving register state and avoiding extra stack operations during forwarding chains.

## Meta-ABI and Struct Generation

The Meta-ABI defines that methods with multiple parameters or floating-point values use a **single pointer** to a parameter struct. This abstraction is implemented in [`src/mulle-metaabi.h`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-metaabi.h), which provides macros for generating these structs:

```c
/* if you need to "manually" call a MetaABI function with a _param block
 * use mulle_metaabi_union to generate it. DO NOT CALL IT `_param` */

```

Callers use `mulle_metaabi_struct` to create properly aligned parameter blocks at the call site:

```c
mulle_metaabi_struct(complex_params) {
    char *name;
    double value;
    int   count;
    struct _mulle_objc_object *other;
};

void call_complex(void *obj)
{
    mulle_metaabi_struct(complex_params) params = {
        .name  = "demo",
        .value = 3.14,
        .count = 7,
        .other = obj
    };
    mulle_objc_object_call( obj,
        mulle_objc_methodid_from_string("processComplex:"), &params );
}

```

## Implementation Details

### Dispatch and Forwarding Logic in [`src/mulle-objc-call.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-call.c)

The core dispatch mechanism recognizes when a method is missing and triggers the forward path. Because the `_params` pointer is opaque to the dispatcher, the same forwarding logic works for methods with any signature complexity. The file handles the transition from normal dispatch to `mulle_objc_object_call_super` without inspecting the struct contents.

### Signature Analysis in [`src/mulle-objc-signature.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-signature.c)

At line 96, the signature parser determines when a method requires a parameter struct based on the current architecture's ABI requirements. The comment explaining "_param structs" indicates that this decision happens during method registration, ensuring the compiler and runtime agree on the calling convention.

### Method Body Implementation

Method implementations unpack arguments from the struct directly. In [`test/demo/demo1.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/test/demo/demo1.c) (lines 301-308), a method receiving two integers extracts them from the `_params` struct:

```c
static void Foo_setA_b_( struct Foo *self,
                         mulle_objc_methodid_t _cmd,
                         struct { int _a; int _b; } *_params )
{
    int a = _params->_a;   // read from the passed-in struct
    int b = _params->_b;
    self->a = a;
    self->b = b;
}

```

Similarly, initializers forward to super using the same pointer (lines 285-293):

```c
static void *Foo_init( struct Foo *self,
                       mulle_objc_methodid_t _cmd,
                       void *_params )
{
    /* forward to super, re-using the same _params pointer */
    self = (void *) mulle_objc_object_call_super( (void *)self,
                                                  _cmd,
                                                  _params,
                                                  ___Foo_init_superid );

    self->a = 1;
    self->b = 2;
    return self;
}

```

## Performance and Design Benefits

- **Single-Pointer ABI**: All complex argument lists collapse to a `void *`, providing a stable calling convention across method signatures.
- **Fast Forwarding**: The forwarder reuses the original `_params` without rebuilding argument lists, avoiding expensive stack frame reconstruction.
- **Cross-Platform Abstraction**: The Meta-ABI shields generated code from architecture-specific differences between ARM and x86 calling conventions.
- **Cleaner Generated Code**: Compiler-generated struct literals make method bodies concise and type-safe while maintaining C-level performance.

## Summary

- The **`_param` struct** packages all method arguments into a single pointer passed as the third parameter to every mulle-objc method.
- **Zero-copy forwarding** is achieved by passing the identical `_params` pointer to `mulle_objc_object_call_super` without unpacking or remarshaling arguments.
- The **Meta-ABI** specification in [`src/mulle-metaabi.h`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-metaabi.h) defines how compilers generate these structs for methods with multiple parameters or floating-point values.
- **Signature analysis** in [`src/mulle-objc-signature.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-signature.c) determines when parameter structs are required based on platform ABI rules.
- Method implementations in [`test/demo/demo1.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/test/demo/demo1.c) demonstrate unpacking arguments from the struct and forwarding to superclass implementations.

## Frequently Asked Questions

### What is the Meta-ABI in mulle-objc?

The Meta-ABI is a calling convention specification that requires methods with more than one parameter or floating-point arguments to package those values into a struct passed by pointer. This abstraction ensures a uniform binary interface across different hardware architectures, allowing the runtime to handle complex method signatures with a single `void *` parameter.

### Why does mulle-objc use a struct for method parameters instead of individual arguments?

Using a parameter struct eliminates variadic calling conventions and reduces the dispatch surface area to a consistent three-parameter signature (`self`, `_cmd`, `_params`). This design simplifies the compiler's code generation, enables trivial message forwarding by pointer passing, and abstracts away platform-specific register and stack allocation rules.

### How does method forwarding work without copying arguments?

When a method is not found, the runtime calls `mulle_objc_object_call_super` (or a generic forward handler) with the original `_params` pointer received by the initial dispatch. Because both the original method slot and the forward target expect the same struct pointer type, the runtime does not need to inspect, copy, or transform the argument data, resulting in zero-overhead forwarding.

### Where is the `_param` struct defined in the source code?

The macros for generating parameter structs are defined in [`src/mulle-metaabi.h`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-metaabi.h), while the logic determining when such structs are required lives in [`src/mulle-objc-signature.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-signature.c). Concrete examples of methods receiving these structs appear in [`test/demo/demo1.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/test/demo/demo1.c), and the dispatch logic that handles forwarding is implemented in [`src/mulle-objc-call.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-call.c).