From 46c303a41e9dd9cd0e6bd50511e8d7253a0ef438 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Wed, 30 Sep 2026 16:16:08 +0100 Subject: Give i64 its own type in modules, lists, stores and the host (016 stage 2) An i64 is an int64_t in a value's 8 bytes, its own stack type: CONST and NUM_TEXT take argument 2 for one, eleven opcodes are appended (ADD, SUB and MUL fail as overflow past its range, DIV and MOD floor and fail as divide by zero, NEG, COMPARE with a relation, MIN, MAX, and I64_FROM and NUM_FROM, which convert only where the value fits and else fail as limit), and moves (LOAD, STORE, locals, items, FIELD, SEND) copy it as they copy a number. Type codes: state 6, record field 3, message field 7 (8 bytes), view 4, local 3. v6 stays: all of it is added. - Folds with PURE_FOLD_INTEGER sum (overflow fails), keep the most or least of, or count the different i64s; distinct's set keeps an i64's -1 apart from its free mark. Blocks with an i64 run item by item. - A list's kinds are its stored header's (number, text, bytes, bool, integer): integer columns are int64_t, ordered as integers as keys (found without guide keys for now), stored in 8 bytes and logged as zigzag varints of any size. Records name their fields' type in one array (record_field_type) in place of text and bool flags. - A number, a bool and an i64 carry over each other by one rule, pure_cell_as, wherever one becomes another: a module replaced, a stored value or item read by another module, a waiting send recast. A u32 field delivers to an i64 one. - The host reads i64 fields from JSON, rows and queries as integer digits in range and nothing else, and shows every digit in views, tables, deliveries and pure-run. Faults are named by one function, pure_fault_name, in place of three tables; overflow is new. test-module: 28 arithmetic cases at the range's ends, relations, conversions both ways, 14 refusals, a keyed i64 list app (order, a sum that overflows, a failed add undone, messages and sends of 8 bytes, every digit as text), recasts, replacement between numbers, bools and i64s, and three stored runs against memory with restarts (46 images written). --- core/module-list.c | 142 ++++++++++++------ core/module-list.h | 34 +++-- core/module-save.c | 51 +++---- core/module-save.h | 10 +- core/module.c | 418 +++++++++++++++++++++++++++++++++++++---------------- core/module.h | 74 ++++++---- core/test-module.c | 333 +++++++++++++++++++++++++++++++++++++++--- 7 files changed, 801 insertions(+), 261 deletions(-) (limited to 'core') diff --git a/core/module-list.c b/core/module-list.c index 20fed2b..51148eb 100644 --- a/core/module-list.c +++ b/core/module-list.c @@ -49,7 +49,7 @@ enum { INNER_SIZE = sizeof(Node) + FANOUT * sizeof(Node *) + sizeof(Guide) }; /* Where a leaf keeps each field's cells, and what an item stores. */ typedef struct { unsigned fields, texts; - uint8_t text_fields[16]; + uint8_t kinds[16]; /* each field's (PURE_CELL_...) */ uint8_t text_field[16]; /* the text fields, in order */ uint32_t column[16]; /* where each field's cells start in a leaf */ size_t leaf_size; @@ -127,17 +127,20 @@ static unsigned child_at(const Node *node, size_t at) { return (unsigned)(base - before) + (*base <= at); } static double *numbers(const Layout *l, const Node *leaf, unsigned f) { return (double *)((char *)leaf + l->column[f]); } +static int64_t *integers(const Layout *l, const Node *leaf, unsigned f) { return (int64_t *)((char *)leaf + l->column[f]); } static PureSpan *texts(const Layout *l, const Node *leaf, unsigned f) { return (PureSpan *)((char *)leaf + l->column[f]); } static PureCell cell_at(const Layout *l, const Node *leaf, unsigned i, unsigned f) { - if (!l->text_fields[f]) return (PureCell){.num = numbers(l, leaf, f)[i]}; + if (l->kinds[f] == PURE_CELL_INTEGER) return (PureCell){.i64 = integers(l, leaf, f)[i]}; + if (!pure_cell_text(l->kinds[f])) return (PureCell){.num = numbers(l, leaf, f)[i]}; PureSpan text = texts(l, leaf, f)[i]; return (PureCell){.text = (unsigned char *)text.data, .size = text.size}; } static void put_cells(const Layout *l, Node *leaf, unsigned i, const PureCell *cells) { for (unsigned f = 0; f < l->fields; ++f) - if (l->text_fields[f]) texts(l, leaf, f)[i] = (PureSpan){cells[f].text, cells[f].size}; + if (pure_cell_text(l->kinds[f])) texts(l, leaf, f)[i] = (PureSpan){cells[f].text, cells[f].size}; + else if (l->kinds[f] == PURE_CELL_INTEGER) integers(l, leaf, f)[i] = cells[f].i64; else numbers(l, leaf, f)[i] = cells[f].num; } @@ -149,7 +152,7 @@ static void move_items(const Layout *l, Node *to, unsigned at, const Node *from, return; } for (unsigned f = 0; f < l->fields; ++f) { - size_t width = l->text_fields[f] ? sizeof(PureSpan) : sizeof(double); + size_t width = pure_cell_text(l->kinds[f]) ? sizeof(PureSpan) : 8; memmove((char *)to + l->column[f] + at * width, (const char *)from + l->column[f] + start * width, count * width); } } @@ -293,7 +296,7 @@ static void drop_node(PureList *list, Node *node) { /* ---- The stored form: each field in order, a number as its binary64 bits * (little-endian), a text as a u32le length and its bytes. */ -static size_t cell_bytes(const Layout *l, unsigned field, PureCell cell) { return l->text_fields[field] ? 4 + cell.size : 8; } +static size_t cell_bytes(const Layout *l, unsigned field, PureCell cell) { return pure_cell_text(l->kinds[field]) ? 4 + cell.size : 8; } static size_t cells_bytes(const Layout *l, const PureCell *cells) { size_t size = l->number_bytes; @@ -556,7 +559,7 @@ static void take_out(PureList *list, Step *path, unsigned depth, unsigned within /* ---- The list */ -PureList *pure_list_new(unsigned fields, const uint8_t *text_fields) { +PureList *pure_list_new(unsigned fields, const uint8_t *kinds) { if (fields > 16) return NULL; PureList *list = calloc(1, sizeof(*list)); if (!list) return NULL; @@ -564,10 +567,10 @@ PureList *pure_list_new(unsigned fields, const uint8_t *text_fields) { l->fields = fields; size_t at = sizeof(Node); for (unsigned f = 0; f < fields; ++f) { - l->text_fields[f] = text_fields[f]; /* 1 text (UTF-8), 2 bytes */ + l->kinds[f] = kinds[f]; l->column[f] = (uint32_t)at; - at += FANOUT * (l->text_fields[f] ? sizeof(PureSpan) : sizeof(double)); - if (l->text_fields[f]) l->text_field[l->texts++] = (uint8_t)f; + at += FANOUT * (pure_cell_text(kinds[f]) ? sizeof(PureSpan) : 8); + if (pure_cell_text(kinds[f])) l->text_field[l->texts++] = (uint8_t)f; else l->number_bytes += 8; } l->leaf_size = at; @@ -588,7 +591,7 @@ static bool rekey(PureList *list, Node **place) { PureResult pure_list_keyed(PureList *list, int field) { Layout *l = &list->layout; - int key = field >= 0 && (unsigned)field < l->fields && !l->text_fields[field] ? field : -1; + int key = field >= 0 && (unsigned)field < l->fields && l->kinds[field] == PURE_CELL_NUMBER ? field : -1; if (key == l->key) return PURE_OK; l->key = key; if (list->root && !rekey(list, &list->root)) { @@ -674,37 +677,69 @@ static bool log_change(PureUndo *undo, Entry entry) { /* A cell the list can keep: a text's bytes copied (NULL when empty). */ static bool own_cell(const PureList *list, unsigned field, PureCell from, PureCell *to) { - *to = (PureCell){.num = list->layout.text_fields[field] ? 0 : from.num}; - if (!list->layout.text_fields[field] || !from.size) return true; + bool text = pure_cell_text(list->layout.kinds[field]); + *to = (PureCell){.i64 = text ? 0 : from.i64}; /* a number's or an integer's bits */ + if (!text || !from.size) return true; if (!(to->text = new_text(list, from.text, from.size))) return false; to->size = from.size; return true; } static unsigned char *encode_cell(const Layout *l, unsigned field, PureCell cell, unsigned char *out) { - uint64_t bits = cell.size; - unsigned width = 4; - if (!l->text_fields[field]) { memcpy(&bits, &cell.num, sizeof(bits)); width = 8; } + bool text = pure_cell_text(l->kinds[field]); + uint64_t bits = text ? cell.size : (uint64_t)cell.i64; /* a number's bits, or an integer's */ + unsigned width = text ? 4 : 8; for (unsigned i = 0; i < width; ++i) *out++ = (unsigned char)(bits >> (8 * i)); - if (l->text_fields[field] && cell.size) { memcpy(out, cell.text, cell.size); out += cell.size; } + if (text && cell.size) { memcpy(out, cell.text, cell.size); out += cell.size; } return out; } +bool pure_cell_fits(uint8_t kind, PureCell cell) { + return kind == PURE_CELL_INTEGER || (kind == PURE_CELL_BOOL ? cell.num == 0 || cell.num == 1 : isfinite(cell.num)); +} + +bool pure_cell_as(uint8_t from, uint8_t to, PureCell *cell) { + if (from == to) return true; + 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; + if (!(v >= -9223372036854775808.0 && v < 9223372036854775808.0) || v != floor(v)) return false; + cell->i64 = (int64_t)v; + return true; + } + if (from == PURE_CELL_INTEGER) { + if (cell->i64 > INT64_C(9007199254740992) || cell->i64 < -INT64_C(9007199254740992)) return false; + cell->num = (double)cell->i64; + } + if (to == PURE_CELL_BOOL) { + if (cell->num != 0 && cell->num != 1) return false; + cell->num = cell->num == 1; /* -0 is false as 0 is */ + } + return true; +} + +/* Whether a number cell holds what its kind may: a finite number, a bool 0 or 1. */ +static bool fits(const Layout *l, unsigned field, double value) { + return pure_cell_fits(l->kinds[field], (PureCell){.num = value}); +} + /* A stored cell of field at *at in bytes (a text's bytes borrowed from - * them): false when it is cut short, not finite or not UTF-8. */ + * them): false when it is cut short, not finite (not 0 or 1 for a bool) or + * not UTF-8. */ static bool decode_cell(const Layout *l, unsigned field, const unsigned char *bytes, size_t size, size_t *at, PureCell *cell) { - size_t width = l->text_fields[field] ? 4 : 8; + bool text = pure_cell_text(l->kinds[field]); + size_t width = text ? 4 : 8; if (size - *at < width) return false; uint64_t bits = 0; for (size_t i = 0; i < width; ++i) bits |= (uint64_t)bytes[*at + i] << (8 * i); *at += width; *cell = (PureCell){0}; - if (!l->text_fields[field]) { - memcpy(&cell->num, &bits, sizeof(bits)); - return isfinite(cell->num); + if (!text) { + cell->i64 = (int64_t)bits; + return l->kinds[field] == PURE_CELL_INTEGER || fits(l, field, cell->num); } - if (bits > size - *at || (l->text_fields[field] == 1 && !pure_utf8_valid((PureSpan){bytes + *at, (size_t)bits}))) + if (bits > size - *at || (l->kinds[field] == PURE_CELL_TEXT && !pure_utf8_valid((PureSpan){bytes + *at, (size_t)bits}))) return false; *cell = (PureCell){.text = (unsigned char *)bytes + *at, .size = (size_t)bits}; *at += (size_t)bits; @@ -716,9 +751,10 @@ static bool decode_cell(const Layout *l, unsigned field, const unsigned char *by * item's fields, for a set the u8 field and its cell. A record is written * compact (kinds 4-6): the position as a varint (LEB128), a number as the * varint 2 * zigzag(n) when it is an integer n below 2^53 in size (not -0), - * else 1 and its binary64 bits (8 bytes, little-endian), a text as a varint - * length and its bytes. Records stored before (kinds 1-3, a list's tree - * value "PLS2") had a u32le position and cells in stored form. */ + * else 1 and its binary64 bits (8 bytes, little-endian), an integer as the + * varint zigzag(n), a text as a varint length and its bytes. Records stored + * before (kinds 1-3, a list's tree value "PLS2") had a u32le position and + * cells in stored form. */ enum { FIXED_INSERT = 1, FIXED_REMOVE = 2, FIXED_SET = 3, RECORD_INSERT = 4, RECORD_REMOVE = 5, RECORD_SET = 6, FIXED_HEAD = 5, RECORD_HEAD = 6, JOURNAL_KEPT = 1 << 20 }; @@ -740,20 +776,23 @@ static bool get_varint(const unsigned char *bytes, size_t size, size_t *at, uint } /* A cell in compact form (the most it takes: compact_bound). */ -static size_t compact_bound(const Layout *l, unsigned field, PureCell cell) { return l->text_fields[field] ? 5 + cell.size : 9; } +static size_t compact_bound(const Layout *l, unsigned field, PureCell cell) { + return pure_cell_text(l->kinds[field]) ? 5 + cell.size : 10; +} + +static uint64_t zigzag(int64_t n) { return (uint64_t)n << 1 ^ (n < 0 ? UINT64_MAX : 0); } +static int64_t unzigzag(uint64_t z) { return z & 1 ? -(int64_t)(z >> 1) - 1 : (int64_t)(z >> 1); } static unsigned char *put_compact(const Layout *l, unsigned field, PureCell cell, unsigned char *out) { - if (l->text_fields[field]) { + if (l->kinds[field] == PURE_CELL_INTEGER) return put_varint(out, zigzag(cell.i64)); + if (pure_cell_text(l->kinds[field])) { out = put_varint(out, cell.size); if (cell.size) memcpy(out, cell.text, cell.size); return out + cell.size; } double v = cell.num; - if (fabs(v) < 9007199254740992.0 && v == (double)(int64_t)v && !(v == 0 && signbit(v))) { - int64_t n = (int64_t)v; - uint64_t zigzag = (uint64_t)n << 1 ^ (n < 0 ? UINT64_MAX : 0); - return put_varint(out, zigzag << 1); - } + if (fabs(v) < 9007199254740992.0 && v == (double)(int64_t)v && !(v == 0 && signbit(v))) + return put_varint(out, zigzag((int64_t)v) << 1); uint64_t bits; memcpy(&bits, &v, sizeof(bits)); *out++ = 1; @@ -766,26 +805,28 @@ static bool get_compact(const Layout *l, unsigned field, const unsigned char *by uint64_t value; *cell = (PureCell){0}; if (!get_varint(bytes, size, at, &value)) return false; - if (l->text_fields[field]) { - if (value > size - *at || (l->text_fields[field] == 1 && !pure_utf8_valid((PureSpan){bytes + *at, (size_t)value}))) + if (l->kinds[field] == PURE_CELL_INTEGER) { + cell->i64 = unzigzag(value); + return true; + } + if (pure_cell_text(l->kinds[field])) { + if (value > size - *at || (l->kinds[field] == PURE_CELL_TEXT && !pure_utf8_valid((PureSpan){bytes + *at, (size_t)value}))) return false; *cell = (PureCell){.text = (unsigned char *)bytes + *at, .size = (size_t)value}; *at += (size_t)value; return true; } if (!(value & 1)) { /* an integer */ - uint64_t zigzag = value >> 1; - if (zigzag >= UINT64_C(1) << 54) return false; - int64_t n = zigzag & 1 ? -(int64_t)(zigzag >> 1) - 1 : (int64_t)(zigzag >> 1); - cell->num = (double)n; - return true; + if (value >> 1 >= UINT64_C(1) << 54) return false; + cell->num = (double)unzigzag(value >> 1); + return fits(l, field, cell->num); } if (value != 1 || size - *at < 8) return false; uint64_t bits = 0; for (unsigned i = 0; i < 8; ++i) bits |= (uint64_t)bytes[*at + i] << (8 * i); *at += 8; memcpy(&cell->num, &bits, sizeof(bits)); - return isfinite(cell->num); + return fits(l, field, cell->num); } /* Room for size more bytes at the journal's end, or NULL without memory. */ @@ -1029,7 +1070,8 @@ PureResult pure_list_set(PureList *list, size_t at, unsigned field, PureCell val list->journal_size += (size_t)(put_compact(l, field, copy, end) - record); } list->bytes += cell_bytes(l, field, copy) - cell_bytes(l, field, old); - if (l->text_fields[field]) texts(l, leaf, field)[within] = (PureSpan){copy.text, copy.size}; + if (pure_cell_text(l->kinds[field])) texts(l, leaf, field)[within] = (PureSpan){copy.text, copy.size}; + else if (l->kinds[field] == PURE_CELL_INTEGER) integers(l, leaf, field)[within] = copy.i64; else numbers(l, leaf, field)[within] = copy.num; if (!undo) let_go_text(list, old.text); if (keyed) sum_path(list, path, depth); @@ -1046,7 +1088,8 @@ PureCell pure_list_get(const PureList *list, size_t at, unsigned field) { static void open_run(const Layout *l, const Node *leaf, PureRun *run) { run->count = leaf->count; for (unsigned f = 0; f < l->fields; ++f) - if (l->text_fields[f]) run->column[f].texts = texts(l, leaf, f); + if (pure_cell_text(l->kinds[f])) run->column[f].texts = texts(l, leaf, f); + else if (l->kinds[f] == PURE_CELL_INTEGER) run->column[f].integers = integers(l, leaf, f); else run->column[f].numbers = numbers(l, leaf, f); } @@ -1095,10 +1138,11 @@ bool pure_list_next(const PureList *list, PureRun *run) { return true; } -/* How two cells of a field order: numbers by value (-0 and +0 are one key), - * texts by their bytes. */ +/* How two cells of a field order: numbers and integers by value (-0 and +0 + * are one key), texts by their bytes. */ static int order(const Layout *l, unsigned field, PureCell a, PureCell b) { - if (!l->text_fields[field]) return (a.num > b.num) - (a.num < b.num); + if (l->kinds[field] == PURE_CELL_INTEGER) return (a.i64 > b.i64) - (a.i64 < b.i64); + if (!pure_cell_text(l->kinds[field])) return (a.num > b.num) - (a.num < b.num); size_t shorter = a.size < b.size ? a.size : b.size; int bytes = shorter ? memcmp(a.text, b.text, shorter) : 0; return bytes ? (bytes > 0) - (bytes < 0) : (a.size > b.size) - (a.size < b.size); @@ -1202,7 +1246,7 @@ PURE_COLD PureResult pure_list_sorted(const PureList *list, unsigned field, Pure PureResult result = PURE_OK; for (size_t i = 1; i < list->count && result == PURE_OK; ++i) if (!order(l, field, ref_cell(l, items[at[i - 1]], field), ref_cell(l, items[at[i]], field))) result = PURE_CONFLICT; - PureList *sorted = result == PURE_OK ? pure_list_new(l->fields, l->text_fields) : NULL; + PureList *sorted = result == PURE_OK ? pure_list_new(l->fields, l->kinds) : NULL; if (result == PURE_OK && !sorted) result = PURE_ALLOCATION; for (size_t i = 0; result == PURE_OK && i < list->count; ++i) { PureCell cells[16]; @@ -1249,11 +1293,13 @@ void pure_undo_rollback(PureUndo *undo, size_t mark) { continue; } list->bytes += cell_bytes(l, e->field, e->old) - cell_bytes(l, e->field, cell_at(l, leaf, within, e->field)); - if (l->text_fields[e->field]) { + if (pure_cell_text(l->kinds[e->field])) { PureSpan *text = &texts(l, leaf, e->field)[within]; free_text(text->data); *text = (PureSpan){e->old.text, e->old.size}; - } else + } else if (l->kinds[e->field] == PURE_CELL_INTEGER) + integers(l, leaf, e->field)[within] = e->old.i64; + else numbers(l, leaf, e->field)[within] = e->old.num; sum_path(list, path, depth); ++list->version; diff --git a/core/module-list.h b/core/module-list.h index 603134a..75a871f 100644 --- a/core/module-list.h +++ b/core/module-list.h @@ -16,9 +16,9 @@ #endif /* Runtime-owned lists of records for the module executor. An item is one - * cell per field: a number, or text bytes the list owns. Every change made - * during an event is logged; a failed event undoes the log in reverse and a - * successful one frees what it replaced. */ + * cell per field: a number, an integer, or text bytes the list owns. Every + * change made during an event is logged; a failed event undoes the log in + * reverse and a successful one frees what it replaced. */ typedef struct { union { double num; int64_t i64; }; unsigned char *text; size_t size; } PureCell; typedef struct PureList PureList; /* An event's list changes; a removed item's cells wait in cells. */ @@ -36,7 +36,7 @@ typedef struct { void *entries; size_t count, capacity; } PureRetired; typedef struct { size_t start; unsigned count; - union { const double *numbers; const PureSpan *texts; } column[16]; + union { const double *numbers; const int64_t *integers; const PureSpan *texts; } column[16]; /* Where the run is in the list's tree, for the next one. */ const void *path[16]; uint8_t taken[16]; @@ -46,9 +46,22 @@ typedef struct { /* A list holds up to PURE_LIST_ITEMS items, a run up to PURE_LIST_RUN. */ enum { PURE_LIST_ITEMS = 1 << 24, PURE_LIST_RUN = 32 }; -/* text_fields: a field's kind, 0 a number, 1 a text (UTF-8, checked when - * read), 2 bytes (any: a runtime list's). */ -PureList *pure_list_new(unsigned fields, const uint8_t *text_fields); +/* A field's kind: a number, a text (UTF-8, checked when read), bytes (any: a + * runtime list's), a bool (a number 0 or 1, kept as a number is) or an + * integer (an int64_t, ordered as one). A stored list's header names its + * fields by these kinds. */ +enum { PURE_CELL_NUMBER = 0, PURE_CELL_TEXT = 1, PURE_CELL_BYTES = 2, PURE_CELL_BOOL = 3, PURE_CELL_INTEGER = 4 }; +static inline bool pure_cell_text(uint8_t kind) { return kind == PURE_CELL_TEXT || kind == PURE_CELL_BYTES; } +/* Whether a number, bool or integer cell holds what its kind may: a finite + * number, a bool 0 or 1 (either zero), any integer. */ +bool pure_cell_fits(uint8_t kind, PureCell cell); +/* A number, a bool or an integer as another of them holds it (a module + * 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. */ +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 *); /* Let every item go (retired where a shared copy may read it). */ void pure_list_clear(PureList *); @@ -97,8 +110,8 @@ PureCell pure_list_get(const PureList *, size_t at, unsigned field); bool pure_list_run(const PureList *, size_t at, PureRun *); bool pure_list_next(const PureList *, PureRun *); /* In a list ordered by field (a keyed list): the first position whose item's - * field does not order below key (numbers by value, texts by bytes), and - * whether that item's field is key. */ + * field does not order below key (numbers and integers by value, texts by + * bytes), and whether that item's field is key. */ size_t pure_list_seek(const PureList *, unsigned field, PureCell key, bool *found); /* The field a keyed list keeps its items in order of: when it is a number * field, seek by it reads each level's keys from one node (field -1, or a @@ -110,7 +123,8 @@ PureResult pure_list_keyed(PureList *, int field); * same key (nothing is made). */ PureResult pure_list_sorted(const PureList *, unsigned field, PureList **out, size_t *moved); /* The stored form of items: each item's fields in order, a number as its - * binary64 bits (little-endian), a text as a u32le length and its bytes. + * binary64 bits and an integer as its two's complement (8 bytes, + * little-endian), a text as a u32le length and its bytes. * pure_list_size is the bytes of all the items so stored, and * pure_list_encode writes them to out. */ size_t pure_list_size(const PureList *); diff --git a/core/module-save.c b/core/module-save.c index ae32c02..1300832 100644 --- a/core/module-save.c +++ b/core/module-save.c @@ -242,7 +242,7 @@ static int read_outputs(const unsigned char *at, size_t size, SentShape *shapes, for (unsigned f = 0; f < fields; ++f) { size_t named = size - k >= 2 ? at[k + 1] : 0; if (size - k < 2 || !named || named >= PURE_MODULE_NAME || size - k < 2 + named || !at[k] || - at[k] == PURE_FIELD_ROWS || at[k] > PURE_FIELD_BOOL) /* an output sends no rows */ + at[k] == PURE_FIELD_ROWS || at[k] > PURE_FIELD_I64) /* an output sends no rows */ return -1; if (shapes) { shapes[t].fields[f] = (PureField){.type = at[k]}; @@ -445,9 +445,9 @@ 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: its fields' kinds and names. */ + /* The stored record: its fields' kinds (PURE_CELL_...: a record's, no runtime bytes) and names. */ char names[PURE_RECORD_FIELDS][PURE_MODULE_NAME] = {{0}}; - uint8_t text[PURE_RECORD_FIELDS] = {0}; + uint8_t kinds[PURE_RECORD_FIELDS] = {0}; unsigned count = 0; size_t at = 0, items = 0; uint64_t holds = 0; @@ -458,7 +458,7 @@ PURE_COLD PureResult pure_save_open_list(PureApp *app, const PureModule *m, uint count = ok ? bytes[4] : 0; at = 5; for (unsigned f = 0; ok && f < count; ++f) { - ok = named(bytes, size, &at, 0, &text[f], names[f]) && (text[f] <= 1 || text[f] == PURE_RECORD_BOOL); + ok = named(bytes, size, &at, 0, &kinds[f], names[f]) && kinds[f] <= PURE_CELL_INTEGER && kinds[f] != PURE_CELL_BYTES; for (unsigned k = 0; k < f; ++k) ok = ok && strcmp(names[k], names[f]); } if (ok && image) { /* the save it holds, then its items */ @@ -474,9 +474,7 @@ PURE_COLD PureResult pure_save_open_list(PureApp *app, const PureModule *m, uint } PureList *from = NULL; if (result == PURE_OK && stored) { - uint8_t layout[PURE_RECORD_FIELDS]; /* a bool is a number as lists go */ - for (unsigned f = 0; f < count; ++f) layout[f] = text[f] == PURE_RECORD_BOOL ? 0 : text[f]; - from = pure_list_new(count, layout); + from = pure_list_new(count, kinds); result = from ? pure_list_read(from, bytes + at, items) : PURE_ALLOCATION; if (result == PURE_OK && !image) result = pure_list_replay(from, bytes + at + items, size - at - items); } @@ -495,7 +493,7 @@ PURE_COLD PureResult pure_save_open_list(PureApp *app, const PureModule *m, uint } bool same = stored && count == r->count; for (unsigned f = 0; same && f < count; ++f) - same = text[f] == pure_record_kind(m, r->first + f) && !strcmp(names[f], m->record_field_names[r->first + f]); + same = kinds[f] == pure_record_kind(m, r->first + f) && !strcmp(names[f], m->record_field_names[r->first + f]); if (result == PURE_OK && same && image) { *out = from; saved->image = holds; @@ -509,8 +507,12 @@ PURE_COLD PureResult pure_save_open_list(PureApp *app, const PureModule *m, uint if (same) { *out = from; from = NULL; - } else if (from) result = pure_list_migrate(out, from, field_names, text, count, m, record); - else if (!(*out = pure_list_new(r->count, m->record_field_text + r->first))) result = PURE_ALLOCATION; + } else if (from) result = pure_list_migrate(out, from, field_names, kinds, count, m, record); + else { + uint8_t own[PURE_RECORD_FIELDS]; + for (unsigned f = 0; f < r->count; ++f) own[f] = pure_record_kind(m, r->first + f); + if (!(*out = pure_list_new(r->count, own))) result = PURE_ALLOCATION; + } } pure_list_free(from); if (result != PURE_OK) { @@ -669,20 +671,14 @@ void pure_app_checkpointed(PureApp *app, PureCheckpoint *c, PureResult result) { } static bool scalar(uint8_t type) { - return type == PURE_STATE_NUM || type == PURE_STATE_BOOL || type == PURE_STATE_CURSOR || type == PURE_STATE_INDEX; + return type == PURE_STATE_NUM || type == PURE_STATE_BOOL || type == PURE_STATE_I64 || type == PURE_STATE_CURSOR || + type == PURE_STATE_INDEX; } -/* A number's or a bool's (each is stored as the other may be read: 0 and 1 are false and true). */ -static bool valued(uint8_t type) { return type == PURE_STATE_NUM || type == PURE_STATE_BOOL; } - -/* A stored value still fits its state: a cursor on a character boundary of - * its text, an index on an item of its list (or 0), a finite number, a bool - * 0 or 1. */ +/* A stored position still fits its state: a cursor on a character boundary + * of its text, an index on an item of its list (or 0). */ PURE_COLD static bool fits(const PureModule *m, PureDraft *const *texts, PureList *const *lists, uint32_t state, double value) { - if (!isfinite(value)) return false; - if (m->state_type[state] == PURE_STATE_NUM) return true; - if (m->state_type[state] == PURE_STATE_BOOL) return value == 0 || value == 1; - if (value < 0 || value != floor(value)) return false; + if (!isfinite(value) || value < 0 || value != floor(value)) return false; if (m->state_type[state] == PURE_STATE_INDEX) { size_t count = pure_list_count(lists[m->state_ref[state]]); return value <= (count ? (double)(count - 1) : 0); @@ -737,13 +733,14 @@ PURE_COLD PureResult pure_save_open_values(PureApp *app, const PureModule *m, Pu uint64_t bits = 0; for (unsigned b = 0; b < 8; ++b) bits |= (uint64_t)bytes[at + b] << (8 * b); at += 8; - double value; - memcpy(&value, &bits, sizeof(value)); int state = pure_module_state(m, name); - if (state >= 0 && kept[state] < 0 && scalar(type) && - (m->state_type[state] == type || (valued(type) && valued(m->state_type[state]))) && - fits(m, texts, lists, (uint32_t)state, value)) - values[state].num = m->state_type[state] == PURE_STATE_BOOL ? pure_as_bool(value, 0) : value; + if (state < 0 || kept[state] >= 0 || !scalar(type)) continue; + /* A number, a bool or an i64, each as the state's type holds it when it fits (pure_cell_as); a position where it fits. */ + uint8_t from = pure_value_kind(type), to = pure_value_kind(m->state_type[state]); + PureCell cell = {.i64 = (int64_t)bits}; + if (from != 0xFF && to != 0xFF ? pure_cell_fits(from, cell) && pure_cell_as(from, to, &cell) + : m->state_type[state] == type && fits(m, texts, lists, (uint32_t)state, cell.num)) + values[state].i64 = cell.i64; } free(bytes); return ok ? PURE_OK : PURE_STORE_ERROR; diff --git a/core/module-save.h b/core/module-save.h index 991c498..5c81504 100644 --- a/core/module-save.h +++ b/core/module-save.h @@ -74,14 +74,12 @@ uint32_t pure_fields_lay_out(PureField *fields, uint32_t count); PureResult pure_send_recast(const PureField *from, uint32_t from_count, const PureField *to, uint32_t to_count, PureSpan payload, unsigned char **out, size_t *size); -/* A record field's kind as a stored list names it: 0 a number, 1 a text, 3 - * a bool (2 is a runtime list's bytes); a list holds a bool as a number. */ -enum { PURE_RECORD_BOOL = 3 }; +/* A record field's kind in a list, as a stored list names it too (PURE_CELL_...). */ uint8_t pure_record_kind(const PureModule *, uint32_t field); /* A list with record's fields, taking from `from` (its fields' names and - * kinds) each field of the same name and kind, or a number and a bool from - * each other (0 and 1 are false and true; a bool's other numbers take the - * default), and giving the others their defaults (module.c). */ + * kinds) each field of the same name and kind, or a number, a bool and an + * i64 from each other when the value fits (pure_cell_as), and giving the + * others their defaults (module.c). */ PureResult pure_list_migrate(PureList **out, const PureList *from, const char *const *names, const uint8_t *kinds, unsigned count, const PureModule *m, uint32_t record); #endif diff --git a/core/module.c b/core/module.c index 99b3088..7c3a0ac 100644 --- a/core/module.c +++ b/core/module.c @@ -2,6 +2,7 @@ #include "module-save.h" #include "log.h" #include "module-text.h" +#include #include #include #include @@ -12,10 +13,10 @@ enum { STATE_BYTES = 16, COLLECTION_BYTES = 12, INPUT_BYTES = 16, FIELD_BYTES = HANDLER_BYTES = 12, OP_BYTES = 16 }; /* T_FIELD and T_ITEM stand for the type of the message or record field * named, T_KEY for the type of the named keyed list's key, T_LOCAL for what - * the named local holds, T_STATE for the type of the number or bool state - * named. */ + * the named local holds, T_STATE for the type of the number, bool or i64 + * state named. */ enum { T_NUM = 1, T_BYTES = 2, T_TEXT = 3, T_CURSOR = 4, T_FIELD = 5, T_INDEX = 6, T_ITEM = 7, T_KEY = 8, T_LOCAL = 9, - T_BOOL = 10, T_STATE = 11 }; + T_BOOL = 10, T_STATE = 11, T_I64 = 12 }; enum { FROM_PAYLOAD, FROM_DOCUMENT, FROM_COPY, FROM_CONSTANT, FROM_TEXT, FROM_ARENA, FROM_ARGUMENTS }; /* Where an opcode may appear. */ /* IN_ARGUMENTS: fields read where nothing is done with them (a view's arguments, a message's fields in a fold). */ @@ -27,16 +28,16 @@ enum { READ = IN_VIEW | IN_HANDLER }; * the type), a list and one of its record's fields, a message field, the * collection, a constant, a unit, the column's row, a branch target, a stack * depth, a fold, a relation, a keyed list, an output, the row of the column - * a fold runs in, a number or a bool state (A_VALUE), or whether the value - * is a bool (A_BOOL: 0 or 1). */ -enum { A_NONE, A_STATE, A_ITEM = A_STATE + PURE_STATE_BOOL + 1, A_FIELD, A_COLLECTION, A_CONSTANT, A_UNIT, - A_ROW, A_TARGET, A_DEPTH, A_FOLD, A_RELATION, A_KEYED, A_OUTPUT, A_OUTER, A_EACH, A_LOCAL, A_VALUE, A_BOOL }; + * a fold runs in, a number, bool or i64 state (A_VALUE), or the value's type + * (A_TYPE: 0 a number, 1 a bool, 2 an i64). */ +enum { A_NONE, A_STATE, A_ITEM = A_STATE + PURE_STATE_I64 + 1, A_FIELD, A_COLLECTION, A_CONSTANT, A_UNIT, + A_ROW, A_TARGET, A_DEPTH, A_FOLD, A_RELATION, A_KEYED, A_OUTPUT, A_OUTER, A_EACH, A_LOCAL, A_VALUE, A_TYPE }; /* A fold's block is verified as a column that leaves one bool (count, first) - * or a number and whether to include it (a bool), a table's filter as a - * column that leaves a bool: these stand for a view type. A walk's block - * (EACH) is verified as its handler's code that may read the item it runs - * for (WALK). */ -enum { FOLD_ONE = 100, FOLD_TWO, FILTER, WALK }; + * or a value and whether to include it (a bool): a number (FOLD_TWO) or an + * i64 (FOLD_I64). A table's filter is verified as a column that leaves a + * bool: these stand for a view type. A walk's block (EACH) is verified as + * its handler's code that may read the item it runs for (WALK). */ +enum { FOLD_ONE = 100, FOLD_TWO, FOLD_I64, FILTER, WALK }; /* The text or list an opcode works on: its cursor and index operands belong * to it, and so does a cursor or index it pushes. O_ANY: no rule. O_OPERAND: * it pushes a position in its operand's text or list. O_ARG: the argument. @@ -50,9 +51,10 @@ static const unsigned char magic[8] = {'P','U','R','E','M','O','D','6'}; #define NUMS(pops) {pops, {T_NUM, T_NUM, T_NUM}, T_NUM, READ, A_NONE, O_ANY} #define COMPARE {2, {T_NUM, T_NUM}, T_BOOL, READ, A_NONE, O_ANY} #define LOGIC(pops) {pops, {T_BOOL, T_BOOL}, T_BOOL, READ, A_NONE, O_ANY} +#define I64S(pops) {pops, {T_I64, T_I64}, T_I64, READ, A_NONE, O_ANY} #define STATE(type) A_STATE + PURE_STATE_##type static const struct { unsigned char pops, in[3], out; unsigned short where; unsigned char arg, of; } signature[PURE_OP_COUNT] = { - [PURE_OP_CONST] = {0, {0}, T_NUM, READ, A_BOOL, O_ANY}, [PURE_OP_ADD] = NUMS(2), [PURE_OP_SUB] = NUMS(2), + [PURE_OP_CONST] = {0, {0}, T_NUM, READ, A_TYPE, O_ANY}, [PURE_OP_ADD] = NUMS(2), [PURE_OP_SUB] = NUMS(2), [PURE_OP_MUL] = NUMS(2), [PURE_OP_DIV] = NUMS(2), [PURE_OP_NEG] = NUMS(1), [PURE_OP_LT] = COMPARE, [PURE_OP_LE] = COMPARE, [PURE_OP_GT] = COMPARE, [PURE_OP_GE] = COMPARE, [PURE_OP_EQ] = COMPARE, [PURE_OP_NE] = COMPARE, [PURE_OP_AND] = LOGIC(2), [PURE_OP_OR] = LOGIC(2), [PURE_OP_NOT] = LOGIC(1), @@ -72,7 +74,7 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi [PURE_OP_TEXT_CONST] = {0, {0}, T_TEXT, READ, A_CONSTANT, O_ANY}, [PURE_OP_TEXT_LOAD] = {0, {0}, T_TEXT, READ, STATE(TEXT), O_ANY}, [PURE_OP_TEXT_INPUT] = {0, {0}, T_TEXT, IN_TEXT, A_NONE, O_ANY}, - [PURE_OP_NUM_TEXT] = {1, {T_NUM}, T_TEXT, READ, A_BOOL, O_ANY}, + [PURE_OP_NUM_TEXT] = {1, {T_NUM}, T_TEXT, READ, A_TYPE, O_ANY}, [PURE_OP_CONCAT] = {2, {T_TEXT, T_TEXT}, T_TEXT, READ, A_NONE, O_ANY}, [PURE_OP_CHARS] = {1, {T_TEXT}, T_NUM, READ, A_NONE, O_ANY}, [PURE_OP_LINES] = {1, {T_TEXT}, T_NUM, READ, A_NONE, O_ANY}, @@ -118,10 +120,16 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi [PURE_OP_LOCAL_LOAD] = {0, {0}, T_LOCAL, IN_LOCALS, A_LOCAL, O_ANY}, [PURE_OP_LOCAL_STORE] = {1, {T_LOCAL}, 0, IN_HANDLER, A_LOCAL, O_ANY}, [PURE_OP_FLOOR] = NUMS(1), [PURE_OP_MOD] = NUMS(2), + [PURE_OP_I64_ADD] = I64S(2), [PURE_OP_I64_SUB] = I64S(2), [PURE_OP_I64_MUL] = I64S(2), [PURE_OP_I64_DIV] = I64S(2), + [PURE_OP_I64_MOD] = I64S(2), [PURE_OP_I64_NEG] = I64S(1), [PURE_OP_I64_MIN] = I64S(2), [PURE_OP_I64_MAX] = I64S(2), + [PURE_OP_I64_COMPARE] = {2, {T_I64, T_I64}, T_BOOL, READ, A_RELATION, O_ANY}, + [PURE_OP_I64_FROM] = {1, {T_NUM}, T_I64, READ, A_NONE, O_ANY}, + [PURE_OP_NUM_FROM] = {1, {T_I64}, T_NUM, READ, A_NONE, O_ANY}, }; #undef NUMS #undef COMPARE #undef LOGIC +#undef I64S #undef STATE static uint32_t read16(const unsigned char *p) { return (uint32_t)p[0] | (uint32_t)p[1] << 8; } @@ -148,6 +156,19 @@ static void write_double(unsigned char *p, double value) { write32(p + 4, (uint32_t)(bits >> 32)); } +static int64_t read_i64(const unsigned char *p) { return (int64_t)(read32(p) | (uint64_t)read32(p + 4) << 32); } + +static void write_i64(unsigned char *p, int64_t value) { + write32(p, (uint32_t)(uint64_t)value); + write32(p + 4, (uint32_t)((uint64_t)value >> 32)); +} + +const char *pure_fault_name(PureFault fault) { + static const char *const names[PURE_FAULT_COUNT] = {"none", "divide", "non_finite", "clamp", "limit", "not_found", + "encoding", "exists", "memory", "overflow"}; + return fault < PURE_FAULT_COUNT ? names[fault] : "none"; +} + /* A bool's value: 0 (not -0) or 1. */ static bool canonical_bool(double value) { return value == 1 || (value == 0 && !signbit(value)); } @@ -166,8 +187,8 @@ uint32_t pure_fields_lay_out(PureField *fields, uint32_t count) { return prefix; } -/* A field's value in a payload: a number, or bytes (of the payload). */ -typedef struct { double num; PureSpan bytes; } FieldValue; +/* A field's value in a payload: a number (a u32's, a bool's) or an i64, or bytes (of the payload). */ +typedef struct { union { double num; int64_t i64; }; PureSpan bytes; } FieldValue; /* Each field's value in payload, laid out by fields (their offsets set): * false when the payload is not so laid out. */ @@ -182,6 +203,7 @@ static bool read_fields(const PureField *fields, uint32_t count, PureSpan payloa values[f] = (FieldValue){0}; if (field->type == PURE_FIELD_U32) values[f].num = read32(payload.data + field->offset); else if (field->type == PURE_FIELD_NUMBER) values[f].num = read_double(payload.data + field->offset); + else if (field->type == PURE_FIELD_I64) values[f].i64 = read_i64(payload.data + field->offset); else if (field->type == PURE_FIELD_BOOL) { if (payload.data[field->offset] > 1) return false; values[f].num = payload.data[field->offset]; @@ -195,6 +217,11 @@ static bool read_fields(const PureField *fields, uint32_t count, PureSpan payloa return at == payload.size; } +/* A fixed field's value as a list cell's kind (a u32 is a number). */ +static uint8_t field_kind(uint8_t type) { + return type == PURE_FIELD_BOOL ? PURE_CELL_BOOL : type == PURE_FIELD_I64 ? PURE_CELL_INTEGER : PURE_CELL_NUMBER; +} + PureResult pure_send_recast(const PureField *from, uint32_t from_count, const PureField *to, uint32_t to_count, PureSpan payload, unsigned char **out, size_t *size) { FieldValue was[PURE_MODULE_FIELDS], now[PURE_MODULE_FIELDS]; @@ -210,13 +237,13 @@ PureResult pure_send_recast(const PureField *from, uint32_t from_count, const Pu while (f < from_count && strcmp(from[f].name, field->name)) ++f; bool value = pure_field_fixed(field->type); if (f < from_count && value && pure_field_fixed(from[f].type)) { - /* A number, a u32 or a bool: the value when the field can hold it - * (a bool 0 or 1), as a state's is kept. */ - double v = was[f].num; - bool fits = field->type == PURE_FIELD_NUMBER ? true - : field->type == PURE_FIELD_U32 ? v >= 0 && v <= UINT32_MAX && v == (double)(uint32_t)v - : v == 0 || v == 1; - now[g].num = fits ? v : 0; + /* A number, a u32, a bool or an i64: the value when the field can + * hold it (pure_cell_as; a u32 a whole number in its range), as a + * state's is kept; else 0. */ + PureCell cell = {.i64 = was[f].i64}; + bool fits = pure_cell_as(field_kind(from[f].type), field_kind(field->type), &cell) && + (field->type != PURE_FIELD_U32 || (cell.num >= 0 && cell.num <= UINT32_MAX && cell.num == floor(cell.num))); + now[g].i64 = fits ? cell.i64 : 0; } else if (f < from_count && !value && (from[f].type == PURE_FIELD_BYTES || from[f].type == PURE_FIELD_TEXT)) { bool fits = field->type == PURE_FIELD_BYTES || from[f].type == PURE_FIELD_TEXT || pure_utf8_valid(was[f].bytes); if (fits) now[g].bytes = was[f].bytes; @@ -235,6 +262,7 @@ PureResult pure_send_recast(const PureField *from, uint32_t from_count, const Pu unsigned char *place = field->offset == PURE_FIELD_REST ? made + at : made + field->offset; if (field->type == PURE_FIELD_U32) write32(place, (uint32_t)now[g].num); else if (field->type == PURE_FIELD_NUMBER) write_double(place, now[g].num); + else if (field->type == PURE_FIELD_I64) write_i64(place, now[g].i64); else if (field->type == PURE_FIELD_BOOL) *place = (unsigned char)now[g].num; else { if (field->offset != PURE_FIELD_REST) write32(place, (uint32_t)now[g].bytes.size); @@ -303,13 +331,26 @@ static uint32_t list_of(uint32_t arg) { return arg & 0xFFFF; } static uint32_t item_field(uint32_t arg) { return arg >> 16; } static unsigned char field_type(const PureModule *m, uint32_t list, uint32_t field) { - uint32_t f = m->record[m->state_ref[list]].first + field; - return m->record_field_text[f] ? T_TEXT : m->record_field_bool[f] ? T_BOOL : T_NUM; + 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}; + return types[m->record_field_type[m->record[m->state_ref[list]].first + field]]; } /* The stack type of a message field's value (a u32 is a number). */ static unsigned char message_type(uint8_t type) { - return type == PURE_FIELD_TEXT ? T_TEXT : type == PURE_FIELD_BYTES ? T_BYTES : type == PURE_FIELD_BOOL ? T_BOOL : T_NUM; + return type == PURE_FIELD_TEXT ? T_TEXT : type == PURE_FIELD_BYTES ? T_BYTES : type == PURE_FIELD_BOOL ? T_BOOL : + type == PURE_FIELD_I64 ? T_I64 : T_NUM; +} + +/* The stack type of a number, bool or i64 state's value. */ +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; +} + +/* What a local holds (its argument's bits 8-15): a number, an index, a bool or an i64. */ +static unsigned char local_type(uint32_t arg) { + static const unsigned char types[] = {T_NUM, T_INDEX, T_BOOL, T_I64}; + return types[arg >> 8 & 3]; } PURE_COLD static bool names_arg(const PureModule *m, unsigned kind, uint32_t arg, const PureInputShape *shape, int row, @@ -324,17 +365,20 @@ PURE_COLD static bool names_arg(const PureModule *m, unsigned kind, uint32_t arg case A_ROW: return (int)arg == row; case A_OUTER: return outer >= 0 && (int)arg == outer; case A_FOLD: case A_EACH: { /* a slice's list is keyed by a number */ - uint32_t list = arg >> 8 & 0xFF, flags = arg & 0xFF; + uint32_t list = arg >> 8 & 0xFF, flags = arg & 0xFF, how = flags & PURE_FOLD_KIND; bool sliced = kind == A_FOLD ? flags & PURE_FOLD_SLICED : flags & PURE_EACH_SLICED; - return (kind == A_FOLD ? (flags & 0x7F) <= PURE_FOLD_DISTINCT : flags <= PURE_EACH_SLICED) && - state_of(m, list, PURE_STATE_LIST) && arg >> 16 && - (!sliced || (m->state_key[list] && field_type(m, list, m->state_key[list] - 1u) == T_NUM)); - } - case A_LOCAL: /* a slot, and a number, an index of a list or a bool */ - return (arg & 0xFF) < PURE_MODULE_LOCALS && (arg >> 8 & 0xFF) <= 2 && + /* An i64's fold sums, keeps the most or least or counts the different values. */ + bool valued = how != PURE_FOLD_COUNT && how != PURE_FOLD_FIRST; + unsigned char key = state_of(m, list, PURE_STATE_LIST) && m->state_key[list] ? + field_type(m, list, m->state_key[list] - 1u) : 0; + return (kind == A_FOLD ? how <= PURE_FOLD_DISTINCT && (valued || !(flags & PURE_FOLD_INTEGER)) : flags <= PURE_EACH_SLICED) && + state_of(m, list, PURE_STATE_LIST) && arg >> 16 && (!sliced || key == T_NUM || key == T_I64); + } + case A_LOCAL: /* a slot, and a number, an index of a list, a bool or an i64 */ + return (arg & 0xFF) < PURE_MODULE_LOCALS && (arg >> 8 & 0xFF) <= 3 && ((arg >> 8 & 0xFF) == 1 ? state_of(m, arg >> 16, PURE_STATE_LIST) : !(arg >> 16)); - case A_VALUE: return state_of(m, arg, PURE_STATE_NUM) || state_of(m, arg, PURE_STATE_BOOL); - case A_BOOL: return arg <= 1; + case A_VALUE: return state_of(m, arg, PURE_STATE_NUM) || state_of(m, arg, PURE_STATE_BOOL) || state_of(m, arg, PURE_STATE_I64); + case A_TYPE: return arg <= 2; case A_RELATION: return arg <= PURE_NOT_EQUAL; case A_KEYED: return state_of(m, arg, PURE_STATE_LIST) && m->state_key[arg]; case A_OUTPUT: return arg < m->inputs && m->input[arg].kind == PURE_KIND_OUTPUT; @@ -371,7 +415,7 @@ PURE_COLD static const char *verify_block(const PureModule *, PureBlock, int vie PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, int view, int row, int outer, uint32_t event, bool locals, uint32_t *at, unsigned char *target) { #define REFUSE(why) do { *at = pc; return why; } while (0) - bool handler = view < 0 || view == WALK, folding = view == FOLD_ONE || view == FOLD_TWO; + bool handler = view < 0 || view == WALK, folding = view == FOLD_ONE || view == FOLD_TWO || view == FOLD_I64; if (!handler && !block.length) return "empty view"; /* A handler's message or text; a view's arguments, or the message of the handler a fold is in. */ int input = event_input(event); @@ -422,9 +466,10 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i REFUSE("message operation outside a message handler"); } uint32_t arg = op->arg; - bool truth = op->op == PURE_OP_CONST && arg; + /* A constant: a finite number, a bool +0 or 1, any i64 (arg says which). */ + bool constant = op->op == PURE_OP_CONST, truth = constant && arg == 1, integer = constant && arg == 2; if ((kind == A_NONE && arg) || - (truth ? !canonical_bool(op->value) : op->op == PURE_OP_CONST ? !isfinite(op->value) : op->value != 0)) + (truth ? !canonical_bool(op->value) : integer ? false : constant ? !isfinite(op->value) : op->integer != 0)) REFUSE("non-canonical operand"); if (!names_arg(m, kind, arg, shape, row, outer)) REFUSE("operand names no such state, field, constant or unit"); /* A keyed list places its items by key, and an item's key stays. */ @@ -454,12 +499,13 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i if (kind == A_FOLD && (folding || view == FILTER)) REFUSE(view == FILTER ? "fold inside a filter" : "fold inside a fold"); if (skip > block.length - i - 1) REFUSE(kind == A_FOLD ? "fold block outside its block" : "each block outside its block"); - unsigned how = arg & 0x7F; + unsigned how = arg & PURE_FOLD_KIND; /* A fold in a walk reads the walk's item as a fold in a column reads its row. */ const char *why = kind == A_EACH ? verify_block(m, (PureBlock){pc + 1, skip}, WALK, (int)(arg >> 8 & 0xFF), -1, event, locals, &inner) : verify_block(m, (PureBlock){pc + 1, skip}, - how == PURE_FOLD_COUNT || how == PURE_FOLD_FIRST ? FOLD_ONE : FOLD_TWO, + how == PURE_FOLD_COUNT || how == PURE_FOLD_FIRST ? FOLD_ONE : + arg & PURE_FOLD_INTEGER ? FOLD_I64 : FOLD_TWO, (int)(arg >> 8 & 0xFF), row, event, locals, &inner); if (why) { *at = inner; return why; } for (uint32_t k = i + 1; k <= i + skip; ++k) { @@ -485,16 +531,17 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i } else if (need[0] == T_KEY) { need[0] = field_type(m, arg, m->state_key[arg] - 1u); } else if (need[0] == T_LOCAL) { - need[0] = (arg >> 8 & 0xFF) == 1 ? T_INDEX : (arg >> 8 & 0xFF) == 2 ? T_BOOL : T_NUM; + need[0] = local_type(arg); } else if (need[0] == T_STATE) { - need[0] = m->state_type[arg] == PURE_STATE_BOOL ? T_BOOL : T_NUM; + need[0] = state_type(m, arg); } else if (op->op == PURE_OP_NUM_TEXT && arg) { - need[0] = T_BOOL; + need[0] = arg == 1 ? T_BOOL : T_I64; } else if ((op->op == PURE_OP_EQ || op->op == PURE_OP_NE) && height >= 2 && types[height - 1] == T_BOOL && types[height - 2] == T_BOOL) { need[0] = need[1] = T_BOOL; /* two bools compare as two numbers do */ } else if ((kind == A_FOLD && arg & PURE_FOLD_SLICED) || (kind == A_EACH && arg & PURE_EACH_SLICED)) { - need[0] = need[1] = T_NUM; /* a slice's low and high keys */ + uint32_t list = arg >> 8 & 0xFF; + need[0] = need[1] = field_type(m, list, m->state_key[list] - 1u); /* a slice's low and high keys */ pops = 2; } if (height < (int)pops) REFUSE("stack underflow"); @@ -512,16 +559,18 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i REFUSE(type == T_CURSOR ? "cursor of another text" : "index of another list"); } unsigned char out = signature[op->op].out; - if (kind == A_FOLD && (arg & 0x7F) == PURE_FOLD_FIRST) { /* an index of its list */ + if (kind == A_FOLD && (arg & PURE_FOLD_KIND) == PURE_FOLD_FIRST) { /* an index of its list */ out = T_INDEX; of = O_ARG; owner = arg >> 8 & 0xFF; - } + } else if (kind == A_FOLD && arg & PURE_FOLD_INTEGER && (arg & PURE_FOLD_KIND) != PURE_FOLD_DISTINCT) + out = T_I64; /* an i64's sum, most or least */ if (out == T_FIELD) out = message_type(m->field[shape->field_start + arg].type); if (out == T_ITEM) out = field_type(m, list_of(arg), item_field(arg)); - if (out == T_LOCAL) out = (arg >> 8 & 0xFF) == 1 ? T_INDEX : (arg >> 8 & 0xFF) == 2 ? T_BOOL : T_NUM; - if (out == T_STATE) out = m->state_type[arg] == PURE_STATE_BOOL ? T_BOOL : T_NUM; + if (out == T_LOCAL) out = local_type(arg); + if (out == T_STATE) out = state_type(m, arg); if (truth) out = T_BOOL; + if (integer) out = T_I64; if (out) { if (height == PURE_MODULE_STACK) REFUSE("stack overflow"); owners[height] = (unsigned char)(out != T_CURSOR && out != T_INDEX ? 0 : of == O_OPERAND ? owners[height] : owner); @@ -552,7 +601,9 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i if (view == FOLD_ONE) return height != 1 || types[0] != T_BOOL ? "fold block must leave one bool" : NULL; if (view == FOLD_TWO) return height != 2 || types[0] != T_NUM || types[1] != T_BOOL ? "fold block must leave a number and a bool" : NULL; - unsigned char result = view == PURE_VIEW_TEXT ? T_TEXT : view == PURE_VIEW_BOOL ? T_BOOL : T_NUM; + if (view == FOLD_I64) + return height != 2 || types[0] != T_I64 || types[1] != T_BOOL ? "fold block must leave an i64 and a bool" : NULL; + unsigned char result = view == PURE_VIEW_TEXT ? T_TEXT : view == PURE_VIEW_BOOL ? T_BOOL : view == PURE_VIEW_I64 ? T_I64 : T_NUM; if (!handler ? height != 1 || types[0] != result : height != 0) return !handler ? "view must leave one value of its type" : "handler must leave no value"; return NULL; @@ -570,21 +621,27 @@ PURE_COLD static const char *verify_block(const PureModule *m, PureBlock block, } /* Whether an instruction has a form that works on the items of a run at - * once: numbers and indexes, with no branch and no text. */ + * once: numbers, bools and indexes, with no branch, no text and no i64. */ PURE_COLD static bool has_lanes(const PureModule *m, const PureOp *op, int input) { switch (op->op) { case PURE_OP_FIELD: { /* a number or bool the message or the arguments give, the same for every item */ uint8_t type = input >= 0 ? m->field[m->input[input].field_start + op->arg].type : 0; return type == PURE_FIELD_U32 || type == PURE_FIELD_NUMBER || type == PURE_FIELD_BOOL; } - case PURE_OP_CONST: case PURE_OP_ADD: case PURE_OP_SUB: case PURE_OP_MUL: case PURE_OP_DIV: case PURE_OP_NEG: + case PURE_OP_CONST: return op->arg != 2; + case PURE_OP_LOAD: return m->state_type[op->arg] != PURE_STATE_I64; + case PURE_OP_LOCAL_LOAD: return local_type(op->arg) != T_I64; + case PURE_OP_ADD: case PURE_OP_SUB: case PURE_OP_MUL: case PURE_OP_DIV: case PURE_OP_NEG: case PURE_OP_LT: case PURE_OP_LE: case PURE_OP_GT: case PURE_OP_GE: case PURE_OP_EQ: case PURE_OP_NE: case PURE_OP_AND: case PURE_OP_OR: case PURE_OP_NOT: case PURE_OP_MIN: case PURE_OP_MAX: case PURE_OP_CLAMP: - case PURE_OP_FLOOR: case PURE_OP_MOD: case PURE_OP_LOAD: case PURE_OP_LIST_COUNT: case PURE_OP_INDEX_LOAD: + case PURE_OP_FLOOR: case PURE_OP_MOD: case PURE_OP_LIST_COUNT: case PURE_OP_INDEX_LOAD: case PURE_OP_INDEX_MOVE: case PURE_OP_INDEX_FIRST: case PURE_OP_INDEX_LAST: case PURE_OP_INDEX_NUM: - case PURE_OP_ROW: case PURE_OP_OUTER_ROW: case PURE_OP_PICK: case PURE_OP_SLIDE: case PURE_OP_LOCAL_LOAD: - return true; - case PURE_OP_ITEM_GET: return field_type(m, list_of(op->arg), item_field(op->arg)) != T_TEXT; + case PURE_OP_ROW: case PURE_OP_OUTER_ROW: case PURE_OP_PICK: case PURE_OP_SLIDE: + return true; /* no i64 comes from the others, so PICK and SLIDE move none */ + case PURE_OP_ITEM_GET: { + unsigned char type = field_type(m, list_of(op->arg), item_field(op->arg)); + return type == T_NUM || type == T_BOOL; + } case PURE_OP_LIST_FIND: case PURE_OP_LIST_HAS: return field_type(m, op->arg, m->state_key[op->arg] - 1u) == T_NUM; default: return false; } @@ -602,7 +659,7 @@ PURE_COLD static void plan_folds(PureModule *m, PureBlock block, int input) { for (uint32_t pc = block.start; pc < block.start + block.length; ++pc) { PureOp *op = &m->code[pc]; if (op->op != PURE_OP_FOLD) continue; - op->plan = block_has_lanes(m, (PureBlock){pc + 1, op->arg >> 16}, input); + op->plan = !(op->arg & PURE_FOLD_INTEGER) && block_has_lanes(m, (PureBlock){pc + 1, op->arg >> 16}, input); pc += op->arg >> 16; } } @@ -620,7 +677,7 @@ PURE_COLD static unsigned arity(uint8_t op) { case PURE_OP_LIST_COUNT: case PURE_OP_INDEX_FIRST: case PURE_OP_INDEX_LAST: case PURE_OP_FIELD: case PURE_OP_LOCAL_LOAD: return 0; case PURE_OP_ITEM_GET: case PURE_OP_LIST_FIND: case PURE_OP_LIST_HAS: case PURE_OP_NEG: case PURE_OP_NOT: - case PURE_OP_FLOOR: case PURE_OP_INDEX_NUM: + case PURE_OP_FLOOR: case PURE_OP_INDEX_NUM: case PURE_OP_I64_NEG: case PURE_OP_I64_FROM: case PURE_OP_NUM_FROM: return 1; case PURE_OP_CLAMP: return 3; default: return 2; @@ -979,10 +1036,11 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule if (constant_bytes) memcpy(out->constant_data, p + size - constant_bytes, constant_bytes); for (uint32_t i = 0; i < out->states; ++i, at += STATE_BYTES) { out->state_type[i] = (uint8_t)read32(at + 4); - if (read32(at + 4) > PURE_STATE_BOOL) REFUSE("state type", i); - /* A number's or a bool's value is its 8 bytes; others have flags: a - * mark (cursor), a keyed list's key field + 1, or a list of a message's rows. */ - bool value = out->state_type[i] == PURE_STATE_NUM || out->state_type[i] == PURE_STATE_BOOL; + if (read32(at + 4) > PURE_STATE_I64) REFUSE("state type", i); + /* A number's, a bool's or an i64's value is its 8 bytes; others have flags: + * a mark (cursor), a keyed list's key field + 1, or a list of a message's rows. */ + bool value = out->state_type[i] == PURE_STATE_NUM || out->state_type[i] == PURE_STATE_BOOL || + out->state_type[i] == PURE_STATE_I64; uint32_t flags = value ? 0 : read32(at + 12); bool cursor = out->state_type[i] == PURE_STATE_CURSOR, list = out->state_type[i] == PURE_STATE_LIST; bool rows = list && flags == PURE_LIST_ROWS; @@ -990,7 +1048,9 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule if (!name_at(text, strings, read32(at), rows ? NAME_PATH : NAME_IDENTIFIER, out->state_names[i]) || (rows && !strchr(out->state_names[i], '.'))) REFUSE("state name", i); - if (value) { + if (out->state_type[i] == PURE_STATE_I64) { + out->initial[i].i64 = (int64_t)(read32(at + 8) | (uint64_t)read32(at + 12) << 32); + } else if (value) { out->initial[i].num = read_double(at + 8); if (!isfinite(out->initial[i].num)) REFUSE("non-finite initial value", i); if (out->state_type[i] == PURE_STATE_BOOL && !canonical_bool(out->initial[i].num)) REFUSE("bool initial value", i); @@ -1050,7 +1110,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule field->type = (uint8_t)type; field->rows = (uint16_t)(type >> 8); /* a rows field's list state */ if (!name_at(text, strings, read32(entry), NAME_PATH, field->name) || field->type < PURE_FIELD_U32 || - field->type > PURE_FIELD_BOOL || (field->type != PURE_FIELD_ROWS && type >> 8) || type >> 24) + field->type > PURE_FIELD_I64 || (field->type != PURE_FIELD_ROWS && type >> 8) || type >> 24) REFUSE("message field", next_field + f); /* Rows fill the list named by the input's name and the field's path; an output sends none. */ if (field->type == PURE_FIELD_ROWS) { @@ -1094,17 +1154,19 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule REFUSE("record", i); for (uint32_t f = record->first; f < record->first + record->count; ++f) { const unsigned char *entry = fields_at + (size_t)f * RECORD_FIELD_BYTES; - uint32_t type = read32(entry + 4); /* 0 a number, 1 a text, 2 a bool */ - out->record_field_text[f] = type == 1; - out->record_field_bool[f] = type == 2; - if (!name_at(text, strings, read32(entry), NAME_IDENTIFIER, out->record_field_names[f]) || type > 2) + uint32_t type = read32(entry + 4); /* PureRecordType */ + out->record_field_type[f] = (uint8_t)type; + if (!name_at(text, strings, read32(entry), NAME_IDENTIFIER, out->record_field_names[f]) || type > PURE_RECORD_I64) 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); - out->record_field_default[f].num = type == 1 ? read32(entry + 8) : read_double(entry + 8); + /* 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); + else if (type == PURE_RECORD_I64) 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 == 1 ? read32(entry + 12) || read32(entry + 8) >= out->constants - : !isfinite(given) || (type == 2 && !canonical_bool(given))) + if (type == PURE_RECORD_TEXT ? read32(entry + 12) || read32(entry + 8) >= out->constants + : type != PURE_RECORD_I64 && (!isfinite(given) || (type == PURE_RECORD_BOOL && !canonical_bool(given)))) REFUSE("record field default", f); } next_record_field += record->count; @@ -1116,7 +1178,8 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule (out->state_type[i] == PURE_STATE_CURSOR && !state_of(out, out->state_ref[i], PURE_STATE_TEXT)) || (out->state_type[i] == PURE_STATE_LIST && (out->state_ref[i] >= out->records || out->state_key[i] > out->record[out->state_ref[i]].count || - (out->state_key[i] && out->record_field_bool[out->record[out->state_ref[i]].first + out->state_key[i] - 1u]))) || + (out->state_key[i] && + 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); /* A table names its list in place of code; its columns follow it. */ @@ -1126,7 +1189,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule out->view_table[i] = (uint16_t)read16(at + 6); out->view_code[i] = (PureBlock){read32(at + 8), read32(at + 12)}; uint32_t table = out->view_table[i]; - if (at[4] > PURE_VIEW_BOOL || (at[5] && at[4] != PURE_VIEW_TABLE) || + if (at[4] > PURE_VIEW_I64 || (at[5] && at[4] != PURE_VIEW_TABLE) || (table && (table > i || out->view_type[table - 1] != PURE_VIEW_TABLE || at[4] == PURE_VIEW_TABLE))) REFUSE("view type", i); /* A table names its list in start, or, filtered, in the byte after its @@ -1273,7 +1336,7 @@ static int named_fields(const unsigned char *bytes, size_t size, PureField *fiel for (unsigned f = 0; f < count; ++f) { size_t length = size - at >= 2 ? bytes[at + 1] : 0; if (size - at < 2 || !length || length >= PURE_MODULE_NAME || size - at < 2 + length || !bytes[at] || - bytes[at] == PURE_FIELD_ROWS || bytes[at] > PURE_FIELD_BOOL) + bytes[at] == PURE_FIELD_ROWS || bytes[at] > PURE_FIELD_I64) return -1; fields[f] = (PureField){.type = bytes[at]}; memcpy(fields[f].name, bytes + at + 2, length); @@ -1296,7 +1359,7 @@ bool pure_module_takes(const PureModule *m, uint32_t input, const unsigned char while (f < s->field_count && strcmp(m->field[s->field_start + f].name, sent[k].name)) ++f; if (f == s->field_count) return false; /* its value would be dropped */ uint8_t type = m->field[s->field_start + f].type, from = sent[k].type; - if (type != from && !(type == PURE_FIELD_NUMBER && from == PURE_FIELD_U32) && + if (type != from && !((type == PURE_FIELD_NUMBER || type == PURE_FIELD_I64) && from == PURE_FIELD_U32) && !(type == PURE_FIELD_BYTES && from == PURE_FIELD_TEXT)) return false; } @@ -1346,6 +1409,12 @@ static double minimum(double a, double b) { return a < b || (a == b && signbit(a static double maximum(double a, double b) { return a > b || (a == b && !signbit(a)) ? a : b; } typedef struct { union { double num; int64_t i64; }; PureBytes bytes; } Slot; + +/* Whether two values that order as order (below zero: the first below) are related as a relation says (PureRelation). */ +static bool related(uint32_t relation, int order) { + return relation == PURE_LESS ? order < 0 : relation == PURE_LESS_EQUAL ? order <= 0 : relation == PURE_GREATER ? order > 0 : + relation == PURE_GREATER_EQUAL ? order >= 0 : relation == PURE_EQUAL ? order == 0 : order != 0; +} /* A walk (EACH): the list, the item it runs for, where it ends (items the * walk removes move it), and whether that item left. */ typedef struct { uint32_t list; size_t at, end; bool removed; } Walk; @@ -1359,7 +1428,7 @@ typedef struct { size_t row; /* the row a column is evaluated for (in a fold, the fold's item) */ size_t outer; /* in a fold, the row of the column it runs in */ Walk *walk; /* the walk the code runs in, or NULL */ - double *locals; /* the event's locals (a handler's, and what runs in it), or NULL */ + PureValue *locals; /* the event's locals (a handler's, and what runs in it), or NULL */ bool arguments; /* FIELD reads the arguments given for a view, not the message */ bool modified, exists; uint32_t (*spans)[2]; /* a message's variable fields: start and size in the payload */ @@ -1485,7 +1554,7 @@ static void move_indices(const PureModule *m, PureValue *values, uint32_t list, } static bool text_field(const PureModule *m, uint32_t list, uint32_t field) { - return m->record_field_text[m->record[m->state_ref[list]].first + field]; + return m->record_field_type[m->record[m->state_ref[list]].first + field] == PURE_RECORD_TEXT; } /* Append bytes to the arena as a value of it. */ @@ -1521,6 +1590,7 @@ static PureResult send(PureApp *app, uint32_t output, const Slot *fields, const const PureField *field = &m->field[shape->field_start + f]; if (field->type == PURE_FIELD_U32) write32(payload + field->offset, (uint32_t)fields[f].num); else if (field->type == PURE_FIELD_BOOL) payload[field->offset] = fields[f].num != 0; + else if (field->type == PURE_FIELD_I64) write_i64(payload + field->offset, fields[f].i64); else if (field->type == PURE_FIELD_NUMBER) { uint64_t bits; memcpy(&bits, &fields[f].num, 8); @@ -1692,7 +1762,7 @@ static bool run_lanes(const PureModule *m, const PureValue *values, PureBlock bl } case PURE_OP_INDEX_LOAD: stack[sp++] = (Lanes){NULL, values[arg].num, false, m->state_ref[arg]}; continue; case PURE_OP_ROW: stack[sp++] = (Lanes){NULL, 0, true, (uint16_t)arg}; continue; - case PURE_OP_LOCAL_LOAD: stack[sp++] = (Lanes){NULL, ev->locals[arg & 0xFF], false, (uint16_t)(arg >> 16)}; continue; + case PURE_OP_LOCAL_LOAD: stack[sp++] = (Lanes){NULL, ev->locals[arg & 0xFF].num, false, (uint16_t)(arg >> 16)}; continue; case PURE_OP_OUTER_ROW: stack[sp++] = (Lanes){NULL, (double)ev->outer, false, (uint16_t)arg}; continue; case PURE_OP_LIST_COUNT: stack[sp++] = (Lanes){NULL, (double)pure_list_count(app->lists[arg]), false, 0}; continue; case PURE_OP_INDEX_FIRST: case PURE_OP_INDEX_LAST: { @@ -1833,22 +1903,28 @@ static bool run_lanes(const PureModule *m, const PureValue *values, PureBlock bl } /* The values a distinct fold has seen: open addressing over their bits (-0 - * as 0), UINT64_MAX free (no finite number's bits). */ -typedef struct { uint64_t *keys; size_t capacity, count; } Seen; + * as 0), UINT64_MAX free (no finite number's bits; an i64's -1, whose + * being seen is marked apart). */ +typedef struct { uint64_t *keys; size_t capacity, count; bool marked; } Seen; -/* A fold's running value: what count, sum, max, min, first or distinct has so far. */ -typedef struct { unsigned how; double total; bool any, done; Seen seen; } Fold; +/* A fold's running value: what count, sum, max, min, first or distinct has + * so far, over numbers (total) or i64s (whole; a sum past the range + * overflows and ends the fold). */ +typedef struct { unsigned how; bool integer, overflow; double total; int64_t whole; bool any, done; Seen seen; } Fold; static size_t seen_slot(uint64_t bits, size_t capacity) { bits ^= bits >> 33, bits *= 0xff51afd7ed558ccdu, bits ^= bits >> 33; return (size_t)bits & (capacity - 1); } -/* Whether value is new to the set (added); false without memory too (*full). */ -static bool seen_new(Seen *seen, double value, bool *full) { - uint64_t bits; - value = value == 0 ? 0 : value; - memcpy(&bits, &value, sizeof(bits)); +/* Whether a value's bits are new to the set (added); false without memory too (*full). */ +static bool seen_bits(Seen *seen, uint64_t bits, bool *full) { + if (bits == UINT64_MAX) { /* the free slots' mark: an i64's -1, kept aside */ + if (seen->marked) return false; + seen->marked = true; + ++seen->count; + return true; + } if (2 * (seen->count + 1) > seen->capacity) { /* half the slots free */ size_t capacity = seen->capacity ? 2 * seen->capacity : 64; uint64_t *keys = malloc(capacity * sizeof(*keys)); @@ -1872,9 +1948,16 @@ static bool seen_new(Seen *seen, double value, bool *full) { return true; } +static bool seen_new(Seen *seen, double value, bool *full) { + uint64_t bits; + value = value == 0 ? 0 : value; + memcpy(&bits, &value, sizeof(bits)); + return seen_bits(seen, bits, full); +} + /* One item: its value, and whether it counts (for count and first, the * value is). false without memory (a distinct fold's set). */ -static bool fold_in(Fold *fold, size_t row, double value, double counts) { +static bool fold_in(Fold *fold, size_t row, PureValue value, double counts) { if (counts == 0) return true; unsigned how = fold->how; if (how == PURE_FOLD_FIRST) { @@ -1884,12 +1967,22 @@ static bool fold_in(Fold *fold, size_t row, double value, double counts) { } if (how == PURE_FOLD_DISTINCT) { bool full = false; - fold->total += seen_new(&fold->seen, value, &full); + fold->total += fold->integer ? seen_bits(&fold->seen, (uint64_t)value.i64, &full) : seen_new(&fold->seen, value.num, &full); fold->any = true; return !full; } - fold->total = how == PURE_FOLD_COUNT ? fold->total + 1 : how == PURE_FOLD_SUM ? fold->total + value : - !fold->any ? value : how == PURE_FOLD_MAX ? maximum(fold->total, value) : minimum(fold->total, value); + if (fold->integer) { /* an i64's sum, most or least */ + int64_t v = value.i64; + if (how == PURE_FOLD_SUM) { + if (__builtin_add_overflow(fold->whole, v, &fold->whole)) fold->overflow = fold->done = true; + } else if (!fold->any || (how == PURE_FOLD_MAX ? v > fold->whole : v < fold->whole)) + fold->whole = v; + fold->any = true; + return true; + } + double number = value.num; + fold->total = how == PURE_FOLD_COUNT ? fold->total + 1 : how == PURE_FOLD_SUM ? fold->total + number : + !fold->any ? number : how == PURE_FOLD_MAX ? maximum(fold->total, number) : minimum(fold->total, number); fold->any = true; return true; } @@ -1905,7 +1998,7 @@ static PureResult fold_rows(const PureModule *m, PureValue *values, PureBlock in PureResult status = run(m, values, inner, per, out); per->arena->used = mark; if (status != PURE_OK) return status; - if (!fold_in(fold, row, out[0].num, one ? out[0].num : out[1].num)) return PURE_ALLOCATION; + if (!fold_in(fold, row, (PureValue){.i64 = out[0].i64}, one ? out[0].num : out[1].num)) return PURE_ALLOCATION; } return PURE_OK; } @@ -1950,16 +2043,17 @@ static PureResult fold_runs(const PureModule *m, PureValue *values, PureBlock in fold->any = any; } else for (unsigned i = first; i < last && !fold->done; ++i) - if (!fold_in(fold, items.start + i, v[i], c[i])) return PURE_ALLOCATION; + if (!fold_in(fold, items.start + i, (PureValue){.num = v[i]}, c[i])) return PURE_ALLOCATION; } return PURE_OK; } /* The positions of the items whose keys are from low up to before high. */ -static void slice_of(const PureList *list, unsigned key, double low, double high, size_t *from, size_t *to) { +/* The items whose keys are from low up to before high (numbers or i64s, as the key is). */ +static void slice_of(const PureList *list, unsigned key, Slot low, Slot high, size_t *from, size_t *to) { bool found; - *from = pure_list_seek(list, key, (PureCell){.num = low}, &found); - *to = pure_list_seek(list, key, (PureCell){.num = high}, &found); + *from = pure_list_seek(list, key, (PureCell){.i64 = low.i64}, &found); + *to = pure_list_seek(list, key, (PureCell){.i64 = high.i64}, &found); if (*to < *from) *to = *from; } @@ -2104,7 +2198,7 @@ static Group *group_of(struct PurePass *pass, double key, unsigned how, bool add } size_t at = group_slot(order_bits(key), pass->capacity); while (pass->groups[at].used) at = (at + 1) & (pass->capacity - 1); - pass->groups[at] = (Group){key, {how, 0, false, false, {0}}, true}; + pass->groups[at] = (Group){key, {.how = how}, true}; ++pass->count; return &pass->groups[at]; } @@ -2128,7 +2222,7 @@ static bool group_pass(const PureModule *m, PureValue *values, PureBlock inner, if (c[i] == 0) continue; Group *group = group_of(pass, keys[i], how, true); if (!group) return false; - if (!group->fold.done && !fold_in(&group->fold, items.start + i, v[i], c[i])) return false; + if (!group->fold.done && !fold_in(&group->fold, items.start + i, (PureValue){.num = v[i]}, c[i])) return false; } } return true; @@ -2225,8 +2319,8 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E uint32_t arg = op->arg; Slot *top = sp ? &stack[sp - 1] : stack; switch (op->op) { - case PURE_OP_CONST: stack[sp++].num = op->value; continue; - case PURE_OP_LOAD: stack[sp++].i64 = values[arg].i64; continue; /* the bits, a number's or a bool's */ + case PURE_OP_CONST: stack[sp++].i64 = op->integer; continue; /* the bits: a number's, a bool's or an i64's */ + case PURE_OP_LOAD: stack[sp++].i64 = values[arg].i64; continue; case PURE_OP_DT: stack[sp++].num = ev->dt; continue; case PURE_OP_NOW: stack[sp++].num = app->now; continue; case PURE_OP_FLOOR: top->num = floor(top->num); continue; @@ -2247,6 +2341,7 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E if (field->type == PURE_FIELD_U32) stack[sp++].num = read32(given + field->offset); else if (field->type == PURE_FIELD_NUMBER) stack[sp++].num = read_double(given + field->offset); else if (field->type == PURE_FIELD_BOOL) stack[sp++].num = given[field->offset]; + else if (field->type == PURE_FIELD_I64) stack[sp++].i64 = read_i64(given + field->offset); else stack[sp++].bytes = (PureBytes){ev->arguments ? FROM_ARGUMENTS : FROM_PAYLOAD, 0, {0, 0}, ev->spans[arg][0], ev->spans[arg][1]}; continue; @@ -2286,7 +2381,8 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E case PURE_OP_TEXT_INPUT: stack[sp++].bytes = (PureBytes){FROM_PAYLOAD, 0, {0, 0}, 0, app->payload.size}; continue; case PURE_OP_NUM_TEXT: { char digits[32]; - size_t length = arg ? (size_t)snprintf(digits, sizeof(digits), "%s", top->num ? "true" : "false") + size_t length = arg == 2 ? (size_t)snprintf(digits, sizeof(digits), "%" PRId64, top->i64) + : arg ? (size_t)snprintf(digits, sizeof(digits), "%s", top->num ? "true" : "false") : pure_format_number(top->num, digits); if ((status = keep_bytes(arena, digits, length, &top->bytes)) != PURE_OK) return status; continue; @@ -2325,10 +2421,10 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E stack[sp++].bytes.owner = (uint16_t)arg; continue; case PURE_OP_LOCAL_LOAD: - stack[sp].num = ev->locals[arg & 0xFF]; + stack[sp].i64 = ev->locals[arg & 0xFF].i64; /* the bits: a number's, an index's, a bool's or an i64's */ stack[sp++].bytes.owner = (uint16_t)(arg >> 16); /* an index's list */ continue; - case PURE_OP_LOCAL_STORE: ev->locals[arg & 0xFF] = stack[--sp].num; continue; + case PURE_OP_LOCAL_STORE: ev->locals[arg & 0xFF].i64 = stack[--sp].i64; continue; case PURE_OP_CUR_MOVE: case PURE_OP_INDEX_MOVE: { double count = stack[--sp].num; Slot *at = &stack[sp - 1]; @@ -2449,7 +2545,7 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E if (at >= pure_list_count(app->lists[list])) { kind = PURE_FAULT_NOT_FOUND; goto failed; } PureCell cell = pure_list_get(app->lists[list], at, field); /* A text is copied: the item may change or go before the value is used. */ - if (!text_field(m, list, field)) top->num = cell.num; + if (!text_field(m, list, field)) top->i64 = cell.i64; /* the bits: a number's, a bool's or an i64's */ else if ((status = keep_bytes(arena, cell.text, cell.size, &top->bytes)) != PURE_OK) return status; continue; } @@ -2483,12 +2579,12 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E PureBlock inner = {pc, arg >> 16}; PureList *list = app->lists[arg >> 8 & 0xFF]; pc += inner.length; - Fold fold = {arg & 0x7F, 0, false, false, {0}}; + Fold fold = {.how = arg & PURE_FOLD_KIND, .integer = arg & PURE_FOLD_INTEGER}; Event per = *ev; per.outer = ev->row; size_t from = 0, to = pure_list_count(list); if (arg & PURE_FOLD_SLICED) { /* the items with keys from low up to before high */ - double high = stack[--sp].num, low = stack[--sp].num; + Slot high = stack[--sp], low = stack[--sp]; slice_of(list, m->state_key[arg >> 8 & 0xFF] - 1u, low, high, &from, &to); } if (!op->shape || !fold_shaped(m, values, op, list, &per, &fold)) { @@ -2499,12 +2595,14 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E free(fold.seen.keys); if (status != PURE_OK) return status; } + if (fold.overflow) { kind = PURE_FAULT_OVERFLOW; goto failed; } if (!fold.any && (fold.how == PURE_FOLD_MAX || fold.how == PURE_FOLD_MIN || fold.how == PURE_FOLD_FIRST)) { kind = PURE_FAULT_NOT_FOUND; goto failed; } - if (!isfinite(fold.total)) { kind = PURE_FAULT_NON_FINITE; goto failed; } - stack[sp].num = fold.total; + if (fold.integer && fold.how != PURE_FOLD_DISTINCT) stack[sp].i64 = fold.whole; + else if (!isfinite(fold.total)) { kind = PURE_FAULT_NON_FINITE; goto failed; } + else stack[sp].num = fold.total; stack[sp++].bytes.owner = (uint16_t)(arg >> 8 & 0xFF); /* first: the list the index is in */ continue; } @@ -2517,7 +2615,7 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E Event per = *ev; per.walk = &walk; if (arg & PURE_EACH_SLICED) { /* the items with keys from low up to before high */ - double high = stack[--sp].num, low = stack[--sp].num; + Slot high = stack[--sp], low = stack[--sp]; slice_of(app->lists[walk.list], m->state_key[walk.list] - 1u, low, high, &walk.at, &walk.end); } else if (op->range) /* a where that bounds the key: only the items in bounds */ range_bounds(m, values, &m->range[op->range - 1], app->lists[walk.list], m->state_key[walk.list] - 1u, &per, @@ -2547,9 +2645,7 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E int order = shorter ? memcmp(a.data, b.data, shorter) : 0; if (!order) order = (a.size > b.size) - (a.size < b.size); arena->used = mark; - stack[sp - 1].num = arg == PURE_LESS ? order < 0 : arg == PURE_LESS_EQUAL ? order <= 0 : - arg == PURE_GREATER ? order > 0 : arg == PURE_GREATER_EQUAL ? order >= 0 : - arg == PURE_EQUAL ? order == 0 : order != 0; + stack[sp - 1].num = related(arg, order); continue; } case PURE_OP_LIST_FIND: case PURE_OP_LIST_HAS: { @@ -2573,6 +2669,52 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E } case PURE_OP_PICK: stack[sp] = stack[sp - 1 - arg]; ++sp; continue; case PURE_OP_SLIDE: stack[sp - 1 - arg] = stack[sp - 1]; sp -= arg; continue; + /* i64s: exact, and failing as OVERFLOW past their range */ + case PURE_OP_I64_ADD: case PURE_OP_I64_SUB: case PURE_OP_I64_MUL: { + int64_t b = stack[--sp].i64, a = stack[sp - 1].i64, r; + bool over = op->op == PURE_OP_I64_ADD ? __builtin_add_overflow(a, b, &r) : + op->op == PURE_OP_I64_SUB ? __builtin_sub_overflow(a, b, &r) : __builtin_mul_overflow(a, b, &r); + if (over) { kind = PURE_FAULT_OVERFLOW; goto failed; } + stack[sp - 1].i64 = r; + continue; + } + case PURE_OP_I64_DIV: case PURE_OP_I64_MOD: { /* floored: the remainder takes the divisor's sign */ + int64_t b = stack[--sp].i64, a = stack[sp - 1].i64; + if (b == 0) { kind = PURE_FAULT_DIVIDE; goto failed; } + if (b == -1) { /* the least i64 over -1 is past the range; any remainder by -1 is 0 */ + if (op->op == PURE_OP_I64_DIV && a == INT64_MIN) { kind = PURE_FAULT_OVERFLOW; goto failed; } + stack[sp - 1].i64 = op->op == PURE_OP_I64_DIV ? -a : 0; + continue; + } + int64_t q = a / b, r = a % b; + if (r != 0 && (r < 0) != (b < 0)) { --q; r += b; } + stack[sp - 1].i64 = op->op == PURE_OP_I64_DIV ? q : r; + continue; + } + case PURE_OP_I64_NEG: + if (top->i64 == INT64_MIN) { kind = PURE_FAULT_OVERFLOW; goto failed; } + top->i64 = -top->i64; + continue; + case PURE_OP_I64_MIN: case PURE_OP_I64_MAX: { + int64_t b = stack[--sp].i64, a = stack[sp - 1].i64; + stack[sp - 1].i64 = (op->op == PURE_OP_I64_MIN ? b < a : b > a) ? b : a; + continue; + } + case PURE_OP_I64_COMPARE: { + int64_t b = stack[--sp].i64, a = stack[sp - 1].i64; + stack[sp - 1].num = related(arg, (a > b) - (a < b)); + continue; + } + case PURE_OP_I64_FROM: case PURE_OP_NUM_FROM: { /* a whole number in range; an i64 within 2^53 */ + PureCell cell = {.i64 = top->i64}; + bool to_i64 = op->op == PURE_OP_I64_FROM; + if (!pure_cell_as(to_i64 ? PURE_CELL_NUMBER : PURE_CELL_INTEGER, to_i64 ? PURE_CELL_INTEGER : PURE_CELL_NUMBER, &cell)) { + kind = PURE_FAULT_LIMIT; + goto failed; + } + top->i64 = cell.i64; + continue; + } default: break; } double b = stack[--sp].num, a = stack[sp - 1].num, value; @@ -2623,7 +2765,10 @@ PURE_COLD static PureResult new_text(PureDraft **out, const PureModule *m, uint3 } PURE_COLD static PureList *new_list(const PureModule *m, uint32_t record) { - return pure_list_new(m->record[record].count, m->record_field_text + m->record[record].first); + const PureRecord *r = &m->record[record]; + uint8_t kinds[PURE_RECORD_FIELDS]; + for (unsigned f = 0; f < r->count; ++f) kinds[f] = pure_record_kind(m, r->first + f); + return pure_list_new(r->count, kinds); } /* Where key is in the pending index: its slot, or the empty one it would take. */ @@ -2944,8 +3089,7 @@ size_t pure_app_state_bytes(const PureApp *app) { * holding more than before, fails. */ static PureResult dispatch(PureApp *app, uint32_t event, Event *ev) { const PureModule *m = app->module; - PureValue pending[PURE_MODULE_STATES]; - double locals[PURE_MODULE_LOCALS] = {0}; + PureValue pending[PURE_MODULE_STATES], locals[PURE_MODULE_LOCALS] = {{0}}; ev->locals = locals; size_t held = app->memory ? pure_app_state_bytes(app) : 0; Mark mark; @@ -3387,6 +3531,13 @@ bool pure_app_view(PureApp *app, uint32_t view, double *out) { return true; } +bool pure_app_view_i64(PureApp *app, uint32_t view, int64_t *out) { + Slot slot; + if (view_of(app, view, false, 0, PURE_VIEW_I64, &slot) != PURE_OK) return false; + *out = slot.i64; + return true; +} + PureResult pure_app_view_text(PureApp *app, uint32_t view, bool (*visit)(void *, PureSpan), void *context) { Slot slot; PureResult result = view_of(app, view, false, 0, PURE_VIEW_TEXT, &slot); @@ -3407,6 +3558,13 @@ bool pure_app_cell(PureApp *app, uint32_t column, size_t row, double *out) { return true; } +bool pure_app_cell_i64(PureApp *app, uint32_t column, size_t row, int64_t *out) { + Slot slot; + if (view_of(app, column, true, row, PURE_VIEW_I64, &slot) != PURE_OK) return false; + *out = slot.i64; + return true; +} + PureResult pure_app_cell_text(PureApp *app, uint32_t column, size_t row, bool (*visit)(void *, PureSpan), void *context) { Slot slot; PureResult result = view_of(app, column, true, row, PURE_VIEW_TEXT, &slot); @@ -3504,12 +3662,14 @@ void pure_app_read(PureApp *reader, const PureSnapshot *snapshot) { } uint8_t pure_record_kind(const PureModule *m, uint32_t field) { - return m->record_field_bool[field] ? PURE_RECORD_BOOL : m->record_field_text[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}; + return kinds[m->record_field_type[field]]; } -/* A number and a bool carry over each other; texts only to texts. */ +/* A number, a bool and an i64 carry over each other (when the value fits); texts only to texts. */ static bool kinds_carry(uint8_t from, uint8_t to) { - return from == to || ((from == 0 || from == PURE_RECORD_BOOL) && (to == 0 || to == PURE_RECORD_BOOL)); + return from == to || (!pure_cell_text(from) && !pure_cell_text(to)); } /* A list with record's fields: each takes the field of `from` with the same @@ -3530,13 +3690,12 @@ PURE_COLD PureResult pure_list_migrate(PureList **out, const PureList *from, con PureCell cells[PURE_RECORD_FIELDS]; for (unsigned f = 0; f < r->count; ++f) { uint32_t field = r->first + f, constant = (uint32_t)m->record_field_default[field].num; - cells[f] = source[f] >= 0 ? pure_list_get(from, at, (unsigned)source[f]) : - m->record_field_text[field] ? - (PureCell){.text = (unsigned char *)m->constant_data + m->constant_offset[constant], - .size = m->constant_length[constant]} : - (PureCell){.i64 = m->record_field_default[field].i64}; - if (m->record_field_bool[field]) - cells[f] = (PureCell){.num = pure_as_bool(cells[f].num, m->record_field_default[field].num)}; + PureCell given = source[f] >= 0 ? pure_list_get(from, at, (unsigned)source[f]) : (PureCell){0}; + if (source[f] >= 0 && pure_cell_as(kinds[source[f]], pure_record_kind(m, field), &given)) cells[f] = given; + else if (m->record_field_type[field] == PURE_RECORD_TEXT) + 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 */ } result = pure_list_insert(*out, at, cells, NULL); } @@ -3698,10 +3857,10 @@ PURE_COLD static PureResult open_module(PureApp *app, const PureModule *module) (app->outbox = pure_runtime_list_new(PURE_LIST_OUTBOX)) ? PURE_OK : PURE_ALLOCATION; for (uint32_t i = 0; i < module->states; ++i) { int previous = old ? pure_module_state(old, module->state_names[i]) : -1; - /* A number and a bool carry over each other: 0 and 1 are false and true (a bool's other numbers start anew). */ + /* A number, a bool and an i64 carry over each other when the value fits (pure_cell_as), else start anew. */ uint8_t was = previous >= 0 ? old->state_type[previous] : 0, now = module->state_type[i]; - bool truth = (was == PURE_STATE_NUM && now == PURE_STATE_BOOL) || (was == PURE_STATE_BOOL && now == PURE_STATE_NUM); - kept[i] = previous >= 0 && (was == now || truth) ? previous : -1; + bool valued = pure_value_kind(was) != 0xFF && pure_value_kind(now) != 0xFF; + kept[i] = previous >= 0 && (was == now || valued) ? previous : -1; if (kept[i] >= 0) saved[i] = app->saved[kept[i]]; if (module->state_type[i] == PURE_STATE_TEXT) { if (kept[i] >= 0) texts[i] = app->texts[kept[i]]; @@ -3722,8 +3881,11 @@ PURE_COLD static PureResult open_module(PureApp *app, const PureModule *module) result = pure_save_open_list(app, module, i, &lists[i], &list_drafts[i], &listed[i]); rewrite[i] = result == PURE_OK && listed[i].rewrite; } else if (!(lists[i] = new_list(module, module->state_ref[i]))) result = PURE_ALLOCATION; + } else if (kept[i] >= 0 && valued) { + PureCell cell = {.i64 = app->values[kept[i]].i64}; + values[i] = pure_cell_as(pure_value_kind(was), pure_value_kind(now), &cell) ? (PureValue){.i64 = cell.i64} + : module->initial[i]; } else values[i] = kept[i] >= 0 ? app->values[kept[i]] : module->initial[i]; - if (module->state_type[i] == PURE_STATE_BOOL) values[i].num = pure_as_bool(values[i].num, module->initial[i].num); } /* A keyed list keeps its items in key order: one that was not keyed, or * keyed by another field, is put in order and its indices follow their diff --git a/core/module.h b/core/module.h index 10fe6c3..e106fc7 100644 --- a/core/module.h +++ b/core/module.h @@ -40,6 +40,8 @@ typedef enum { PURE_OP_PICK, PURE_OP_SLIDE, PURE_OP_FOLD, PURE_OP_TEXT_COMPARE, PURE_OP_LIST_FIND, PURE_OP_LIST_HAS, PURE_OP_NOW, PURE_OP_FLOOR, PURE_OP_MOD, PURE_OP_SEND, PURE_OP_OUTER_ROW, PURE_OP_EACH, PURE_OP_LOCAL_LOAD, PURE_OP_LOCAL_STORE, + PURE_OP_I64_ADD, PURE_OP_I64_SUB, PURE_OP_I64_MUL, PURE_OP_I64_DIV, PURE_OP_I64_MOD, PURE_OP_I64_NEG, + PURE_OP_I64_COMPARE, PURE_OP_I64_MIN, PURE_OP_I64_MAX, PURE_OP_I64_FROM, PURE_OP_NUM_FROM, PURE_OP_COUNT } PureOpcode; /* A bool is its own type (docs/design/016-bool-and-i64.md), held as a @@ -48,7 +50,15 @@ typedef enum { * and EQ and NE compare two bools as they compare two numbers. CONST with * argument 1 pushes a bool (its value 0 or 1); LOAD and STORE work on a * number or a bool state as its type says; NUM_TEXT with argument 1 makes - * a bool text (true, false). */ + * a bool text (true, false). + * An i64 is its own type too, an int64_t in a value's 8 bytes: CONST with + * argument 2 pushes one (its constant's 8 bytes), NUM_TEXT with argument 2 + * makes its decimal text, the I64_ opcodes compute on them (ADD, SUB, MUL + * and NEG fail as OVERFLOW past its range; DIV and MOD floor, and fail as + * DIVIDE by zero; COMPARE takes a relation as TEXT_COMPARE does and gives a + * bool), I64_FROM makes one of a whole number in range and NUM_FROM a + * number of one within 2^53 (else LIMIT). LOAD, STORE, the item and local + * opcodes, FIELD and SEND move them as they move numbers. */ /* A local: one of an event's PURE_MODULE_LOCALS slots, 0 when the event * begins and never saved. LOCAL_LOAD and LOCAL_STORE name the slot (bits * 0-7), what it holds (bits 8-15: 0 a number, 1 an index, 2 a bool) and an index's @@ -61,13 +71,16 @@ enum { PURE_MODULE_LOCALS = 64 }; * when it began, in order, with ROW the item; the block may change states, * the list's items and other lists, but not insert into the list it walks * (an item it removes, or one before it, moves the walk with it). */ -/* FOLD's argument: the kind (bits 0-7), the list state (8-15) and the length - * of the block that follows (16-31), run once per item as a column of that - * list. count counts the items whose block leaves true; sum, max, min and - * distinct take a number and whether to include it (a bool) from their - * block (distinct counts the different values, -0 as 0); first gives the +/* FOLD's argument: the kind (bits 0-5, with flags above: an i64's, a + * slice's), the list state (8-15) and the length of the block that follows + * (16-31), run once per item as a column of that list. count counts the + * items whose block leaves true; sum, max, min and distinct take a value + * and whether to include it (a bool) from their block, the value a number + * or, with PURE_FOLD_INTEGER, an i64 (a sum past its range fails as + * OVERFLOW; distinct counts the different values, -0 as 0); first gives the * index of the first item whose block leaves true. */ typedef enum { PURE_FOLD_COUNT, PURE_FOLD_SUM, PURE_FOLD_MAX, PURE_FOLD_MIN, PURE_FOLD_FIRST, PURE_FOLD_DISTINCT } PureFold; +enum { PURE_FOLD_KIND = 0x3F, PURE_FOLD_INTEGER = 0x40 }; /* A slice: a fold whose kind has PURE_FOLD_SLICED, or a walk whose argument * has PURE_EACH_SLICED, first takes two numbers, a low and a high key, and * runs for the items of its list (keyed by a number) whose keys are from @@ -89,8 +102,8 @@ typedef enum { PURE_KIND_KEY = 1, PURE_KIND_MESSAGE = 2, PURE_KIND_TEXT = 3, PUR enum { PURE_TIMER_LONGEST = 86400000, PURE_SOURCE = 64 }; /* The largest number a delivery may have: it is kept as a binary64. */ #define PURE_SEQ_MOST (UINT64_C(1) << 53) -/* A message field: a u32le, a binary64 or a bool's one byte (fixed, at its - * offset in the payload's prefix), or bytes, text or rows of any length. The +/* A message field: a u32le, a binary64, a bool's one byte or an i64's eight + * (fixed, at its offset in the payload's prefix), or bytes, text or rows of any length. The * prefix also holds each variable field's u32le length but the last one's * (its offset is PURE_FIELD_REST), which takes what follows the others; the * variable fields' bytes follow the prefix in declaration order. A text is @@ -99,22 +112,27 @@ enum { PURE_TIMER_LONGEST = 86400000, PURE_SOURCE = 64 }; * list's stored form: a message fills that list with them, and fails when * they are not whole. */ typedef enum { PURE_FIELD_U32 = 1, PURE_FIELD_BYTES = 2, PURE_FIELD_NUMBER = 3, PURE_FIELD_TEXT = 4, - PURE_FIELD_ROWS = 5, PURE_FIELD_BOOL = 6 } PureFieldType; + PURE_FIELD_ROWS = 5, PURE_FIELD_BOOL = 6, PURE_FIELD_I64 = 7 } PureFieldType; enum { PURE_FIELD_REST = UINT32_MAX }; -/* A field held in its payload's prefix (a u32, a number, a bool), and the bytes it takes there. */ +/* A field held in its payload's prefix (a u32, a number, a bool, an i64), and the bytes it takes there. */ static inline bool pure_field_fixed(uint8_t type) { - return type == PURE_FIELD_U32 || type == PURE_FIELD_NUMBER || type == PURE_FIELD_BOOL; + return type == PURE_FIELD_U32 || type == PURE_FIELD_NUMBER || type == PURE_FIELD_BOOL || type == PURE_FIELD_I64; } static inline uint32_t pure_field_size(uint8_t type) { - return type == PURE_FIELD_NUMBER ? 8 : type == PURE_FIELD_BOOL ? 1 : 4; + return type == PURE_FIELD_NUMBER || type == PURE_FIELD_I64 ? 8 : type == PURE_FIELD_BOOL ? 1 : 4; } -/* A number as a bool: 0 (of either sign) is false, 1 true, any other the - * default given. A state or field that becomes a bool keeps its value so. */ -static inline double pure_as_bool(double value, double otherwise) { return value == 0 ? 0 : value == 1 ? 1 : otherwise; } typedef enum { PURE_STATE_NUM = 0, PURE_STATE_TEXT = 1, PURE_STATE_CURSOR = 2, PURE_STATE_LIST = 3, PURE_STATE_INDEX = 4, - PURE_STATE_BOOL = 5 + PURE_STATE_BOOL = 5, PURE_STATE_I64 = 6 } PureStateType; +/* A number's, a bool's or an i64's state as a list cell's kind (PURE_CELL_..., + * for pure_cell_as), or 0xFF for any other state. */ +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 } PureRecordType; /* 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 @@ -127,19 +145,23 @@ typedef union { double num; int64_t i64; } PureValue; * every event ends, and it is never saved nor logged for undoing. */ enum { PURE_CURSOR_STAYS = 1, PURE_LIST_ROWS = 256 }; /* A table view has one row per item of a list, or per item its filter - * keeps; its columns are number, bool or text views evaluated for one row at - * a time. */ -typedef enum { PURE_VIEW_NUM = 0, PURE_VIEW_TEXT = 1, PURE_VIEW_TABLE = 2, PURE_VIEW_BOOL = 3 } PureViewType; + * keeps; its columns are number, bool, i64 or text views evaluated for one + * row at a time. */ +typedef enum { PURE_VIEW_NUM = 0, PURE_VIEW_TEXT = 1, PURE_VIEW_TABLE = 2, PURE_VIEW_BOOL = 3, PURE_VIEW_I64 = 4 } PureViewType; typedef enum { PURE_UNIT_CHAR = 0, PURE_UNIT_LINE = 1 } PureUnit; typedef enum { PURE_EDGE_DOWN, PURE_EDGE_UP } PureEdge; /* EXISTS: an item with that key is already in the keyed list (the event * reports PURE_CONFLICT). MEMORY: the event would leave the states holding - * more than the app's budget (pure_app_memory; it reports PURE_BUDGET). */ + * more than the app's budget (pure_app_memory; it reports PURE_BUDGET). + * OVERFLOW: an i64 past its range. */ typedef enum { PURE_FAULT_NONE, PURE_FAULT_DIVIDE, PURE_FAULT_NON_FINITE, PURE_FAULT_CLAMP, PURE_FAULT_LIMIT, PURE_FAULT_NOT_FOUND, PURE_FAULT_ENCODING, PURE_FAULT_EXISTS, - PURE_FAULT_MEMORY + PURE_FAULT_MEMORY, PURE_FAULT_OVERFLOW, PURE_FAULT_COUNT } PureFault; +/* A fault's name as hosts report it: none, divide, non_finite, clamp, limit, + * not_found, encoding, exists, memory, overflow. */ +const char *pure_fault_name(PureFault); /* plan, shape and range are the runtime's, set at load: a fold whose block * has a form for every instruction that works on a leaf of its list at once @@ -221,9 +243,8 @@ typedef struct { char record_names[PURE_MODULE_RECORDS][PURE_MODULE_NAME]; PureRecord record[PURE_MODULE_RECORDS]; char record_field_names[PURE_MODULE_RECORD_FIELDS][PURE_MODULE_NAME]; - uint8_t record_field_text[PURE_MODULE_RECORD_FIELDS]; - uint8_t record_field_bool[PURE_MODULE_RECORD_FIELDS]; /* a bool: a number field (0 or 1) as lists go */ - PureValue record_field_default[PURE_MODULE_RECORD_FIELDS]; /* a number (a bool's 0 or 1), or a text constant's index */ + uint8_t record_field_type[PURE_MODULE_RECORD_FIELDS]; /* PureRecordType */ + PureValue record_field_default[PURE_MODULE_RECORD_FIELDS]; /* a number (a bool's 0 or 1), an i64, or a text constant's index */ } PureModule; /* Decode and verify into out, which must be zeroed (a static one is) or hold @@ -448,8 +469,11 @@ PureResult pure_app_message_fields(PureApp *, uint32_t input, const char *key, i PureResult pure_app_reply(const PureApp *, bool (*visit)(void *, PureSpan), void *context); void pure_app_finish(PureApp *); /* A number view, or a bool view (0 or 1: its type says which); false for a - * text view or a failed evaluation. */ + * text or i64 view or a failed evaluation. */ bool pure_app_view(PureApp *, uint32_t view, double *out); +/* An i64 view, and an i64 column's cell; false for any other or a failed evaluation. */ +bool pure_app_view_i64(PureApp *, uint32_t view, int64_t *out); +bool pure_app_cell_i64(PureApp *, uint32_t column, size_t row, int64_t *out); /* A text view, lent in order until the next view evaluation. */ PureResult pure_app_view_text(PureApp *, uint32_t view, bool (*visit)(void *, PureSpan), void *context); /* The rows of a table view (its list's items, or those its filter keeps), diff --git a/core/test-module.c b/core/test-module.c index 131d9d9..4fe5182 100644 --- a/core/test-module.c +++ b/core/test-module.c @@ -6,6 +6,7 @@ #include "store.h" #include #include +#include #ifdef __GLIBC__ #include #endif @@ -36,18 +37,21 @@ typedef struct { const char *name, *key; int kind; bool keyed; const F *fields; #define KEY(name, key) {name, key, 0, false, NULL, 0} /* A timer's period (milliseconds) rides in nfields. */ #define TIMER(name, period) {name, NULL, PURE_KIND_TIMER, false, NULL, period} -/* A number view, or a bool view when its length carries BOOL_VIEW. */ +/* A number view, or a bool (i64) view when its length carries BOOL_VIEW (I64_VIEW). */ typedef struct { const char *name; uint32_t start, length; } V; #define BOOL_VIEW (1u << 31) +#define I64_VIEW (1u << 30) typedef struct { uint32_t event, start, length; } H; typedef struct { uint8_t op; uint32_t arg; double value; } O; /* Text (type 1, ref = constant), cursor (type 2, ref = text state), list - * (type 3, ref = record), index (type 4, ref = list state) and bool (type 5, - * ref = its initial value) states follow the number states; text views + * (type 3, ref = record), index (type 4, ref = list state), bool (type 5, + * ref = its initial value) and i64 (type 6, ref = its initial value, a + * 32-bit two's complement) states follow the number states; text views * follow the number views. */ typedef struct { const char *name; uint32_t type, ref; } T; /* type: the state type, its flags from bit 8 */ /* A record field: a number and its default, text (text = 1) whose default - * is the constant numbered initial, or a bool (text = 2) and its default. */ + * is the constant numbered initial, a bool (text = 2) or an i64 (text = 3) + * and its default. */ 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 @@ -125,6 +129,12 @@ static size_t build(unsigned char *out, Spec spec) { p = put32(p, offset[n++]); p = put32(p, spec.t[i].type & 0xFF); if ((spec.t[i].type & 0xFF) == PURE_STATE_BOOL) { p = put_double(p, spec.t[i].ref); continue; } + if ((spec.t[i].type & 0xFF) == PURE_STATE_I64) { + int64_t initial = (int32_t)spec.t[i].ref; + p = put32(p, (uint32_t)(uint64_t)initial); + p = put32(p, (uint32_t)((uint64_t)initial >> 32)); + continue; + } p = put32(p, spec.t[i].ref); p = put32(p, spec.t[i].type >> 8); } @@ -165,13 +175,18 @@ static size_t build(unsigned char *out, Spec spec) { 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); } + else if (field->text == 3) { + int64_t initial = (int64_t)field->initial; + p = put32(p, (uint32_t)(uint64_t)initial); + p = put32(p, (uint32_t)((uint64_t)initial >> 32)); + } else p = put_double(p, field->initial); } for (size_t i = 0; i < spec.nv; ++i) { p = put32(p, offset[n++]); - p = put32(p, spec.v[i].length & BOOL_VIEW ? PURE_VIEW_BOOL : 0); + p = put32(p, spec.v[i].length & BOOL_VIEW ? PURE_VIEW_BOOL : spec.v[i].length & I64_VIEW ? PURE_VIEW_I64 : 0); p = put32(p, spec.v[i].start); - p = put32(p, spec.v[i].length & ~BOOL_VIEW); + p = put32(p, spec.v[i].length & ~(BOOL_VIEW | I64_VIEW)); } for (size_t i = 0; i < spec.ntv; ++i) { p = put32(p, offset[n++]); p = put32(p, 1); p = put32(p, spec.tv[i].start); p = put32(p, spec.tv[i].length); } for (size_t i = 0; i < spec.nw; ++i) { @@ -649,7 +664,7 @@ static void messages(void) { static const H note_handler[] = {{1, 0, 2}}; bad = (Spec){states, 1, unkeyed, 1, views, 0, note_handler, 1, doc_read, 2, collections, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}; CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "document operation outside")); - static const F unknown_type[] = {{"insert", 7}, {"start", 1}}; + static const F unknown_type[] = {{"insert", 8}, {"start", 1}}; static const I misplaced[] = {{"edit", NULL, PURE_KIND_MESSAGE, true, unknown_type, 2}}; bad = (Spec){states, 1, misplaced, 1, views, 0, NULL, 0, NULL, 0, collections, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}; CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "message field")); @@ -1473,7 +1488,7 @@ static void list_mutations(void) { for (size_t at = 0; at < pure_list_count(app.lists[s]); ++at) for (unsigned f = 0; f < record->count; ++f) { PureCell cell = pure_list_get(app.lists[s], at, f); - if (module_b.record_field_text[record->first + f]) + if ((module_b.record_field_type[record->first + f] == PURE_RECORD_TEXT)) CHECK(pure_utf8_valid((PureSpan){cell.text, cell.size})); else CHECK(isfinite(cell.num)); } @@ -3567,7 +3582,7 @@ static void keyed_mutations(void) { } if (module_b.state_type[s] != PURE_STATE_LIST || !module_b.state_key[s]) continue; unsigned key = module_b.state_key[s] - 1u; - bool text = module_b.record_field_text[module_b.record[module_b.state_ref[s]].first + key]; + bool text = module_b.record_field_type[module_b.record[module_b.state_ref[s]].first + key] == PURE_RECORD_TEXT; for (size_t at = 1; at < pure_list_count(app.lists[s]); ++at) { PureCell a = pure_list_get(app.lists[s], at - 1, key), b = pure_list_get(app.lists[s], at, key); if (!text) { CHECK(a.num < b.num); continue; } @@ -3611,7 +3626,7 @@ static void describe(PureApp *app, Shown *s) { for (size_t at = 0; at < pure_list_count(app->lists[i]); ++at) for (unsigned f = 0; f < r->count; ++f) { PureCell cell = pure_list_get(app->lists[i], at, f); - if (m->record_field_text[r->first + f]) show_span(s, (PureSpan){cell.text, cell.size}); + if (m->record_field_type[r->first + f] == PURE_RECORD_TEXT) show_span(s, (PureSpan){cell.text, cell.size}); else show(s, "%g", cell.num); show(s, f + 1 < r->count ? "," : ";", 0); } @@ -4656,7 +4671,7 @@ static void bools(void) { static const O two[] = {{C, 1, 2}, {ST, B_ON, 0}}; static const O minus_zero[] = {{C, 1, -0.0}, {ST, B_ON, 0}}; static const O half[] = {{C, 1, 0.5}, {ST, B_ON, 0}}; - static const O kind_two[] = {{C, 2, 1}, {ST, B_ON, 0}}; + static const O kind_three[] = {{C, 3, 1}, {ST, B_ON, 0}}; static const struct { const O *code; size_t count; uint32_t event; const char *reason; } refused[] = { {add_bools, LEN(add_bools), 1, "operand type"}, {branch_number, LEN(branch_number), 1, "operand type"}, @@ -4676,7 +4691,7 @@ static void bools(void) { {two, LEN(two), 1, "non-canonical operand"}, {minus_zero, LEN(minus_zero), 1, "non-canonical operand"}, {half, LEN(half), 1, "non-canonical operand"}, - {kind_two, LEN(kind_two), 1, "operand names no such"}, + {kind_three, LEN(kind_three), 1, "operand names no such"}, }; for (size_t i = 0; i < LEN(refused); ++i) { H handler = {refused[i].event, 0, (uint32_t)refused[i].count}; @@ -4690,7 +4705,7 @@ static void bools(void) { } static const O one_number[] = {{C, 0, 1}}, one_bool[] = {{C, 1, 1}}; static const O text_of_number[] = {{C, 0, 1}, {NT, 1, 0}}, text_of_bool[] = {{C, 1, 1}, {NT, 0, 0}}, - text_kind[] = {{C, 1, 1}, {NT, 2, 0}}; + text_kind[] = {{C, 1, 1}, {NT, 3, 0}}; V as_bool = {"v", 0, 1 | BOOL_VIEW}, as_number = {"v", 0, 1}, as_text = {"v", 0, 2}; Spec bad = spec; bad.h = NULL, bad.nh = 0, bad.tv = NULL, bad.ntv = 0, bad.no = 1, bad.nv = 1; @@ -4706,20 +4721,20 @@ static void bools(void) { bad.o = text_kind; CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "operand names no such")); /* A bool's initial value and default are 0 or 1; types past bool are none. */ - static const T two_initial[] = {{"on", 5, 2}, {"off", 5, 0}}, sixth[] = {{"on", 6, 0}, {"off", 5, 0}}; + static const T two_initial[] = {{"on", 5, 2}, {"off", 5, 0}}, seventh[] = {{"on", 7, 0}, {"off", 5, 0}}; static const RF two_default[] = {{"done", 2, 2}}; static const R two_record[] = {{"r", two_default, 1}}; static const T with_list[] = {{"on", 5, 1}, {"off", 5, 0}, {"rs", 3, 0}}; - static const W fourth_view[] = {{"x", 4, 0, 25, 1}}; + static const W fifth_view[] = {{"x", 5, 0, 25, 1}}; bad = spec; bad.t = two_initial; CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "bool initial value")); - bad.t = sixth; + bad.t = seventh; CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "state type")); bad.t = with_list, bad.nt = 3, bad.r = two_record, bad.nr = 1; CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "record field default")); bad = spec; - bad.w = fourth_view, bad.nw = 1; + bad.w = fifth_view, bad.nw = 1; CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "view type")); /* Stored: n and the items' done, numbers, become bools (1 kept, 2 and 3 @@ -4749,6 +4764,289 @@ static void bools(void) { printf("{\"bools\":{\"refused\":%zu}}\n", LEN(refused) + 9); } +/* ---- i64s (docs/design/016-bool-and-i64.md) ------------------------------ + * An i64 is its own type, exact over its range: arithmetic past it fails + * as overflow, division and remainder floor, conversions to and from + * numbers happen only where the value fits. Keyed lists order i64 keys as + * integers; folds sum, keep the most or least, count the different ones; + * stores, logs, messages, sends and replacements keep every bit. */ +enum { IADD = PURE_OP_I64_ADD, ISUB = PURE_OP_I64_SUB, IMUL = PURE_OP_I64_MUL, IDIV = PURE_OP_I64_DIV, + IMOD = PURE_OP_I64_MOD, INEG = PURE_OP_I64_NEG, ICMP = PURE_OP_I64_COMPARE, IMIN = PURE_OP_I64_MIN, + IMAX = PURE_OP_I64_MAX, IFROM = PURE_OP_I64_FROM, NFROM = PURE_OP_NUM_FROM }; +static const int64_t STEP = INT64_MAX / 7; /* seven of them make the largest i64 */ + +/* An i64 constant's 8 bytes where an instruction keeps its value. */ +static double integer_bits(int64_t value) { + double bits; + memcpy(&bits, &value, sizeof(bits)); + return bits; +} +#define IC(v) ((O){C, 2, integer_bits(v)}) + +/* Code run as a key's handler over the states n (a number), i (an i64) and + * b (a bool): the fault it ends with, and the states after. */ +typedef struct { PureFault fault; PureValue values[3]; } Ran; +static Ran run_code(const O *code, uint32_t count) { + static const S nums[] = {{"n", 0}}; + static const T others[] = {{"i", PURE_STATE_I64, 0}, {"b", PURE_STATE_BOOL, 0}}; + static const I inputs[] = {KEY("go", "KeyG")}; + H handler = {1, 0, count}; + Spec spec = {nums, 1, inputs, 1, NULL, 0, &handler, 1, code, count, NULL, 0, others, 2, NULL, 0, NULL, 0, NULL, 0, NULL, 0}; + if (load(spec, &module_b) != PURE_OK) { fprintf(stderr, "%s\n", error); abort(); } + PureApp app; + CHECK(pure_app_start(&app, &module_b, (PureData){0}) == PURE_OK); + Ran ran = {pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK ? PURE_FAULT_NONE : app.fault, {{0}}}; + memcpy(ran.values, app.values, sizeof(ran.values)); + pure_app_stop(&app); + return ran; +} + +/* The keyed list app: items keyed by an i64 id, next the id to add. */ +enum { KI_N = 0, KI_NEXT = 1, KI_ITEMS = 2, KI_FOUND = 3 }; +static const S keyed_i64_nums[] = {{"n", 0}}; +static const T keyed_i64_states[] = {{"next", PURE_STATE_I64, 0}, {"items", 3 | 1 << 8, 0}, {"found", PURE_STATE_BOOL, 0}}; +static const RF item_i64_fields[] = {{"id", 3, 0}, {"qty", 0, 0}}; +static const R item_i64_records[] = {{"Item", item_i64_fields, 2}}; +static const F put_i64_fields[] = {{"id", PURE_FIELD_I64}, {"qty", PURE_FIELD_NUMBER}}; +static const F echo_i64_fields[] = {{"id", PURE_FIELD_I64}}; +static const I keyed_i64_inputs[] = {KEY("add", "KeyA"), KEY("low", "KeyL"), KEY("drop", "KeyD"), KEY("ask", "KeyK"), + {"put", NULL, PURE_KIND_MESSAGE, false, put_i64_fields, 2}, + {"echo", NULL, PURE_KIND_OUTPUT, false, echo_i64_fields, 1}}; + +static Spec keyed_i64_spec(O *code) { + O made[] = { + /* 0 add: items.add(Item { id: next, qty: n }); next = next + STEP; n = n + 1 */ + {L, KI_NEXT, 0}, {L, KI_N, 0}, {LA, KI_ITEMS, 0}, {L, KI_NEXT, 0}, IC(STEP), {IADD, 0, 0}, {ST, KI_NEXT, 0}, + {L, KI_N, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, KI_N, 0}, + /* 11 low: items.add(Item { id: -next, qty: -1 }) */ + {L, KI_NEXT, 0}, {INEG, 0, 0}, {C, 0, -1}, {LA, KI_ITEMS, 0}, + /* 15 drop: items.remove(items.first) */ + {XF, KI_ITEMS, 0}, {LR, KI_ITEMS, 0}, + /* 17 ask: found = items.has(next - STEP) */ + {L, KI_NEXT, 0}, IC(STEP), {ISUB, 0, 0}, {HAS, KI_ITEMS, 0}, {ST, KI_FOUND, 0}, + /* 22 view total = sum(i.id for i in items), 26 most, 30 least, 34 kinds (distinct) */ + {FOLD, FOLDING(PURE_FOLD_SUM | PURE_FOLD_INTEGER, KI_ITEMS, 3), 0}, {ROW, KI_ITEMS, 0}, {IG, ITEM(KI_ITEMS, 0), 0}, {C, 1, 1}, + {FOLD, FOLDING(PURE_FOLD_MAX | PURE_FOLD_INTEGER, KI_ITEMS, 3), 0}, {ROW, KI_ITEMS, 0}, {IG, ITEM(KI_ITEMS, 0), 0}, {C, 1, 1}, + {FOLD, FOLDING(PURE_FOLD_MIN | PURE_FOLD_INTEGER, KI_ITEMS, 3), 0}, {ROW, KI_ITEMS, 0}, {IG, ITEM(KI_ITEMS, 0), 0}, {C, 1, 1}, + {FOLD, FOLDING(PURE_FOLD_DISTINCT | PURE_FOLD_INTEGER, KI_ITEMS, 3), 0}, {ROW, KI_ITEMS, 0}, {IG, ITEM(KI_ITEMS, 0), 0}, + {C, 1, 1}, + /* 38 view first_id = items.first.id, 40 view said = "{next}" */ + {XF, KI_ITEMS, 0}, {IG, ITEM(KI_ITEMS, 0), 0}, {L, KI_NEXT, 0}, {NT, 2, 0}, + /* 42 put: items.add(Item { id: put.id, qty: put.qty }); send echo { id: put.id } */ + {FIELD, 0, 0}, {FIELD, 1, 0}, {LA, KI_ITEMS, 0}, {FIELD, 0, 0}, {SEND, 5, 0}, + }; + memcpy(code, made, sizeof(made)); + static const H handlers[] = {{1, 0, 11}, {3, 11, 4}, {5, 15, 2}, {7, 17, 5}, {9, 42, 5}}; + static const V views[] = {{"total", 22, 4 | I64_VIEW}, {"most", 26, 4 | I64_VIEW}, {"least", 30, 4 | I64_VIEW}, + {"kinds", 34, 4}, {"first_id", 38, 2 | I64_VIEW}}; + static const V texts[] = {{"said", 40, 2}}; + return (Spec){keyed_i64_nums, 1, keyed_i64_inputs, LEN(keyed_i64_inputs), views, LEN(views), handlers, LEN(handlers), + code, LEN(made), NULL, 0, keyed_i64_states, LEN(keyed_i64_states), NULL, 0, texts, 1, item_i64_records, 1, + NULL, 0}; +} + +static int64_t i64_view(PureApp *app, uint32_t view) { + int64_t value = 0; + CHECK(pure_app_view_i64(app, view, &value)); + return value; +} + +static void integers(void) { + /* Arithmetic at the ends of the range, floored division and remainders. */ + static const struct { uint8_t op; int64_t a, b; PureFault fault; int64_t result; } cases[] = { + {IADD, INT64_MAX - 1, 1, PURE_FAULT_NONE, INT64_MAX}, {IADD, INT64_MAX, 1, PURE_FAULT_OVERFLOW, 0}, + {IADD, INT64_MIN, -1, PURE_FAULT_OVERFLOW, 0}, {ISUB, INT64_MIN + 1, 1, PURE_FAULT_NONE, INT64_MIN}, + {ISUB, INT64_MIN, 1, PURE_FAULT_OVERFLOW, 0}, {ISUB, 0, INT64_MIN, PURE_FAULT_OVERFLOW, 0}, + {IMUL, 3037000499, 3037000499, PURE_FAULT_NONE, INT64_C(9223372030926249001)}, + {IMUL, INT64_MAX, 2, PURE_FAULT_OVERFLOW, 0}, {IMUL, INT64_MIN, -1, PURE_FAULT_OVERFLOW, 0}, + {IMUL, INT64_MIN, 1, PURE_FAULT_NONE, INT64_MIN}, + {IDIV, 7, 2, PURE_FAULT_NONE, 3}, {IDIV, -7, 2, PURE_FAULT_NONE, -4}, {IDIV, 7, -2, PURE_FAULT_NONE, -4}, + {IDIV, -7, -2, PURE_FAULT_NONE, 3}, {IDIV, INT64_MIN, -1, PURE_FAULT_OVERFLOW, 0}, + {IDIV, INT64_MIN, 1, PURE_FAULT_NONE, INT64_MIN}, {IDIV, INT64_MAX, -1, PURE_FAULT_NONE, -INT64_MAX}, + {IDIV, 5, 0, PURE_FAULT_DIVIDE, 0}, + {IMOD, 7, 2, PURE_FAULT_NONE, 1}, {IMOD, -7, 2, PURE_FAULT_NONE, 1}, {IMOD, 7, -2, PURE_FAULT_NONE, -1}, + {IMOD, -7, -2, PURE_FAULT_NONE, -1}, {IMOD, INT64_MIN, -1, PURE_FAULT_NONE, 0}, {IMOD, 6, -3, PURE_FAULT_NONE, 0}, + {IMOD, INT64_MIN, INT64_MAX, PURE_FAULT_NONE, INT64_MAX - 1}, {IMOD, 5, 0, PURE_FAULT_DIVIDE, 0}, + {IMIN, -1, INT64_MIN, PURE_FAULT_NONE, INT64_MIN}, {IMAX, -1, INT64_MAX, PURE_FAULT_NONE, INT64_MAX}, + }; + for (size_t k = 0; k < LEN(cases); ++k) { + O code[] = {IC(cases[k].a), IC(cases[k].b), {cases[k].op, 0, 0}, {ST, 1, 0}}; + Ran ran = run_code(code, LEN(code)); + if (ran.fault != cases[k].fault || ran.values[1].i64 != (cases[k].fault ? 0 : cases[k].result)) { + fprintf(stderr, "i64 case %zu: fault %d, %" PRId64 "\n", k, (int)ran.fault, ran.values[1].i64); + abort(); + } + } + O neg_most[] = {IC(INT64_MIN), {INEG, 0, 0}, {ST, 1, 0}}, neg_max[] = {IC(INT64_MAX), {INEG, 0, 0}, {ST, 1, 0}}; + CHECK(run_code(neg_most, 3).fault == PURE_FAULT_OVERFLOW); + CHECK(run_code(neg_max, 3).values[1].i64 == -INT64_MAX); + /* Comparisons give bools, by each relation. */ + static const struct { int64_t a, b; uint8_t truths; } pairs[] = {{-1, 0, 0x23}, {INT64_MAX, INT64_MIN, 0x2C}, {5, 5, 0x1A}}; + for (size_t k = 0; k < LEN(pairs); ++k) + for (uint32_t relation = 0; relation <= PURE_NOT_EQUAL; ++relation) { + O code[] = {IC(pairs[k].a), IC(pairs[k].b), {ICMP, relation, 0}, {ST, 2, 0}}; + CHECK(run_code(code, LEN(code)).values[2].num == (pairs[k].truths >> relation & 1)); + } + /* Conversions only where the value fits. */ + static const struct { double from; PureFault fault; int64_t to; } wholes[] = { + {9007199254740994.0, PURE_FAULT_NONE, INT64_C(9007199254740994)}, {-0.0, PURE_FAULT_NONE, 0}, + {-9223372036854775808.0, PURE_FAULT_NONE, INT64_MIN}, {9223372036854775808.0, PURE_FAULT_LIMIT, 0}, + {0.5, PURE_FAULT_LIMIT, 0}, {-1e300, PURE_FAULT_LIMIT, 0}}; + for (size_t k = 0; k < LEN(wholes); ++k) { + O code[] = {{C, 0, wholes[k].from}, {IFROM, 0, 0}, {ST, 1, 0}}; + Ran ran = run_code(code, LEN(code)); + CHECK(ran.fault == wholes[k].fault && ran.values[1].i64 == wholes[k].to); + } + static const struct { int64_t from; PureFault fault; double to; } numbers_[] = { + {INT64_C(9007199254740992), PURE_FAULT_NONE, 9007199254740992.0}, {-INT64_C(9007199254740992), PURE_FAULT_NONE, -9007199254740992.0}, + {INT64_C(9007199254740993), PURE_FAULT_LIMIT, 0}, {INT64_MIN, PURE_FAULT_LIMIT, 0}}; + for (size_t k = 0; k < LEN(numbers_); ++k) { + O code[] = {IC(numbers_[k].from), {NFROM, 0, 0}, {ST, 0, 0}}; + Ran ran = run_code(code, LEN(code)); + CHECK(ran.fault == numbers_[k].fault && ran.values[0].num == numbers_[k].to); + } + + /* The keyed list app: ids ordered as integers, a sum that overflows, every digit shown. */ + O code[64]; + Spec spec = keyed_i64_spec(code); + if (load(spec, &module_a) != PURE_OK) { fprintf(stderr, "%s\n", error); abort(); } + CHECK(module_a.state_type[KI_NEXT] == PURE_STATE_I64 && module_a.view_type[0] == PURE_VIEW_I64 && + module_a.input[4].prefix == 16 && module_a.input[5].prefix == 8); + PureApp app; + CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK); + int64_t value; + CHECK(i64_view(&app, 0) == 0 && !pure_app_view_i64(&app, 1, &value) && !pure_app_view_i64(&app, 4, &value)); + double number; + CHECK(!pure_app_view(&app, 0, &number) && pure_app_view(&app, 3, &number) && number == 0); + for (int k = 0; k < 3; ++k) CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK); + CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK); /* -3 steps */ + CHECK(pure_list_count(app.lists[KI_ITEMS]) == 4 && pure_list_get(app.lists[KI_ITEMS], 0, 0).i64 == -3 * STEP && + pure_list_get(app.lists[KI_ITEMS], 1, 0).i64 == 0 && pure_list_get(app.lists[KI_ITEMS], 3, 0).i64 == 2 * STEP); + CHECK(i64_view(&app, 0) == 0 && i64_view(&app, 1) == 2 * STEP && i64_view(&app, 2) == -3 * STEP && + pure_app_view(&app, 3, &number) && number == 4 && i64_view(&app, 4) == -3 * STEP); + CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_CONFLICT && app.fault == PURE_FAULT_EXISTS); /* -3 steps again */ + for (int k = 0; k < 4; ++k) CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK); + CHECK(app.values[KI_NEXT].i64 == INT64_MAX && !pure_app_view_i64(&app, 0, &value)); /* the sum overflows */ + CHECK(!strcmp(view_text(&app, 5), "9223372036854775807")); + CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_INVALID && app.fault == PURE_FAULT_OVERFLOW); + CHECK(pure_list_count(app.lists[KI_ITEMS]) == 8 && app.values[KI_NEXT].i64 == INT64_MAX && app.values[KI_N].num == 7); + CHECK(pure_app_input(&app, 3, PURE_EDGE_DOWN) == PURE_OK && app.values[KI_FOUND].num == 1); + CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_OK && i64_view(&app, 4) == 0); + /* A message's i64 is 8 bytes, a send's too. */ + unsigned char payload[16]; + int64_t least = INT64_MIN; + double qty = 2.5; + memcpy(payload, &least, 8); + memcpy(payload + 8, &qty, 8); + CHECK(pure_app_message(&app, 4, NULL, -1, (PureSpan){payload, 16}) == PURE_OK && i64_view(&app, 2) == INT64_MIN); + pure_app_finish(&app); + Outbox o = outbox_of(&app); + CHECK(o.count == 1 && o.items[0].size == 8 && !memcmp(o.items[0].payload, &least, 8)); + pure_app_stop(&app); + + /* Recast by name: a u32 widens to an i64; to and from numbers only where the value fits. */ + PureField wide[2] = {{"id", PURE_FIELD_I64, 0, 0}, {"n", PURE_FIELD_NUMBER, 0, 0}}; + PureField narrow[2] = {{"id", PURE_FIELD_NUMBER, 0, 0}, {"n", PURE_FIELD_I64, 0, 0}}; + PureField small[1] = {{"id", PURE_FIELD_U32, 0, 0}}; + CHECK(pure_fields_lay_out(wide, 2) == 16 && pure_fields_lay_out(narrow, 2) == 16 && pure_fields_lay_out(small, 1) == 4); + unsigned char given[16], *made; + size_t size; + int64_t id = INT64_C(9007199254740993); + double n = 2.5; + memcpy(given, &id, 8); + memcpy(given + 8, &n, 8); + CHECK(pure_send_recast(wide, 2, narrow, 2, (PureSpan){given, 16}, &made, &size) == PURE_OK && size == 16); + double read_id; + int64_t read_n; + memcpy(&read_id, made, 8); + memcpy(&read_n, made + 8, 8); + CHECK(read_id == 0 && read_n == 0); /* 2^53 + 1 is no number, 2.5 no i64 */ + free(made); + uint32_t u = UINT32_MAX; + CHECK(pure_send_recast(small, 1, wide, 2, (PureSpan){(unsigned char *)&u, 4}, &made, &size) == PURE_OK && size == 16); + memcpy(&id, made, 8); + CHECK(id == UINT32_MAX); + free(made); + static const unsigned char u32_id[] = {1, PURE_FIELD_U32, 2, 'i', 'd'}, number_id[] = {1, PURE_FIELD_NUMBER, 2, 'i', 'd'}; + CHECK(pure_module_takes(&module_a, 4, u32_id, sizeof(u32_id)) && !pure_module_takes(&module_a, 4, number_id, sizeof(number_id))); + + /* What mixes i64s and numbers is refused. */ + const struct { O code[4]; uint32_t count; const char *reason; } refused[] = { + {{{C, 0, 1}, {C, 0, 2}, {IADD, 0, 0}, {ST, 1, 0}}, 4, "operand type"}, + {{IC(1), IC(2), {ADD, 0, 0}, {ST, 1, 0}}, 4, "operand type"}, + {{IC(1), {ST, 0, 0}}, 2, "operand type"}, + {{{C, 0, 1}, {ST, 1, 0}}, 2, "operand type"}, + {{IC(1), IC(2), {ICMP, PURE_NOT_EQUAL + 1, 0}, {ST, 2, 0}}, 4, "operand names no such"}, + {{IC(1), IC(2), {LT, 0, 0}, {ST, 2, 0}}, 4, "operand type"}, + {{IC(1), {JZ, 2, 0}}, 2, "operand type"}, + {{{C, 0, 1}, {NFROM, 0, 0}, {ST, 0, 0}}, 3, "operand type"}, + {{IC(1), {IFROM, 0, 0}, {ST, 1, 0}}, 3, "operand type"}, + {{{C, 0, 1}, {LOCAL_S, 3u << 8, 0}}, 2, "operand type"}, + {{IC(1), {LOCAL_S, LOCAL(0, -1), 0}}, 2, "operand type"}, + }; + for (size_t k = 0; k < LEN(refused); ++k) { + static const S nums[] = {{"n", 0}}; + static const T others[] = {{"i", PURE_STATE_I64, 0}, {"b", PURE_STATE_BOOL, 0}}; + static const I inputs[] = {KEY("go", "KeyG")}; + H handler = {1, 0, refused[k].count}; + Spec bad = {nums, 1, inputs, 1, NULL, 0, &handler, 1, refused[k].code, refused[k].count, NULL, 0, others, 2, NULL, 0, + NULL, 0, NULL, 0, NULL, 0}; + if (load(bad, &module_b) != PURE_INVALID || !strstr(error, refused[k].reason)) { + fprintf(stderr, "i64 refusal %zu: expected '%s', got '%s'\n", k, refused[k].reason, error); + abort(); + } + } + /* A fold's value is a number or an i64 as its kind says; count and first take no i64. */ + O sum_number[] = {{FOLD, FOLDING(PURE_FOLD_SUM | PURE_FOLD_INTEGER, KI_ITEMS, 3), 0}, {ROW, KI_ITEMS, 0}, + {IG, ITEM(KI_ITEMS, 1), 0}, {C, 1, 1}}; + O sum_i64[] = {{FOLD, FOLDING(PURE_FOLD_SUM, KI_ITEMS, 3), 0}, {ROW, KI_ITEMS, 0}, {IG, ITEM(KI_ITEMS, 0), 0}, {C, 1, 1}}; + O count_i64[] = {{FOLD, FOLDING(PURE_FOLD_COUNT | PURE_FOLD_INTEGER, KI_ITEMS, 1), 0}, {C, 1, 1}}; + V as_i64 = {"v", 0, 4 | I64_VIEW}, as_number = {"v", 0, 4}, as_count = {"v", 0, 2}; + Spec bad = spec; + bad.h = NULL, bad.nh = 0, bad.tv = NULL, bad.ntv = 0, bad.nv = 1; + bad.o = sum_number, bad.no = 4, bad.v = &as_i64; + CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "fold block must leave an i64 and a bool")); + bad.o = sum_i64, bad.v = &as_number; + CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "fold block must leave a number and a bool")); + bad.o = count_i64, bad.no = 2, bad.v = &as_count; + CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "operand names no such")); + + /* Replaced: a number, a bool and an i64 carry over each other where the value fits, else take the default. */ + static const S was_nums[] = {{"x", 5}, {"y", 2.5}}; + static const T was_others[] = {{"w", PURE_STATE_I64, 7}, {"v", PURE_STATE_I64, 0}, {"rows", 3, 0}}; + static const RF was_fields[] = {{"a", 0, 0}}; + static const R was_records[] = {{"Row", was_fields, 1}}; + static const S now_nums[] = {{"w", 3}}; + static const T now_others[] = {{"x", PURE_STATE_I64, (uint32_t)-1}, {"y", PURE_STATE_I64, (uint32_t)-2}, + {"v", PURE_STATE_BOOL, 0}, {"rows", 3, 0}}; + static const RF now_fields[] = {{"a", 3, 9}}; + static const R now_records[] = {{"Row", now_fields, 1}}; + Spec was = {was_nums, 2, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, was_others, 3, NULL, 0, NULL, 0, was_records, 1, NULL, 0}; + Spec now = {now_nums, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, now_others, 4, NULL, 0, NULL, 0, now_records, 1, NULL, 0}; + CHECK(load(was, &module_a) == PURE_OK && load(now, &module_b) == PURE_OK); + CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK && app.values[2].i64 == 7); + app.values[3].i64 = INT64_C(9007199254740993); + static const double rows[] = {3, 2.5, -0.0, 1e300}; + for (size_t k = 0; k < LEN(rows); ++k) CHECK(pure_list_insert(app.lists[4], k, (PureCell[]){{.num = rows[k]}}, NULL) == PURE_OK); + CHECK(pure_app_replace(&app, &module_b) == PURE_OK); + /* now: w (a number), x, y (i64s), v (a bool), rows of i64s */ + CHECK(app.values[0].num == 7 && app.values[1].i64 == 5 && app.values[2].i64 == -2 && app.values[3].num == 0); + static const int64_t migrated[] = {3, 9, 0, 9}; + for (size_t k = 0; k < LEN(migrated); ++k) CHECK(pure_list_get(app.lists[4], k, 0).i64 == migrated[k]); + app.values[3].num = 1; /* v true: an i64 1 when it goes back */ + CHECK(pure_app_replace(&app, &module_a) == PURE_OK); + CHECK(app.values[0].num == 5 && app.values[1].num == -2 && app.values[2].i64 == 7 && app.values[3].i64 == 1); + for (size_t k = 0; k < LEN(migrated); ++k) CHECK(pure_list_get(app.lists[4], k, 0).num == (double)migrated[k]); + pure_app_stop(&app); + + /* Stored: random adds, drops and asks, restarting now and then, as in memory; extremes logged and imaged. */ + static const char *const none[] = {""}; + unsigned long checkpoints = pure_save_test_checkpoints; + for (uint32_t seed = 1; seed <= 3; ++seed) stored_equivalence(keyed_i64_spec(code), none, 1, seed * 0x2545f491u); + printf("{\"integers\":{\"cases\":%zu,\"refused\":%zu,\"checkpoints\":%lu}}\n", LEN(cases), LEN(refused) + 3, + pure_save_test_checkpoints - checkpoints); +} + /* 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 }; @@ -4866,6 +5164,7 @@ int main(void) { send_recast(); output_meaning(); bools(); + integers(); memory_budget(); fold_mutations(); pointing(); -- cgit v1.2.3