111 lines
4.1 KiB
Markdown
111 lines
4.1 KiB
Markdown
# 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
|
|
|
|
```c
|
|
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
|
|
|
|
```c
|
|
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's `context.call` root.
|
|
The WASM core will later map this to the decoded request context root.
|
|
- `uce_dv_get()` creates the child if absent. `uce_dv_find()` returns `NULL`
|
|
if 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 `NULL` inputs return `NULL`, zero, or no-op rather than trapping.
|
|
|
|
## Iteration
|
|
|
|
```c
|
|
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:
|
|
|
|
```cpp
|
|
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:
|
|
|
|
```c
|
|
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.
|