aboutsummaryrefslogtreecommitdiffstats
path: root/core/runtime.h
blob: fd195259508c3f2f5e717fcc85828cb3efa958a4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#ifndef PURE_RUNTIME_H
#define PURE_RUNTIME_H
#include "flow.h"
#include "result.h"
#include <stdint.h>

/* The optional metrics profile changes PureRuntime's layout. Give its init
 * function a distinct link name so mixed-profile objects cannot link quietly. */
#ifdef PURE_RUNTIME_METRICS
#define pure_runtime_init pure_runtime_init_metrics
#endif

/* One owner thread controls admission, payload credits, completion and loans.
 * An application definition is opaque to this core. Hosts interpret it and
 * perform effects; no parser, storage, socket or OS type is linked here. */
typedef struct PureRuntime PureRuntime;
typedef struct PureWork PureWork;
typedef struct { PureRuntime *runtime; size_t slot; uint64_t generation; } PureTicket;
typedef enum { PURE_INPUT_READ, PURE_INPUT_WRITE } PureInput;

/* The host owns this resource until it successfully returns it to the core.
 * release must end all access and free/unmap allocation exactly once. It runs
 * while that domain is busy; calls back into the same domain are rejected.
 * A resource the host sends in pieces (such as extents of a mapping) has no
 * contiguous body: body.data is NULL and body.size is still what it holds and
 * is charged. Its outcome has no body to read and cannot be forwarded. */
typedef struct {
    PureSpan body;
    void *allocation;
    size_t allocation_size;
    void (*release)(void *allocation, size_t size);
} PureResource;
typedef struct {
    void *context;
    bool (*allocate)(void *, size_t, PureResource *);
    PureResult (*read)(void *, const void *definition, const char *,
                       size_t available, PureResource *, int64_t *revision);
    /* A successful reply may borrow only from input. The runtime retains its
     * backing until all reply consumers finish. Effect errors publish no reply. */
    PureResult (*write)(void *, const void *definition, const char *,
                        int64_t expected, PureSpan input, PureSpan *reply,
                        int64_t *revision);
} PureHost;
typedef struct { PureResult status; int64_t revision; PureSpan body; } PureOutcome;

/* Caller-provided work slots: no background thread or per-domain heap. These
 * records are implementation storage, not an ABI or a wire representation. */
/* Internal storage order packs the fixed key around small flags. This is not
 * a public ABI; the layout is checked as part of the core cost gate. */
struct PureWork {
    PureTicket parent;
    unsigned state;
    PureInput input;
    bool held;
    char key[65];
    int64_t expected;
    PureOutcome outcome;
    PureResource resource;
    uint64_t generation;
    size_t next, charged, pins;
};
struct PureRuntime {
    const void *definition;
    PureHost host;
    PureWork *work;
    size_t capacity, free_head, ready_head, ready_tail;
    size_t budget, retained;
#ifdef PURE_RUNTIME_METRICS
    size_t peak_retained;
#endif
    size_t active;
#ifdef PURE_RUNTIME_METRICS
    size_t peak_active;
#endif
    uint64_t serial;
#ifdef PURE_RUNTIME_METRICS
    uint64_t accepted, commits, gets, conflicts, budget_rejections;
    uint64_t releases, borrowed_output_bytes;
#endif
    bool accepting, running, async_effects;
};

/* Enforce repeated storage cost on 64-bit C hosts, including macOS where the
 * Linux object-section checker is unavailable. */
#if SIZE_MAX == UINT64_MAX && UINTPTR_MAX == UINT64_MAX
_Static_assert(sizeof(PureWork) == 216, "work-slot layout exceeded its reviewed size");
#ifdef PURE_RUNTIME_METRICS
_Static_assert(sizeof(PureRuntime) == 192, "metrics domain layout changed");
#else
_Static_assert(sizeof(PureRuntime) == 120, "domain layout changed");
#endif
#endif

/* Initialize fresh storage once. A NULL work pointer with capacity zero starts
 * an idle domain; bind work before admission. Use restart, not init, to reuse
 * a domain so its monotonic ticket sequence survives restart. definition is
 * host-owned and remains alive until all work has drained. */
bool pure_runtime_init(PureRuntime *, PureWork *, size_t capacity,
                       const void *definition, PureHost, size_t budget);
bool pure_runtime_restart(PureRuntime *, const void *definition, PureHost, size_t budget);
void pure_runtime_stop(PureRuntime *); /* stop admission, allow queued work to drain */
/* Replace caller-owned work storage only after all tickets and loans retire.
 * A zero-capacity binding releases idle slot storage without resetting budget,
 * ticket generation or optional metrics. The caller retains the bound array's life. */
bool pure_runtime_bind(PureRuntime *, PureWork *, size_t capacity);

/* READ queues immediately. WRITE reserves its slot, budget and host allocation
 * before returning. Fill ingress then publish once with ready. A partial input
 * released without ready never executes. On error, out is unchanged. */
PureResult pure_runtime_accept(PureRuntime *, PureInput, const char *key,
                               int64_t expected, size_t input_size, PureTicket *out);
unsigned char *pure_runtime_ingress(PureTicket, size_t *size);
/* A WRITE input still being filled grows to size with its bytes kept (the
 * ingress moves: take it again), charged to the budget as its admission was;
 * BUDGET or ALLOCATION leave it as it was. */
PureResult pure_runtime_grow(PureTicket, size_t size);
bool pure_runtime_ready(PureTicket);
bool pure_runtime_step(PureRuntime *); /* execute at most one ready job */
bool pure_runtime_result(PureTicket, PureOutcome *out);

/* Pin a completed reply and queue it as another domain's WRITE input, with no
 * allocation, serialization or payload copy. Source may be released as soon
 * as this succeeds; the core retains its backing until the consumer ends.
 * Each domain charges its own retained input bytes as admission credits.
 * Credits across domains are NOT distinct physical memory and must not be summed. */
PureResult pure_runtime_forward(PureTicket source, PureRuntime *destination,
                                const char *key, int64_t expected, PureTicket *out);
bool pure_runtime_cancel(PureTicket); /* only before execution; keeps a completion */
bool pure_runtime_release(PureTicket); /* invalidates caller's ticket, preserves pins */

#ifdef PURE_ASYNC_EFFECTS
/* Optional owner-thread effect suspension. Enable before accepting work. The
 * host may complete effects in any order, but must call complete on the owner
 * thread after workers stop accessing its borrowed spans. An in-flight effect
 * keeps its input alive even if the caller releases the ticket. Effects
 * borrowed by take stay valid until completion. */
typedef enum { PURE_TAKE_EMPTY, PURE_TAKE_EFFECT } PureTake;
typedef struct {
    PureTicket ticket;
    PureInput input;
    const char *key;
    int64_t expected;
    PureSpan value; /* raw ingress for WRITE; empty for READ */
    size_t available; /* remaining payload credits for READ */
} PureEffect;
bool pure_runtime_enable_async(PureRuntime *);
PureTake pure_runtime_take(PureRuntime *, PureEffect *);
/* A valid completion consumes read_resource (even on failure); a rejected call
 * leaves it untouched. write_reply borrows from the retained ingress. */
bool pure_runtime_complete(PureTicket, PureResult status,
                           PureResource *read_resource, PureSpan write_reply,
                           int64_t revision);
#endif
#endif