4.1 KiB
WASM Phase 1: DValue C ABI and UCEB1
Phase 1 freezes the native DValue ABI that the future WASM core and units use
as their shared structured-value contract. The implementation is in
src/lib/dvalue.{h,cpp} and is available in the native runtime before any WASM
backend is enabled.
Opaque handle
typedef struct DValue uce_dvalue;
uce_dvalue* is a borrowed pointer owned by the active request/workspace. It
must not be freed by ABI callers and it must not be retained beyond that
workspace lifetime.
Accessors
uce_dvalue* uce_dv_root(void);
uce_dvalue* uce_dv_get(uce_dvalue* value, const char* key, size_t key_len);
uce_dvalue* uce_dv_find(uce_dvalue* value, const char* key, size_t key_len);
const char* uce_dv_value(uce_dvalue* value, size_t* len_out);
void uce_dv_set_value(uce_dvalue* value, const char* bytes, size_t len);
size_t uce_dv_count(uce_dvalue* value);
int uce_dv_is_list(uce_dvalue* value);
uce_dv_root()returns the current native request'scontext.callroot. The WASM core will later map this to the decoded request context root.uce_dv_get()creates the child if absent.uce_dv_find()returnsNULLif absent.- String inputs and outputs are length-delimited and binary-safe.
uce_dv_value()returns a borrowed pointer valid until the next ABI value call on the same thread.- Bad
NULLinputs returnNULL, zero, or no-op rather than trapping.
Iteration
typedef struct uce_dv_iter { size_t position; size_t reserved[3]; } uce_dv_iter;
uce_dv_iter uce_dv_iter_begin(uce_dvalue* value);
int uce_dv_iter_next(uce_dvalue* value, uce_dv_iter* iter,
const char** key_out, size_t* key_len_out,
uce_dvalue** child_out);
Map iteration follows DValue's native order. List-shaped maps iterate in numeric
index order (0, 1, ...), matching DValue::each(), dv_values(), and the
serializers. The reserved iterator fields are caller-opaque and must be
zero-preserved by callers that copy the iterator; they provide ABI headroom for
future non-linear keyed-map iteration without changing the struct size.
UCEB1 wire format
UCEB1 is the membrane/cross-instance binary DValue encoding.
document := "UCEB" version node
version := 0x01
node := flags scalar children
flags := u8 bitset; bit0 = list-shaped map
scalar := varuint length, bytes
children := varuint count, count * (key, node)
key := varuint length, bytes
Varuint is unsigned LEB128. Strings are byte sequences; the codec does not
assume NUL termination and preserves embedded NUL bytes. The Phase 1 layout stores scalar values as their native string representation
plus child nodes and the list-shape flag. Floating-point values use
max_digits10 precision so numeric scalars can round-trip through the string
form. Pointer/reference identity is intentionally not part of the wire contract;
pointer nodes encode as an empty scalar rather than leaking process addresses.
An empty non-list map has no wire distinction from an empty scalar in UCEB1 v1.
Documents that contain both scalar bytes and child nodes are reserved for future
use; the v1 decoder accepts the children and ignores the scalar.
Codec APIs
C++/UCE-visible helpers:
String ucb_encode(const DValue& value);
DValue ucb_decode(const String& encoded);
bool ucb_decode(const String& encoded, DValue& out, String* error_out = 0);
C ABI helpers:
size_t uce_dv_encode(uce_dvalue* value, char* buf, size_t cap);
uce_dvalue* uce_dv_decode(const char* buf, size_t len);
const char* uce_dv_last_error(void);
uce_dv_encode() returns the required byte length even when buf is NULL or
cap is zero. uce_dv_decode() returns a thread-local decoded root, or NULL
with uce_dv_last_error() populated. The returned decoded root is valid until
the next uce_dv_decode() call on the same thread. Decoding rejects documents
deeper than 1024 nested nodes so malformed input cannot recurse without bound.
Test coverage
site/tests/core.uce covers:
- UCEB1 round-trip for maps, nested values, lists, empty lists, and embedded NUL scalar bytes.
- C ABI get/find/value/count/list/iteration/encode/decode behavior.