skip to content
==> tests/spec.c <==
1/* Spec-style checks: every trap and its wasmtime code, limits, imports, host calls, interrupts,
2 * globals, tables, bulk memory, multi-value, the start function, canonical NaNs. */
3#include "mesh/check.h"
4#include "wasm/builder.h"
5#include "wasm/interp.h"
6
7#include <stdio.h>
8#include <string.h>
9
10static char err[256];
11
12/* A module of one exported function "f" of sig with locals and body; memory of 1 page (max 2),
13 * a table of 4 with func 0 at [1], a mutable i32 global = 5; plus extra setup by the caller. */
14typedef struct {
15    wasm_builder w;
16    wasm_compiled *c;
17    wasm_instance *I;
18    int64_t f;
19} T;
20
21static wasm_buf *start(T *t, const char *sig, const char *locals) {
22    memset(t, 0, sizeof *t);
23    uint32_t ty = wasm_builder_type(&t->w, sig);
24    wasm_builder_type(&t->w, "i>i"); /* type 1 */
25    wasm_builder_type(&t->w, "i>ii"); /* type 2 */
26    uint32_t f;
27    wasm_buf *b = wasm_builder_func_add(&t->w, ty, locals, &f);
28    wasm_builder_add_export(&t->w, "f", WASM_EXTERN_FUNC, f);
29    wasm_builder_memory(&t->w, 1, 2, true);
30    wasm_builder_table(&t->w, 4, 8, true);
31    wb_i32_const(wasm_builder_add_global(&t->w, WASM_I32, true), 5);
32    return b;
33}
34
35static bool finish(T *t, const char *sig, const wasm_host_func *imports, size_t n, const wasm_store_limits *lim) {
36    wasm_buf out;
37    wasm_builder_finish(&t->w, &out);
38    t->c = wasm_compile(out.data, out.len, WASM_F_INTERP, err, sizeof err);
39    wasm_buf_free(&out);
40    if (!t->c) return false;
41    t->I = wasm_instantiate(t->c, imports, n, lim, NULL, err, sizeof err);
42    if (!t->I) return false;
43    t->f = wasm_instance_func(t->I, "f", sig, err, sizeof err);
44    return t->f >= 0;
45}
46
47static void done(T *t) {
48    wasm_instance_free(t->I);
49    wasm_compiled_free(t->c);
50    wasm_builder_free(&t->w);
51}
52
53/* Runs body as f(sig) on args; returns the trap (or OK) and the first result in *r. */
54static int run(const char *sig, const char *locals, const uint8_t *body, size_t n, uint64_t a0, uint64_t a1,
55               uint64_t *r) {
56    T t;
57    wasm_buf *b = start(&t, sig, locals);
58    wb_bytes(b, body, n);
59    /* A second function for the table and call_indirect: (i32) -> i32, x + 100. */
60    uint32_t g;
61    wasm_buf *gb = wasm_builder_func_add(&t.w, 1, "", &g);
62    wb_op_u32(gb, 0x20, 0);
63    wb_i32_const(gb, 100);
64    wb_op(gb, 0x6a);
65    uint32_t el[] = {g};
66    wasm_builder_elem(&t.w, 1, el, 1);
67    wasm_builder_data(&t.w, 16, "\x01\x02\x03\x04\x05\x06\x07\x08", 8);
68    int trap = -100;
69    if (finish(&t, sig, NULL, 0, NULL)) {
70        uint64_t io[4] = {a0, a1, 0, 0};
71        trap = wasm_call(t.I, (uint32_t)t.f, io);
72        *r = io[0];
73    } else {
74        fprintf(stderr, "setup: %s\n", err);
75    }
76    done(&t);
77    return trap;
78}
79
80#define RUN(sig, locals, a0, a1, r, ...) \
81    run(sig, locals, (const uint8_t[]){__VA_ARGS__}, sizeof((const uint8_t[]){__VA_ARGS__}), a0, a1, r)
82
83static int host_add(wasm_instance *I, void *ctx, uint64_t *io) {
84    (void)I;
85    *(int *)ctx += 1;
86    io[0] = (uint32_t)io[0] + (uint32_t)io[1];
87    return WASM_NUM_OK;
88}
89
90static int host_trap(wasm_instance *I, void *ctx, uint64_t *io) {
91    (void)I, (void)ctx, (void)io;
92    return WASM_TRAP_UNREACHABLE;
93}
94
95int main(void) {
96    uint64_t r = 0;
97    /* Traps, with wasmtime's codes. */
98    CHECK_I(RUN(">", "", 0, 0, &r, 0x00), WASM_TRAP_UNREACHABLE);
99    CHECK_I(RUN("ii>i", "", 7, 0, &r, 0x20, 0, 0x20, 1, 0x6d), WASM_TRAP_INTEGER_DIVISION_BY_ZERO);
100    CHECK_I(RUN("ii>i", "", 0x80000000u, 0xffffffffu, &r, 0x20, 0, 0x20, 1, 0x6d), WASM_TRAP_INTEGER_OVERFLOW);
101    CHECK_I(RUN("ii>i", "", 0x80000000u, 0xffffffffu, &r, 0x20, 0, 0x20, 1, 0x6f), WASM_NUM_OK);
102    CHECK_U(r, 0);
103    CHECK_I(RUN("f>i", "", 0x7fc00000u, 0, &r, 0x20, 0, 0xa8), WASM_TRAP_BAD_CONVERSION_TO_INTEGER);
104    CHECK_I(RUN("f>i", "", 0x4f000000u, 0, &r, 0x20, 0, 0xa8), WASM_TRAP_INTEGER_OVERFLOW); /* 2^31 */
105    CHECK_I(RUN("f>i", "", 0x4f000000u, 0, &r, 0x20, 0, 0xfc, 0x00), WASM_NUM_OK);
106    CHECK_U(r, 0x7fffffffu);
107    /* Memory bounds: the last valid word, one past it, offset overflow past 4 GiB. */
108    CHECK_I(RUN("i>i", "", 65532, 0, &r, 0x20, 0, 0x28, 2, 0), WASM_NUM_OK);
109    CHECK_I(RUN("i>i", "", 65533, 0, &r, 0x20, 0, 0x28, 2, 0), WASM_TRAP_MEMORY_OUT_OF_BOUNDS);
110    CHECK_I(RUN("i>i", "", 0xffffffffu, 0, &r, 0x20, 0, 0x28, 0, 0xff, 0xff, 0xff, 0xff, 0x0f), WASM_TRAP_MEMORY_OUT_OF_BOUNDS);
111    CHECK_I(RUN("i>", "", 65535, 0, &r, 0x20, 0, 0x41, 1, 0x3b, 1, 0), WASM_TRAP_MEMORY_OUT_OF_BOUNDS);
112    /* The data segment, little-endian loads, sign extension. */
113    CHECK_I(RUN(">l", "", 0, 0, &r, 0x41, 16, 0x29, 3, 0), WASM_NUM_OK);
114    CHECK_U(r, 0x0807060504030201ull);
115    CHECK_I(RUN(">i", "", 0, 0, &r, 0x41, 0, 0x28, 2, 16), WASM_NUM_OK);
116    CHECK_U(r, 0x04030201u);
117    CHECK_I(RUN(">l", "", 0, 0, &r, 0x41, 0, 0x41, 0x80, 0x01, 0x3a, 0, 0, 0x41, 0, 0x30, 0, 0), WASM_NUM_OK);
118    CHECK_U(r, 0xffffffffffffff80ull);
119    /* call_indirect: a hit, a null slot, out of the table, the wrong signature. */
120    CHECK_I(RUN("i>i", "", 5, 0, &r, 0x20, 0, 0x41, 1, 0x11, 1, 0), WASM_NUM_OK);
121    CHECK_U(r, 105);
122    CHECK_I(RUN("i>i", "", 5, 0, &r, 0x20, 0, 0x41, 0, 0x11, 1, 0), WASM_TRAP_INDIRECT_CALL_TO_NULL);
123    CHECK_I(RUN("i>i", "", 5, 0, &r, 0x20, 0, 0x41, 4, 0x11, 1, 0), WASM_TRAP_TABLE_OUT_OF_BOUNDS);
124    CHECK_I(RUN("i>i", "", 5, 0, &r, 0x20, 0, 0x41, 1, 0x11, 2, 0, 0x1a), WASM_TRAP_BAD_SIGNATURE);
125    /* Stack overflow by unbounded recursion. */
126    CHECK_I(RUN(">", "", 0, 0, &r, 0x10, 0), WASM_TRAP_STACK_OVERFLOW);
127    /* memory.grow: within the max, past it (-1); memory.size. */
128    CHECK_I(RUN(">i", "", 0, 0, &r, 0x41, 1, 0x40, 0, 0x1a, 0x3f, 0), WASM_NUM_OK);
129    CHECK_U(r, 2);
130    CHECK_I(RUN(">i", "", 0, 0, &r, 0x41, 2, 0x40, 0), WASM_NUM_OK);
131    CHECK_U(r, 0xffffffffu);
132    /* Globals. */
133    CHECK_I(RUN(">i", "", 0, 0, &r, 0x23, 0, 0x41, 2, 0x6c, 0x24, 0, 0x23, 0), WASM_NUM_OK);
134    CHECK_U(r, 10);
135    /* br_table: index 0, 1, and past the end (the default). */
136    for (uint32_t i = 0; i < 4; i++) {
137        CHECK_I(RUN("i>i", "", i, 0, &r, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40, 0x20, 0, 0x0e, 2, 0, 1, 2, 0x0b, 0x41, 10,
138                    0x0f, 0x0b, 0x41, 20, 0x0f, 0x0b, 0x41, 30),
139                WASM_NUM_OK);
140        CHECK_U(r, i == 0 ? 10 : i == 1 ? 20 : 30);
141    }
142    /* A branch out of a block carrying a value, dropping the rest of the stack. */
143    CHECK_I(RUN(">i", "", 0, 0, &r, 0x02, 0x7f, 0x41, 1, 0x41, 2, 0x41, 3, 0x0c, 0, 0x0b), WASM_NUM_OK);
144    CHECK_U(r, 3);
145    /* Multi-value block (type 2: i32 -> i32 i32), then add. */
146    CHECK_I(RUN("i>i", "", 21, 0, &r, 0x20, 0, 0x02, 2, 0x20, 0, 0x0b, 0x6a), WASM_NUM_OK);
147    CHECK_U(r, 42);
148    /* if/else and select. */
149    CHECK_I(RUN("i>i", "", 0, 0, &r, 0x20, 0, 0x04, 0x7f, 0x41, 1, 0x05, 0x41, 2, 0x0b), WASM_NUM_OK);
150    CHECK_U(r, 2);
151    CHECK_I(RUN("i>i", "", 3, 0, &r, 0x41, 7, 0x41, 9, 0x20, 0, 0x1b), WASM_NUM_OK);
152    CHECK_U(r, 7);
153    /* Loop with br_if: count to the argument. */
154    CHECK_I(RUN("i>i", "i", 1000, 0, &r, 0x03, 0x40, 0x20, 1, 0x41, 1, 0x6a, 0x22, 1, 0x20, 0, 0x49, 0x0d, 0, 0x0b, 0x20, 1),
155            WASM_NUM_OK);
156    CHECK_U(r, 1000);
157    /* Bulk memory: fill, copy (overlapping), and their bounds. */
158    CHECK_I(RUN(">i", "", 0, 0, &r, 0x41, 40, 0x41, 0x2a, 0x41, 8, 0xfc, 11, 0, 0x41, 42, 0x41, 40, 0x41, 8, 0xfc, 10, 0,
159                0, 0x41, 44, 0x28, 2, 0),
160            WASM_NUM_OK);
161    CHECK_U(r, 0x2a2a2a2au);
162    CHECK_I(RUN(">", "", 0, 0, &r, 0x41, 0, 0x41, 0, 0x41, 0x80, 0x80, 0x04, 0xfc, 11, 0), WASM_NUM_OK); /* exactly 64 KiB */
163    CHECK_I(RUN(">", "", 0, 0, &r, 0x41, 1, 0x41, 0, 0x41, 0x80, 0x80, 0x04, 0xfc, 11, 0), WASM_TRAP_MEMORY_OUT_OF_BOUNDS);
164    /* Tables: size, grow within and past the max, get/set, fill bounds, ref.is_null. */
165    CHECK_I(RUN(">i", "", 0, 0, &r, 0xfc, 16, 0), WASM_NUM_OK);
166    CHECK_U(r, 4);
167    CHECK_I(RUN(">i", "", 0, 0, &r, 0xd0, 0x70, 0x41, 4, 0xfc, 15, 0), WASM_NUM_OK);
168    CHECK_U(r, 4);
169    CHECK_I(RUN(">i", "", 0, 0, &r, 0xd0, 0x70, 0x41, 5, 0xfc, 15, 0), WASM_NUM_OK);
170    CHECK_U(r, 0xffffffffu);
171    CHECK_I(RUN(">i", "", 0, 0, &r, 0x41, 0, 0x25, 0, 0xd1), WASM_NUM_OK);
172    CHECK_U(r, 1);
173    CHECK_I(RUN(">i", "", 0, 0, &r, 0x41, 1, 0x25, 0, 0xd1), WASM_NUM_OK);
174    CHECK_U(r, 0);
175    CHECK_I(RUN(">", "", 0, 0, &r, 0x41, 3, 0xd0, 0x70, 0x41, 2, 0xfc, 17, 0), WASM_TRAP_TABLE_OUT_OF_BOUNDS);
176    /* Canonical NaNs from arithmetic; neg keeps the payload. */
177    CHECK_I(RUN("ff>f", "", 0x7fa00001u, 0x3f800000u, &r, 0x20, 0, 0x20, 1, 0x92), WASM_NUM_OK);
178    CHECK_U((uint32_t)r, 0x7fc00000u);
179    CHECK_I(RUN("f>f", "", 0x7fa00001u, 0, &r, 0x20, 0, 0x8c), WASM_NUM_OK);
180    CHECK_U((uint32_t)r, 0xffa00001u);
181    CHECK_I(RUN("d>f", "", 0x7ff0000000000001ull, 0, &r, 0x20, 0, 0xb6), WASM_NUM_OK);
182    CHECK_U((uint32_t)r, 0x7fc00000u);
183    CHECK_I(RUN("ll>l", "", 1, 63, &r, 0x20, 0, 0x20, 1, 0x86), WASM_NUM_OK);
184    CHECK_U(r, 0x8000000000000000ull);
185
186    /* Imports: a host function called with its args, a host trap, a missing and a mistyped import. */
187    {
188        T t;
189        memset(&t, 0, sizeof t);
190        uint32_t ty = wasm_builder_type(&t.w, "ii>i");
191        uint32_t t0 = wasm_builder_type(&t.w, ">");
192        wasm_builder_import_func(&t.w, "mesh", "add", ty);
193        wasm_builder_import_func(&t.w, "mesh", "boom", t0);
194        uint32_t f, g;
195        wasm_buf *b = wasm_builder_func_add(&t.w, ty, "", &f);
196        wb_op_u32(b, 0x20, 0);
197        wb_op_u32(b, 0x20, 1);
198        wb_op_u32(b, 0x10, 0);
199        wb_i32_const(b, 1);
200        wb_op(b, 0x6a);
201        b = wasm_builder_func_add(&t.w, t0, "", &g);
202        wb_op_u32(b, 0x10, 1);
203        wasm_builder_add_export(&t.w, "f", WASM_EXTERN_FUNC, f);
204        wasm_builder_add_export(&t.w, "g", WASM_EXTERN_FUNC, g);
205        int calls = 0;
206        wasm_host_func hosts[] = {{"mesh", "add", "ii>i", host_add, &calls}, {"mesh", "boom", ">", host_trap, NULL}};
207        CHECK(finish(&t, "ii>i", hosts, 2, NULL));
208        uint64_t io[2] = {40, 1};
209        CHECK_I(wasm_call(t.I, (uint32_t)t.f, io), WASM_NUM_OK);
210        CHECK_U(io[0], 42);
211        CHECK_I(calls, 1);
212        CHECK_I(wasm_call(t.I, (uint32_t)wasm_instance_func(t.I, "g", ">", err, sizeof err), io), WASM_TRAP_UNREACHABLE);
213        CHECK_I(wasm_instance_func(t.I, "f", "i>i", err, sizeof err), -1);
214        CHECK(strstr(err, "type mismatch") != NULL);
215        wasm_instance_free(t.I);
216        t.I = wasm_instantiate(t.c, hosts, 1, NULL, NULL, err, sizeof err);
217        CHECK(t.I == NULL);
218        CHECK_S(err, "unknown import: `mesh::boom` has not been defined");
219        wasm_host_func wrong[] = {{"mesh", "add", "i>i", host_add, &calls}, {"mesh", "boom", ">", host_trap, NULL}};
220        t.I = wasm_instantiate(t.c, wrong, 2, NULL, NULL, err, sizeof err);
221        CHECK(t.I == NULL);
222        CHECK_S(err, "incompatible import type for `mesh::add`");
223        done(&t);
224    }
225    /* Store limits: a memory past the limit fails to instantiate; growth stops at it. */
226    {
227        T t;
228        wasm_buf *b = start(&t, ">i", "");
229        wb_i32_const(b, 1);
230        wb_op(b, 0x40);
231        wb_byte(b, 0);
232        wasm_store_limits small = {WASM_PAGE / 2, 0, 0, 0, 0};
233        CHECK(!finish(&t, ">i", NULL, 0, &small));
234        CHECK_S(err, "memory minimum size of 1 pages exceeds memory limits");
235        wasm_compiled_free(t.c);
236        t.c = NULL;
237        wasm_store_limits one = {WASM_PAGE, 0, 0, 0, 0};
238        CHECK(finish(&t, ">i", NULL, 0, &one));
239        uint64_t io[1];
240        CHECK_I(wasm_call(t.I, (uint32_t)t.f, io), WASM_NUM_OK);
241        CHECK_U(io[0], 0xffffffffu);
242        done(&t);
243    }
244    /* Interrupts: a loop that never ends traps once the epoch reaches the deadline. */
245    {
246        T t;
247        wasm_buf *b = start(&t, ">", "");
248        wb_op(b, 0x03);
249        wb_byte(b, 0x40);
250        wb_op_u32(b, 0x0c, 0);
251        wb_op(b, 0x0b);
252        CHECK(finish(&t, ">", NULL, 0, NULL));
253        static _Atomic uint64_t epoch = 5;
254        wasm_set_epoch(t.I, &epoch);
255        wasm_set_deadline(t.I, 5);
256        uint64_t io[1];
257        CHECK_I(wasm_call(t.I, (uint32_t)t.f, io), WASM_TRAP_INTERRUPT);
258        CHECK_U(wasm_deadline(t.I), 5);
259        done(&t);
260    }
261    /* A start function runs at instantiation; a trapping one fails it. */
262    {
263        T t;
264        wasm_buf *b = start(&t, ">i", "");
265        wb_op_u32(b, 0x23, 0);
266        uint32_t s;
267        wasm_buf *sb = wasm_builder_func_add(&t.w, wasm_builder_type(&t.w, ">"), "", &s);
268        wb_i32_const(sb, 77);
269        wb_op_u32(sb, 0x24, 0);
270        t.w.has_start = true;
271        t.w.start = s;
272        CHECK(finish(&t, ">i", NULL, 0, NULL));
273        uint64_t io[1];
274        CHECK_I(wasm_call(t.I, (uint32_t)t.f, io), WASM_NUM_OK);
275        CHECK_U(io[0], 77);
276        done(&t);
277        b = start(&t, ">", "");
278        sb = wasm_builder_func_add(&t.w, wasm_builder_type(&t.w, ">"), "", &s);
279        wb_op(sb, 0x00);
280        t.w.has_start = true;
281        t.w.start = s;
282        CHECK(!finish(&t, ">", NULL, 0, NULL));
283        CHECK_S(err, "wasm trap: wasm `unreachable` instruction executed (in the start function)");
284        done(&t);
285    }
286    /* Not run: SIMD (refused at compile, with the executor's reason). */
287    {
288        T t;
289        wasm_buf *b = start(&t, ">", "");
290        wb_byte(b, 0xfd);
291        wb_u32(b, 0x0c);
292        for (int i = 0; i < 16; i++) wb_byte(b, 0);
293        wb_op(b, 0x1a);
294        CHECK(!finish(&t, ">", NULL, 0, NULL));
295        done(&t);
296        wasm_buf out;
297        b = start(&t, ">", "");
298        wb_byte(b, 0xfd);
299        wb_u32(b, 0x0c);
300        for (int i = 0; i < 16; i++) wb_byte(b, 0);
301        wb_op(b, 0x1a);
302        wasm_builder_finish(&t.w, &out);
303        t.c = wasm_compile(out.data, out.len, WASM_F_INTERP | WASM_F_SIMD, err, sizeof err);
304        CHECK(t.c == NULL);
305        CHECK(strstr(err, "SIMD is not supported by this executor") != NULL);
306        wasm_buf_free(&out);
307        done(&t);
308    }
309    return mesh_check_done();
310}