/src/wasm3/source/m3_validate.c
Line | Count | Source |
1 | | // |
2 | | // m3_validate.c |
3 | | // |
4 | | // Pre-pass WebAssembly bytecode validator. |
5 | | // Implements the spec's type-checking algorithm with operand/control stacks. |
6 | | // |
7 | | |
8 | | #include "m3_validate.h" |
9 | | #include "m3_exception.h" |
10 | | #include "m3_info.h" |
11 | | |
12 | | #if d_m3EnableValidation |
13 | | |
14 | | // Sentinel type for polymorphic (unknown) operands |
15 | 6.22k | #define c_valUnknown 0xFF |
16 | | |
17 | | // ---------- Control frame ---------- |
18 | | |
19 | | typedef struct { |
20 | | m3opcode_t opcode; |
21 | | u16 height; // operand stack height at block entry |
22 | | u16 param_count; |
23 | | u16 result_count; |
24 | | IM3FuncType type; // block type (for params/results) |
25 | | bool is_unreachable; |
26 | | } ValCtrlFrame; |
27 | | |
28 | | // ---------- Validator context ---------- |
29 | | |
30 | | typedef struct { |
31 | | bytes_t wasm; |
32 | | bytes_t wasmEnd; |
33 | | IM3Module module; |
34 | | IM3Function function; |
35 | | |
36 | | u8 opd [d_m3ValStack]; |
37 | | u16 opdTop; |
38 | | |
39 | | ValCtrlFrame ctrl [d_m3ValCtrlDepth]; |
40 | | u16 ctrlTop; |
41 | | |
42 | | u8 localTypes [d_m3ValStack]; |
43 | | u16 numLocals; |
44 | | } ValCtx; |
45 | | |
46 | | // A memory op is only valid if the module defines or imports one |
47 | | static bool v_has_memory (ValCtx * v) |
48 | 14 | { |
49 | 14 | return v->module and (v->module->memoryImported or v->module->memoryDeclared); |
50 | 14 | } |
51 | | |
52 | | // Spec: the alignment immediate of a memory access must not be larger than the |
53 | | // natural alignment of the operation. Natural alignment: 8-bit=0, 16-bit=1, |
54 | | // 32-bit=2, 64-bit=3. |
55 | | static u32 v_max_align (m3opcode_t opcode) |
56 | 41 | { |
57 | 41 | switch (opcode) { |
58 | 3 | case 0x2c: case 0x2d: // i32.load8_s, i32.load8_u |
59 | 5 | case 0x30: case 0x31: // i64.load8_s, i64.load8_u |
60 | 6 | case 0x3a: // i32.store8 |
61 | 7 | case 0x3c: // i64.store8 |
62 | 7 | return 0; |
63 | 5 | case 0x2e: case 0x2f: // i32.load16_s, i32.load16_u |
64 | 7 | case 0x32: case 0x33: // i64.load16_s, i64.load16_u |
65 | 13 | case 0x3b: // i32.store16 |
66 | 16 | case 0x3d: // i64.store16 |
67 | 16 | return 1; |
68 | 3 | case 0x29: // i64.load |
69 | 3 | case 0x2b: // f64.load |
70 | 5 | case 0x37: // i64.store |
71 | 6 | case 0x39: // f64.store |
72 | 6 | return 3; |
73 | 12 | default: // 32-bit accesses, and a safe fallback |
74 | 12 | return 2; |
75 | 41 | } |
76 | 41 | } |
77 | | |
78 | | // ---------- Operand stack ---------- |
79 | | |
80 | | static M3Result v_push (ValCtx * v, u8 type) |
81 | 967 | { |
82 | 967 | if (v->opdTop >= d_m3ValStack) |
83 | 0 | return m3Err_functionStackOverflow; |
84 | 967 | v->opd[v->opdTop++] = type; |
85 | 967 | return m3Err_none; |
86 | 967 | } |
87 | | |
88 | | static M3Result v_pop (ValCtx * v, u8 * o_type) |
89 | 1.34k | { |
90 | 1.34k | ValCtrlFrame * f = &v->ctrl[v->ctrlTop - 1]; |
91 | 1.34k | if (v->opdTop == f->height) { |
92 | 875 | if (f->is_unreachable) { *o_type = c_valUnknown; return m3Err_none; } |
93 | 62 | return m3Err_functionStackUnderrun; |
94 | 875 | } |
95 | 469 | *o_type = v->opd[--v->opdTop]; |
96 | 469 | return m3Err_none; |
97 | 1.34k | } |
98 | | |
99 | | static M3Result v_pop_expect (ValCtx * v, u8 expect, u8 * o_actual) |
100 | 1.16k | { |
101 | 1.16k | u8 actual; |
102 | 1.16k | M3Result r = v_pop(v, &actual); |
103 | 1.16k | if (r) return r; |
104 | 1.10k | if (expect != c_valUnknown && actual != c_valUnknown && actual != expect) |
105 | 135 | return m3Err_typeMismatch; |
106 | 973 | *o_actual = (actual == c_valUnknown) ? expect : actual; |
107 | 973 | return m3Err_none; |
108 | 1.10k | } |
109 | | |
110 | | // ---------- Control stack ---------- |
111 | | |
112 | | static M3Result v_push_ctrl (ValCtx * v, m3opcode_t op, IM3FuncType type) |
113 | 728 | { |
114 | 728 | if (v->ctrlTop >= d_m3ValCtrlDepth) |
115 | 0 | return m3Err_functionStackOverflow; |
116 | 728 | ValCtrlFrame * f = &v->ctrl[v->ctrlTop++]; |
117 | 728 | f->opcode = op; |
118 | 728 | f->type = type; |
119 | 728 | f->param_count = type ? type->numArgs : 0; |
120 | 728 | f->result_count = type ? type->numRets : 0; |
121 | 728 | f->height = v->opdTop; |
122 | 728 | f->is_unreachable = false; |
123 | 728 | return m3Err_none; |
124 | 728 | } |
125 | | |
126 | | static M3Result v_pop_ctrl (ValCtx * v, ValCtrlFrame * o_frame) |
127 | 25 | { |
128 | 25 | if (v->ctrlTop == 0) |
129 | 0 | return m3Err_wasmMalformed; |
130 | 25 | ValCtrlFrame * f = &v->ctrl[v->ctrlTop - 1]; |
131 | | // pop result types |
132 | 25 | if (f->type) { |
133 | 60 | for (u16 i = f->result_count; i > 0; i--) { |
134 | 41 | u8 a; |
135 | 41 | M3Result r = v_pop_expect(v, f->type->types[i - 1], &a); |
136 | 41 | if (r) return r; |
137 | 41 | } |
138 | 25 | } |
139 | 19 | if (v->opdTop != f->height) |
140 | 1 | return m3Err_typeCountMismatch; |
141 | 18 | if (o_frame) *o_frame = *f; |
142 | 18 | v->ctrlTop--; |
143 | 18 | return m3Err_none; |
144 | 19 | } |
145 | | |
146 | | static void v_unreachable (ValCtx * v) |
147 | 654 | { |
148 | 654 | ValCtrlFrame * f = &v->ctrl[v->ctrlTop - 1]; |
149 | 654 | v->opdTop = f->height; |
150 | 654 | f->is_unreachable = true; |
151 | 654 | } |
152 | | |
153 | | // Label types: loop -> params, block/if/else/func -> results |
154 | | static u16 v_label_n (ValCtrlFrame * f) |
155 | 177 | { |
156 | 177 | return (f->opcode == 0x03) ? f->param_count : f->result_count; |
157 | 177 | } |
158 | | |
159 | | static u8 v_label_t (ValCtrlFrame * f, u16 i) |
160 | 19 | { |
161 | 19 | if (!f->type) return c_m3Type_none; |
162 | 19 | if (f->opcode == 0x03) |
163 | 0 | return f->type->types[f->type->numRets + i]; // params |
164 | 19 | return f->type->types[i]; // results |
165 | 19 | } |
166 | | |
167 | | // Pop label types for branch target |
168 | | static M3Result v_pop_labels (ValCtx * v, ValCtrlFrame * tgt) |
169 | 88 | { |
170 | 88 | u16 n = v_label_n(tgt); |
171 | 97 | for (u16 i = n; i > 0; i--) { |
172 | 10 | u8 a; |
173 | 10 | M3Result r = v_pop_expect(v, v_label_t(tgt, i - 1), &a); |
174 | 10 | if (r) return r; |
175 | 10 | } |
176 | 87 | return m3Err_none; |
177 | 88 | } |
178 | | |
179 | | // Push label types back |
180 | | static M3Result v_push_labels (ValCtx * v, ValCtrlFrame * tgt) |
181 | 55 | { |
182 | 55 | u16 n = v_label_n(tgt); |
183 | 58 | for (u16 i = 0; i < n; i++) { |
184 | 3 | M3Result r = v_push(v, v_label_t(tgt, i)); |
185 | 3 | if (r) return r; |
186 | 3 | } |
187 | 55 | return m3Err_none; |
188 | 55 | } |
189 | | |
190 | | // ---------- Block type resolution ---------- |
191 | | |
192 | | static M3Result v_read_blocktype (ValCtx * v, IM3FuncType * o_type) |
193 | 270 | { |
194 | 270 | if (v->wasm >= v->wasmEnd) |
195 | 8 | return m3Err_wasmUnderrun; |
196 | | |
197 | 262 | i64 type; |
198 | 262 | M3Result r = ReadLebSigned(&type, 33, &v->wasm, v->wasmEnd); |
199 | 262 | if (r) return r; |
200 | | |
201 | 254 | if (type < 0) { |
202 | 87 | u8 valtype; |
203 | 87 | r = NormalizeType(&valtype, (i8)type); |
204 | 87 | if (r) return r; |
205 | 68 | IM3Environment env = v->module->environment; |
206 | 68 | *o_type = env->retFuncTypes[valtype]; |
207 | 167 | } else { |
208 | 167 | if ((u32)type >= v->module->numFuncTypes) return m3Err_wasmMalformed; |
209 | 129 | *o_type = v->module->funcTypes[(u32)type]; |
210 | 129 | } |
211 | 197 | return m3Err_none; |
212 | 254 | } |
213 | | |
214 | | // ---------- Convenience ---------- |
215 | | |
216 | | static M3Result v_unop (ValCtx * v, u8 in, u8 out) |
217 | 185 | { |
218 | 185 | u8 a; M3Result r = v_pop_expect(v, in, &a); |
219 | 185 | if (r) return r; |
220 | 120 | return v_push(v, out); |
221 | 185 | } |
222 | | |
223 | | static M3Result v_binop (ValCtx * v, u8 t) |
224 | 101 | { |
225 | 101 | u8 a; M3Result r; |
226 | 101 | r = v_pop_expect(v, t, &a); if (r) return r; |
227 | 74 | r = v_pop_expect(v, t, &a); if (r) return r; |
228 | 74 | return v_push(v, t); |
229 | 74 | } |
230 | | |
231 | | static M3Result v_relop (ValCtx * v, u8 t) |
232 | 94 | { |
233 | 94 | u8 a; M3Result r; |
234 | 94 | r = v_pop_expect(v, t, &a); if (r) return r; |
235 | 63 | r = v_pop_expect(v, t, &a); if (r) return r; |
236 | 62 | return v_push(v, c_m3Type_i32); |
237 | 63 | } |
238 | | |
239 | | static M3Result v_testop (ValCtx * v, u8 t) |
240 | 12 | { |
241 | 12 | return v_unop(v, t, c_m3Type_i32); |
242 | 12 | } |
243 | | |
244 | | static M3Result v_cvtop (ValCtx * v, u8 in, u8 out) |
245 | 99 | { |
246 | 99 | return v_unop(v, in, out); |
247 | 99 | } |
248 | | |
249 | | |
250 | | // ---------- Main validation loop ---------- |
251 | | |
252 | | static M3Result v_validate_body (ValCtx * v) |
253 | 546 | { |
254 | 546 | M3Result r = m3Err_none; |
255 | 546 | u8 a; |
256 | | |
257 | 2.44k | while (v->wasm < v->wasmEnd) |
258 | 2.39k | { |
259 | 2.39k | m3opcode_t opcode; |
260 | 2.39k | r = Read_opcode(&opcode, &v->wasm, v->wasmEnd); |
261 | 2.39k | if (r) return r; |
262 | | |
263 | 2.39k | switch (opcode) |
264 | 2.39k | { |
265 | | // ---- Control ---- |
266 | 592 | case 0x00: // unreachable |
267 | 592 | v_unreachable(v); |
268 | 592 | break; |
269 | | |
270 | 51 | case 0x01: // nop |
271 | 51 | break; |
272 | | |
273 | 86 | case 0x02: // block |
274 | 203 | case 0x03: // loop |
275 | 270 | case 0x04: // if |
276 | 270 | { |
277 | 270 | IM3FuncType bt; |
278 | 270 | r = v_read_blocktype(v, &bt); |
279 | 270 | if (r) return r; |
280 | 197 | if (opcode == 0x04) { |
281 | 31 | r = v_pop_expect(v, c_m3Type_i32, &a); |
282 | 31 | if (r) return r; |
283 | 31 | } |
284 | | // Pop block params from caller stack |
285 | 189 | if (bt) { |
286 | 234 | for (u16 i = bt->numArgs; i > 0; i--) { |
287 | 58 | r = v_pop_expect(v, bt->types[bt->numRets + i - 1], &a); |
288 | 58 | if (r) return r; |
289 | 58 | } |
290 | 183 | } |
291 | 182 | r = v_push_ctrl(v, opcode, bt); |
292 | 182 | if (r) return r; |
293 | | // Push params inside block |
294 | 182 | if (bt) { |
295 | 227 | for (u16 i = 0; i < bt->numArgs; i++) { |
296 | 51 | r = v_push(v, bt->types[bt->numRets + i]); |
297 | 51 | if (r) return r; |
298 | 51 | } |
299 | 176 | } |
300 | 182 | break; |
301 | 182 | } |
302 | | |
303 | 182 | case 0x05: // else |
304 | 5 | { |
305 | 5 | ValCtrlFrame frame; |
306 | 5 | r = v_pop_ctrl(v, &frame); |
307 | 5 | if (r) return r; |
308 | 1 | if (frame.opcode != 0x04) |
309 | 1 | return m3Err_wasmMalformed; |
310 | 0 | r = v_push_ctrl(v, 0x05, frame.type); |
311 | 0 | if (r) return r; |
312 | 0 | if (frame.type) { |
313 | 0 | for (u16 i = 0; i < frame.type->numArgs; i++) { |
314 | 0 | r = v_push(v, frame.type->types[frame.type->numRets + i]); |
315 | 0 | if (r) return r; |
316 | 0 | } |
317 | 0 | } |
318 | 0 | break; |
319 | 0 | } |
320 | | |
321 | 20 | case 0x0b: // end |
322 | 20 | { |
323 | 20 | ValCtrlFrame frame; |
324 | 20 | r = v_pop_ctrl(v, &frame); |
325 | 20 | if (r) return r; |
326 | | // Push results |
327 | 17 | if (frame.type) { |
328 | 52 | for (u16 i = 0; i < frame.result_count; i++) { |
329 | 35 | r = v_push(v, frame.type->types[i]); |
330 | 35 | if (r) return r; |
331 | 35 | } |
332 | 17 | } |
333 | | // If this was the outermost frame, we're done |
334 | 17 | if (v->ctrlTop == 0) |
335 | 10 | return m3Err_none; |
336 | 7 | break; |
337 | 17 | } |
338 | | |
339 | 31 | case 0x0c: // br |
340 | 31 | { |
341 | 31 | u32 depth; |
342 | 31 | r = ReadLEB_u32(&depth, &v->wasm, v->wasmEnd); |
343 | 31 | if (r) return r; |
344 | 30 | if (depth >= v->ctrlTop) return m3Err_wasmMalformed; |
345 | 22 | ValCtrlFrame * tgt = &v->ctrl[v->ctrlTop - 1 - depth]; |
346 | 22 | r = v_pop_labels(v, tgt); |
347 | 22 | if (r) return r; |
348 | 21 | v_unreachable(v); |
349 | 21 | break; |
350 | 22 | } |
351 | | |
352 | 74 | case 0x0d: // br_if |
353 | 74 | { |
354 | 74 | u32 depth; |
355 | 74 | r = ReadLEB_u32(&depth, &v->wasm, v->wasmEnd); |
356 | 74 | if (r) return r; |
357 | 70 | if (depth >= v->ctrlTop) return m3Err_wasmMalformed; |
358 | 65 | r = v_pop_expect(v, c_m3Type_i32, &a); |
359 | 65 | if (r) return r; |
360 | 55 | ValCtrlFrame * tgt = &v->ctrl[v->ctrlTop - 1 - depth]; |
361 | 55 | r = v_pop_labels(v, tgt); |
362 | 55 | if (r) return r; |
363 | 55 | r = v_push_labels(v, tgt); |
364 | 55 | if (r) return r; |
365 | 55 | break; |
366 | 55 | } |
367 | | |
368 | 55 | case 0x0e: // br_table |
369 | 49 | { |
370 | 49 | u32 count; |
371 | 49 | r = ReadLEB_u32(&count, &v->wasm, v->wasmEnd); |
372 | 49 | if (r) return r; |
373 | 48 | u32 defDepth = 0; |
374 | 48 | u16 arity = 0; |
375 | | // First pass: read all depths and validate arity + types match default |
376 | 48 | bytes_t savedPos = v->wasm; |
377 | | // Read all targets to find the default (last one) |
378 | 135 | for (u32 i = 0; i <= count; i++) { |
379 | 123 | u32 d; |
380 | 123 | r = ReadLEB_u32(&d, &v->wasm, v->wasmEnd); |
381 | 123 | if (r) return r; |
382 | 116 | if (d >= v->ctrlTop) return m3Err_wasmMalformed; |
383 | 87 | if (i == count) defDepth = d; |
384 | 87 | } |
385 | | // Now validate all labels match the default's types |
386 | 12 | ValCtrlFrame * defTgt = &v->ctrl[v->ctrlTop - 1 - defDepth]; |
387 | 12 | arity = v_label_n(defTgt); |
388 | 12 | v->wasm = savedPos; |
389 | 34 | for (u32 i = 0; i <= count; i++) { |
390 | 22 | u32 d; |
391 | 22 | r = ReadLEB_u32(&d, &v->wasm, v->wasmEnd); |
392 | 22 | if (r) return r; |
393 | 22 | ValCtrlFrame * t = &v->ctrl[v->ctrlTop - 1 - d]; |
394 | 22 | u16 n = v_label_n(t); |
395 | 22 | if (n != arity) return m3Err_typeCountMismatch; |
396 | | // Spec: label types must be identical, not just same arity |
397 | 25 | for (u16 j = 0; j < n; j++) { |
398 | 3 | if (v_label_t(t, j) != v_label_t(defTgt, j)) |
399 | 0 | return m3Err_typeMismatch; |
400 | 3 | } |
401 | 22 | } |
402 | 12 | r = v_pop_expect(v, c_m3Type_i32, &a); |
403 | 12 | if (r) return r; |
404 | 11 | ValCtrlFrame * dt = &v->ctrl[v->ctrlTop - 1 - defDepth]; |
405 | 11 | r = v_pop_labels(v, dt); |
406 | 11 | if (r) return r; |
407 | 11 | v_unreachable(v); |
408 | 11 | break; |
409 | 11 | } |
410 | | |
411 | 32 | case 0x0f: // return |
412 | 32 | { |
413 | 32 | IM3FuncType ft = v->function->funcType; |
414 | 32 | if (ft) { |
415 | 42 | for (u16 i = ft->numRets; i > 0; i--) { |
416 | 12 | r = v_pop_expect(v, ft->types[i - 1], &a); |
417 | 12 | if (r) return r; |
418 | 12 | } |
419 | 32 | } |
420 | 30 | v_unreachable(v); |
421 | 30 | break; |
422 | 32 | } |
423 | | |
424 | | // ---- Call ---- |
425 | 42 | case 0x10: // call |
426 | 42 | { |
427 | 42 | u32 idx; |
428 | 42 | r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd); |
429 | 42 | if (r) return r; |
430 | 39 | if (idx >= v->module->numFunctions) return m3Err_wasmMalformed; |
431 | 29 | IM3FuncType ft = v->module->functions[idx].funcType; |
432 | 29 | if (ft) { |
433 | 32 | for (u16 i = ft->numArgs; i > 0; i--) { |
434 | 5 | r = v_pop_expect(v, ft->types[ft->numRets + i - 1], &a); |
435 | 5 | if (r) return r; |
436 | 5 | } |
437 | 79 | for (u16 i = 0; i < ft->numRets; i++) { |
438 | 52 | r = v_push(v, ft->types[i]); |
439 | 52 | if (r) return r; |
440 | 52 | } |
441 | 27 | } |
442 | 27 | break; |
443 | 29 | } |
444 | | |
445 | 27 | case 0x11: // call_indirect |
446 | 11 | { |
447 | 11 | u32 typeIdx; |
448 | 11 | r = ReadLEB_u32(&typeIdx, &v->wasm, v->wasmEnd); |
449 | 11 | if (r) return r; |
450 | 11 | u32 tableIdx; |
451 | 11 | r = ReadLEB_u32(&tableIdx, &v->wasm, v->wasmEnd); |
452 | 11 | if (r) return r; |
453 | 10 | if (typeIdx >= v->module->numFuncTypes) return m3Err_wasmMalformed; |
454 | | // Spec: table must exist (MVP requires table index 0 and table must be defined) |
455 | 6 | if (tableIdx != 0) return m3Err_wasmMalformed; |
456 | 2 | if (!v->module->hasTable) return m3Err_wasmMalformed; |
457 | 0 | IM3FuncType ft = v->module->funcTypes[typeIdx]; |
458 | 0 | r = v_pop_expect(v, c_m3Type_i32, &a); // table index operand |
459 | 0 | if (r) return r; |
460 | 0 | if (ft) { |
461 | 0 | for (u16 i = ft->numArgs; i > 0; i--) { |
462 | 0 | r = v_pop_expect(v, ft->types[ft->numRets + i - 1], &a); |
463 | 0 | if (r) return r; |
464 | 0 | } |
465 | 0 | for (u16 i = 0; i < ft->numRets; i++) { |
466 | 0 | r = v_push(v, ft->types[i]); |
467 | 0 | if (r) return r; |
468 | 0 | } |
469 | 0 | } |
470 | 0 | break; |
471 | 0 | } |
472 | | |
473 | | // ---- Parametric ---- |
474 | 15 | case 0x1a: // drop |
475 | 15 | r = v_pop(v, &a); |
476 | 15 | if (r) return r; |
477 | 13 | break; |
478 | | |
479 | 181 | case 0x1b: // select |
480 | 181 | { |
481 | 181 | r = v_pop_expect(v, c_m3Type_i32, &a); |
482 | 181 | if (r) return r; |
483 | 162 | u8 t2; |
484 | 162 | r = v_pop(v, &t2); |
485 | 162 | if (r) return r; |
486 | 161 | u8 t1; |
487 | 161 | r = v_pop_expect(v, t2, &t1); |
488 | 161 | if (r) return r; |
489 | 158 | r = v_push(v, (t2 == c_valUnknown) ? t1 : t2); |
490 | 158 | if (r) return r; |
491 | 158 | break; |
492 | 158 | } |
493 | | |
494 | | // ---- Variable ---- |
495 | 158 | case 0x20: // local.get |
496 | 101 | { |
497 | 101 | u32 idx; |
498 | 101 | r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd); |
499 | 101 | if (r) return r; |
500 | 98 | if (idx >= v->numLocals) return m3Err_wasmMalformed; |
501 | 88 | r = v_push(v, v->localTypes[idx]); |
502 | 88 | if (r) return r; |
503 | 88 | break; |
504 | 88 | } |
505 | | |
506 | 88 | case 0x21: // local.set |
507 | 36 | { |
508 | 36 | u32 idx; |
509 | 36 | r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd); |
510 | 36 | if (r) return r; |
511 | 35 | if (idx >= v->numLocals) return m3Err_wasmMalformed; |
512 | 24 | r = v_pop_expect(v, v->localTypes[idx], &a); |
513 | 24 | if (r) return r; |
514 | 19 | break; |
515 | 24 | } |
516 | | |
517 | 32 | case 0x22: // local.tee |
518 | 32 | { |
519 | 32 | u32 idx; |
520 | 32 | r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd); |
521 | 32 | if (r) return r; |
522 | 31 | if (idx >= v->numLocals) return m3Err_wasmMalformed; |
523 | 22 | r = v_pop_expect(v, v->localTypes[idx], &a); |
524 | 22 | if (r) return r; |
525 | 19 | r = v_push(v, v->localTypes[idx]); |
526 | 19 | if (r) return r; |
527 | 19 | break; |
528 | 19 | } |
529 | | |
530 | 19 | case 0x23: // global.get |
531 | 2 | { |
532 | 2 | u32 idx; |
533 | 2 | r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd); |
534 | 2 | if (r) return r; |
535 | 1 | if (idx >= v->module->numGlobals) return m3Err_wasmMalformed; |
536 | 0 | r = v_push(v, v->module->globals[idx].type); |
537 | 0 | if (r) return r; |
538 | 0 | break; |
539 | 0 | } |
540 | | |
541 | 6 | case 0x24: // global.set |
542 | 6 | { |
543 | 6 | u32 idx; |
544 | 6 | r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd); |
545 | 6 | if (r) return r; |
546 | 4 | if (idx >= v->module->numGlobals) return m3Err_wasmMalformed; |
547 | 0 | r = v_pop_expect(v, v->module->globals[idx].type, &a); |
548 | 0 | if (r) return r; |
549 | 0 | break; |
550 | 0 | } |
551 | | |
552 | | // ---- Memory load ---- |
553 | 8 | case 0x28: case 0x29: case 0x2a: case 0x2b: // i32/i64/f32/f64.load |
554 | 16 | case 0x2c: case 0x2d: case 0x2e: case 0x2f: // i32.load8/16 s/u |
555 | 25 | case 0x30: case 0x31: case 0x32: case 0x33: // i64.load8/16 s/u |
556 | 29 | case 0x34: case 0x35: // i64.load32 s/u |
557 | 29 | { |
558 | 29 | u32 align, offset; |
559 | 29 | r = ReadLEB_u32(&align, &v->wasm, v->wasmEnd); if (r) return r; |
560 | 26 | r = ReadLEB_u32(&offset, &v->wasm, v->wasmEnd); if (r) return r; |
561 | 21 | if (align > v_max_align(opcode)) return m3Err_wasmMalformed; |
562 | 4 | if (not v_has_memory(v)) return m3Err_wasmMalformed; |
563 | 0 | r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; |
564 | 0 | u8 result; |
565 | 0 | if (opcode == 0x28) result = c_m3Type_i32; |
566 | 0 | else if (opcode == 0x29) result = c_m3Type_i64; |
567 | 0 | else if (opcode == 0x2a) result = c_m3Type_f32; |
568 | 0 | else if (opcode == 0x2b) result = c_m3Type_f64; |
569 | 0 | else if (opcode <= 0x2f) result = c_m3Type_i32; |
570 | 0 | else result = c_m3Type_i64; |
571 | 0 | r = v_push(v, result); |
572 | 0 | if (r) return r; |
573 | 0 | break; |
574 | 0 | } |
575 | | |
576 | | // ---- Memory store ---- |
577 | 6 | case 0x36: case 0x37: case 0x38: case 0x39: // i32/i64/f32/f64.store |
578 | 14 | case 0x3a: case 0x3b: // i32.store8/16 |
579 | 21 | case 0x3c: case 0x3d: case 0x3e: // i64.store8/16/32 |
580 | 21 | { |
581 | 21 | u32 align, offset; |
582 | 21 | r = ReadLEB_u32(&align, &v->wasm, v->wasmEnd); if (r) return r; |
583 | 21 | r = ReadLEB_u32(&offset, &v->wasm, v->wasmEnd); if (r) return r; |
584 | 20 | if (align > v_max_align(opcode)) return m3Err_wasmMalformed; |
585 | 8 | if (not v_has_memory(v)) return m3Err_wasmMalformed; |
586 | 0 | u8 valtype; |
587 | 0 | if (opcode == 0x36) valtype = c_m3Type_i32; |
588 | 0 | else if (opcode == 0x37) valtype = c_m3Type_i64; |
589 | 0 | else if (opcode == 0x38) valtype = c_m3Type_f32; |
590 | 0 | else if (opcode == 0x39) valtype = c_m3Type_f64; |
591 | 0 | else if (opcode <= 0x3b) valtype = c_m3Type_i32; |
592 | 0 | else valtype = c_m3Type_i64; |
593 | 0 | r = v_pop_expect(v, valtype, &a); if (r) return r; |
594 | 0 | r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; |
595 | 0 | break; |
596 | 0 | } |
597 | | |
598 | | // ---- Memory size/grow ---- |
599 | 11 | case 0x3f: // memory.size |
600 | 11 | { |
601 | 11 | u32 memidx; |
602 | 11 | r = ReadLEB_u32(&memidx, &v->wasm, v->wasmEnd); if (r) return r; |
603 | 10 | if (memidx != 0 or not v_has_memory(v)) return m3Err_wasmMalformed; |
604 | 0 | r = v_push(v, c_m3Type_i32); if (r) return r; |
605 | 0 | break; |
606 | 0 | } |
607 | 15 | case 0x40: // memory.grow |
608 | 15 | { |
609 | 15 | u32 memidx; |
610 | 15 | r = ReadLEB_u32(&memidx, &v->wasm, v->wasmEnd); if (r) return r; |
611 | 14 | if (memidx != 0 or not v_has_memory(v)) return m3Err_wasmMalformed; |
612 | 0 | r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; |
613 | 0 | r = v_push(v, c_m3Type_i32); if (r) return r; |
614 | 0 | break; |
615 | 0 | } |
616 | | |
617 | | // ---- Constants ---- |
618 | 138 | case 0x41: { // i32.const |
619 | 138 | i32 val; |
620 | 138 | r = ReadLEB_i32(&val, &v->wasm, v->wasmEnd); if (r) return r; |
621 | 133 | r = v_push(v, c_m3Type_i32); if (r) return r; |
622 | 133 | break; |
623 | 133 | } |
624 | 164 | case 0x42: { // i64.const |
625 | 164 | i64 val; |
626 | 164 | r = ReadLEB_i64(&val, &v->wasm, v->wasmEnd); if (r) return r; |
627 | 159 | r = v_push(v, c_m3Type_i64); if (r) return r; |
628 | 159 | break; |
629 | 159 | } |
630 | 159 | case 0x43: { // f32.const |
631 | 15 | if (v->wasm + 4 > v->wasmEnd) return m3Err_wasmUnderrun; |
632 | 12 | v->wasm += 4; |
633 | 12 | r = v_push(v, c_m3Type_f32); if (r) return r; |
634 | 12 | break; |
635 | 12 | } |
636 | 12 | case 0x44: { // f64.const |
637 | 2 | if (v->wasm + 8 > v->wasmEnd) return m3Err_wasmUnderrun; |
638 | 1 | v->wasm += 8; |
639 | 1 | r = v_push(v, c_m3Type_f64); if (r) return r; |
640 | 1 | break; |
641 | 1 | } |
642 | | |
643 | | |
644 | | // ---- i32 comparison ---- |
645 | 11 | case 0x45: r = v_testop(v, c_m3Type_i32); break; // i32.eqz |
646 | 16 | case 0x46: case 0x47: case 0x48: case 0x49: case 0x4a: |
647 | 45 | case 0x4b: case 0x4c: case 0x4d: case 0x4e: case 0x4f: |
648 | 45 | r = v_relop(v, c_m3Type_i32); break; |
649 | | |
650 | | // ---- i64 comparison ---- |
651 | 1 | case 0x50: r = v_testop(v, c_m3Type_i64); break; // i64.eqz |
652 | 8 | case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: |
653 | 10 | case 0x56: case 0x57: case 0x58: case 0x59: case 0x5a: |
654 | 10 | r = v_relop(v, c_m3Type_i64); break; |
655 | | |
656 | | // ---- f32 comparison ---- |
657 | 27 | case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f: case 0x60: |
658 | 27 | r = v_relop(v, c_m3Type_f32); break; |
659 | | |
660 | | // ---- f64 comparison ---- |
661 | 12 | case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: |
662 | 12 | r = v_relop(v, c_m3Type_f64); break; |
663 | | |
664 | | // ---- i32 unary ---- |
665 | 4 | case 0x67: case 0x68: case 0x69: // clz, ctz, popcnt |
666 | 4 | r = v_unop(v, c_m3Type_i32, c_m3Type_i32); break; |
667 | | |
668 | | // ---- i32 binary ---- |
669 | 28 | case 0x6a: case 0x6b: case 0x6c: case 0x6d: case 0x6e: case 0x6f: |
670 | 33 | case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: |
671 | 38 | case 0x76: case 0x77: case 0x78: // add..rotr |
672 | 38 | r = v_binop(v, c_m3Type_i32); break; |
673 | | |
674 | | // ---- i64 unary ---- |
675 | 8 | case 0x79: case 0x7a: case 0x7b: // clz, ctz, popcnt |
676 | 8 | r = v_unop(v, c_m3Type_i64, c_m3Type_i64); break; |
677 | | |
678 | | // ---- i64 binary ---- |
679 | 32 | case 0x7c: case 0x7d: case 0x7e: case 0x7f: case 0x80: case 0x81: |
680 | 38 | case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: |
681 | 41 | case 0x88: case 0x89: case 0x8a: // add..rotr |
682 | 41 | r = v_binop(v, c_m3Type_i64); break; |
683 | | |
684 | | // ---- f32 unary ---- |
685 | 5 | case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f: |
686 | 9 | case 0x90: case 0x91: // abs, neg, ceil, floor, trunc, nearest, sqrt |
687 | 9 | r = v_unop(v, c_m3Type_f32, c_m3Type_f32); break; |
688 | | |
689 | | // ---- f32 binary ---- |
690 | 10 | case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: |
691 | 13 | case 0x97: case 0x98: // add, sub, mul, div, min, max, copysign |
692 | 13 | r = v_binop(v, c_m3Type_f32); break; |
693 | | |
694 | | // ---- f64 unary ---- |
695 | 24 | case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d: |
696 | 28 | case 0x9e: case 0x9f: // abs, neg, ceil, floor, trunc, nearest, sqrt |
697 | 28 | r = v_unop(v, c_m3Type_f64, c_m3Type_f64); break; |
698 | | |
699 | | // ---- f64 binary ---- |
700 | 8 | case 0xa0: case 0xa1: case 0xa2: case 0xa3: case 0xa4: |
701 | 9 | case 0xa5: case 0xa6: // add, sub, mul, div, min, max, copysign |
702 | 9 | r = v_binop(v, c_m3Type_f64); break; |
703 | | |
704 | | // ---- Conversions ---- |
705 | 0 | case 0xa7: r = v_cvtop(v, c_m3Type_i64, c_m3Type_i32); break; // i32.wrap/i64 |
706 | 4 | case 0xa8: case 0xa9: // i32.trunc_s/f32, i32.trunc_u/f32 |
707 | 4 | r = v_cvtop(v, c_m3Type_f32, c_m3Type_i32); break; |
708 | 4 | case 0xaa: case 0xab: // i32.trunc_s/f64, i32.trunc_u/f64 |
709 | 4 | r = v_cvtop(v, c_m3Type_f64, c_m3Type_i32); break; |
710 | 7 | case 0xac: case 0xad: // i64.extend_s/i32, i64.extend_u/i32 |
711 | 7 | r = v_cvtop(v, c_m3Type_i32, c_m3Type_i64); break; |
712 | 4 | case 0xae: case 0xaf: // i64.trunc_s/f32, i64.trunc_u/f32 |
713 | 4 | r = v_cvtop(v, c_m3Type_f32, c_m3Type_i64); break; |
714 | 4 | case 0xb0: case 0xb1: // i64.trunc_s/f64, i64.trunc_u/f64 |
715 | 4 | r = v_cvtop(v, c_m3Type_f64, c_m3Type_i64); break; |
716 | 9 | case 0xb2: case 0xb3: // f32.convert_s/i32, f32.convert_u/i32 |
717 | 9 | r = v_cvtop(v, c_m3Type_i32, c_m3Type_f32); break; |
718 | 4 | case 0xb4: case 0xb5: // f32.convert_s/i64, f32.convert_u/i64 |
719 | 4 | r = v_cvtop(v, c_m3Type_i64, c_m3Type_f32); break; |
720 | 2 | case 0xb6: // f32.demote/f64 |
721 | 2 | r = v_cvtop(v, c_m3Type_f64, c_m3Type_f32); break; |
722 | 5 | case 0xb7: case 0xb8: // f64.convert_s/i32, f64.convert_u/i32 |
723 | 5 | r = v_cvtop(v, c_m3Type_i32, c_m3Type_f64); break; |
724 | 11 | case 0xb9: case 0xba: // f64.convert_s/i64, f64.convert_u/i64 |
725 | 11 | r = v_cvtop(v, c_m3Type_i64, c_m3Type_f64); break; |
726 | 5 | case 0xbb: // f64.promote/f32 |
727 | 5 | r = v_cvtop(v, c_m3Type_f32, c_m3Type_f64); break; |
728 | 7 | case 0xbc: // i32.reinterpret/f32 |
729 | 7 | r = v_cvtop(v, c_m3Type_f32, c_m3Type_i32); break; |
730 | 6 | case 0xbd: // i64.reinterpret/f64 |
731 | 6 | r = v_cvtop(v, c_m3Type_f64, c_m3Type_i64); break; |
732 | 15 | case 0xbe: // f32.reinterpret/i32 |
733 | 15 | r = v_cvtop(v, c_m3Type_i32, c_m3Type_f32); break; |
734 | 3 | case 0xbf: // f64.reinterpret/i64 |
735 | 3 | r = v_cvtop(v, c_m3Type_i64, c_m3Type_f64); break; |
736 | | |
737 | | // ---- Sign-extension (MVP post) ---- |
738 | 8 | case 0xc0: case 0xc1: // i32.extend8_s, i32.extend16_s |
739 | 8 | r = v_unop(v, c_m3Type_i32, c_m3Type_i32); break; |
740 | 17 | case 0xc2: case 0xc3: case 0xc4: // i64.extend8/16/32_s |
741 | 17 | r = v_unop(v, c_m3Type_i64, c_m3Type_i64); break; |
742 | | |
743 | | // ---- 0xFC prefix (saturating truncations + bulk memory) ---- |
744 | 24 | case 0xfc: |
745 | 24 | { |
746 | 24 | u32 sub; |
747 | 24 | r = ReadLEB_u32(&sub, &v->wasm, v->wasmEnd); |
748 | 24 | if (r) return r; |
749 | 24 | switch (sub) { |
750 | 7 | case 0x00: case 0x01: // i32.trunc_sat_f32_s/u |
751 | 7 | r = v_cvtop(v, c_m3Type_f32, c_m3Type_i32); break; |
752 | 2 | case 0x02: case 0x03: // i32.trunc_sat_f64_s/u |
753 | 2 | r = v_cvtop(v, c_m3Type_f64, c_m3Type_i32); break; |
754 | 0 | case 0x04: case 0x05: // i64.trunc_sat_f32_s/u |
755 | 0 | r = v_cvtop(v, c_m3Type_f32, c_m3Type_i64); break; |
756 | 0 | case 0x06: case 0x07: // i64.trunc_sat_f64_s/u |
757 | 0 | r = v_cvtop(v, c_m3Type_f64, c_m3Type_i64); break; |
758 | 5 | case 0x0a: // memory.copy |
759 | 5 | { |
760 | 5 | u32 dst, src; |
761 | 5 | r = ReadLEB_u32(&dst, &v->wasm, v->wasmEnd); if (r) return r; |
762 | 4 | r = ReadLEB_u32(&src, &v->wasm, v->wasmEnd); if (r) return r; |
763 | 3 | r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // n |
764 | 3 | r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // src |
765 | 2 | r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // dst |
766 | 2 | break; |
767 | 2 | } |
768 | 8 | case 0x0b: // memory.fill |
769 | 8 | { |
770 | 8 | u32 memidx; |
771 | 8 | r = ReadLEB_u32(&memidx, &v->wasm, v->wasmEnd); if (r) return r; |
772 | 8 | r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // n |
773 | 6 | r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // val |
774 | 6 | r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // dst |
775 | 6 | break; |
776 | 6 | } |
777 | 6 | default: |
778 | | // Unknown FC sub-opcode: skip validation (allow forward compat) |
779 | 2 | break; |
780 | 24 | } |
781 | 19 | break; |
782 | 24 | } |
783 | | |
784 | 58 | default: |
785 | | // Unknown opcode - skip rather than fail for forward compat |
786 | | // (the compiler will reject truly unsupported ops later) |
787 | 58 | break; |
788 | | |
789 | 2.39k | } // switch |
790 | | |
791 | 2.02k | if (r) return r; |
792 | | |
793 | 2.02k | } // while |
794 | | |
795 | | // If we ran out of bytes without hitting the final end |
796 | 50 | return m3Err_wasmMalformed; |
797 | 546 | } |
798 | | |
799 | | // ---------- Public entry point ---------- |
800 | | |
801 | | M3Result ValidateFunction (IM3Function i_function) |
802 | 580 | { |
803 | 580 | if (!i_function->wasm) return m3Err_none; |
804 | | |
805 | 580 | IM3FuncType funcType = i_function->funcType; |
806 | 580 | IM3Module module = i_function->module; |
807 | | |
808 | | // Set up context on stack |
809 | 580 | ValCtx v; |
810 | 580 | memset(&v, 0, sizeof(v)); |
811 | 580 | v.module = module; |
812 | 580 | v.function = i_function; |
813 | 580 | v.wasm = i_function->wasm; |
814 | 580 | v.wasmEnd = i_function->wasmEnd; |
815 | | |
816 | | // Skip code size LEB |
817 | 580 | u32 size; |
818 | 580 | M3Result r = ReadLEB_u32(&size, &v.wasm, v.wasmEnd); |
819 | 580 | if (r) return r; |
820 | | |
821 | | // Parse locals |
822 | 580 | u32 numLocalBlocks; |
823 | 580 | r = ReadLEB_u32(&numLocalBlocks, &v.wasm, v.wasmEnd); |
824 | 580 | if (r) return r; |
825 | | |
826 | | // First: params. Running out of room has to be an error, not a truncation: |
827 | | // a short localTypes would make later local.get indices read as unknown |
828 | 579 | u16 numParams = funcType ? funcType->numArgs : 0; |
829 | 579 | if (numParams > d_m3ValStack) return m3Err_functionStackOverflow; |
830 | 694 | for (u16 i = 0; i < numParams; i++) { |
831 | 115 | v.localTypes[v.numLocals++] = funcType->types[funcType->numRets + i]; |
832 | 115 | } |
833 | | |
834 | | // Then: declared locals |
835 | 793 | for (u32 b = 0; b < numLocalBlocks; b++) { |
836 | 247 | u32 count; |
837 | 247 | r = ReadLEB_u32(&count, &v.wasm, v.wasmEnd); |
838 | 247 | if (r) return r; |
839 | 229 | i8 waType; |
840 | 229 | r = ReadLEB_i7(&waType, &v.wasm, v.wasmEnd); |
841 | 229 | if (r) return r; |
842 | 222 | u8 normalized; |
843 | 222 | r = NormalizeType(&normalized, waType); |
844 | 222 | if (r) return r; |
845 | 216 | if (count > (u32) (d_m3ValStack - v.numLocals)) return m3Err_functionStackOverflow; |
846 | 119k | for (u32 c = 0; c < count; c++) { |
847 | 119k | v.localTypes[v.numLocals++] = normalized; |
848 | 119k | } |
849 | 214 | } |
850 | | |
851 | | // Push the function-level control frame |
852 | 546 | r = v_push_ctrl(&v, 0x00, funcType); // opcode 0x00 marks function frame |
853 | 546 | if (r) return r; |
854 | | |
855 | | // Push params onto operand stack (they're part of the function body's initial stack) |
856 | | // Actually per the spec, locals are indexed but not on the operand stack. |
857 | | // The function frame's params are NOT pushed to the operand stack. |
858 | | // Only block params would be pushed (and for the function frame there are no block params |
859 | | // since the function body's "block type" has results = function returns, params = 0). |
860 | | // The function frame's label_types = results (since it's not a loop). |
861 | | |
862 | | // Validate the body |
863 | 546 | r = v_validate_body(&v); |
864 | 546 | if (r) return r; |
865 | | |
866 | | // After validation, control stack should be empty |
867 | 10 | if (v.ctrlTop != 0) |
868 | 0 | return m3Err_wasmMalformed; |
869 | | |
870 | 10 | return m3Err_none; |
871 | 10 | } |
872 | | |
873 | | #else // !d_m3EnableValidation |
874 | | |
875 | | M3Result ValidateFunction (IM3Function i_function) |
876 | | { |
877 | | (void)i_function; |
878 | | return m3Err_none; |
879 | | } |
880 | | |
881 | | #endif // d_m3EnableValidation |