Embedding › 02 Your first host
Write the smallest useful host: initialize the process, load a module, create a VM, run an entry class, and shut down.
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:
native_init must come before loading because binding takes a
lock that it creates.vm_destroy frees runtime state; the VM struct and the modules
live in the arena, which is freed separately.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;
}
# 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.
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.
loth_engine_load returns -1 and leaves the
Engine unchanged; print the reason and stop.loth_engine_entry returns NULL if the class is
missing or does not implement Main.vm_run returns the entry's value; an uncaught exception in the
entry produces a non-zero code and a message on stderr.