diff options
Diffstat (limited to 'core/test-module.c')
| -rw-r--r-- | core/test-module.c | 188 |
1 files changed, 187 insertions, 1 deletions
diff --git a/core/test-module.c b/core/test-module.c index f45cada..893aeab 100644 --- a/core/test-module.c +++ b/core/test-module.c @@ -62,12 +62,18 @@ 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 * view numbered table - 1. */ typedef struct { const char *name; uint32_t type, table, start, length; } W; +/* A function (027's 2c): its block, and its parameters' then its results' types (a record field's: 0 a number, 1 a + * text, 2 a bool, 3 an i64, 4 a list whose record is beside it; 5 bytes). */ +typedef struct { uint32_t start, length; uint8_t params, results, types[16]; uint16_t records[16]; } FN; typedef struct { const S *s; size_t ns; const I *in; size_t ni; const V *v; size_t nv; const H *h; size_t nh; const O *o; size_t no; const char *const *c; size_t nc; const T *t; size_t nt; const char *const *k; size_t nk; const V *tv; size_t ntv; const R *r; size_t nr; const W *w; size_t nw; } Spec; +/* The functions the next build writes (load_functions sets them a moment). */ +static const FN *spec_fn; +static size_t spec_nfn; static unsigned char *put32(unsigned char *p, uint32_t v) { for (unsigned i = 0; i < 4; ++i) p[i] = (unsigned char)(v >> (8 * i)); @@ -125,7 +131,7 @@ static size_t build(unsigned char *out, Spec spec) { put32(p + 16, (uint32_t)spec.no); put32(p + 20, (uint32_t)used); p[24] = (unsigned char)fields; p[26] = (unsigned char)spec.nc; p[28] = (unsigned char)spec.nk; - p[30] = (unsigned char)spec.nr; p[36] = (unsigned char)record_fields; + p[30] = (unsigned char)spec.nr; p[36] = (unsigned char)record_fields; p[38] = (unsigned char)spec_nfn; put32(p + 32, (uint32_t)blob_used); p += 40; size_t n = 0; @@ -203,6 +209,16 @@ static size_t build(unsigned char *out, Spec spec) { p = put32(p, w->length); } for (size_t i = 0; i < spec.nh; ++i) { p = put32(p, spec.h[i].event); p = put32(p, spec.h[i].start); p = put32(p, spec.h[i].length); } + for (size_t i = 0; i < spec_nfn; ++i) { + p = put32(p, spec_fn[i].start); + p = put32(p, spec_fn[i].length); + *p++ = spec_fn[i].params; *p++ = spec_fn[i].results; *p++ = 0; *p++ = 0; + } + for (size_t i = 0; i < spec_nfn; ++i) + for (unsigned k = 0; k < (unsigned)spec_fn[i].params + spec_fn[i].results; ++k) { + *p++ = spec_fn[i].types[k]; *p++ = 0; + *p++ = (unsigned char)spec_fn[i].records[k]; *p++ = (unsigned char)(spec_fn[i].records[k] >> 8); + } for (size_t i = 0; i < spec.no; ++i) { *p++ = spec.o[i].op; *p++ = 0; *p++ = 0; *p++ = 0; p = put32(p, spec.o[i].arg); @@ -222,6 +238,13 @@ static PureResult load(Spec spec, PureModule *out) { return pure_module_load(bytes, size, out, error, sizeof(error)); } +static PureResult load_functions(Spec spec, const FN *fns, size_t count, PureModule *out) { + spec_fn = fns, spec_nfn = count; + PureResult result = load(spec, out); + spec_fn = NULL, spec_nfn = 0; + return result; +} + #define LEN(a) (sizeof(a) / sizeof(*(a))) #define SPEC(s, in, v, h, o) ((Spec){s, LEN(s), in, LEN(in), v, LEN(v), h, LEN(h), o, LEN(o), NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}) @@ -6318,6 +6341,168 @@ static void lists_in_records(void) { printf("{\"lists_in_records\":{\"rounds\":3,\"refused\":%zu}}\n", LEN(refused) + 1); } +/* Functions and characters (027's 2c): a function calls itself; each call has its own locals, lists, registers and + * held values (a caller's as they were when it returns); a walk goes on around a call; calls count as work and memory; + * the verifier keeps to a function's types and to what a walk around a call keeps to. */ +enum { CALL = PURE_OP_CALL, RET = PURE_OP_RETURN, CHAT = PURE_OP_CHAR_AT, CHAR_ = PURE_OP_CHAR, FN_TEXT = 1, FN_I64 = 3, + FN_LIST = 4 }; +#define I64_LOCAL(slot) ((uint32_t)(slot) | 3u << 8) +#define WALK_OF(list, length) ((uint32_t)(list) << 8 | (uint32_t)(length) << 16) +#define LIST_LOCAL(slot, record) ((uint32_t)(slot) | 4u << 8 | (uint32_t)(record) << 16) +static void functions_and_characters(void) { + static const S nums[] = {{"n", 0}}; + static const RF leaf_fields[] = {{"v", 3, 0}}; + static const RF node_fields[] = {{"v", 3, 0}, {"kids", 4, 1}}; /* kids: a list of Node */ + static const R records[] = {{"Leaf", leaf_fields, 1}, {"Node", node_fields, 2}}; + enum { IV = 1, SAID, OWN, NODES, KIDS, R_, STORED, NONE }; + static const T others[] = {{"i", PURE_STATE_I64, 0}, {"said", 1, 0}, {"let.own", 3 | (uint32_t)PURE_LIST_ROWS << 8, 0}, + {"let.nodes", 3 | (uint32_t)PURE_LIST_ROWS << 8, 1}, {"let.kids", 3 | (uint32_t)PURE_LIST_ROWS << 8, 1}, + {"let.r", 3 | (uint32_t)(PURE_LIST_ROWS | PURE_LIST_BOUND) << 8, 1}, {"stored", 3, 0}, + {"let.none", 3 | (uint32_t)PURE_LIST_ROWS << 8, 1}}; + static const char *const constants[] = {"", "a\xc3\xa9\xe2\x82\xac\xf0\x9f\x98\x80"}; /* a é € 😀 */ + static const I inputs[] = {KEY("fib", "KeyF"), KEY("tree", "KeyT"), KEY("own", "KeyO"), KEY("held", "KeyH"), + KEY("chars", "KeyC"), KEY("deep", "KeyD")}; + enum { LS_ = PURE_OP_LOCAL_STORE, LL_ = PURE_OP_LOCAL_LOAD }; + const O code[] = { + /* 0 fib(n): n < 2 ? n : fib(n - 1) + fib(n - 2) */ + {LS_, I64_LOCAL(0), 0}, {LL_, I64_LOCAL(0), 0}, IC(2), {ICMP, PURE_LESS, 0}, {JZ, 7, 0}, + {LL_, I64_LOCAL(0), 0}, {RET, 0, 0}, + {LL_, I64_LOCAL(0), 0}, IC(1), {ISUB, 0, 0}, {CALL, 0, 0}, {LL_, I64_LOCAL(0), 0}, IC(2), {ISUB, 0, 0}, {CALL, 0, 0}, + {IADD, 0, 0}, {RET, 0, 0}, + /* 17 sum(xs): the v of every node in xs and under it, by a walk of its register that calls itself */ + {BIND, R_, 0}, IC(0), {LS_, I64_LOCAL(0), 0}, {EACH, WALK_OF(R_, 9), 0}, + {LL_, I64_LOCAL(0), 0}, {ROW, R_, 0}, {IG, ITEM(R_, 0), 0}, {IADD, 0, 0}, {ROW, R_, 0}, {IG, ITEM(R_, 1), 0}, {CALL, 1, 0}, + {IADD, 0, 0}, {LS_, I64_LOCAL(0), 0}, + {LL_, I64_LOCAL(0), 0}, {RET, 1, 0}, + /* 32 own(n): own.append(n); n == 0 ? own.count : own(n - 1) * 10 + own.count (each call's own list holds one) */ + {LS_, I64_LOCAL(0), 0}, {LL_, I64_LOCAL(0), 0}, {LA, OWN, 0}, {LL_, I64_LOCAL(0), 0}, IC(0), {ICMP, PURE_EQUAL, 0}, + {JZ, 42, 0}, {LC, OWN, 0}, {IFROM, 0, 0}, {RET, 2, 0}, + {LL_, I64_LOCAL(0), 0}, IC(1), {ISUB, 0, 0}, {CALL, 2, 0}, IC(10), {IMUL, 0, 0}, {LC, OWN, 0}, {IFROM, 0, 0}, {IADD, 0, 0}, + {RET, 2, 0}, + /* 52 held(t, k): s = t + "k"; k == 0 ? s : held(s, k - 1) + s (each call's s its own) */ + {LS_, I64_LOCAL(0), 0}, {LL_, I64_LOCAL(0), 0}, {NTX, 2, 0}, {CAT, 0, 0}, {HST, 16, 0}, + {LL_, I64_LOCAL(0), 0}, IC(0), {ICMP, PURE_EQUAL, 0}, {JZ, 63, 0}, {HLD, 16, 0}, {RET, 3, 0}, + {HLD, 16, 0}, {LL_, I64_LOCAL(0), 0}, IC(1), {ISUB, 0, 0}, {CALL, 3, 0}, {HLD, 16, 0}, {CAT, 0, 0}, {RET, 3, 0}, + /* 71 deep(n): deep(n + 1), never returning */ + IC(1), {IADD, 0, 0}, {CALL, 4, 0}, {RET, 4, 0}, + /* 75 handler fib: i = fib(20) */ + IC(20), {CALL, 0, 0}, {ST, IV, 0}, + /* 78 handler tree: kids = [4, 1 [2 [4], 3]]; i = sum(kids) + r[0].v, r bound by the handler around the calls */ + IC(4), {LV, NONE, 0}, {LA, KIDS, 0}, IC(2), {LV, KIDS, 0}, {LA, NODES, 0}, IC(3), {LV, NONE, 0}, {LA, NODES, 0}, + {LV, NONE, 0}, {CALL, 1, 0}, {ST, IV, 0}, /* sum of nothing: 0 */ + IC(1), {LV, NODES, 0}, {LA, KIDS, 0}, {LV, KIDS, 0}, {BIND, R_, 0}, + {LV, KIDS, 0}, {LS_, LIST_LOCAL(0, 1), 0}, {LL_, LIST_LOCAL(0, 1), 0}, {CALL, 1, 0}, /* by way of a local */ + {XF, R_, 0}, {IG, ITEM(R_, 0), 0}, {IADD, 0, 0}, {ST, IV, 0}, /* 14 + 4 */ + /* 103 handler own: own = [7]; i = own(3); n = own.count */ + IC(7), {LA, OWN, 0}, IC(3), {CALL, 2, 0}, {ST, IV, 0}, {LC, OWN, 0}, {ST, 0, 0}, + /* 110 handler held: said = held("", 2) */ + {TK, 0, 0}, IC(2), {CALL, 3, 0}, {PURE_OP_TEXT_SET, SAID, 0}, + /* 114 handler chars: i = the code points at 0, 1, 3 and 6, packed; said = char(...) joined, the constant again */ + {TK, 1, 0}, IC(0), {CHAT, 0, 0}, {TK, 1, 0}, IC(1), {CHAT, 0, 0}, {IADD, 0, 0}, {TK, 1, 0}, IC(3), {CHAT, 0, 0}, + {IADD, 0, 0}, {TK, 1, 0}, IC(6), {CHAT, 0, 0}, {IADD, 0, 0}, {ST, IV, 0}, + IC(0x61), {CHAR_, 0, 0}, IC(0xE9), {CHAR_, 0, 0}, {CAT, 0, 0}, IC(0x20AC), {CHAR_, 0, 0}, {CAT, 0, 0}, IC(0x1F600), {CHAR_, 0, 0}, + {CAT, 0, 0}, {PURE_OP_TEXT_SET, SAID, 0}, + /* 142 handler deep: own = [5]; deep(0) */ + IC(5), {LA, OWN, 0}, IC(0), {CALL, 4, 0}, {ST, IV, 0}, + }; + static const FN fns[] = {{0, 17, 1, 1, {FN_I64, FN_I64}, {0}}, {17, 15, 1, 1, {FN_LIST, FN_I64}, {1, 0}}, + {32, 20, 1, 1, {FN_I64, FN_I64}, {0}}, {52, 19, 2, 1, {FN_TEXT, FN_I64, FN_TEXT}, {0}}, + {71, 4, 1, 1, {FN_I64, FN_I64}, {0}}}; + static const H handlers[] = {{1, 75, 3}, {3, 78, 25}, {5, 103, 7}, {7, 110, 4}, {9, 114, 28}, {11, 142, 5}}; + CHECK(LEN(code) == 147); + Spec spec = {nums, 1, inputs, LEN(inputs), NULL, 0, handlers, LEN(handlers), code, LEN(code), NULL, 0, + others, LEN(others), constants, LEN(constants), NULL, 0, records, 2, NULL, 0}; + if (load_functions(spec, fns, LEN(fns), &module_b) != PURE_OK) { fprintf(stderr, "functions: %s\n", error); abort(); } + CHECK(module_b.function[2].lists == UINT32_C(1) << OWN && module_b.function[1].registers == UINT32_C(1) << R_ && + module_b.function[3].held == UINT32_C(1) << 16 && module_b.runner == 2); + PureApp app; + CHECK(pure_app_start(&app, &module_b, (PureData){0}) == PURE_OK); + for (int round = 0; round < 3; ++round) { /* the frames' room used again, each event alike */ + CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK && app.values[IV].i64 == 6765); + CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && app.values[IV].i64 == 18); + CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_OK && app.values[IV].i64 == 1111 && app.values[0].num == 1); + CHECK(pure_app_input(&app, 3, PURE_EDGE_DOWN) == PURE_OK && !strcmp(state_text(&app, SAID), "210212")); + CHECK(pure_app_input(&app, 4, PURE_EDGE_DOWN) == PURE_OK && app.values[IV].i64 == 0x61 + 0xE9 + 0x20AC + 0x1F600 && + !strcmp(state_text(&app, SAID), constants[1])); + } + /* A call without end: the room runs out (MEMORY) where the work does not; with a smaller work budget, WORK. Either + * way nothing changed, and the lists are the app's own again. */ + uint64_t before = (uint64_t)app.values[IV].i64; + CHECK(pure_app_input(&app, 5, PURE_EDGE_DOWN) == PURE_BUDGET && app.fault == PURE_FAULT_MEMORY); + CHECK((uint64_t)app.values[IV].i64 == before && pure_list_count(app.lists[OWN]) == 0 && app.calls.at == 0 && !app.calls.used); + pure_app_work(&app, 100000); + CHECK(pure_app_input(&app, 5, PURE_EDGE_DOWN) == PURE_LIMIT && app.fault == PURE_FAULT_WORK); + CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_OK && app.values[IV].i64 == 1111 && app.values[0].num == 1); + pure_app_stop(&app); + /* Characters that fail: within one (é at 2), past the end, a surrogate, past U+10FFFF, below 0. */ + const O chars_bad[][3] = {{{TK, 1, 0}, IC(2), {CHAT, 0, 0}}, {{TK, 1, 0}, IC(10), {CHAT, 0, 0}}, + {IC(0xD800), {CHAR_, 0, 0}, {PURE_OP_TEXT_SET, SAID, 0}}, {IC(0x110000), {CHAR_, 0, 0}, {PURE_OP_TEXT_SET, SAID, 0}}, + {IC(-1), {CHAR_, 0, 0}, {PURE_OP_TEXT_SET, SAID, 0}}}; + static const PureFault chars_fault[] = {PURE_FAULT_LIMIT, PURE_FAULT_LIMIT, PURE_FAULT_ENCODING, PURE_FAULT_LIMIT, PURE_FAULT_LIMIT}; + for (size_t k = 0; k < LEN(chars_bad); ++k) { + O one[4] = {chars_bad[k][0], chars_bad[k][1], chars_bad[k][2], {ST, IV, 0}}; + H go = {1, 0, chars_bad[k][2].op == CHAT ? 4 : 3}; + Spec bad = {nums, 1, inputs, 1, NULL, 0, &go, 1, one, LEN(one), NULL, 0, others, LEN(others), constants, LEN(constants), + NULL, 0, records, 2, NULL, 0}; + if (load(bad, &module_a) != PURE_OK) { fprintf(stderr, "chars %zu: %s\n", k, error); abort(); } + CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK); + CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) != PURE_OK && app.fault == chars_fault[k]); + pure_app_stop(&app); + } + /* Refused: no return; a result of another type; code after a return; RETURN outside a function; a call with an + * argument of another type, in a view, or changing a list a walk around it walks; a message's field in a function; + * a loop above values; a signature's type or record. */ + const struct { O code[6]; uint32_t count; FN fn; const char *why; } refused[] = { + {{IC(1), {ST, IV, 0}}, 2, {0, 2, 0, 0, {0}, {0}}, "function ends without return"}, + {{{C, 0, 1}, {RET, 0, 0}}, 2, {0, 2, 0, 1, {FN_I64}, {0}}, "operand type"}, + {{{RET, 0, 0}, {C, 0, 0}}, 2, {0, 2, 0, 0, {0}, {0}}, "unreachable instruction"}, + {{{C, 0, 1}, {CALL, 0, 0}, {RET, 0, 0}}, 3, {0, 3, 1, 0, {FN_I64}, {0}}, "operand type"}, + {{IC(1), {LA, STORED, 0}, {RET, 0, 0}}, 3, {0, 3, 0, 0, {0}, {0}}, NULL}, + {{{PURE_OP_FIELD, 0, 0}, {RET, 0, 0}}, 2, {0, 2, 0, 0, {0}, {0}}, "message operation outside a message handler"}, + {{IC(0), IC(1), IC(0), IC(1), {PURE_OP_LOOP, PURE_LOOP_INTEGER, 0}, {RET, 0, 0}}, 6, + {0, 6, 0, 0, {0}, {0}}, "loop above values"}, + {{{RET, 0, 0}}, 1, {0, 1, 0, 0, {6}, {0}}, "function signature"}, + }; + for (size_t k = 0; k < LEN(refused); ++k) { + FN fn = refused[k].fn; + if (k == 7) fn.params = 1; + O all[12]; + uint32_t n = refused[k].count; + memcpy(all, refused[k].code, n * sizeof(O)); + H go = {1, n, 1}; + all[n] = (O){C, 0, 0}; /* a handler that stores nothing: C, ST */ + all[n + 1] = (O){ST, 0, 0}; + go.length = 2; + if (k == 4) { /* the call in a walk of the list it appends to */ + all[n] = (O){EACH, WALK_OF(STORED, 1), 0}, all[n + 1] = (O){CALL, 0, 0}; + } + Spec bad = {nums, 1, inputs, 1, NULL, 0, &go, 1, all, n + 2, NULL, 0, others, LEN(others), constants, LEN(constants), + NULL, 0, records, 2, NULL, 0}; + const char *why = refused[k].why ? refused[k].why : "call that changes a list walked"; + if (load_functions(bad, &fn, 1, &module_a) == PURE_OK || !strstr(error, why)) { + fprintf(stderr, "functions: refused %zu: %s\n", k, error); + abort(); + } + } + static const O in_view[] = {{CALL, 0, 0}, {RET, 0, 0}, {RET, 0, 0}}; + static const V call_view[] = {{"v", 0, 1}}; + static const FN nothing = {1, 1, 0, 0, {0}, {0}}; + Spec viewed = {nums, 1, inputs, 1, call_view, 1, NULL, 0, in_view, 2, NULL, 0, others, LEN(others), constants, LEN(constants), + NULL, 0, records, 2, NULL, 0}; + CHECK(load_functions(viewed, ¬hing, 1, &module_a) != PURE_OK && strstr(error, "call outside a handler or a function")); + static const H returning = {1, 2, 1}; + Spec handler_return = {nums, 1, inputs, 1, NULL, 0, &returning, 1, in_view, 3, NULL, 0, others, + LEN(others), constants, LEN(constants), NULL, 0, records, 2, NULL, 0}; + CHECK(load_functions(handler_return, ¬hing, 1, &module_a) != PURE_OK && strstr(error, "return outside a function")); + /* A handler's loop above values: its block would begin above them (once past the run's stack). */ + const O above[] = {IC(0), IC(0), IC(1), {PURE_OP_LOOP, PURE_LOOP_INTEGER, 0}, {ST, IV, 0}}; + static const H whole = {1, 0, 5}; + Spec looped = {nums, 1, inputs, 1, NULL, 0, &whole, 1, above, LEN(above), NULL, 0, others, LEN(others), constants, + LEN(constants), NULL, 0, records, 2, NULL, 0}; + CHECK(load(looped, &module_a) != PURE_OK && strstr(error, "loop above values")); + printf("{\"functions_and_characters\":{\"rounds\":3,\"refused\":%zu}}\n", LEN(refused) + 3); +} + /* 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 }; @@ -6694,6 +6879,7 @@ int main(void) { bits_and_bytes(); formats_and_held(); lists_in_records(); + functions_and_characters(); memory_budget(); work_budget(); fold_mutations(); |
