aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-18 18:52:48 +0100
committereunhhu <[email protected]>2026-10-18 18:52:48 +0100
commite8b3499289022c8729900a58ed218d961a85fec8 (patch)
treefef2531be1dfc5a1568c3e2545430a8d5718d26f /core
parent531200a6c24842784fad68102878c3ab77391b13 (diff)
downloadpure-e8b3499289022c8729900a58ed218d961a85fec8.tar.gz
pure-e8b3499289022c8729900a58ed218d961a85fec8.zip
Module format and executor: 027's 4b-1, a record field holding bytes (type 5): held as a text is but for UTF-8, its default a constant; a function's bytes in a signature this type too (PURE_SIGNATURE_BYTES gone); no list keyed by bytes; replaced, a text field becomes a bytes field keeping its value (pure_cell_as, kinds_carry); images' headers take its kind; the JSON edge reads it from a string; pure-run shows it as hex; an append asks a record's fields held as bytes from a mask the load makes (record_held) instead of each field's type twice, so b32 runs fewer instructions than b30 (BFS without batches -1.05%, join -0.25%, BFS with batches -0.36%, the rest within 0.04%; the bank's queries s30 against s32 -0.01% to +0.30%); unit test bytes_fields (bytes not UTF-8, a text field replaced by a bytes field, three refused); server_trees with a bytes field; 008 and 027
Diffstat (limited to 'core')
-rw-r--r--core/module-jit.c2
-rw-r--r--core/module-list.c2
-rw-r--r--core/module-list.h2
-rw-r--r--core/module-save.c6
-rw-r--r--core/module.c86
-rw-r--r--core/module.h13
-rw-r--r--core/test-module.c61
7 files changed, 117 insertions, 55 deletions
diff --git a/core/module-jit.c b/core/module-jit.c
index 328a69f..d5c5d3a 100644
--- a/core/module-jit.c
+++ b/core/module-jit.c
@@ -1245,7 +1245,7 @@ static bool event_list(const PureModule *m, uint32_t list) {
static bool numbers_only(const PureModule *m, uint32_t list) {
const PureRecord *record = &m->record[m->state_ref[list]];
for (unsigned f = 0; f < record->count; ++f)
- if (m->record_field_type[record->first + f] == PURE_RECORD_TEXT || m->record_field_type[record->first + f] == PURE_RECORD_LIST)
+ if (pure_record_bytes(m->record_field_type[record->first + f]) || m->record_field_type[record->first + f] == PURE_RECORD_LIST)
return false; /* a list in a record is shared by count (module.c's APPEND_IN) */
return true;
}
diff --git a/core/module-list.c b/core/module-list.c
index 57e44c4..2f2c359 100644
--- a/core/module-list.c
+++ b/core/module-list.c
@@ -2072,7 +2072,7 @@ bool pure_cell_fits(uint8_t kind, PureCell cell) {
}
bool pure_cell_as(uint8_t from, uint8_t to, PureCell *cell) {
- if (from == to) return true;
+ if (from == to || (from == PURE_CELL_TEXT && to == PURE_CELL_BYTES)) return true; /* a text is bytes */
if (pure_cell_text(from) || pure_cell_text(to)) return false;
if (to == PURE_CELL_INTEGER) { /* a whole number in range: -2^63 is, 2^63 is not */
double v = cell->num;
diff --git a/core/module-list.h b/core/module-list.h
index 928b12a..13c2550 100644
--- a/core/module-list.h
+++ b/core/module-list.h
@@ -65,7 +65,7 @@ bool pure_cell_fits(uint8_t kind, PureCell cell);
* replaced, a stored value read by another module, a waiting send recast):
* true with *cell made the other when it fits (a bool from 0 or 1, either
* zero false; a number from an integer within 2^53; an integer from a
- * whole number in its range), else false. Texts only as themselves. */
+ * whole number in its range), else false. Texts as themselves and as bytes, bytes as themselves. */
bool pure_cell_as(uint8_t from, uint8_t to, PureCell *cell);
PureList *pure_list_new(unsigned fields, const uint8_t *kinds);
void pure_list_free(PureList *);
diff --git a/core/module-save.c b/core/module-save.c
index 3b1284c..f87cd24 100644
--- a/core/module-save.c
+++ b/core/module-save.c
@@ -221,7 +221,7 @@ static bool read_fields(const unsigned char *bytes, size_t size, size_t *at, uns
for (unsigned f = 0; f < count; ++f) {
if (!named(bytes, size, at, 0, &l->record[k].kinds[f], l->record[k].names[f])) return false;
uint8_t kind = l->record[k].kinds[f];
- if (kind > PURE_CELL_LIST || kind == PURE_CELL_BYTES) return false;
+ if (kind > PURE_CELL_LIST) return false;
for (unsigned g = 0; g < f; ++g) if (!strcmp(l->record[k].names[g], l->record[k].names[f])) return false;
l->record[k].items[f] = 0;
if (kind != PURE_CELL_LIST) continue;
@@ -497,8 +497,8 @@ PURE_COLD PureResult pure_save_open_list(PureApp *app, const PureModule *m, uint
if (result == PURE_OK && pure_draft_revision(*legacy) && pure_draft_size(*legacy))
result = read_whole(*legacy, &bytes, &size);
}
- /* The stored record graph: its fields' kinds (PURE_CELL_...: a record's, no runtime bytes) and names, and the
- * records of the lists in its fields (an image's alone, 027's stage 3). */
+ /* The stored record graph: its fields' kinds (PURE_CELL_...: a record's, its lists' PURE_CELL_LIST) and names, and
+ * the records of the lists in its fields (an image's alone, 027's stage 3). */
PureLayout stored_layout = {.records = 1};
unsigned count = 0;
size_t at = 0, items = 0;
diff --git a/core/module.c b/core/module.c
index 03cc434..b8782f3 100644
--- a/core/module.c
+++ b/core/module.c
@@ -352,10 +352,14 @@ static bool state_of(const PureModule *m, uint32_t state, uint8_t type) {
/* The list an item operation names: its low 16 bits (the field is above). */
+/* The stack type of a record field's type (PureRecordType), and of a record field (the module's numbering). */
+static unsigned char record_type_of(uint8_t type) {
+ static const unsigned char types[] = {[PURE_RECORD_NUMBER] = T_NUM, [PURE_RECORD_TEXT] = T_TEXT, [PURE_RECORD_BOOL] = T_BOOL,
+ [PURE_RECORD_I64] = T_I64, [PURE_RECORD_LIST] = T_LIST, [PURE_RECORD_BYTES] = T_BYTES};
+ return types[type];
+}
static unsigned char field_type(const PureModule *m, uint32_t list, uint32_t field) {
- static const unsigned char types[] = {[PURE_RECORD_NUMBER] = T_NUM, [PURE_RECORD_TEXT] = T_TEXT,
- [PURE_RECORD_BOOL] = T_BOOL, [PURE_RECORD_I64] = T_I64, [PURE_RECORD_LIST] = T_LIST};
- return types[m->record_field_type[m->record[m->state_ref[list]].first + field]];
+ return record_type_of(m->record_field_type[m->record[m->state_ref[list]].first + field]);
}
/* The record of the lists a LIST field of a record holds (field: the module's numbering), or UINT32_MAX. */
@@ -378,18 +382,10 @@ static bool bound_list(const PureModule *m, uint32_t list) {
return event_list_state(m, list) && m->state_flags[list] & PURE_LIST_BOUND >> 8;
}
-/* The stack type of a record field's type (PureRecordType), and of a record field (the module's numbering). */
-static unsigned char record_type_of(uint8_t type) {
- static const unsigned char types[] = {[PURE_RECORD_NUMBER] = T_NUM, [PURE_RECORD_TEXT] = T_TEXT,
- [PURE_RECORD_BOOL] = T_BOOL, [PURE_RECORD_I64] = T_I64, [PURE_RECORD_LIST] = T_LIST};
- return types[type];
-}
static unsigned char record_type(const PureModule *m, uint32_t field) { return record_type_of(m->record_field_type[field]); }
-/* The stack type of a function's signature item (a record field's type, or PURE_SIGNATURE_BYTES). */
-static unsigned char signature_stack_type(const PureModule *m, uint32_t item) {
- return m->signature_type[item] == PURE_SIGNATURE_BYTES ? T_BYTES : record_type_of(m->signature_type[item]);
-}
+/* The stack type of a function's signature item (a record field's type). */
+static unsigned char signature_stack_type(const PureModule *m, uint32_t item) { return record_type_of(m->signature_type[item]); }
/* The record of the list value an instruction takes as its operand k (bottom 0). */
static unsigned list_wanted(const PureModule *m, const PureOp *op, unsigned k) {
@@ -419,15 +415,15 @@ static unsigned char state_type(const PureModule *m, uint32_t state) {
return m->state_type[state] == PURE_STATE_BOOL ? T_BOOL : m->state_type[state] == PURE_STATE_I64 ? T_I64 : T_NUM;
}
-/* Whether a list's record field is a text. */
+/* Whether a list's record field is held as bytes are: a text or bytes. */
static bool text_field(const PureModule *m, uint32_t list, uint32_t field) {
- return m->record_field_type[m->record[m->state_ref[list]].first + field] == PURE_RECORD_TEXT;
+ return pure_record_bytes(m->record_field_type[m->record[m->state_ref[list]].first + field]);
}
-/* Whether its cells are bytes: a text, or a list in a list kept past an event (027's stage 3). */
+/* Whether its cells are bytes: a text, bytes, or a list in a list kept past an event (027's stage 3). */
static bool bytes_field(const PureModule *m, uint32_t list, uint32_t field) {
uint8_t type = m->record_field_type[m->record[m->state_ref[list]].first + field];
- return type == PURE_RECORD_TEXT || (type == PURE_RECORD_LIST && !(m->state_flags[list] & PURE_LIST_ROWS >> 8));
+ return pure_record_bytes(type) || (type == PURE_RECORD_LIST && !(m->state_flags[list] & PURE_LIST_ROWS >> 8));
}
/* What a local holds (its argument's bits 8-15): a number, an index, a bool, an i64 or a list (027's 2c). */
@@ -1793,21 +1789,22 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
const unsigned char *entry = fields_at + (size_t)f * RECORD_FIELD_BYTES;
uint32_t type = read32(entry + 4); /* PureRecordType */
out->record_field_type[f] = (uint8_t)type;
- if (!name_at(text, strings, read32(entry), NAME_PATH, out->record_field_names[f]) || type > PURE_RECORD_LIST)
+ if (!name_at(text, strings, read32(entry), NAME_PATH, out->record_field_names[f]) || type > PURE_RECORD_BYTES)
REFUSE("record field", f);
for (uint32_t k = record->first; k < f; ++k)
if (!strcmp(out->record_field_names[k], out->record_field_names[f])) REFUSE("duplicate record field", f);
- /* A default: a text's constant, an i64's 8 bytes, a number (a bool's +0 or 1). */
- if (type == PURE_RECORD_TEXT) out->record_field_default[f].num = read32(entry + 8);
+ /* A default: a text's or bytes' constant, an i64's 8 bytes, a number (a bool's +0 or 1). */
+ if (pure_record_bytes((uint8_t)type)) out->record_field_default[f].num = read32(entry + 8);
else if (type == PURE_RECORD_I64 || type == PURE_RECORD_LIST) /* a LIST's: the record of its items */
out->record_field_default[f].i64 = (int64_t)(read32(entry + 8) | (uint64_t)read32(entry + 12) << 32);
else out->record_field_default[f].num = read_double(entry + 8);
double given = out->record_field_default[f].num;
- if (type == PURE_RECORD_TEXT ? read32(entry + 12) || read32(entry + 8) >= out->constants
+ if (pure_record_bytes((uint8_t)type) ? read32(entry + 12) || read32(entry + 8) >= out->constants
: type == PURE_RECORD_LIST ? (uint64_t)out->record_field_default[f].i64 >= out->records
: type != PURE_RECORD_I64 && (!isfinite(given) || (type == PURE_RECORD_BOOL && !canonical_bool(given))))
REFUSE("record field default", f);
if (type == PURE_RECORD_LIST) out->record_nested[i] = true;
+ if (pure_record_bytes((uint8_t)type)) out->record_held[i] |= (uint16_t)(1u << (f - record->first));
}
next_record_field += record->count;
}
@@ -1822,11 +1819,12 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
out->record_field_type[out->record[out->state_ref[i]].first + out->state_key[i] - 1u] == PURE_RECORD_BOOL))) ||
(out->state_type[i] == PURE_STATE_INDEX && !state_of(out, out->state_ref[i], PURE_STATE_LIST)))
REFUSE("state reference", i);
- /* No list is keyed by a list in its records; a register is no message's rows. */
+ /* No list is keyed by a list or bytes in its records (a key is a number or a text); a register is no message's rows. */
for (uint32_t i = 0; i < out->states; ++i) {
- if (out->state_type[i] != PURE_STATE_LIST) continue;
- if (out->state_key[i] && out->record_field_type[out->record[out->state_ref[i]].first + out->state_key[i] - 1u] == PURE_RECORD_LIST)
- REFUSE("list in a record as a key", i);
+ if (out->state_type[i] != PURE_STATE_LIST || !out->state_key[i]) continue;
+ uint8_t key = out->record_field_type[out->record[out->state_ref[i]].first + out->state_key[i] - 1u];
+ if (key == PURE_RECORD_LIST) REFUSE("list in a record as a key", i);
+ if (key == PURE_RECORD_BYTES) REFUSE("bytes as a key", i);
}
for (uint32_t f = 0; f < out->fields; ++f)
if (out->field[f].type == PURE_FIELD_ROWS && out->state_flags[out->field[f].rows] & PURE_LIST_BOUND >> 8)
@@ -1885,7 +1883,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
for (uint32_t k = 0; k < signatures; ++k, at += SIGNATURE_BYTES) {
out->signature_type[k] = at[0];
out->signature_record[k] = (uint16_t)read16(at + 2);
- if (at[1] || at[0] > PURE_SIGNATURE_BYTES ||
+ if (at[1] || at[0] > PURE_RECORD_BYTES ||
(at[0] == PURE_RECORD_LIST ? out->signature_record[k] >= out->records : out->signature_record[k] != 0))
REFUSE("function signature", k);
}
@@ -2486,7 +2484,7 @@ static size_t value_size(const PureApp *app, uint32_t record, uint64_t list) {
for (unsigned f = 0; f < r->count; ++f) {
uint8_t type = m->record_field_type[r->first + f];
PureCell cell = pure_list_get(items, at, f);
- size += type == PURE_RECORD_TEXT ? 4 + cell.size :
+ size += pure_record_bytes(type) ? 4 + cell.size :
type == PURE_RECORD_LIST ? 4 + value_size(app, nested_record(m, r->first + f), (uint64_t)cell.i64) : 8;
}
return size;
@@ -2500,7 +2498,7 @@ static unsigned char *value_put(const PureApp *app, uint32_t record, uint64_t li
for (unsigned f = 0; f < r->count; ++f) {
uint8_t type = m->record_field_type[r->first + f];
PureCell cell = pure_list_get(items, at, f);
- if (type == PURE_RECORD_TEXT) {
+ if (pure_record_bytes(type)) {
write32(out, (uint32_t)cell.size);
if (cell.size) memcpy(out + 4, cell.text, cell.size);
out += 4 + cell.size;
@@ -2532,7 +2530,7 @@ static uint64_t value_take(PureApp *app, uint32_t record, const unsigned char *b
for (unsigned f = 0; f < r->count; ++f) {
uint32_t field = r->first + f;
uint8_t type = m->record_field_type[field];
- if (type == PURE_RECORD_TEXT || type == PURE_RECORD_LIST) {
+ if (pure_record_bytes(type) || type == PURE_RECORD_LIST) {
if (size - at < 4) return *bad = true, *made = false, 0;
uint32_t length = read32(bytes + at);
if (length > size - at - 4 || (type == PURE_RECORD_TEXT && !pure_utf8_valid((PureSpan){bytes + at + 4, length})))
@@ -2580,7 +2578,7 @@ static __attribute__((cold, noinline)) PureResult kept_cells(PureApp *app, PureA
for (unsigned f = 0; f < r->count; ++f) {
uint8_t type = m->record_field_type[r->first + f];
total += type == PURE_RECORD_LIST ? value_size(app, nested_record(m, r->first + f), (uint64_t)values[f].i64) :
- type == PURE_RECORD_TEXT ? (size_t)values[f].bytes.length : 0;
+ pure_record_bytes(type) ? (size_t)values[f].bytes.length : 0;
}
if (!reserve(arena, total)) return PURE_ALLOCATION;
for (unsigned f = 0; f < r->count; ++f) {
@@ -2588,7 +2586,7 @@ static __attribute__((cold, noinline)) PureResult kept_cells(PureApp *app, PureA
PureSpan span = {0};
PureResult status = PURE_OK;
if (type == PURE_RECORD_LIST) status = value_cell(app, arena, nested_record(m, r->first + f), (uint64_t)values[f].i64, &cells[f]);
- else if (type == PURE_RECORD_TEXT && (status = contiguous(app, arena, values[f].bytes, &span)) == PURE_OK)
+ else if (pure_record_bytes(type) && (status = contiguous(app, arena, values[f].bytes, &span)) == PURE_OK)
cells[f] = (PureCell){.text = (unsigned char *)span.data, .size = span.size};
else cells[f] = (PureCell){.i64 = values[f].i64};
if (status != PURE_OK) return status;
@@ -2627,12 +2625,12 @@ static __attribute__((cold, noinline)) int append_kept(PureApp *app, PureArena *
if (!own) return *status = PURE_ALLOCATION, 3;
size_t mark = arena->used, total = 0;
for (unsigned f = 0; f < record->count; ++f)
- if (m->record_field_type[record->first + f] == PURE_RECORD_TEXT) total += (size_t)values[f].bytes.length;
+ if (pure_record_bytes(m->record_field_type[record->first + f])) total += (size_t)values[f].bytes.length;
if (!reserve(arena, total)) return *status = PURE_ALLOCATION, 3;
PureCell cells[PURE_RECORD_FIELDS];
for (unsigned f = 0; f < record->count; ++f) {
PureSpan span = {0};
- if (m->record_field_type[record->first + f] == PURE_RECORD_TEXT &&
+ if (pure_record_bytes(m->record_field_type[record->first + f]) &&
(*status = contiguous(app, arena, values[f].bytes, &span)) != PURE_OK)
return 3;
cells[f] = (PureCell){.i64 = values[f].i64, .text = (unsigned char *)span.data, .size = span.size};
@@ -3901,15 +3899,16 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp
kind = PURE_FAULT_NOT_FOUND;
FAILED;
}
- /* Room for every text first, so no span moves while the next is copied. */
+ /* Room for every text and bytes first, so no span moves while the next is copied. */
size_t mark = arena->used, total = 0;
+ uint32_t held = m->record_held[m->state_ref[arg]];
for (unsigned f = 0; f < record->count; ++f)
- if (text_field(m, arg, f)) total += (size_t)stack[base + f].bytes.length;
+ if (held >> f & 1) total += (size_t)stack[base + f].bytes.length;
if (!reserve(arena, total)) STOPPED(PURE_ALLOCATION);
PureCell cells[PURE_RECORD_FIELDS];
for (unsigned f = 0; f < record->count; ++f) {
PureSpan span = {0};
- if (text_field(m, arg, f) && (status = contiguous(app, arena, stack[base + f].bytes, &span)) != PURE_OK)
+ if (held >> f & 1 && (status = contiguous(app, arena, stack[base + f].bytes, &span)) != PURE_OK)
STOPPED(status);
cells[f] = (PureCell){.i64 = stack[base + f].i64, .text = (unsigned char *)span.data, .size = span.size};
}
@@ -4244,12 +4243,12 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp
PureList *in = app->nested.list[into - 1];
size_t mark = arena->used, total = 0;
for (unsigned f = 0; f < record->count; ++f)
- if (m->record_field_type[record->first + f] == PURE_RECORD_TEXT) total += (size_t)stack[base + f].bytes.length;
+ if (pure_record_bytes(m->record_field_type[record->first + f])) total += (size_t)stack[base + f].bytes.length;
if (!reserve(arena, total)) STOPPED(PURE_ALLOCATION);
PureCell cells[PURE_RECORD_FIELDS];
for (unsigned f = 0; f < record->count; ++f) {
PureSpan span = {0};
- if (m->record_field_type[record->first + f] == PURE_RECORD_TEXT &&
+ if (pure_record_bytes(m->record_field_type[record->first + f]) &&
(status = contiguous(app, arena, stack[base + f].bytes, &span)) != PURE_OK)
STOPPED(status);
cells[f] = (PureCell){.i64 = stack[base + f].i64, .text = (unsigned char *)span.data, .size = span.size};
@@ -4550,7 +4549,7 @@ static Call *give_back(PureApp *app, Call *call, Event *ev) {
/* A bytes or text value a call would lose (held by name in a slot the callee has its own of): a copy in the arena. */
static bool kept_apart(PureApp *app, PureArena *arena, const PureFunction *f, uint8_t type, PureBytes *v) {
- if ((type != PURE_RECORD_TEXT && type != PURE_SIGNATURE_BYTES) || v->source != FROM_HELD || !(f->held >> v->owner & 1))
+ if (!pure_record_bytes(type) || v->source != FROM_HELD || !(f->held >> v->owner & 1))
return true;
size_t start = arena->used;
if (append_value(app, arena, *v) != PURE_OK) return false;
@@ -5778,7 +5777,7 @@ void pure_app_read(PureApp *reader, const PureSnapshot *snapshot) {
uint8_t pure_record_kind(const PureModule *m, uint32_t field) {
static const uint8_t kinds[] = {[PURE_RECORD_NUMBER] = PURE_CELL_NUMBER, [PURE_RECORD_TEXT] = PURE_CELL_TEXT,
[PURE_RECORD_BOOL] = PURE_CELL_BOOL, [PURE_RECORD_I64] = PURE_CELL_INTEGER,
- [PURE_RECORD_LIST] = PURE_CELL_LIST};
+ [PURE_RECORD_LIST] = PURE_CELL_LIST, [PURE_RECORD_BYTES] = PURE_CELL_BYTES};
return kinds[m->record_field_type[field]];
}
@@ -5825,9 +5824,10 @@ PURE_COLD bool pure_layout_same(const PureLayout *a, const PureLayout *b) {
return true;
}
-/* A number, a bool and an i64 carry over each other (when the value fits); texts only to texts, lists to lists. */
+/* A number, a bool and an i64 carry over each other (when the value fits); texts to texts and bytes, bytes to bytes, lists
+ * to lists. */
static bool kinds_carry(uint8_t from, uint8_t to) {
- return from == to || (!stored_bytes(from) && !stored_bytes(to));
+ return from == to || (from == PURE_CELL_TEXT && to == PURE_CELL_BYTES) || (!stored_bytes(from) && !stored_bytes(to));
}
/* Bytes made for a list value's stored form as it moves (a growing buffer). */
@@ -5938,7 +5938,7 @@ PURE_COLD PureResult pure_list_migrate(PureList **out, const PureList *from, con
} else if (m->record_field_type[field] == PURE_RECORD_LIST) cells[f] = (PureCell){.i64 = 0}; /* an event's: none */
else if (source[f] >= 0 && pure_cell_as(from_layout->record[0].kinds[source[f]], pure_record_kind(m, field), &given))
cells[f] = given;
- else if (m->record_field_type[field] == PURE_RECORD_TEXT)
+ else if (pure_record_bytes(m->record_field_type[field]))
cells[f] = (PureCell){.text = (unsigned char *)m->constant_data + m->constant_offset[constant],
.size = m->constant_length[constant]};
else cells[f] = (PureCell){.i64 = m->record_field_default[field].i64}; /* a number's, a bool's or an i64's bits */
diff --git a/core/module.h b/core/module.h
index 349c4d2..d23cb49 100644
--- a/core/module.h
+++ b/core/module.h
@@ -137,7 +137,7 @@ enum { PURE_FORMAT_WIDTH = 8, PURE_FORMAT_ZEROS = 1 << 16, PURE_FORMAT_FORM = 17
* point), failing as LIMIT where no character begins there; CHAR makes the
* text of a code point, failing as LIMIT outside 0-0x10FFFF and as ENCODING
* for a surrogate. */
-enum { PURE_MODULE_FUNCTIONS = 64, PURE_MODULE_SIGNATURES = 1024, PURE_SIGNATURE_BYTES = 5 };
+enum { PURE_MODULE_FUNCTIONS = 64, PURE_MODULE_SIGNATURES = 1024 };
/* What a call's frame counts in the event's room: a table, as gas is, not the implementation's sizes (each at least
* what it stands for), so a module's calls fail at the same depth wherever and on whatever build they run: so much a
* frame, a value of its stack's height, a local, a loop it nests, a list of its own, a held value and a register. */
@@ -237,8 +237,12 @@ static inline uint8_t pure_value_kind(uint8_t state_type) {
return state_type == PURE_STATE_NUM ? PURE_CELL_NUMBER : state_type == PURE_STATE_BOOL ? PURE_CELL_BOOL :
state_type == PURE_STATE_I64 ? PURE_CELL_INTEGER : 0xFF;
}
-/* A record field: a number, a text (its default a constant), a bool or an i64. */
-typedef enum { PURE_RECORD_NUMBER = 0, PURE_RECORD_TEXT = 1, PURE_RECORD_BOOL = 2, PURE_RECORD_I64 = 3, PURE_RECORD_LIST = 4 } PureRecordType;
+/* A record field: a number, a text (its default a constant), a bool, an i64, a list (027's 2b) or bytes (4b: its
+ * default a constant too, held as a text is but for UTF-8). */
+typedef enum { PURE_RECORD_NUMBER = 0, PURE_RECORD_TEXT = 1, PURE_RECORD_BOOL = 2, PURE_RECORD_I64 = 3, PURE_RECORD_LIST = 4,
+ PURE_RECORD_BYTES = 5 } PureRecordType;
+/* A field held as bytes are: a text or bytes. */
+static inline bool pure_record_bytes(uint8_t type) { return type == PURE_RECORD_TEXT || type == PURE_RECORD_BYTES; }
/* A value's 8 bytes: a number (a bool's 0 or 1, a cursor's or an index's
* position too), or an integer. What a value is its state's or field's type
* says, and the verifier keeps to it: moves copy the bytes, and only what
@@ -291,7 +295,7 @@ typedef struct { union { double value; int64_t integer; }; uint32_t arg; uint8_t
typedef struct { uint32_t start, length; } PureBlock;
typedef struct { uint32_t event; PureBlock code; } PureHandler;
/* A function's block and its parameters' and results' types, from first among the module's signature items: a record
- * field's type (PureRecordType: a list's with its record) or PURE_SIGNATURE_BYTES. Set at load: the event's lists,
+ * field's type (PureRecordType: a list's with its record, bytes as PURE_RECORD_BYTES). Set at load: the event's lists,
* registers and held values each call has its own of (a bit each), the lists not an event's it changes (or a function
* it calls does), and its frame: its stack's height at most, its locals and its loops. */
typedef struct {
@@ -393,6 +397,7 @@ typedef struct {
PureValue record_field_default[PURE_MODULE_RECORD_FIELDS]; /* a number (a bool's 0 or 1), an i64, a text constant's index
* or a list's record (LIST, its i64) */
bool record_nested[PURE_MODULE_RECORDS]; /* a record with a LIST field (set at load) */
+ uint16_t record_held[PURE_MODULE_RECORDS]; /* its fields held as bytes are (pure_record_bytes), a bit each (set at load) */
uint16_t functions;
PureFunction function[PURE_MODULE_FUNCTIONS];
uint8_t signature_type[PURE_MODULE_SIGNATURES];
diff --git a/core/test-module.c b/core/test-module.c
index 9d925dd..181f96d 100644
--- a/core/test-module.c
+++ b/core/test-module.c
@@ -56,7 +56,8 @@ typedef struct { const char *name; uint32_t type, ref; } T; /* type: the state t
#define TR (1u << 24) /* in a T's type: a transient state */
/* A record field: a number and its default, text (text = 1) whose default
* is the constant numbered initial, a bool (text = 2) or an i64 (text = 3)
- * and its default. */
+ * and its default, a list (4, initial its record) or bytes (5, its default
+ * a constant as a text's). */
typedef struct { const char *name; uint32_t text; double initial; } RF;
typedef struct { const char *name; const RF *fields; size_t nfields; } R;
/* A further view: a table (type 2, start = list state) or a column of the
@@ -185,7 +186,7 @@ static size_t build(unsigned char *out, Spec spec) {
const RF *field = &spec.r[i].fields[f];
p = put32(p, offset[++n]);
p = put32(p, field->text);
- if (field->text == 1) { p = put32(p, (uint32_t)field->initial); p = put32(p, 0); }
+ if (field->text == 1 || field->text == 5) { p = put32(p, (uint32_t)field->initial); p = put32(p, 0); }
else if (field->text == 3 || field->text == 4) { /* an i64's default, a list's record */
int64_t initial = (int64_t)field->initial;
p = put32(p, (uint32_t)(uint64_t)initial);
@@ -6686,6 +6687,61 @@ static void nested_rows(void) {
printf("{\"nested_rows\":{\"rounds\":3,\"refused\":%zu}}\n", LEN(cuts) + 1);
}
+/* 027's 4b: a record field holding bytes. A message's bytes (any, UTF-8 or not) go into a list's item and come back
+ * as they were; a text goes where bytes go only as bytes (BYTES_FROM), and bytes never where a text goes. Replaced, a
+ * text field becomes a bytes field keeping its value, and a new bytes field takes its constant. */
+static void bytes_fields(void) {
+ enum { BF = PURE_OP_BYTES_FROM, TC = PURE_OP_TEXT_CONST, XL_ = PURE_OP_INDEX_LAST, ITEMS = 1, BLOB = 2 };
+ static const S nums[] = {{"n", 0}};
+ static const char *const constants[] = {"", "zz"};
+ static const RF text_fields[] = {{"t", 1, 0}};
+ static const RF bytes_fields_[] = {{"t", 5, 0}, {"b", 5, 1}};
+ static const R before[] = {{"Item", text_fields, 1}}, after[] = {{"Item", bytes_fields_, 2}};
+ static const T others[] = {{"items", 3, 0}, {"blob", PURE_STATE_TEXT, 0}};
+ static const F text_in[] = {{"t", PURE_FIELD_TEXT}}, bytes_in[] = {{"b", PURE_FIELD_BYTES}};
+ static const I inputs_a[] = {{"put", NULL, PURE_KIND_MESSAGE, false, text_in, 1}};
+ static const I inputs_b[] = {{"put", NULL, PURE_KIND_MESSAGE, false, bytes_in, 1}};
+ /* A: items.append(Item { t: put.t }); n = items.count */
+ static const O code_a[] = {{FIELD, 0, 0}, {LA, ITEMS, 0}, {LC, ITEMS, 0}, {ST, 0, 0}};
+ /* B: items.append(Item { t: put.b, b: bytes("zz") + put.b }); n = items.last.b's length */
+ static const O code_b[] = {{FIELD, 0, 0}, {TC, 1, 0}, {BF, 0, 0}, {FIELD, 0, 0}, {CAT, 1, 0}, {LA, ITEMS, 0},
+ {XL_, ITEMS, 0}, {IG, ITEM(ITEMS, 1), 0}, {PURE_OP_LEN, 0, 0}, {ST, 0, 0}};
+ H ha = {1, 0, LEN(code_a)}, hb = {1, 0, LEN(code_b)};
+ Spec a = {nums, 1, inputs_a, 1, NULL, 0, &ha, 1, code_a, LEN(code_a), NULL, 0, others, LEN(others), constants, LEN(constants),
+ NULL, 0, before, 1, NULL, 0};
+ Spec b = {nums, 1, inputs_b, 1, NULL, 0, &hb, 1, code_b, LEN(code_b), NULL, 0, others, LEN(others), constants, LEN(constants),
+ NULL, 0, after, 1, NULL, 0};
+ if (load(a, &module_a) != PURE_OK || load(b, &module_b) != PURE_OK) { fprintf(stderr, "bytes fields: %s\n", error); abort(); }
+ PureApp app;
+ CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK);
+ CHECK(pure_app_message(&app, 0, NULL, -1, (PureSpan){(const unsigned char *)"héllo", 6}) == PURE_OK);
+ CHECK(pure_app_replace(&app, &module_b) == PURE_OK);
+ PureCell t = pure_list_get(app.lists[ITEMS], 0, 0), zz = pure_list_get(app.lists[ITEMS], 0, 1);
+ CHECK(t.size == 6 && !memcmp(t.text, "héllo", 6) && zz.size == 2 && !memcmp(zz.text, "zz", 2));
+ static const unsigned char raw[] = {0xFF, 0x00, 0xC3, 0x28, 0x80}; /* no UTF-8 */
+ CHECK(pure_app_message(&app, 0, NULL, -1, (PureSpan){raw, sizeof(raw)}) == PURE_OK && app.values[0].num == 2 + sizeof(raw));
+ PureCell back = pure_list_get(app.lists[ITEMS], 1, 0), joined = pure_list_get(app.lists[ITEMS], 1, 1);
+ CHECK(back.size == sizeof(raw) && !memcmp(back.text, raw, sizeof(raw)) && joined.size == 2 + sizeof(raw) &&
+ !memcmp(joined.text, "zz", 2) && !memcmp(joined.text + 2, raw, sizeof(raw)));
+ pure_app_stop(&app);
+ /* Refused: a text where bytes go, bytes where a text goes (the field read into a text state). */
+ static const O text_in_bytes[] = {{TC, 0, 0}, {TC, 1, 0}, {LA, ITEMS, 0}, {C, 0, 0}, {ST, 0, 0}};
+ static const O bytes_as_text[] = {{XL_, ITEMS, 0}, {IG, ITEM(ITEMS, 0), 0}, {PURE_OP_TEXT_SET, BLOB, 0}, {C, 0, 0}, {ST, 0, 0}};
+ const O *refused[] = {text_in_bytes, bytes_as_text};
+ for (size_t k = 0; k < LEN(refused); ++k) {
+ H one = {1, 0, 5};
+ Spec bad = {nums, 1, inputs_b, 1, NULL, 0, &one, 1, refused[k], 5, NULL, 0, others, LEN(others), constants, LEN(constants),
+ NULL, 0, after, 1, NULL, 0};
+ CHECK(load(bad, &module_a) != PURE_OK && strstr(error, "operand type"));
+ }
+ static const T keyed[] = {{"items", 3 | 1u << 8, 0}, {"blob", PURE_STATE_TEXT, 0}}; /* keyed by t, bytes */
+ H one = {1, 0, LEN(code_b)};
+ Spec by_bytes = {nums, 1, inputs_b, 1, NULL, 0, &one, 1, code_b, LEN(code_b), NULL, 0, keyed, LEN(keyed), constants,
+ LEN(constants), NULL, 0, after, 1, NULL, 0};
+ CHECK(load(by_bytes, &module_a) != PURE_OK && strstr(error, "bytes as a key"));
+ printf("{\"bytes_fields\":{\"items\":2,\"refused\":%zu}}\n", LEN(refused) + 1);
+}
+
/* A timer's tick sees the time the host set; floor and a floored modulo
* take it apart. now is for handlers only. */
enum { NOW = PURE_OP_NOW, FLOOR = PURE_OP_FLOOR, MOD = PURE_OP_MOD };
@@ -7066,6 +7122,7 @@ int main(void) {
nested_crashes();
nested_replace();
nested_rows();
+ bytes_fields();
memory_budget();
work_budget();
fold_mutations();