diff options
| -rw-r--r-- | cli/pure-run.c | 19 | ||||
| -rw-r--r-- | core/module-list.c | 142 | ||||
| -rw-r--r-- | core/module-list.h | 34 | ||||
| -rw-r--r-- | core/module-save.c | 51 | ||||
| -rw-r--r-- | core/module-save.h | 10 | ||||
| -rw-r--r-- | core/module.c | 418 | ||||
| -rw-r--r-- | core/module.h | 74 | ||||
| -rw-r--r-- | core/test-module.c | 333 | ||||
| -rw-r--r-- | docs/design/008-module-format.md | 25 | ||||
| -rw-r--r-- | serve/deliver.c | 11 | ||||
| -rw-r--r-- | serve/message-json.c | 42 | ||||
| -rw-r--r-- | serve/serve.c | 27 | ||||
| -rw-r--r-- | serve/test-message-json.c | 21 |
13 files changed, 912 insertions, 295 deletions
diff --git a/cli/pure-run.c b/cli/pure-run.c index b9eb222..64eb6ee 100644 --- a/cli/pure-run.c +++ b/cli/pure-run.c @@ -1,6 +1,7 @@ #define _POSIX_C_SOURCE 200809L #include "module-store.h" #include "store.h" +#include <inttypes.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -14,7 +15,6 @@ * Text states and text views print as JSON strings; cursors as byte positions; * lists as arrays of objects; indices as positions; a table view as an array * with an object of its columns per row. A failed view or cell is null. */ -static const char *const fault_names[] = {"none", "divide", "non_finite", "clamp", "limit", "not_found", "encoding", "exists"}; /* time MS sets the time events see (milliseconds since 1970); tick NAME is a timer's tick. */ static bool load(const char *path, PureModule *module) { @@ -53,8 +53,10 @@ static void show_list(const PureModule *m, const PureList *list, const PureRecor for (unsigned f = 0; f < record->count; ++f) { PureCell cell = pure_list_get(list, at, f); printf("%s\"%s\":", f ? "," : "", m->record_field_names[record->first + f]); - if (m->record_field_bool[record->first + f]) printf("%s", cell.num ? "true" : "false"); - else if (!m->record_field_text[record->first + f]) printf("%.17g", cell.num); + uint8_t type = m->record_field_type[record->first + f]; + if (type == PURE_RECORD_BOOL) printf("%s", cell.num ? "true" : "false"); + else if (type == PURE_RECORD_I64) printf("%" PRId64, cell.i64); + else if (type == PURE_RECORD_NUMBER) printf("%.17g", cell.num); else { putchar('"'); json_span(NULL, (PureSpan){cell.text, cell.size}); @@ -66,15 +68,19 @@ static void show_list(const PureModule *m, const PureList *list, const PureRecor putchar(']'); } -/* A number, bool or text view, or a column at a row. */ +/* A number, bool, i64 or text view, or a column at a row. */ static void show_value(PureApp *app, uint32_t view, size_t row) { const PureModule *m = app->module; static Collected text; text.used = 0; text.fits = true; double value; + int64_t integer; bool column = m->view_table[view]; - if (m->view_type[view] == PURE_VIEW_TEXT) { + if (m->view_type[view] == PURE_VIEW_I64) { + if (!(column ? pure_app_cell_i64(app, view, row, &integer) : pure_app_view_i64(app, view, &integer))) printf("null"); + else printf("%" PRId64, integer); + } else if (m->view_type[view] == PURE_VIEW_TEXT) { PureResult result = column ? pure_app_cell_text(app, view, row, collect, &text) : pure_app_view_text(app, view, collect, &text); if (result != PURE_OK || !text.fits) printf("null"); @@ -99,6 +105,7 @@ static void show(PureApp *app, const char *failure) { putchar('"'); } else if (m->state_type[i] == PURE_STATE_LIST) show_list(m, app->lists[i], &m->record[m->state_ref[i]]); else if (m->state_type[i] == PURE_STATE_BOOL) printf("%s", app->values[i].num ? "true" : "false"); + else if (m->state_type[i] == PURE_STATE_I64) printf("%" PRId64, app->values[i].i64); else printf("%.17g", app->values[i].num); } printf("},\"views\":{"); @@ -121,7 +128,7 @@ static void show(PureApp *app, const char *failure) { putchar(']'); } printf("},\"tick\":%llu,\"successfulEvents\":%llu,\"fault\":\"%s\",\"error\":%s%s%s}\n", - (unsigned long long)app->ticks, (unsigned long long)app->events, fault_names[app->fault], + (unsigned long long)app->ticks, (unsigned long long)app->events, pure_fault_name(app->fault), failure ? "\"" : "", failure ? failure : "null", failure ? "\"" : ""); } 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 <inttypes.h> #include <math.h> #include <stdio.h> #include <stdlib.h> @@ -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 <dirent.h> #include <fcntl.h> +#include <inttypes.h> #ifdef __GLIBC__ #include <malloc.h> #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(); diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md index 5b2ff05..02f8900 100644 --- a/docs/design/008-module-format.md +++ b/docs/design/008-module-format.md @@ -19,6 +19,8 @@ v5에는 뒤에 집계(FOLD), 걸러진 표, 마크, 그리고 **키 있는 리� v6은 **bool**을 수와 다른 스택 종류로 더한다([016](016-bool-and-i64.md)). 비교, `has`, 논리가 bool을 내고 분기, `require`, 필터, 집계의 포함 여부가 bool을 받는다. 새 명령은 없다(CONST와 NUM_TEXT의 인자, 새 타입 번호). 비교의 결과 종류가 바뀌므로 v5 모듈은 받지 않는다. 다시 빌드한다. +v6에는 뒤에 **i64**가 더해졌다([016](016-bool-and-i64.md)의 2단계): 새 명령, 새 타입 번호, 0이던 자리의 값(CONST와 NUM_TEXT의 +인자 2, 집계의 PURE_FOLD_INTEGER)이라 판은 그대로다. ## 바이트 배치 @@ -31,14 +33,14 @@ v6은 **bool**을 수와 다른 스택 종류로 더한다([016](016-bool-and-i6 | 16 | u32 명령 수, u32 문자열 바이트 수 | | 24 | u16 필드, u16 컬렉션, u16 상수, u16 레코드 | | 32 | u32 상수 블롭 바이트 수, u16 레코드 필드, u16 0 | -| 40 | 상태 × 16 B: u32 이름, u32 타입(0 수, 1 text, 2 커서, 3 리스트, 4 인덱스, 5 bool), 8 B 값: 수와 bool은 f64 초기값(bool은 0이나 1), text는 u32 초기 상수, 커서는 u32 대상 text 상태와 u32 플래그(1이면 마크: 그 자리에 넣은 글 앞에 남는다), 리스트는 u32 레코드와 u32 키(0이면 없음, 아니면 키 필드 + 1, 256이면 메시지의 행 목록: 이름은 그 필드의 경로 `입력.필드`이고 키가 없다), 인덱스는 u32 대상 리스트 상태 | +| 40 | 상태 × 16 B: u32 이름, u32 타입(0 수, 1 text, 2 커서, 3 리스트, 4 인덱스, 5 bool, 6 i64), 8 B 값: 수와 bool은 f64 초기값(bool은 0이나 1), i64는 초기값(부호 있는 little-endian), text는 u32 초기 상수, 커서는 u32 대상 text 상태와 u32 플래그(1이면 마크: 그 자리에 넣은 글 앞에 남는다), 리스트는 u32 레코드와 u32 키(0이면 없음, 아니면 키 필드 + 1, 256이면 메시지의 행 목록: 이름은 그 필드의 경로 `입력.필드`이고 키가 없다), 인덱스는 u32 대상 리스트 상태 | | | 컬렉션 × 12 B: u32 이름, u32 1(바이트), u32 1(키별) | | | 입력 × 16 B: u32 이름, u8 종류(1 키, 2 메시지, 3 text, 4 타이머, 5 출력: 앱이 보내는 메시지, 모양은 메시지와 같고 핸들러가 없다, 6 인자: 한 보기가 받는 인자, 모양은 메시지와 같고 핸들러가 없으며 이름은 `보기.args`), u8 키별 여부, u16 0, u32 키 이름(키 입력)·주기(타이머, 1 ms–24시간을 ms로)·보기 번호(인자) 또는 0, u16 첫 필드, u16 필드 수 | -| | 필드 × 8 B: u32 이름(식별자, 또는 그룹 안의 필드면 점으로 이은 경로 `user.name`), u32 타입(1 u32le, 2 바이트, 3 수(binary64), 4 text, 5 행: 비트 8–23에 그 행이 채우는 행 목록 상태, 6 bool: 1바이트 0이나 1). 페이로드는 접두부와 가변 필드의 바이트다. 접두부에는 선언 순서로 고정 필드의 값과 가변 필드(바이트·text)의 u32 길이가 들어가는데, 마지막 가변 필드는 길이 없이 나머지를 차지한다. 가변 필드의 바이트는 접두부 뒤에 선언 순서로 이어진다(가변 필드가 하나면 예전의 "바이트 꼬리"와 같다) | +| | 필드 × 8 B: u32 이름(식별자, 또는 그룹 안의 필드면 점으로 이은 경로 `user.name`), u32 타입(1 u32le, 2 바이트, 3 수(binary64), 4 text, 5 행: 비트 8–23에 그 행이 채우는 행 목록 상태, 6 bool: 1바이트 0이나 1, 7 i64: 8바이트 부호 있는 little-endian). 페이로드는 접두부와 가변 필드의 바이트다. 접두부에는 선언 순서로 고정 필드의 값과 가변 필드(바이트·text)의 u32 길이가 들어가는데, 마지막 가변 필드는 길이 없이 나머지를 차지한다. 가변 필드의 바이트는 접두부 뒤에 선언 순서로 이어진다(가변 필드가 하나면 예전의 "바이트 꼬리"와 같다) | | | 상수 × 8 B: u32 블롭 오프셋, u32 길이 (UTF-8) | | | 레코드 × 8 B: u32 이름, u16 첫 레코드 필드, u16 필드 수(1–16) | -| | 레코드 필드 × 16 B: u32 이름, u32 타입(0 수, 1 text, 2 bool), 8 B 기본값: 수와 bool은 f64(bool은 0이나 1), text는 u32 상수 | -| | 보기 × 16 B: u32 이름, u8 타입(0 수, 1 text, 2 표, 3 bool), u8 걸러진 표의 리스트 상태 + 1(그 밖에는 0), u16 표(열이면 그 표 보기 + 1, 아니면 0), u32 시작 명령, u32 명령 수. 표는 시작 명령 자리에 리스트 상태, 명령 수 0. 걸러진 표는 타입 다음 바이트에 리스트 상태 + 1, 시작·명령 수에 필터 블록 | +| | 레코드 필드 × 16 B: u32 이름, u32 타입(0 수, 1 text, 2 bool, 3 i64), 8 B 기본값: 수와 bool은 f64(bool은 0이나 1), i64는 정수, text는 u32 상수 | +| | 보기 × 16 B: u32 이름, u8 타입(0 수, 1 text, 2 표, 3 bool, 4 i64), u8 걸러진 표의 리스트 상태 + 1(그 밖에는 0), u16 표(열이면 그 표 보기 + 1, 아니면 0), u32 시작 명령, u32 명령 수. 표는 시작 명령 자리에 리스트 상태, 명령 수 0. 걸러진 표는 타입 다음 바이트에 리스트 상태 + 1, 시작·명령 수에 필터 블록 | | | 핸들러 × 12 B: u32 이벤트, u32 시작 명령, u32 명령 수 | | | 명령 × 16 B: u8 opcode, 0 3바이트, u32 인자, f64 값 | | | 문자열: `[u8 길이][바이트]`의 연속 | @@ -113,9 +115,18 @@ v6은 **bool**을 수와 다른 스택 종류로 더한다([016](016-bool-and-i6 | 73 | OUTER_ROW | → i | 열 안의 집계 블록에서 그 열의 행(집계의 항목은 ROW). 걷기 안의 집계에서는 걷는 항목 | | 74 | EACH | | 걷기. `인자`의 비트 8–15는 리스트 상태, 16–31은 뒤따르는 블록의 길이 L. 그 L개 명령이 리스트가 시작할 때 가진 항목마다 차례로 핸들러의 코드로 돈다(`ROW`가 항목). 블록은 상태, 그 리스트의 항목(필드 바꾸기, 지우기), 다른 리스트를 바꿀 수 있지만 걷는 리스트에 넣을 수는 없다. 걷는 항목이나 그 앞의 항목을 지우면 걷기가 따라가 다음 항목을 건너뛰지 않는다. 핸들러 전용 | | 75 | LOCAL_LOAD | → n, bool 또는 i | 이벤트의 지역 칸. `인자`의 비트 0–7은 칸(64개), 8–15는 담는 것(0 수, 1 인덱스, 2 bool), 16–31은 인덱스의 리스트. 칸은 이벤트가 시작할 때 0이고 저장되지 않는다. 핸들러와 그 안의 걷기, 그 안의 집계에서 읽는다 | -| 76 | LOCAL_STORE | n, bool 또는 i → | 그 칸에 넣는다. 핸들러와 그 안의 걷기에서. 한 핸들러는 한 칸을 한 가지(수, bool, 또는 한 리스트의 인덱스)로만 쓴다 | - -bool은 실행 중에 수의 칸에 든 0이나 1이다. `f`는 레코드 필드의 타입(수, text 또는 bool)이고, `k`는 그 리스트의 키 필드 타입이다. 수 연산 결과가 유한하지 않으면 실패한다. 실패하면 그 이벤트의 모든 +| 76 | LOCAL_STORE | n, bool, i64 또는 i → | 그 칸에 넣는다. 핸들러와 그 안의 걷기에서. 담는 것 3은 i64. 한 핸들러는 한 칸을 한 가지(수, bool, i64, 또는 한 리스트의 인덱스)로만 쓴다 | +| 77–79 | I64_ADD I64_SUB I64_MUL | i64 i64 → i64 | 범위를 넘으면 `overflow` | +| 80–81 | I64_DIV I64_MOD | i64 i64 → i64 | 내림한 몫과 나머지(나머지는 나누는 수의 부호). 0으로 나누면 `divide`, 가장 작은 i64 / −1은 `overflow` | +| 82 | I64_NEG | i64 → i64 | 가장 작은 i64는 `overflow` | +| 83 | I64_COMPARE | i64 i64 → bool | `인자`는 관계(TEXT_COMPARE와 같다) | +| 84–85 | I64_MIN I64_MAX | i64 i64 → i64 | | +| 86 | I64_FROM | n → i64 | 범위 안의 정수만. 아니면 `limit` | +| 87 | NUM_FROM | i64 → n | \|i\| ≤ 2^53일 때만(정확하다). 아니면 `limit` | + +bool은 실행 중에 수의 칸에 든 0이나 1이고, i64는 같은 8바이트의 정수다. CONST의 인자 2는 i64 상수(값의 8바이트), NUM_TEXT의 +인자 2는 i64의 10진 글자다. LOAD·STORE·지역 칸·항목·FIELD·SEND는 i64를 수처럼 옮긴다. FOLD의 종류에 64(PURE_FOLD_INTEGER)를 +더하면 sum·max·min·distinct가 i64 값을 받는다(sum이 범위를 넘으면 `overflow`). i64 키 목록은 정수로 비교한다. `f`는 레코드 필드의 타입(수, text, bool 또는 i64)이고, `k`는 그 리스트의 키 필드 타입이다. 수 연산 결과가 유한하지 않으면 실패한다. 실패하면 그 이벤트의 모든 핸들러가 한 일이 취소된다. ## 로드할 때 검증하는 것 diff --git a/serve/deliver.c b/serve/deliver.c index 6e402a5..9ed4529 100644 --- a/serve/deliver.c +++ b/serve/deliver.c @@ -1,5 +1,6 @@ #define _POSIX_C_SOURCE 200809L #include "deliver.h" +#include <inttypes.h> #include "wait.h" #include "seal.h" #include "message-json.h" @@ -151,6 +152,9 @@ static bool encode(const PureWire *w, PureSpan payload, Text *t) { put(t, digits, (size_t)snprintf(digits, sizeof(digits), "%u", (unsigned)read_u32(payload.data + field->offset))); } else if (field->type == PURE_FIELD_BOOL) { put(t, payload.data[field->offset] ? "true" : "false", payload.data[field->offset] ? 4 : 5); + } else if (field->type == PURE_FIELD_I64) { /* every digit */ + uint64_t bits = read_u32(payload.data + field->offset) | (uint64_t)read_u32(payload.data + field->offset + 4) << 32; + put(t, digits, (size_t)snprintf(digits, sizeof(digits), "%" PRId64, (int64_t)bits)); } else if (field->type == PURE_FIELD_NUMBER) { uint64_t bits = read_u32(payload.data + field->offset) | (uint64_t)read_u32(payload.data + field->offset + 4) << 32; double value; @@ -428,10 +432,11 @@ static void drop_connection(PureWire *w) { pthread_mutex_unlock(&w->lock); } -/* A fault a Pure receiver answers when its app refused the message. */ +/* A fault a Pure receiver answers when its app refused the message: any + * but memory (its budget: it may take the message later). */ static bool refusal(const char *error) { - static const char *const faults[] = {"divide", "non_finite", "clamp", "limit", "not_found", "encoding", "exists"}; - for (size_t i = 0; i < sizeof(faults) / sizeof(*faults); ++i) if (!strcmp(error, faults[i])) return true; + for (unsigned f = PURE_FAULT_DIVIDE; f < PURE_FAULT_COUNT; ++f) + if (f != PURE_FAULT_MEMORY && !strcmp(error, pure_fault_name((PureFault)f))) return true; return false; } diff --git a/serve/message-json.c b/serve/message-json.c index afbd541..0905b34 100644 --- a/serve/message-json.c +++ b/serve/message-json.c @@ -83,6 +83,20 @@ static bool json_double(const unsigned char *text, size_t size, double *value) { return isfinite(*value); } +/* A JSON integer in an i64's range (no point, no exponent), exactly: false for any other text. */ +static bool json_integer(const unsigned char *text, size_t size, int64_t *value) { + bool integer, negative = size && text[0] == '-'; + if (!size || json_number(text, size, &integer) != size || !integer) return false; + uint64_t n = 0, most = negative ? (uint64_t)INT64_MAX + 1 : (uint64_t)INT64_MAX; + for (size_t i = negative; i < size; ++i) { + unsigned digit = (unsigned)(text[i] - '0'); + if (n > (most - digit) / 10) return false; + n = n * 10 + digit; + } + *value = !negative ? (int64_t)n : n ? -(int64_t)(n - 1) - 1 : 0; /* -2^63 is; -0 is 0 */ + return true; +} + /* A JSON true or false at text: its length (4 or 5), or 0. */ static size_t json_bool(const unsigned char *text, size_t size, bool *value) { if (size >= 4 && !memcmp(text, "true", 4)) return *value = true, 4; @@ -91,7 +105,8 @@ static size_t json_bool(const unsigned char *text, size_t size, bool *value) { } /* A fixed field's value from its text (a query value or a JSON value) into - * the prefix: a u32 in range, a finite number, or true or false for a bool. */ + * the prefix: a u32 in range, a finite number, true or false for a bool, an + * integer in range for an i64. */ bool pure_json_fixed(const PureField *field, const unsigned char *text, size_t size, unsigned char *prefix) { if (field->type == PURE_FIELD_BOOL) { /* true or false, as a JSON value says it */ bool value; @@ -99,6 +114,12 @@ bool pure_json_fixed(const PureField *field, const unsigned char *text, size_t s prefix[field->offset] = value; return true; } + if (field->type == PURE_FIELD_I64) { + int64_t value; + if (!json_integer(text, size, &value)) return false; + for (unsigned i = 0; i < 8; ++i) prefix[field->offset + i] = (unsigned char)((uint64_t)value >> (8 * i)); + return true; + } bool integer; if (!size || json_number(text, size, &integer) != size || size > 40) return false; if (field->type == PURE_FIELD_U32) { @@ -289,7 +310,7 @@ static const char *rows_of(PureJsonMessage *b, unsigned char *json, size_t size, *at = pure_json_space(json, *at, size); } if (*at == size || json[(*at)++] != '{') return "json_type"; - double numbers[PURE_RECORD_FIELDS]; + PureValue numbers[PURE_RECORD_FIELDS]; /* a number's, a bool's or an i64's */ PureSpan texts[PURE_RECORD_FIELDS]; bool given[PURE_RECORD_FIELDS] = {false}; *at = pure_json_space(json, *at, size); @@ -317,11 +338,12 @@ static const char *rows_of(PureJsonMessage *b, unsigned char *json, size_t size, } if (given[k]) return "json_field"; given[k] = true; - bool text = m->record_field_text[record->first + k], truth; - if (m->record_field_bool[record->first + k]) { /* true or false, nothing else */ + uint8_t type = m->record_field_type[record->first + k]; + bool text = type == PURE_RECORD_TEXT, truth; + if (type == PURE_RECORD_BOOL) { /* true or false, nothing else */ size_t word = json_bool(json + *at, size - *at, &truth); if (!word) return "json_type"; - numbers[k] = truth; + numbers[k].num = truth; *at = pure_json_space(json, *at + word, size); continue; } @@ -341,14 +363,16 @@ static const char *rows_of(PureJsonMessage *b, unsigned char *json, size_t size, size_t length = json_number(json + *at, size - *at, &integer); if (!length) return *at < size && (json[*at] == '[' || json[*at] == '{' || json[*at] == 't' || json[*at] == 'f' || json[*at] == 'n') ? "json_type" : "json"; - if (text || !json_double(json + *at, length, &numbers[k])) return "json_type"; + if (text || !(type == PURE_RECORD_I64 ? json_integer(json + *at, length, &numbers[k].i64) + : json_double(json + *at, length, &numbers[k].num))) + return "json_type"; *at = pure_json_space(json, *at + length, size); } if (*at == size) return "json"; ++*at; for (unsigned k = 0; k < record->count; ++k) { uint32_t at_field = record->first + k; - if (m->record_field_text[at_field]) { + if (m->record_field_type[at_field] == PURE_RECORD_TEXT) { uint32_t constant = (uint32_t)m->record_field_default[at_field].num; PureSpan text = given[k] ? texts[k] : (PureSpan){m->constant_data + m->constant_offset[constant], m->constant_length[constant]}; @@ -357,9 +381,7 @@ static const char *rows_of(PureJsonMessage *b, unsigned char *json, size_t size, if (text.size) memcpy(b->rows + b->rows_size + 4, text.data, text.size); b->rows_size += 4 + text.size; } else { - double value = given[k] ? numbers[k] : m->record_field_default[at_field].num; - uint64_t bits; - memcpy(&bits, &value, 8); + uint64_t bits = (uint64_t)(given[k] ? numbers[k] : m->record_field_default[at_field]).i64; /* its bits */ if (!rows_room(b, 8)) return "memory"; for (unsigned i = 0; i < 8; ++i) b->rows[b->rows_size + i] = (unsigned char)(bits >> (8 * i)); b->rows_size += 8; diff --git a/serve/serve.c b/serve/serve.c index 43297c1..6d9d5d6 100644 --- a/serve/serve.c +++ b/serve/serve.c @@ -525,15 +525,12 @@ static void answer(Connection *c, PureOutcome result) { if (!result.body.size) release_ticket(c); } -static const char *const fault_names[] = {"none", "divide", "non_finite", "clamp", "limit", "not_found", "encoding", - "exists", "memory"}; - /* An app route's answer: a view's value, or what an event replied. A failed * event names its fault: 404 not_found, 409 exists, 503 memory (the app's * budget: again later), 422 for the others; a view without a value is 404 * no_value. */ static void answer_app(Connection *c, PureOutcome result, bool view, PureFault fault, const char *type) { - const char *named = fault != PURE_FAULT_NONE ? fault_names[fault] : NULL; + const char *named = fault != PURE_FAULT_NONE ? pure_fault_name(fault) : NULL; if (result.status == PURE_OK) { struct iovec extent = {(void *)result.body.data, result.body.size}; respond_typed(c, 200, "OK", "none", 0, type ? type : "application/octet-stream", &extent, @@ -1225,6 +1222,12 @@ static void put_number(Output *o, double value) { put_bytes(o, digits, pure_format_number(value, digits)); } +/* An i64's every digit (a JSON number too: a reader keeping binary64s loses those past 2^53). */ +static void put_integer(Output *o, int64_t value) { + char digits[24]; + put_bytes(o, digits, (size_t)snprintf(digits, sizeof(digits), "%" PRId64, value)); +} + /* A view's arguments from a query's name=value pairs (values %XX decoded; * from and count page a table): a payload of their input's shape, the * fixed fields in its prefix, the texts and bytes after it in order, each's @@ -1303,7 +1306,12 @@ static PureResult show_given(PureApp *app, const Route *route, size_t available, PureResult status = PURE_OK; double value; if (m->view_type[view] == PURE_VIEW_TABLE && !(o.bytes = malloc(o.capacity = 1024))) return PURE_ALLOCATION; - if (m->view_type[view] == PURE_VIEW_NUM || m->view_type[view] == PURE_VIEW_BOOL) { + if (m->view_type[view] == PURE_VIEW_I64) { + int64_t integer; + *type = "text/plain; charset=utf-8"; + if (!pure_app_view_i64(app, view, &integer)) status = PURE_NOT_FOUND; + else put_integer(&o, integer); + } else if (m->view_type[view] == PURE_VIEW_NUM || m->view_type[view] == PURE_VIEW_BOOL) { *type = "text/plain; charset=utf-8"; if (!pure_app_view(app, view, &value)) status = PURE_NOT_FOUND; else if (m->view_type[view] == PURE_VIEW_BOOL) put_bytes(&o, value ? "true" : "false", value ? 4 : 5); @@ -1343,6 +1351,12 @@ static PureResult show_given(PureApp *app, const Route *route, size_t available, for (unsigned k = 0; k < count; ++k) { put_bytes(&o, columns[k].key + !k, columns[k].length - !k); uint8_t kind = m->view_type[columns[k].view]; + if (kind == PURE_VIEW_I64) { + int64_t integer; + if (!pure_app_cell_i64(app, columns[k].view, row, &integer)) put_bytes(&o, "null", 4); + else put_integer(&o, integer); + continue; + } if (kind == PURE_VIEW_NUM || kind == PURE_VIEW_BOOL) { if (!pure_app_cell(app, columns[k].view, row, &value)) put_bytes(&o, "null", 4); else if (kind == PURE_VIEW_BOOL) put_bytes(&o, value ? "true" : "false", value ? 4 : 5); @@ -1858,8 +1872,7 @@ static bool same_states(const PureModule *a, const PureModule *b) { a->record[i].count != b->record[i].count) return false; for (uint32_t i = 0; i < a->record_fields; ++i) - if (strcmp(a->record_field_names[i], b->record_field_names[i]) || a->record_field_text[i] != b->record_field_text[i] || - a->record_field_bool[i] != b->record_field_bool[i]) + if (strcmp(a->record_field_names[i], b->record_field_names[i]) || a->record_field_type[i] != b->record_field_type[i]) return false; for (uint32_t i = 0; i < a->collections; ++i) if (strcmp(a->collection_names[i], b->collection_names[i])) return false; diff --git a/serve/test-message-json.c b/serve/test-message-json.c index b2bad37..eb98f47 100644 --- a/serve/test-message-json.c +++ b/serve/test-message-json.c @@ -1,7 +1,8 @@ /* JSON numbers are read as strtod reads them, bit for bit: in the forms a * client writes (a program's shortest digits, fixed decimals, exponents), at * the edges of the exact path (2^53, 10^22) and past them, and refused when - * not finite or not JSON. A bool is true or false, nothing else. */ + * not finite or not JSON. A bool is true or false, nothing else; an i64 an + * integer's digits, exact in its range. */ #include "message-json.h" #include <math.h> #include <stdint.h> @@ -52,6 +53,24 @@ int main(void) { CHECK(!pure_json_fixed(&truth, (const unsigned char *)not_bools[i], strlen(not_bools[i]), prefix) && prefix[1] == 0); double number; CHECK(!read_number("true", &number) && !read_number("false", &number)); + /* An i64 is an integer's digits, exactly, in its range. */ + static const PureField whole = {.type = PURE_FIELD_I64, .offset = 0}; + static const struct { const char *text; int64_t value; } integers[] = { + {"0", 0}, {"-0", 0}, {"9007199254740993", INT64_C(9007199254740993)}, {"9223372036854775807", INT64_MAX}, + {"-9223372036854775808", INT64_MIN}, {"-1", -1}}; + for (size_t i = 0; i < sizeof(integers) / sizeof(*integers); ++i) { + unsigned char bytes[8]; + int64_t value; + CHECK(pure_json_fixed(&whole, (const unsigned char *)integers[i].text, strlen(integers[i].text), bytes)); + memcpy(&value, bytes, 8); + CHECK(value == integers[i].value); + } + static const char *const not_integers[] = {"9223372036854775808", "-9223372036854775809", "1.0", "1e3", "+1", "01", "\"1\"", + "", "-", "1.5", "true", "99999999999999999999"}; + for (size_t i = 0; i < sizeof(not_integers) / sizeof(*not_integers); ++i) { + unsigned char bytes[8]; + CHECK(!pure_json_fixed(&whole, (const unsigned char *)not_integers[i], strlen(not_integers[i]), bytes)); + } uint64_t seed = 0x6a736f6eu; size_t read = 0; for (unsigned i = 0; i < 400000; ++i) { |
