==> 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}