# Understanding the Mulle-ObjC Universe: How to Run Multiple Independent Runtimes

> Learn about the Mulle-ObjC universe, a global state container enabling multiple independent runtimes. Discover how isolated Objective-C environments coexist within a single process.

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

---

**The Mulle-ObjC universe is a global state container (`struct _mulle_objc_universe`) that encapsulates all runtime data—including class tables, method caches, and memory allocators—for a single Objective-C environment, enabling multiple isolated runtimes to coexist within the same process.**

The mulle-objc/mulle-objc-runtime implements a unique architecture where the entire Objective-C execution context lives inside a "universe" data structure. Unlike conventional runtimes that rely on a single global state, this design allows developers to instantiate multiple independent universes, each maintaining completely isolated class hierarchies, protocol tables, and garbage collection states.

## What Is the Mulle-ObjC Universe?

In the Mulle-ObjC runtime, a **universe** is the root data structure that owns every aspect of runtime state for a single Objective-C environment. Defined in [`src/mulle-objc-universe.h`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-universe.h) as `struct _mulle_objc_universe`, this C struct contains:

- Class and protocol hash tables
- Method caches and selector tables
- Load information and tagged-pointer support
- Garbage-collection state and memory allocators
- Thread-local configuration data

By default, the library creates **one global universe per process**, accessed through `mulle_objc_global_get_defaultuniverse()` or its inline wrapper `mulle_objc_global_get_universe_inline()`. All standard Objective-C operations implicitly target this default universe unless configured otherwise.

## Why Run Multiple Independent Runtimes?

Multiple universes solve critical architectural challenges where complete isolation is required between Objective-C environments. Common scenarios include:

- **Plugin architectures** where third-party modules must not pollute the host application's class namespace
- **Isolated test harnesses** that require clean runtime states between test suites
- **Embedded systems** running distinct subsystems with conflicting class names

To support this, the runtime provides the compiler option `-fobjc-universename=<name>`, which causes generated code to reference a named universe instead of the default global one. Each named universe maintains its own `struct _mulle_objc_universe` instance with completely separate memory allocators and method caches.

## Creating and Initializing a Custom Universe

Establishing an independent runtime requires three distinct phases: registration, initialization, and thread binding.

### Registering a Universe

The entry point for universe creation is `mulle_objc_global_register_universe()`, declared in [`src/mulle-objc-universe.h`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-universe.h). This function allocates a new universe structure (or returns an existing one) and registers it in the global hash table:

```c
struct _mulle_objc_universe *
mulle_objc_global_register_universe( mulle_objc_universeid_t id,
                                     char *name );

```

