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.
Linking in with OSE_LINK_MODULES
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.
Link it in
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