13.3. Protocols
|
|
This section is primarily of interest to users extending the Morloc ecosystem (e.g., adding a new language backend) or debugging at the binary level. |
This section describes the binary formats used for communication between the nexus and pools: the manifest, the packet protocol, the shared memory layout, and the voidstar data format.
13.3.1. The manifest
The manifest is a standalone manifest.json file written into the program’s
<name>-build/ directory. The launcher script that morloc make produces is a
thin wrapper carrying no embedded payload; it execs the shared morloc-nexus
runtime against an absolute path to this file, so the launcher can be freely
moved as long as its build directory stays put. The manifest describes the
program’s structure. Key fields:
| Field | Description |
|---|---|
|
Manifest format version (currently |
|
Program name |
|
Absolute path to the build directory |
|
Array of pool descriptors (see below) |
|
Array of exported commands (see below) |
Each pool entry:
-
lang— Language name (e.g.,"python3","cpp") -
exec— Command-line tokens to launch the pool, with the pool path relative tobuild_dir(e.g.,["python3", "pools/python3/pool.py"]) -
socket— Unix domain socket basename (e.g.,"pipe-python3")
Each command entry:
-
name— CLI subcommand name -
type—"remote"(dispatched to a pool) or"pure"(evaluated in the nexus) -
mid— Manifold index identifying the function in the pool -
pool— Index into thepoolsarray -
needed_pools— Indices of all pools that must be running -
arg_schemas/return_schema— Schema strings describing argument and return types (see Schema strings) -
args— CLI argument descriptors
13.3.2. Packet protocol
All communication uses a binary packet protocol over Unix domain sockets. Every packet starts with a 32-byte packed header:
| Field | Type | Width | Description |
|---|---|---|---|
|
uint32_t |
4 |
Constant |
|
uint16_t |
2 |
Plain membership (reserved, always 0) |
|
uint16_t |
2 |
Format version (currently 0) |
|
uint16_t |
2 |
Metadata convention (reserved) |
|
uint16_t |
2 |
Evaluation mode (reserved) |
|
union |
8 |
Type-specific command data (see below) |
|
uint32_t |
4 |
Bytes of metadata between header and payload |
|
uint64_t |
8 |
Payload length in bytes |
Total packet size is always 32 + offset + length.
Packet types
The command field’s first byte is a type tag:
Data packet (0x00) — Carries data or error messages:
| Field | Type | Width | Description |
|---|---|---|---|
|
uint8_t |
1 |
|
|
uint8_t |
1 |
|
|
uint8_t |
1 |
|
|
uint8_t |
1 |
Reserved, always 0 |
|
uint8_t |
1 |
Reserved, always 0 |
|
uint8_t |
1 |
|
|
uint8_t |
2 |
Zero |
For small data (up to 64 KB serialized), the most common combination is
source=MESG, format=VOIDSTAR — the voidstar binary is embedded directly in
the packet payload, avoiding shared memory entirely. For large data, the
combination is source=RPTR, format=VOIDSTAR — only an 8-byte relative
pointer travels over the socket, and the data lives in shared memory.
When status=FAIL, the packet carries a UTF-8 error message (source=MESG,
format=TEXT).
Call packet (0x01) — Instructs a pool to execute a function:
| Field | Type | Width | Description |
|---|---|---|---|
|
uint8_t |
1 |
|
|
uint8_t |
1 |
|
|
uint8_t |
2 |
Zero |
|
uint32_t |
4 |
Manifold index (which function to call) |
The payload is a contiguous sequence of data packets, one per argument.
Ping packet (0x02) — Header-only, no payload. The nexus pings pools to
check readiness; the pool echoes it back as a pong.
13.3.3. Shared memory
Pools share data through POSIX shared memory segments rather than copying over sockets. Only relative pointers (8 bytes) travel over the wire.
Volumes
Shared memory is organized as multiple volumes (/dev/shm/morloc-<hash>_0,
morloc-<hash>_1, etc.). The nexus creates the first volume (64 KB). New
volumes are created automatically when space runs out (up to 32 volumes). If
/dev/shm is too small (common in Docker), volumes fall back to files in the
temporary directory. Under Apptainer/Singularity the host’s /dev/shm is
shared into the container at host size, so this fallback is rarely
triggered.
Pointer types
| Type | Description |
|---|---|
|
Virtual address in the current process. Different per process. |
|
Offset within a single volume (0 = first byte after the header). |
|
Global offset across all volumes. This is the pointer type shared between processes — it appears in data packets and in voidstar data structures. |
volume 0 (size=20) volume 1
---xxxxxx........----xxxxxx............---->
relptr 0 7 8 19
Volume header (shm_t)
| Field | Type | Description |
|---|---|---|
|
unsigned int |
Constant |
|
char[256] |
Volume identifier |
|
int |
Index in the pool (0, 1, 2, …) |
|
size_t |
Usable data capacity (excludes header) |
|
size_t |
Sum of all prior volumes' sizes |
|
pthread_rwlock_t |
Process-shared read-write lock |
|
volptr_t |
Current free block (allocator hint) |
Block header (block_header_t, packed)
| Field | Type | Description |
|---|---|---|
|
unsigned int |
Constant |
|
atomic unsigned int |
Active references (0 = free) |
|
size_t |
Payload size in bytes (excludes header) |
Blocks use reference counting. shmalloc allocates with first-fit and lazy
coalescing. shfree decrements the reference count; blocks are merged during
the next allocation scan.
13.3.4. Schema strings
Schema strings are a compact encoding of a data type’s binary layout. They appear in the manifest and in packet metadata.
Primitives:
| Schema | Type |
|---|---|
|
nil (1 byte) |
|
bool (1 byte) |
|
signed int (1/2/4/8 bytes) |
|
unsigned int (1/2/4/8 bytes) |
|
float (4/8 bytes) |
|
variable-length UTF-8 string |
Compounds:
| Pattern | Description |
|---|---|
|
Array. |
|
Tuple. |
|
Record with length-prefixed keys. |
13.3.5. Voidstar binary format
Every Morloc general type maps unambiguously to a binary form that consists of several fixed-width literal types, a list container, and a tuple container. The literal types include a unit type, a boolean, signed integers (8, 16, 32, and 64 bit), unsigned integers (8, 16, 32, and 64 bit), and IEEE floats (32 and 64 bit). The list container is represented by a 64-bit size integer and a pointer to an unboxed vector. The tuple is represented as a set of values in contiguous memory. These basic types are listed below:
| Type | Domain | Schema | Width (bytes) |
|---|---|---|---|
Unit |
|
z |
1 |
Bool |
|
b |
1 |
U8 |
u1 |
1 |
|
U16 |
u2 |
2 |
|
U32 |
u4 |
4 |
|
U64 |
u8 |
8 |
|
I8 |
i1 |
1 |
|
I16 |
i2 |
2 |
|
I32 |
i4 |
4 |
|
I64 |
i8 |
8 |
|
F32 |
IEEE float |
f4 |
4 |
F64 |
IEEE double |
f8 |
8 |
List x |
lists |
a{x} |
|
Tuple2 x1 x2 |
2-ples |
t2{x1}{x2} |
|
TupleX |
k-ples |
||
records |
All basic types may be written to a schema that is used internally to direct
conversions between Morloc binary and native basic types. The schema values
are shown in the table above. For example, the type [(Bool, [I8])] would
have the schema at2bai1. You will not usually have to worry about these
schemas, since they are mostly used internally. They are worth knowing, though,
since they appear in low-level tests, generated source code, and binary data
packets.
Here is an example of how the type ([U8], Bool), with the value
([3,4,5],True), might be laid out in memory:
---
03 00 00 00 00 00 00 00 -- first tuple element, specifies list length (little-endian)
30 00 00 00 00 00 00 00 -- first tuple element, pointer to list
01 00 00 00 00 00 00 00 -- second tuple element, with 0-padding
03 04 05 -- 8-bit values of 3, 4, and 5
---
Records and tables (described in detail earlier) are represented as tuples in
voidstar format — field names are stored only in the type schemas. The table
annotation is not just syntactic sugar for a record of lists; it is preserved
through the compiler to the translator, where language-specific serialization
functions may have special handling for tables.
record Person = Person { name :: Str, age :: U8 }
table People = People { name :: Str, age :: Int }
alice = { name = "Alice", age = 27 }
students = { name = ["Alice", "Bob"], age = [27, 25] }
The Morloc type signatures can be translated to schema strings that may be parsed by a foundational Morloc C library into a type structure. Every supported language in the Morloc ecosystem must provide a library that wraps this Morloc C library and translates to/from Morloc binary given the Morloc type schema.