Modules

A module extends the machine with functions written in C or C++. It is a shared library with one entry point:

void ose_main(ose_bundle osevm);

It is handed the machine and pushes one message per function it provides: the address the function will answer to, and a pointer to it. A module that offers two functions looks like this:

#include <string.h>
#include "ose.h"
#include "ose_context.h"
#include "ose_stackops.h"
#include "ose_vm.h"
#include "ose_abi.h"

static void my_blink(ose_bundle osevm) { /* arguments are on the stack */ }
static void my_read(ose_bundle osevm) { /* ... */ }

OSE_ABI_DECLARE;

void ose_main(ose_bundle osevm)
{
    ose_bundle vm_s = OSEVM_STACK(osevm);
    OSE_START_BUNDLE(vm_s);
    ose_pushMessage(vm_s, "/led/blink", strlen("/led/blink"),
                    1, OSETT_ALIGNEDPTR, my_blink);
    ose_pushMessage(vm_s, "/sensor/read", strlen("/sensor/read"),
                    1, OSETT_ALIGNEDPTR, my_read);
    OSE_END_BUNDLE(vm_s);
}

OSE_START_BUNDLE opens a bundle on the stack and OSE_END_BUNDLE closes it around whatever was pushed in between. A function bound this way takes the machine and finds its arguments on the stack.

The host decides which register the module’s bundle goes into. The command line moves it into the host register, /_x, and from then on /!/led/blink is an address like any other.

Loading with /!/load

On a host with a dynamic loader, /!/load with the path of the shared library does dlopen, finds ose_main, checks the ABI record, and calls it. The module is a separate file, built on its own, and can be loaded while the host is running.

On a host without dlopen — the browser, a board — the module’s source is compiled into the host with OSE_LINK_MODULES defined. The one difference: the entry point is renamed (ose_osen_init, ose_stdlib_init), so several modules can share one binary, and the host calls each at start-up instead of loading it.

Make a module

From an empty directory. ASOMA is your clone of the workspace, built (make -C $ASOMA/hosts/unix.o.se).

One C file, ose_hello.c. /hello takes nothing and pushes a string:

#include <string.h>
#include "ose.h"
#include "ose_abi.h"
#include "ose_stackops.h"
#include "ose_vm.h"

static void ose_hello_hello(ose_bundle osevm)
{
    ose_bundle vm_s = OSEVM_STACK(osevm);
    ose_pushString(vm_s, "Hello, World!");
}

OSE_ABI_DECLARE;

void ose_main(ose_bundle osevm)
{
    ose_bundle vm_s = OSEVM_STACK(osevm);
    OSE_START_BUNDLE(vm_s);
    ose_pushMessage(vm_s, "/hello", strlen("/hello"),
                    1, OSETT_ALIGNEDPTR, ose_hello_hello);
    OSE_END_BUNDLE(vm_s);
}

A two-line Makefile; the kit’s shared rules do the rest:

BASENAME=hello

include $(OSEKITDIR)/makefiles/makefile-module.mk

Build it through the command-line host’s own modules target, so it is compiled with the flags o.se was built with; a module built with other flags is refused at load (the ABI record):

make -C $ASOMA/hosts/unix.o.se release MODULES=$PWD

That leaves o.se.hello.so beside the source. Load it into o.se and call it, three instructions on the command line: load the file, append the bundle it pushed to the host register, call /hello:

cd $ASOMA/hosts/unix.o.se
./o.se -f o.se.osen/osen.ose "/,/s/$OLDPWD/o.se.hello.so" /!/load '/<</_x' \
    /!/hello /!/println /!/quit
Hello, World!

To call it from osen, wrap it the way osen.ose wraps /time/now, and /o/hello is a function like any other at the prompt:

./o.se -f o.se.osen/osen.ose "/,/s/$OLDPWD/o.se.hello.so" /!/load '/<</_x' \
    '/,/s//!/hello' '/,/s//o/hello' /!/o/unwrap
> () /o/hello
: "Hello, World!"

Built any other way — cc -shared with no flags, say — the loader says so and the name stays unbound:

ose: bad.so was built against a different libose (types: module expects 0, this build has 16383). Rebuild it.

The same module, compiled into a host of your own. In ose_hello.c, the entry point takes the linked-in name when OSE_LINK_MODULES is defined, and the ABI record is left out, since nothing will load it:

#ifdef OSE_LINK_MODULES
void ose_hello_init(ose_bundle osevm)
#else
OSE_ABI_DECLARE;

void ose_main(ose_bundle osevm)
#endif
{

The host, host.c: a machine in a static buffer with five 8 KB registers, the module’s bundle moved into the environment (where the library’s own lookup searches), one instruction in the input, one run, and the answer read off the stack. The library alone; no kit.

#include <stdio.h>
#include <string.h>
#include "ose.h"
#include "ose_context.h"
#include "ose_stackops.h"
#include "ose_vm.h"

void ose_hello_init(ose_bundle osevm);

#define REG 8192
static char bytes[6 * (REG + OSE_CONTEXT_MAX_OVERHEAD)];

int main(void)
{
    ose_bundle bundle = ose_newBundleFromCBytes(sizeof(bytes), bytes);
    ose_bundle vm = osevm_init(bundle, REG, REG, REG, REG, REG);
    ose_bundle vm_s = OSEVM_STACK(vm);
    ose_bundle vm_e = OSEVM_ENV(vm);
    ose_bundle vm_i = OSEVM_INPUT(vm);

    ose_hello_init(vm);              /* the module's bundle, on the stack */
    ose_appendBundle(vm_s, vm_e);    /* its contents into the environment */

    ose_pushMessage(vm_i, "/!/hello", strlen("/!/hello"), 0);
    osevm_run(vm);

    printf("%s\n", ose_peekString(vm_s));
    return 0;
}

Build the library once under a BUILD_ID of your own — the archive lands in build/hello/, beside any other configuration’s, and build/hello/.flags records the flags it was built with, which the host and the module must share:

make -C $ASOMA/lib/osekit/source/libose BUILD_ID=hello CFLAGS="-O2 -Wall"

Then one line for the host and the module together:

L=$ASOMA/lib/osekit/source/libose
cc -O2 -Wall -DOSE_LINK_MODULES -I$L host.c ose_hello.c \
    $L/build/hello/libose.a -lm -o hello-host
./hello-host
Hello, World!

The ABI record

A module reads bundle structure using offsets compiled into it, so one built against a different configuration of the library reads the wrong bytes rather than failing to link. The ABI record is how a loader tells, and refuses.


Embed it · The kit · C reference · The hosts