Internally, this calls the low-level `__register_mulle_objc_universe()` function defined in [`src/mulle-objc-universe.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-universe.c).

### Initializing with the Bang Routine

Registration alone does not prepare the universe for use. The initialization phase, triggered by `_mulle_objc_universe_bang()`, performs heavy-weight setup including method-cache creation, tagged-pointer table initialization, and garbage-collector configuration:

```c
void _mulle_objc_universe_bang( struct _mulle_objc_universe *universe,
                                void (*bang)( struct _mulle_objc_universe *,
                                              struct mulle_allocator *,
                                              void *),
                                void *userinfo,
                                struct mulle_allocator *allocator);

```

You can supply a custom callback function to modify initialization behavior, such as substituting a custom memory allocator. If no custom logic is needed, pass `NULL` for the bang parameter to use default initialization.

### Thread Association

Each thread must be explicitly bound to a universe via `mulle_objc_thread_setup_threadinfo()` (defined in [`src/mulle-objc-universe.h`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-universe.h)). This function creates the thread-local `struct _mulle_objc_threadinfo` and registers it with the garbage collector:

```c
void mulle_objc_thread_setup_threadinfo( struct _mulle_objc_universe *universe );

```

Once bound, all subsequent Objective-C operations on that thread automatically resolve to the associated universe through thread-local storage lookups.

## Running Multiple Runtimes: Compile-Time and Runtime

### Compile-Time Configuration

To create fully isolated plugin modules, compile each with a distinct universe name:

```bash
clang -fobjc-universename=PluginA -c plugin_a.m -o plugin_a.o
clang -shared plugin_a.o -o libplugin_a.so

clang -fobjc-universename=PluginB -c plugin_b.m -o plugin_b.o
clang -shared plugin_b.o -o libplugin_b.so

```

This generates object files referencing different global symbols (e.g., `__register_mulle_objc_universe$PluginA`), ensuring complete isolation at the linker level.

### Runtime Implementation

The host program loads and manages these independent universes programmatically:

```c
#include <mulle-objc-runtime/mulle-objc-runtime.h>

int main(void)
{
    /* Initialize first plugin universe */
    struct _mulle_objc_universe *uA =
        mulle_objc_global_register_universe(
            mulle_objc_universeid_from_string("PluginA"),
            "PluginA");
    
    _mulle_objc_universe_bang(uA, NULL, NULL, NULL);
    mulle_objc_thread_setup_threadinfo(uA);
    
    /* Use PluginA classes... */
    
    /* Initialize second plugin universe */
    struct _mulle_objc_universe *uB =
        mulle_objc_global_register_universe(
            mulle_objc_universeid_from_string("PluginB"),
            "PluginB");
    
    _mulle_objc_universe_bang(uB, NULL, NULL, NULL);
    mulle_objc_thread_setup_threadinfo(uB);
    
    /* Use PluginB classes... */
    
    /* Cleanup */
    _mulle_objc_universe_crunch(uA, _mulle_objc_universe_defaultcrunch);
    _mulle_objc_universe_crunch(uB, _mulle_objc_universe_defaultcrunch);
    
    return 0;
}

```

Because each universe owns its own `universe->memory.allocator` and class tables, objects created in `uA` cannot be looked up or messaged from `uB`, preventing cross-contamination between runtime environments.

## Multithreaded Universe Isolation

For true parallelism, assign each universe to a dedicated thread:

```c
#include <pthread.h>
#include <mulle-objc-runtime/mulle-objc-runtime.h>

static void *run_universe(void *arg)
{
    const char *name = (const char *)arg;
    struct _mulle_objc_universe *uni =
        mulle_objc_global_register_universe(
            mulle_objc_universeid_from_string(name), (char *)name);

    _mulle_objc_universe_bang(uni, NULL, NULL, NULL);
    mulle_objc_thread_setup_threadinfo(uni);

    /* Universe-specific work */
    struct _mulle_objc_infraclass *cls =
        _mulle_objc_universe_lookup_infraclass(uni,
            mulle_objc_classid_from_string("UniverseSpecificClass"));
    
    return NULL;
}

int main(void)
{
    pthread_t t1, t2;
    pthread_create(&t1, NULL, run_universe, (void *)"UniverseA");
    pthread_create(&t2, NULL, run_universe, (void *)"UniverseB");
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    return 0;
}

```

## Summary

- The **Mulle-ObjC universe** (`struct _mulle_objc_universe`) defined in [`src/mulle-objc-universe.h`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-universe.h) is the root container for all runtime state, including class tables, method caches, and memory allocators.
- **Multiple independent runtimes** are created using `mulle_objc_global_register_universe()` and initialized via `_mulle_objc_universe_bang()` from [`src/mulle-objc-universe.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-universe.c).
- **Thread binding** through `mulle_objc_thread_setup_threadinfo()` determines which universe handles Objective-C messages on that thread.
- The **`-fobjc-universename`** compiler flag generates code targeting specific named universes, enabling plugin architectures with zero symbol collision risk.
- Each universe maintains **complete isolation**—objects, classes, and static strings from one universe are invisible to others, including separate garbage collection domains.

## Frequently Asked Questions

### What is the difference between the default universe and a named universe?

The **default universe** is created automatically when the process starts and is accessed via `mulle_objc_global_get_defaultuniverse()`. A **named universe** is explicitly created via `mulle_objc_global_register_universe()` with a unique identifier and name string. Named universes reside in a global hash table and can be retrieved by name, whereas the default universe uses a hardcoded global pointer. According to [`dox/API_UNIVERSE.md`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/dox/API_UNIVERSE.md), named universes are essential for isolation scenarios, while the default universe provides backward-compatible behavior for single-runtime applications.

### Can objects be shared between different Mulle-ObjC universes?

No. Each universe maintains its own memory allocator (`universe->memory.allocator`) and class table. Because class pointers and object headers are universe-specific, passing an object pointer from one universe to another results in undefined behavior—likely crashes during message sending or method lookup. As implemented in [`src/mulle-objc-universe.c`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-universe.c), the runtime performs no cross-universe validation; isolation is absolute by design.

### How do I switch between universes in a single-threaded application?

Call `mulle_objc_thread_setup_threadinfo()` with the target universe pointer to rebind the current thread. This updates the thread-local storage that the runtime checks via `mulle_objc_global_get_universe_inline()`. However, you must ensure no Objective-C objects from the previous universe remain on the stack or in registers, as they will become invalid once the switch occurs. For frequent switching, consider using separate threads instead to avoid state corruption.

### What happens if I don't call `_mulle_objc_universe_bang()`?

The universe remains uninitialized, lacking critical structures such as method caches, tagged-pointer tables, and garbage-collection roots. Attempting to create classes or send messages before "banging" the universe results in immediate segmentation faults or assertion failures. The runtime relies on `_mulle_objc_universe_bang()` (documented in [`book/chapter11-universe-configuration.md`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/book/chapter11-universe-configuration.md)) to establish the invariant state required for all subsequent operations.