aboutsummaryrefslogtreecommitdiffstats
path: root/prototypes/connections/module.h
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-25 11:48:51 +0100
committereunhhu <[email protected]>2026-09-25 11:48:51 +0100
commitefba82d75e4357c7e82b027b04a5ec9937f8443b (patch)
tree9c874527466faf836e6fb4a03dd25c9c7160410a /prototypes/connections/module.h
parent12410be7ec2a030e8c3adcf318d87d5ac2954e1d (diff)
downloadpure-efba82d75e4357c7e82b027b04a5ec9937f8443b.tar.gz
pure-efba82d75e4357c7e82b027b04a5ec9937f8443b.zip
Add typed messages and keyed documents to Pure modules
Module format v2 adds byte values, message inputs with u32 and bytes-tail fields, one keyed byte collection, replies and require. The verifier checks every operand's type and the context each operation may run in, and allows branches and joins only at an empty stack, so typed values never merge. A message event opens its key's working version through a PureData host, conflicts early on a stale base revision, runs its handlers, and commits only if they succeed; a failed event leaves documents and states unchanged. Replies lend payload or document extents, and a document reply is copied before commit. pure_tree_data implements the host on tree store drafts, and pure-edit-module offers the document CLI contract of pure-edit from a module. test-module adds document semantics on an in-memory host (not found, create, conflict before bounds, limits, short payloads, busy commits, stable replies, require, state rollback), typed verifier rules, and 20,000 damaged message modules run safely under ASan/UBSan.
Diffstat (limited to 'prototypes/connections/module.h')
-rw-r--r--prototypes/connections/module.h74
1 files changed, 63 insertions, 11 deletions
diff --git a/prototypes/connections/module.h b/prototypes/connections/module.h
index ab2d6fc..16306e5 100644
--- a/prototypes/connections/module.h
+++ b/prototypes/connections/module.h
@@ -1,43 +1,61 @@
#ifndef PURE_MODULE_H
#define PURE_MODULE_H
+#include "flow.h"
#include "result.h"
+#include "state.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
-/* Pure module format v1 (docs/design/008-module-format.md). A toolchain such
- * as aup produces it; Pure verifies it completely before anything executes, so
- * a loaded module cannot overflow the stack, branch backwards or name a
- * missing state. Fixed capacity: no allocation in load, run or replace. */
+/* Pure module format v2 (docs/design/008-module-format.md). A toolchain such
+ * as aup produces it; Pure verifies it completely before anything executes:
+ * operand types, stack heights, forward-only branches, names and effects.
+ * Fixed capacity: loading and running allocate nothing except a copied reply. */
enum {
PURE_MODULE_STATES = 32, PURE_MODULE_INPUTS = 8, PURE_MODULE_VIEWS = 24,
PURE_MODULE_HANDLERS = 64, PURE_MODULE_CODE = 512, PURE_MODULE_STACK = 64,
- PURE_MODULE_NAME = 32, PURE_MODULE_STRINGS = 32768, PURE_MODULE_HEADER = 32
+ PURE_MODULE_NAME = 32, PURE_MODULE_STRINGS = 32768, PURE_MODULE_HEADER = 32,
+ PURE_MODULE_FIELDS = 32, PURE_MODULE_COLLECTIONS = 1
};
typedef enum {
PURE_OP_CONST, PURE_OP_LOAD, PURE_OP_DT, PURE_OP_ADD, PURE_OP_SUB, PURE_OP_MUL,
PURE_OP_DIV, PURE_OP_NEG, PURE_OP_LT, PURE_OP_LE, PURE_OP_GT, PURE_OP_GE,
PURE_OP_EQ, PURE_OP_NE, PURE_OP_AND, PURE_OP_OR, PURE_OP_NOT, PURE_OP_MIN,
- PURE_OP_MAX, PURE_OP_CLAMP, PURE_OP_STORE, PURE_OP_JZ, PURE_OP_JMP, PURE_OP_COUNT
+ PURE_OP_MAX, PURE_OP_CLAMP, PURE_OP_STORE, PURE_OP_JZ, PURE_OP_JMP,
+ PURE_OP_FIELD, PURE_OP_DOC, PURE_OP_DOC_SIZE, PURE_OP_LEN, PURE_OP_REPLACE,
+ PURE_OP_REPLY, PURE_OP_REQUIRE, PURE_OP_COUNT
} PureOpcode;
+typedef enum { PURE_KIND_KEY = 1, PURE_KIND_MESSAGE = 2 } PureInputKind;
+typedef enum { PURE_FIELD_U32 = 1, PURE_FIELD_BYTES = 2 } PureFieldType;
typedef enum { PURE_EDGE_DOWN, PURE_EDGE_UP } PureEdge;
typedef enum {
- PURE_FAULT_NONE, PURE_FAULT_DIVIDE, PURE_FAULT_NON_FINITE, PURE_FAULT_CLAMP
+ PURE_FAULT_NONE, PURE_FAULT_DIVIDE, PURE_FAULT_NON_FINITE, PURE_FAULT_CLAMP,
+ PURE_FAULT_LIMIT, PURE_FAULT_NOT_FOUND
} PureFault;
typedef struct { double value; uint32_t arg; uint8_t op; } PureOp;
typedef struct { uint32_t start, length; } PureBlock;
typedef struct { uint32_t event; PureBlock code; } PureHandler;
+typedef struct { char name[PURE_MODULE_NAME]; uint8_t type; uint32_t offset; } PureField;
+typedef struct {
+ uint8_t kind;
+ bool keyed;
+ uint16_t field_start, field_count;
+ uint32_t prefix; /* fixed payload bytes before an optional bytes tail */
+} PureInputShape;
typedef struct {
- uint16_t states, inputs, views, handlers;
+ uint16_t states, inputs, views, handlers, fields, collections;
uint32_t code_count;
char state_names[PURE_MODULE_STATES][PURE_MODULE_NAME];
char input_names[PURE_MODULE_INPUTS][PURE_MODULE_NAME];
char view_names[PURE_MODULE_VIEWS][PURE_MODULE_NAME];
+ char collection_names[PURE_MODULE_COLLECTIONS][PURE_MODULE_NAME];
char input_keys[PURE_MODULE_INPUTS][PURE_MODULE_NAME];
+ PureInputShape input[PURE_MODULE_INPUTS];
+ PureField field[PURE_MODULE_FIELDS];
double initial[PURE_MODULE_STATES];
PureBlock view_code[PURE_MODULE_VIEWS];
PureHandler handler[PURE_MODULE_HANDLERS];
@@ -52,19 +70,53 @@ int pure_module_state(const PureModule *, const char *name);
int pure_module_input(const PureModule *, const char *name);
int pure_module_view(const PureModule *, const char *name);
-/* One app instance: state values of a module. Every event is atomic: on a
- * fault the values are exactly those before the event. */
+/* Keyed collections live in a data host (for example the tree store). One
+ * event opens at most one document, the message's key, at its current
+ * version; edits stay in that working version until the event commits it. */
+typedef struct {
+ void *context;
+ PureResult (*open)(void *context, uint32_t collection, const char *key,
+ void **document, int64_t *revision);
+ PureVersion (*version)(void *context, void *document);
+ PureResult (*read)(void *context, void *document, PureVersion, size_t offset, size_t length,
+ bool (*visit)(void *, PureSpan), void *visit_context);
+ PureResult (*splice)(void *context, void *document, size_t offset, size_t remove, PureSpan insert);
+ PureResult (*commit)(void *context, void *document, int64_t *revision);
+ void (*close)(void *context, void *document);
+} PureData;
+
+/* A byte value: a range of the event's payload, of a document version, or of
+ * a reply copied before commit. Internal to one event. */
+typedef struct { uint8_t source; PureVersion version; uint64_t offset, length; } PureBytes;
+
+/* One app instance: state values of a module and at most one open event.
+ * Every event is atomic: on failure states and documents are unchanged. */
typedef struct {
const PureModule *module;
+ PureData data;
double values[PURE_MODULE_STATES];
PureFault fault;
uint32_t fault_pc;
uint64_t ticks, events;
+ /* Current message event, kept until pure_app_finish. */
+ void *document;
+ PureSpan payload;
+ bool has_reply;
+ PureBytes reply;
+ unsigned char *copied;
+ int64_t revision;
} PureApp;
-void pure_app_start(PureApp *, const PureModule *);
+void pure_app_start(PureApp *, const PureModule *, PureData data);
PureResult pure_app_frame(PureApp *);
PureResult pure_app_input(PureApp *, uint32_t input, PureEdge);
+/* Deliver a message. base is the revision the sender edited, or -1 for none;
+ * a handler that edits the document needs one. payload stays alive until
+ * pure_app_finish. On return app->revision is the document's revision. */
+PureResult pure_app_message(PureApp *, uint32_t input, const char *key, int64_t base, PureSpan payload);
+/* Visit the reply of the last message in order, without copying. */
+PureResult pure_app_reply(const PureApp *, bool (*visit)(void *, PureSpan), void *context);
+void pure_app_finish(PureApp *);
bool pure_app_view(const PureApp *, uint32_t view, double *out);
/* Switch to another verified module. States keep their value by name; new
* states start at their initial value. The module must outlive the app. */