Embedding › 02 Your first host

Your first host

Write the smallest useful host: initialize the process, load a module, create a VM, run an entry class, and shut down.

1. The lifecycle, in order

loth_plat_init()        // process init: threads, clocks, locks
native_init()           // native binding lock (before any loading)
vm_output_init()        // process-wide output lock for Std.print
native_set_lib_dir()    // where lib/Std.so and lib/Thread.so live

loth_engine_new()       // shared module space
loth_engine_load(...)   // dependencies first, then their users
loth_engine_bind_native()   // once, before any VM is created

vm_new(&arena)          // runtime state
loth_engine_setup_vm()  // attach the Engine's modules to this VM

vm_run(vm, entry, argc, argv)   // run an entry class

vm_destroy(vm)          // after all threads have joined
arena_free_all(&arena)
loth_engine_free(engine)

Each step has a reason, and the order matters:

2. A complete host

Here is a complete host program, with the two-app parts removed for clarity. It runs one entry class:

#include "engine.h"
#include "native.h"
#include "loth_plat.h"
#include <stdio.h>

int main(int argc, char** argv) {
    if (loth_plat_init() != 0) return 2;
    native_init();
    vm_output_init();
    native_set_lib_dir("/path/to/lopolith/lib");

    Engine* e = loth_engine_new();
    if (!e) return 2;
    if (loth_engine_load(e, "build/Std.lothc") != 0 ||
        loth_engine_load(e, "build/com/example/App.lothc") != 0) {
        fprintf(stderr, "load failed\n");
        return 2;
    }
    loth_engine_bind_native(e);

    Arena a;
    arena_init(&a);
    VM* vm = vm_new(&a);
    loth_engine_setup_vm(e, vm);

    ClassMeta* entry = loth_engine_entry(e, "com.example.App");
    if (!entry) { fprintf(stderr, "no entry\n"); return 2; }
    int rc = vm_run(vm, entry, 0, NULL);

    vm_destroy(vm);
    arena_free_all(&a);
    loth_engine_free(e);
    return rc;
}

3. Build and link

# 1) compile the language module (writes its dependency closure too)
loth compile com/example/App.loth -o build

# 2) build the host: link the VM objects, excluding the CLI's main.o
cc -O2 -std=gnu11 -I src host.c \
   $(ls src/*.o | grep -v '/main.o$') \
   -rdynamic -lm -ldl -lpthread -o build/host

-rdynamic is required: the standard library plugins are loaded with dlopen and resolve VM symbols such as vm_new_string from the host executable. Without it you get undefined symbol at load time.

4. What the host sees

The example host prints:

AppA: shared n=...
AppB: shared n=5000
total=5000 expect=5000
HOST OK

HOST OK is printed by the host itself after verifying that both apps returned 0 and that the shared counter reached 5000 — proof that both entries ran in the same VM.

5. Errors you will meet