Coverage Report

Created: 2026-09-13 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
// clang-format off
15
16
// The spec's bottom type: an operand of unknown type on an unreachable stack,
17
// which unifies with every concrete type. Deliberately not c_m3Type_unknown -
18
// that one means "invalid type" and must never be accepted by a type check.
19
61.4k
#define c_valBottom         0xFF
20
21
// ---------- Control frame ----------
22
23
typedef struct {
24
    m3opcode_t  opcode;
25
    u16         height;         // operand stack height at block entry
26
    u16         param_count;
27
    u16         result_count;
28
    IM3FuncType type;           // block type (for params/results)
29
    bool        is_unreachable;
30
} ValCtrlFrame;
31
32
// ---------- Validator context ----------
33
34
struct ValCtx {
35
    bytes_t     wasm;
36
    bytes_t     wasmEnd;
37
    IM3Module   module;
38
    IM3Function function;
39
40
    m3type_t    opd [d_m3ValStack];
41
    u16         opdTop;
42
43
    ValCtrlFrame ctrl [d_m3ValCtrlDepth];
44
    u16          ctrlTop;
45
46
    m3type_t    localTypes [d_m3ValStack];
47
    u16         numLocals;
48
};
49
50
// A memory op is only valid if the memory it names is in the module's index
51
// space; for a module that declares none, every index is out of range.
52
static bool v_has_memory_idx (ValCtx * v, u32 i_memoryIdx)
53
25
{
54
25
    return v->module and i_memoryIdx < v->module->numMemories;
55
25
}
56
57
// The type of the addresses, sizes and page counts a memory instruction takes:
58
// whichever the memory it names was declared with. Only ever called after
59
// v_has_memory_idx has said the index is in range.
60
static m3type_t v_memory_addrtype (ValCtx * v, u32 i_memoryIdx)
61
41
{
62
41
    return Memory_AddrType (v->module->memories [i_memoryIdx]);
63
41
}
64
65
// Spec: the static offset of a memory access must be in range of the address
66
// type, so anything at all for a 64-bit memory and below 2^32 for a 32-bit one.
67
static bool v_offset_in_range (ValCtx * v, u32 i_memoryIdx, u64 i_offset)
68
21
{
69
21
    return v_memory_addrtype (v, i_memoryIdx) == c_m3Type_i64 or i_offset <= 0xFFFFFFFFull;
70
21
}
71
72
// The type a table's indexes and sizes are given in - table64 is the same
73
// choice as memory64, made per table. Only ever called once the table index has
74
// been checked against the module's index space.
75
static m3type_t v_table_addrtype (ValCtx * v, u32 i_tableIdx)
76
380
{
77
380
    return Table_AddrType (v->module->tables [i_tableIdx]);
78
380
}
79
80
// Spec: the alignment immediate of a memory access must not be larger than the
81
// natural alignment of the operation. Natural alignment: 8-bit=0, 16-bit=1,
82
// 32-bit=2, 64-bit=3.
83
static u32 v_max_align (m3opcode_t opcode)
84
23
{
85
23
    switch (opcode) {
86
0
    case 0x2c: case 0x2d:   // i32.load8_s, i32.load8_u
87
1
    case 0x30: case 0x31:   // i64.load8_s, i64.load8_u
88
14
    case 0x3a:              // i32.store8
89
14
    case 0x3c:              // i64.store8
90
14
        return 0;
91
0
    case 0x2e: case 0x2f:   // i32.load16_s, i32.load16_u
92
0
    case 0x32: case 0x33:   // i64.load16_s, i64.load16_u
93
0
    case 0x3b:              // i32.store16
94
0
    case 0x3d:              // i64.store16
95
0
        return 1;
96
0
    case 0x29:              // i64.load
97
0
    case 0x2b:              // f64.load
98
0
    case 0x37:              // i64.store
99
0
    case 0x39:              // f64.store
100
0
        return 3;
101
9
    default:                // 32-bit accesses, and a safe fallback
102
9
        return 2;
103
23
    }
104
23
}
105
106
// ---------- Operand stack ----------
107
108
static M3Result v_push (ValCtx * v, m3type_t type)
109
6.01k
{
110
6.01k
    if (v->opdTop >= d_m3ValStack)
111
0
        return m3Err_functionStackOverflow;
112
6.01k
    v->opd[v->opdTop++] = type;
113
6.01k
    return m3Err_none;
114
6.01k
}
115
116
static M3Result v_pop (ValCtx * v, m3type_t * o_type)
117
9.65k
{
118
9.65k
    ValCtrlFrame * f = &v->ctrl[v->ctrlTop - 1];
119
9.65k
    if (v->opdTop == f->height) {
120
5.66k
        if (f->is_unreachable) { *o_type = c_valBottom; return m3Err_none; }
121
3
        return m3Err_functionStackUnderrun;
122
5.66k
    }
123
3.99k
    *o_type = v->opd[--v->opdTop];
124
3.99k
    return m3Err_none;
125
9.65k
}
126
127
static M3Result v_pop_expect (ValCtx * v, m3type_t expect, m3type_t * o_actual)
128
8.75k
{
129
8.75k
    m3type_t actual = c_valBottom;
130
8.75k
    M3Result r = v_pop(v, &actual);
131
8.75k
    if (r) return r;
132
8.74k
    if (expect != c_valBottom && actual != c_valBottom && actual != expect)
133
44
        return m3Err_typeMismatch;
134
8.70k
    *o_actual = (actual == c_valBottom) ? expect : actual;
135
8.70k
    return m3Err_none;
136
8.74k
}
137
138
// ---------- Control stack ----------
139
140
static M3Result v_push_ctrl (ValCtx * v, m3opcode_t op, IM3FuncType type)
141
842
{
142
842
    if (v->ctrlTop >= d_m3ValCtrlDepth)
143
0
        return m3Err_functionStackOverflow;
144
842
    ValCtrlFrame * f = &v->ctrl[v->ctrlTop++];
145
842
    f->opcode       = op;
146
842
    f->type         = type;
147
842
    f->param_count  = type ? type->numArgs : 0;
148
842
    f->result_count = type ? type->numRets : 0;
149
842
    f->height       = v->opdTop;
150
842
    f->is_unreachable = false;
151
842
    return m3Err_none;
152
842
}
153
154
static M3Result v_pop_ctrl (ValCtx * v, ValCtrlFrame * o_frame)
155
587
{
156
587
    if (v->ctrlTop == 0)
157
0
        return m3Err_wasmMalformed;
158
587
    ValCtrlFrame * f = &v->ctrl[v->ctrlTop - 1];
159
    // pop result types
160
587
    if (f->type) {
161
654
        for (u16 i = f->result_count; i > 0; i--) {
162
75
            m3type_t a;
163
75
            M3Result r = v_pop_expect(v, BaseTypeOf(f->type->types[i - 1]), &a);
164
75
            if (r) return r;
165
75
        }
166
580
    }
167
586
    if (v->opdTop != f->height)
168
15
        return m3Err_typeCountMismatch;
169
571
    if (o_frame) *o_frame = *f;
170
571
    v->ctrlTop--;
171
571
    return m3Err_none;
172
586
}
173
174
static void v_unreachable (ValCtx * v)
175
3.67k
{
176
3.67k
    ValCtrlFrame * f = &v->ctrl[v->ctrlTop - 1];
177
3.67k
    v->opdTop = f->height;
178
3.67k
    f->is_unreachable = true;
179
3.67k
}
180
181
// Label types: loop -> params, block/if/else/func -> results
182
static u16 v_label_n (ValCtrlFrame * f)
183
1.08k
{
184
1.08k
    return (f->opcode == 0x03) ? f->param_count : f->result_count;
185
1.08k
}
186
187
static m3type_t v_label_t (ValCtrlFrame * f, u16 i)
188
203
{
189
203
    if (!f->type) return c_m3Type_none;
190
203
    if (f->opcode == 0x03)
191
148
        return BaseTypeOf(f->type->types[f->type->numRets + i]); // params
192
55
    return BaseTypeOf(f->type->types[i]); // results
193
203
}
194
195
// The operand i_depth below the top, without popping. Anything at or below the
196
// current frame's height is implicitly bottom in an unreachable frame; reporting
197
// unknown there defers a genuine underrun to the pop that follows.
198
static m3type_t v_peek (ValCtx * v, u16 i_depth)
199
51
{
200
51
    ValCtrlFrame * f = &v->ctrl[v->ctrlTop - 1];
201
202
51
    if (v->opdTop <= i_depth || (u16)(v->opdTop - i_depth - 1) < f->height)
203
28
        return c_valBottom;
204
205
23
    return v->opd[v->opdTop - i_depth - 1];
206
51
}
207
208
// Pop label types for branch target
209
static M3Result v_pop_labels (ValCtx * v, ValCtrlFrame * tgt)
210
442
{
211
442
    u16 n = v_label_n(tgt);
212
538
    for (u16 i = n; i > 0; i--) {
213
97
        m3type_t a;
214
97
        M3Result r = v_pop_expect(v, v_label_t(tgt, i - 1), &a);
215
97
        if (r) return r;
216
97
    }
217
441
    return m3Err_none;
218
442
}
219
220
// Push label types back
221
static M3Result v_push_labels (ValCtx * v, ValCtrlFrame * tgt)
222
296
{
223
296
    u16 n = v_label_n(tgt);
224
351
    for (u16 i = 0; i < n; i++) {
225
55
        M3Result r = v_push(v, v_label_t(tgt, i));
226
55
        if (r) return r;
227
55
    }
228
296
    return m3Err_none;
229
296
}
230
231
// ---------- Block type resolution ----------
232
233
static M3Result v_read_blocktype (ValCtx * v, IM3FuncType * o_type)
234
333
{
235
333
    if (v->wasm >= v->wasmEnd)
236
0
        return m3Err_wasmUnderrun;
237
238
#if d_m3HasTypedRefs
239
    if (*v->wasm == d_waEncode_ref or *v->wasm == d_waEncode_refNull) {
240
        m3type_t refType;
241
        M3Result rr = ParseValueType(v->module, &refType, &v->wasm, v->wasmEnd);
242
        if (rr) return rr;
243
        *o_type = v->module->environment->retFuncTypes[BaseTypeOf(refType)];
244
        return m3Err_none;
245
    }
246
#endif
247
248
333
    i64 type;
249
333
    M3Result r = ReadLebSigned(&type, 33, &v->wasm, v->wasmEnd);
250
333
    if (r) return r;
251
252
331
    if (type < 0) {
253
73
        if (type < -64) return m3Err_invalidTypeId;
254
59
        u8 valtype;
255
59
        r = NormalizeType(&valtype, (i8)type);
256
59
        if (r) return r;
257
59
        IM3Environment env = v->module->environment;
258
59
        *o_type = env->retFuncTypes[valtype];
259
258
    } else {
260
258
        if ((u32)type >= v->module->numFuncTypes)
261
5
            return m3Err_unknownType;
262
253
        *o_type = v->module->funcTypes[(u32)type];
263
253
    }
264
312
    return m3Err_none;
265
331
}
266
267
// ---------- Convenience ----------
268
269
static M3Result v_unop (ValCtx * v, m3type_t in, m3type_t out)
270
1.45k
{
271
1.45k
    m3type_t a; M3Result r = v_pop_expect(v, in, &a);
272
1.45k
    if (r) return r;
273
1.44k
    return v_push(v, out);
274
1.45k
}
275
276
// return_call/return_call_indirect return the callee's results straight to the enclosing
277
// function's caller, so the callee's result types must be the enclosing function's
278
static M3Result v_check_tail_results (ValCtx * v, IM3FuncType i_calleeType)
279
62
{
280
62
    IM3FuncType ft = v->function ? v->function->funcType : NULL;
281
282
62
    u16 numCalleeRets = i_calleeType ? i_calleeType->numRets : 0;
283
62
    u16 numFuncRets   = ft ? ft->numRets : 0;
284
285
62
    if (numCalleeRets != numFuncRets)
286
0
        return m3Err_typeMismatch;
287
288
62
    for (u16 i = 0; i < numCalleeRets; i++) {
289
0
        if (i_calleeType->types[i] != ft->types[i])
290
0
            return m3Err_typeMismatch;
291
0
    }
292
293
62
    return m3Err_none;
294
62
}
295
296
static M3Result v_binop (ValCtx * v, m3type_t t)
297
1.19k
{
298
1.19k
    m3type_t a; M3Result r;
299
1.19k
    r = v_pop_expect(v, t, &a); if (r) return r;
300
1.19k
    r = v_pop_expect(v, t, &a); if (r) return r;
301
1.19k
    return v_push(v, t);
302
1.19k
}
303
304
static M3Result v_relop (ValCtx * v, m3type_t t)
305
728
{
306
728
    m3type_t a; M3Result r;
307
728
    r = v_pop_expect(v, t, &a); if (r) return r;
308
720
    r = v_pop_expect(v, t, &a); if (r) return r;
309
717
    return v_push(v, c_m3Type_i32);
310
720
}
311
312
static M3Result v_testop (ValCtx * v, m3type_t t)
313
40
{
314
40
    return v_unop(v, t, c_m3Type_i32);
315
40
}
316
317
static M3Result v_cvtop (ValCtx * v, m3type_t in, m3type_t out)
318
670
{
319
670
    return v_unop(v, in, out);
320
670
}
321
322
323
// ---------- Main validation loop ----------
324
325
#if d_m3HasExceptionHandling
326
327
// One catch clause of a try_table. The label is resolved in the context the
328
// try_table itself appears in - the spec checks the clauses against C, not
329
// against C extended with the block's own label - so depth 0 names the block
330
// enclosing the try, not the try. What the clause hands the label is the tag's
331
// payload, optionally followed by the exnref that reifies the caught exception.
332
static M3Result v_catch_clause (ValCtx * v)
333
44
{
334
44
    u8 kind;
335
44
    M3Result r = Read_u8(&kind, &v->wasm, v->wasmEnd);
336
44
    if (r) return r;
337
42
    if (kind > 0x03) return m3Err_wasmMalformed;
338
339
42
    bool hasTag = (kind == 0x00 || kind == 0x01);
340
42
    bool hasRef = (kind == 0x01 || kind == 0x03);
341
342
42
    IM3FuncType tagType = NULL;
343
42
    if (hasTag) {
344
5
        u32 tagIndex;
345
5
        r = ReadLEB_u32(&tagIndex, &v->wasm, v->wasmEnd);
346
5
        if (r) return r;
347
5
        if (!v->module || tagIndex >= v->module->numTags) return m3Err_unknownTag;
348
0
        tagType = v->module->tags[tagIndex].type;
349
0
    }
350
351
37
    u32 depth;
352
37
    r = ReadLEB_u32(&depth, &v->wasm, v->wasmEnd);
353
37
    if (r) return r;
354
37
    if (depth >= v->ctrlTop) return m3Err_unknownLabel;
355
356
31
    ValCtrlFrame * tgt = &v->ctrl[v->ctrlTop - 1 - depth];
357
358
31
    u16 numPayload = tagType ? tagType->numArgs : 0;
359
31
    u16 numLabel   = v_label_n(tgt);
360
361
31
    if (numLabel != numPayload + (hasRef ? 1u : 0u))
362
0
        return m3Err_typeCountMismatch;
363
364
31
    for (u16 i = 0; i < numPayload; i++) {
365
0
        if (v_label_t(tgt, i) != BaseTypeOf(tagType->types[tagType->numRets + i]))
366
0
            return m3Err_typeMismatch;
367
0
    }
368
369
31
    if (hasRef and v_label_t(tgt, numPayload) != c_m3Type_exnref)
370
0
        return m3Err_typeMismatch;
371
372
31
    return m3Err_none;
373
31
}
374
375
#endif // d_m3HasExceptionHandling
376
377
378
static M3Result v_validate_body (ValCtx * v)
379
535
{
380
535
    M3Result r = m3Err_none;
381
535
    m3type_t a = c_valBottom;
382
383
12.0k
    while (v->wasm < v->wasmEnd)
384
12.0k
    {
385
12.0k
        m3opcode_t opcode;
386
12.0k
        r = Read_opcode(&opcode, &v->wasm, v->wasmEnd);
387
12.0k
        if (r) return r;
388
389
12.0k
        switch (opcode)
390
12.0k
        {
391
        // ---- Control ----
392
3.16k
        case 0x00: // unreachable
393
3.16k
            v_unreachable(v);
394
3.16k
            break;
395
396
424
        case 0x01: // nop
397
424
            break;
398
399
76
        case 0x02: // block
400
194
        case 0x03: // loop
401
267
        case 0x04: // if
402
267
        {
403
267
            IM3FuncType bt = NULL;
404
267
            r = v_read_blocktype(v, &bt);
405
267
            if (r) return r;
406
247
            if (opcode == 0x04) {
407
63
                r = v_pop_expect(v, c_m3Type_i32, &a);
408
63
                if (r) return r;
409
63
            }
410
            // Pop block params from caller stack
411
247
            if (bt) {
412
331
                for (u16 i = bt->numArgs; i > 0; i--) {
413
91
                    r = v_pop_expect(v, BaseTypeOf(bt->types[bt->numRets + i - 1]), &a);
414
91
                    if (r) return r;
415
91
                }
416
241
            }
417
246
            r = v_push_ctrl(v, opcode, bt);
418
246
            if (r) return r;
419
            // Push params inside block
420
246
            if (bt) {
421
330
                for (u16 i = 0; i < bt->numArgs; i++) {
422
90
                    r = v_push(v, BaseTypeOf(bt->types[bt->numRets + i]));
423
90
                    if (r) return r;
424
90
                }
425
240
            }
426
246
            break;
427
246
        }
428
429
246
#if d_m3HasExceptionHandling
430
246
        case 0x1f: // try_table
431
66
        {
432
66
            IM3FuncType bt = NULL;
433
66
            r = v_read_blocktype(v, &bt);
434
66
            if (r) return r;
435
65
            if (bt) {
436
95
                for (u16 i = bt->numArgs; i > 0; i--) {
437
30
                    r = v_pop_expect(v, BaseTypeOf(bt->types[bt->numRets + i - 1]), &a);
438
30
                    if (r) return r;
439
30
                }
440
65
            }
441
            // the clauses are checked before the frame goes on, so their
442
            // labels count from outside the try block
443
65
            u32 numCatch;
444
65
            r = ReadLEB_u32(&numCatch, &v->wasm, v->wasmEnd);
445
65
            if (r) return r;
446
96
            for (u32 i = 0; i < numCatch; i++) {
447
44
                r = v_catch_clause(v);
448
44
                if (r) return r;
449
44
            }
450
451
52
            r = v_push_ctrl(v, opcode, bt);
452
52
            if (r) return r;
453
454
52
            if (bt) {
455
82
                for (u16 i = 0; i < bt->numArgs; i++) {
456
30
                    r = v_push(v, BaseTypeOf(bt->types[bt->numRets + i]));
457
30
                    if (r) return r;
458
30
                }
459
52
            }
460
52
            break;
461
52
        }
462
463
52
        case 0x08: // throw
464
1
        {
465
1
            u32 tagIndex;
466
1
            r = ReadLEB_u32(&tagIndex, &v->wasm, v->wasmEnd);
467
1
            if (r) return r;
468
0
            if (!v->module || tagIndex >= v->module->numTags) return m3Err_unknownTag;
469
0
            IM3FuncType tt = v->module->tags[tagIndex].type;
470
0
            if (tt) {
471
0
                for (u16 i = tt->numArgs; i > 0; i--) {
472
0
                    r = v_pop_expect(v, BaseTypeOf(tt->types[tt->numRets + i - 1]), &a);
473
0
                    if (r) return r;
474
0
                }
475
0
            }
476
0
            v_unreachable(v);
477
0
            break;
478
0
        }
479
480
169
        case 0x0a: // throw_ref
481
169
            r = v_pop_expect(v, c_m3Type_exnref, &a);
482
169
            if (r) return r;
483
168
            v_unreachable(v);
484
168
            break;
485
0
#endif // d_m3HasExceptionHandling
486
487
10
        case 0x05: // else
488
10
        {
489
10
            ValCtrlFrame frame;
490
10
            r = v_pop_ctrl(v, &frame);
491
10
            if (r) return r;
492
9
            if (frame.opcode != 0x04)
493
0
                return m3Err_wasmMalformed;
494
9
            r = v_push_ctrl(v, 0x05, frame.type);
495
9
            if (r) return r;
496
9
            if (frame.type) {
497
10
                for (u16 i = 0; i < frame.type->numArgs; i++) {
498
2
                    r = v_push(v, BaseTypeOf(frame.type->types[frame.type->numRets + i]));
499
2
                    if (r) return r;
500
2
                }
501
8
            }
502
9
            break;
503
9
        }
504
505
577
        case 0x0b: // end
506
577
        {
507
577
            ValCtrlFrame frame;
508
577
            r = v_pop_ctrl(v, &frame);
509
577
            if (r) return r;
510
            // Push results
511
562
            if (frame.type) {
512
625
                for (u16 i = 0; i < frame.result_count; i++) {
513
69
                    r = v_push(v, BaseTypeOf(frame.type->types[i]));
514
69
                    if (r) return r;
515
69
                }
516
556
            }
517
            // If this was the outermost frame, we're done
518
562
            if (v->ctrlTop == 0)
519
329
                return m3Err_none;
520
233
            break;
521
562
        }
522
523
233
        case 0x0c: // br
524
84
        {
525
84
            u32 depth;
526
84
            r = ReadLEB_u32(&depth, &v->wasm, v->wasmEnd);
527
84
            if (r) return r;
528
83
            if (depth >= v->ctrlTop) return m3Err_unknownLabel;
529
83
            ValCtrlFrame * tgt = &v->ctrl[v->ctrlTop - 1 - depth];
530
83
            r = v_pop_labels(v, tgt);
531
83
            if (r) return r;
532
83
            v_unreachable(v);
533
83
            break;
534
83
        }
535
536
304
        case 0x0d: // br_if
537
304
        {
538
304
            u32 depth;
539
304
            r = ReadLEB_u32(&depth, &v->wasm, v->wasmEnd);
540
304
            if (r) return r;
541
303
            if (depth >= v->ctrlTop) return m3Err_unknownLabel;
542
297
            r = v_pop_expect(v, c_m3Type_i32, &a);
543
297
            if (r) return r;
544
296
            ValCtrlFrame * tgt = &v->ctrl[v->ctrlTop - 1 - depth];
545
296
            r = v_pop_labels(v, tgt);
546
296
            if (r) return r;
547
296
            r = v_push_labels(v, tgt);
548
296
            if (r) return r;
549
296
            break;
550
296
        }
551
552
296
        case 0x0e: // br_table
553
65
        {
554
65
            u32 count;
555
65
            r = ReadLEB_u32(&count, &v->wasm, v->wasmEnd);
556
65
            if (r) return r;
557
65
            u32 defDepth = 0;
558
65
            u16 arity = 0;
559
            // First pass: read all depths and validate arity + types match default
560
65
            bytes_t savedPos = v->wasm;
561
            // Read all targets to find the default (last one)
562
321
            for (u32 i = 0; i <= count; i++) {
563
258
                u32 d;
564
258
                r = ReadLEB_u32(&d, &v->wasm, v->wasmEnd);
565
258
                if (r) return r;
566
258
                if (d >= v->ctrlTop) return m3Err_unknownLabel;
567
256
                if (i == count) defDepth = d;
568
256
            }
569
            // Now validate all labels match the default's types
570
63
            ValCtrlFrame * defTgt = &v->ctrl[v->ctrlTop - 1 - defDepth];
571
63
            arity = v_label_n(defTgt);
572
63
            v->wasm = savedPos;
573
574
63
            r = v_pop_expect(v, c_m3Type_i32, &a);      // the index, before the labels are inspected
575
63
            if (r) return r;
576
577
317
            for (u32 i = 0; i <= count; i++) {
578
254
                u32 d;
579
254
                r = ReadLEB_u32(&d, &v->wasm, v->wasmEnd);
580
254
                if (r) return r;
581
254
                ValCtrlFrame * t = &v->ctrl[v->ctrlTop - 1 - d];
582
254
                u16 n = v_label_n(t);
583
254
                if (n != arity) return m3Err_typeCountMismatch;
584
                // Spec: each target's types must be compatible with the operand
585
                // stack, not identical to the default's. In unreachable code the
586
                // operands are bottom, so the targets may legitimately differ.
587
305
                for (u16 j = 0; j < n; j++) {
588
51
                    m3type_t want = v_label_t(t, n - 1 - j);
589
51
                    m3type_t have = v_peek(v, j);
590
51
                    if (want != c_valBottom && have != c_valBottom && want != have)
591
0
                        return m3Err_typeMismatch;
592
51
                }
593
254
            }
594
63
            ValCtrlFrame * dt = &v->ctrl[v->ctrlTop - 1 - defDepth];
595
63
            r = v_pop_labels(v, dt);
596
63
            if (r) return r;
597
62
            v_unreachable(v);
598
62
            break;
599
63
        }
600
601
134
        case 0x0f: // return
602
134
        {
603
134
            IM3FuncType ft = v->function->funcType;
604
134
            if (ft) {
605
136
                for (u16 i = ft->numRets; i > 0; i--) {
606
2
                    r = v_pop_expect(v, BaseTypeOf(ft->types[i - 1]), &a);
607
2
                    if (r) return r;
608
2
                }
609
134
            }
610
134
            v_unreachable(v);
611
134
            break;
612
134
        }
613
614
        // ---- Call ----
615
77
        case 0x10: // call
616
77
        {
617
77
            u32 idx;
618
77
            r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
619
77
            if (r) return r;
620
77
            if (idx >= v->module->numFunctions) return m3Err_unknownFunction;
621
74
            IM3FuncType ft = v->module->functions[idx].funcType;
622
74
            if (ft) {
623
98
                for (u16 i = ft->numArgs; i > 0; i--) {
624
24
                    r = v_pop_expect(v, BaseTypeOf(ft->types[ft->numRets + i - 1]), &a);
625
24
                    if (r) return r;
626
24
                }
627
74
                for (u16 i = 0; i < ft->numRets; i++) {
628
0
                    r = v_push(v, BaseTypeOf(ft->types[i]));
629
0
                    if (r) return r;
630
0
                }
631
74
            }
632
74
            break;
633
74
        }
634
635
74
        case 0x11: // call_indirect
636
13
        {
637
13
            u32 typeIdx;
638
13
            r = ReadLEB_u32(&typeIdx, &v->wasm, v->wasmEnd);
639
13
            if (r) return r;
640
13
            u32 tableIdx;
641
13
            r = ReadLEB_u32(&tableIdx, &v->wasm, v->wasmEnd);
642
13
            if (r) return r;
643
13
            if (typeIdx >= v->module->numFuncTypes) return m3Err_unknownType;
644
            // the table must exist and hold funcrefs
645
12
            if (tableIdx >= v->module->numTables) return m3Err_unknownTable;
646
12
            if (v->module->tables[tableIdx]->type != c_m3Type_funcref) return m3Err_typeMismatch;
647
11
            IM3FuncType ft = v->module->funcTypes[typeIdx];
648
11
            r = v_pop_expect(v, v_table_addrtype(v, tableIdx), &a); // table index operand
649
11
            if (r) return r;
650
11
            if (ft) {
651
11
                for (u16 i = ft->numArgs; i > 0; i--) {
652
0
                    r = v_pop_expect(v, BaseTypeOf(ft->types[ft->numRets + i - 1]), &a);
653
0
                    if (r) return r;
654
0
                }
655
11
                for (u16 i = 0; i < ft->numRets; i++) {
656
0
                    r = v_push(v, BaseTypeOf(ft->types[i]));
657
0
                    if (r) return r;
658
0
                }
659
11
            }
660
11
            break;
661
11
        }
662
663
34
        case 0x12: // return_call
664
34
        {
665
34
            u32 idx;
666
34
            r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
667
34
            if (r) return r;
668
34
            if (idx >= v->module->numFunctions) return m3Err_unknownFunction;
669
34
            IM3FuncType ft = v->module->functions[idx].funcType;
670
34
            r = v_check_tail_results(v, ft);
671
34
            if (r) return r;
672
34
            if (ft) {
673
42
                for (u16 i = ft->numArgs; i > 0; i--) {
674
8
                    r = v_pop_expect(v, BaseTypeOf(ft->types[ft->numRets + i - 1]), &a);
675
8
                    if (r) return r;
676
8
                }
677
34
            }
678
34
            v_unreachable(v);
679
34
            break;
680
34
        }
681
682
33
        case 0x13: // return_call_indirect
683
33
        {
684
33
            u32 typeIdx;
685
33
            r = ReadLEB_u32(&typeIdx, &v->wasm, v->wasmEnd);
686
33
            if (r) return r;
687
33
            u32 tableIdx;
688
33
            r = ReadLEB_u32(&tableIdx, &v->wasm, v->wasmEnd);
689
33
            if (r) return r;
690
32
            if (typeIdx >= v->module->numFuncTypes) return m3Err_unknownType;
691
            // the table must exist and hold funcrefs
692
29
            if (tableIdx >= v->module->numTables) return m3Err_unknownTable;
693
28
            if (v->module->tables[tableIdx]->type != c_m3Type_funcref) return m3Err_typeMismatch;
694
28
            IM3FuncType ft = v->module->funcTypes[typeIdx];
695
28
            r = v_check_tail_results(v, ft);
696
28
            if (r) return r;
697
28
            r = v_pop_expect(v, v_table_addrtype(v, tableIdx), &a); // table index operand
698
28
            if (r) return r;
699
28
            if (ft) {
700
28
                for (u16 i = ft->numArgs; i > 0; i--) {
701
0
                    r = v_pop_expect(v, BaseTypeOf(ft->types[ft->numRets + i - 1]), &a);
702
0
                    if (r) return r;
703
0
                }
704
28
            }
705
28
            v_unreachable(v);
706
28
            break;
707
28
        }
708
709
#if d_m3HasTypedRefs
710
        case 0x14: // call_ref
711
        case 0x15: // return_call_ref
712
        {
713
            u32 typeIdx;
714
            r = ReadLEB_u32(&typeIdx, &v->wasm, v->wasmEnd);
715
            if (r) return r;
716
            if (typeIdx >= v->module->numFuncTypes) return m3Err_unknownType;
717
            IM3FuncType ft = v->module->funcTypes[typeIdx];
718
            bool isTail = (opcode == 0x15);
719
            if (isTail) {
720
                r = v_check_tail_results(v, ft);
721
                if (r) return r;
722
            }
723
            // the callee itself, as a reference to a function of this type
724
            r = v_pop_expect(v, c_m3Type_funcref, &a);
725
            if (r) return r;
726
            if (ft) {
727
                for (u16 i = ft->numArgs; i > 0; i--) {
728
                    r = v_pop_expect(v, BaseTypeOf(ft->types[ft->numRets + i - 1]), &a);
729
                    if (r) return r;
730
                }
731
                if (not isTail) {
732
                    for (u16 i = 0; i < ft->numRets; i++) {
733
                        r = v_push(v, BaseTypeOf(ft->types[i]));
734
                        if (r) return r;
735
                    }
736
                }
737
            }
738
            if (isTail) v_unreachable(v);
739
            break;
740
        }
741
742
        case 0xd4: // ref.as_non_null
743
        {
744
            r = v_pop_expect(v, c_m3Type_funcref, &a);
745
            if (r) return r;
746
            r = v_push(v, (a == c_valBottom) ? c_m3Type_funcref : a);
747
            if (r) return r;
748
            break;
749
        }
750
#endif // d_m3HasTypedRefs
751
752
        // ---- Parametric ----
753
69
        case 0x1a: // drop
754
69
            r = v_pop(v, &a);
755
69
            if (r) return r;
756
69
            break;
757
758
796
        case 0x1b: // select
759
796
        {
760
796
            r = v_pop_expect(v, c_m3Type_i32, &a);
761
796
            if (r) return r;
762
793
            m3type_t t2 = c_valBottom;
763
793
            r = v_pop(v, &t2);
764
793
            if (r) return r;
765
793
            m3type_t t1;
766
793
            r = v_pop_expect(v, t2, &t1);
767
793
            if (r) return r;
768
            // untyped select is numeric only; references need the 0x1c form
769
793
            if (t2 != c_valBottom && IsRefType(t2)) return m3Err_typeMismatch;
770
793
            r = v_push(v, (t2 == c_valBottom) ? t1 : t2);
771
793
            if (r) return r;
772
793
            break;
773
793
        }
774
775
793
#if d_m3HasRefTypes
776
793
        case 0x1c: // select with an explicit result type
777
9
        {
778
9
            u32 numTypes;
779
9
            r = ReadLEB_u32(&numTypes, &v->wasm, v->wasmEnd);
780
9
            if (r) return r;
781
9
            if (numTypes != 1) return m3Err_wasmMalformed;
782
783
9
            m3type_t selType;
784
9
            r = ParseValueType(v->module, &selType, &v->wasm, v->wasmEnd);  if (r) return r;
785
9
            u8 t = BaseTypeOf(selType);
786
787
9
            r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r;
788
9
            r = v_pop_expect(v, t, &a);            if (r) return r;
789
9
            r = v_pop_expect(v, t, &a);            if (r) return r;
790
9
            r = v_push(v, t);                      if (r) return r;
791
9
            break;
792
9
        }
793
794
59
        case 0x25: // table.get
795
121
        case 0x26: // table.set
796
121
        {
797
121
            u32 tableIdx;
798
121
            r = ReadLEB_u32(&tableIdx, &v->wasm, v->wasmEnd);
799
121
            if (r) return r;
800
121
            if (tableIdx >= v->module->numTables) return m3Err_unknownTable;
801
113
            u8 t = BaseTypeOf(v->module->tables[tableIdx]->type);
802
113
            m3type_t at = v_table_addrtype(v, tableIdx);
803
804
113
            if (opcode == 0x26) {
805
58
                r = v_pop_expect(v, t, &a);   if (r) return r;
806
58
                r = v_pop_expect(v, at, &a);  if (r) return r;
807
58
            } else {
808
55
                r = v_pop_expect(v, at, &a);  if (r) return r;
809
54
                r = v_push(v, t);             if (r) return r;
810
54
            }
811
112
            break;
812
113
        }
813
814
112
        case 0xd0: // ref.null
815
5
        {
816
#if d_m3HasTypedRefs
817
            m3type_t heapBits;
818
            r = ParseHeapType(v->module, &heapBits, &v->wasm, v->wasmEnd);  if (r) return r;
819
            u8 t = (heapBits & d_m3Type_refExtern) ? c_m3Type_externref : c_m3Type_funcref;
820
#else
821
5
            i8 waType;
822
5
            u8 t;
823
5
            r = ReadLEB_i7(&waType, &v->wasm, v->wasmEnd);   if (r) return r;
824
4
            r = NormalizeType(&t, waType);                   if (r) return r;
825
4
            if (!IsRefType(t)) return m3Err_wasmMalformed;
826
4
#endif
827
4
            r = v_push(v, t);                                if (r) return r;
828
4
            break;
829
4
        }
830
831
45
        case 0xd1: // ref.is_null
832
45
        {
833
45
            m3type_t t = c_valBottom;
834
45
            r = v_pop(v, &t);
835
45
            if (r) return r;
836
45
            if (t != c_valBottom && !IsRefType(t)) return m3Err_typeMismatch;
837
44
            r = v_push(v, c_m3Type_i32);
838
44
            if (r) return r;
839
44
            break;
840
44
        }
841
842
44
        case 0xd2: // ref.func
843
0
        {
844
0
            u32 funcIdx;
845
0
            r = ReadLEB_u32(&funcIdx, &v->wasm, v->wasmEnd);
846
0
            if (r) return r;
847
0
            if (funcIdx >= v->module->numFunctions) return m3Err_unknownFunction;
848
0
            if (!Module_IsFunctionDeclared(v->module, funcIdx)) return m3Err_undeclaredFuncRef;
849
0
            r = v_push(v, c_m3Type_funcref);
850
0
            if (r) return r;
851
0
            break;
852
0
        }
853
0
#endif
854
855
        // ---- Variable ----
856
492
        case 0x20: // local.get
857
492
        {
858
492
            u32 idx;
859
492
            r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
860
492
            if (r) return r;
861
491
            if (idx >= v->numLocals) return m3Err_unknownLocal;
862
491
            r = v_push(v, v->localTypes[idx]);
863
491
            if (r) return r;
864
491
            break;
865
491
        }
866
867
491
        case 0x21: // local.set
868
139
        {
869
139
            u32 idx;
870
139
            r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
871
139
            if (r) return r;
872
139
            if (idx >= v->numLocals) return m3Err_unknownLocal;
873
136
            r = v_pop_expect(v, v->localTypes[idx], &a);
874
136
            if (r) return r;
875
136
            break;
876
136
        }
877
878
161
        case 0x22: // local.tee
879
161
        {
880
161
            u32 idx;
881
161
            r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
882
161
            if (r) return r;
883
160
            if (idx >= v->numLocals) return m3Err_unknownLocal;
884
155
            r = v_pop_expect(v, v->localTypes[idx], &a);
885
155
            if (r) return r;
886
155
            r = v_push(v, v->localTypes[idx]);
887
155
            if (r) return r;
888
155
            break;
889
155
        }
890
891
155
        case 0x23: // global.get
892
1
        {
893
1
            u32 idx;
894
1
            r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
895
1
            if (r) return r;
896
1
            if (idx >= v->module->numGlobals) return m3Err_unknownGlobal;
897
0
            r = v_push(v, BaseTypeOf(v->module->globals[idx].type));
898
0
            if (r) return r;
899
0
            break;
900
0
        }
901
902
3
        case 0x24: // global.set
903
3
        {
904
3
            u32 idx;
905
3
            r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
906
3
            if (r) return r;
907
3
            if (idx >= v->module->numGlobals) return m3Err_unknownGlobal;
908
0
            r = v_pop_expect(v, BaseTypeOf(v->module->globals[idx].type), &a);
909
0
            if (r) return r;
910
0
            break;
911
0
        }
912
913
        // ---- Memory load ----
914
7
        case 0x28: case 0x29: case 0x2a: case 0x2b: // i32/i64/f32/f64.load
915
10
        case 0x2c: case 0x2d: case 0x2e: case 0x2f: // i32.load8/16 s/u
916
13
        case 0x30: case 0x31: case 0x32: case 0x33: // i64.load8/16 s/u
917
13
        case 0x34: case 0x35:                         // i64.load32 s/u
918
13
        {
919
13
            u32 align, memidx; u64 offset;
920
13
            r = ReadMemoryArg(&align, &memidx, &offset, &v->wasm, v->wasmEnd); if (r) return r;
921
6
            if (align > v_max_align(opcode)) return m3Err_invalidAlignment;
922
5
            if (not v_has_memory_idx(v, memidx)) return m3Err_unknownMemory;
923
5
            if (not v_offset_in_range(v, memidx, offset)) return m3Err_wasmMalformed;
924
5
            r = v_pop_expect(v, v_memory_addrtype(v, memidx), &a); if (r) return r;
925
5
            u8 result;
926
5
            if      (opcode == 0x28) result = c_m3Type_i32;
927
5
            else if (opcode == 0x29) result = c_m3Type_i64;
928
5
            else if (opcode == 0x2a) result = c_m3Type_f32;
929
1
            else if (opcode == 0x2b) result = c_m3Type_f64;
930
1
            else if (opcode <= 0x2f) result = c_m3Type_i32;
931
1
            else                     result = c_m3Type_i64;
932
5
            r = v_push(v, result);
933
5
            if (r) return r;
934
5
            break;
935
5
        }
936
937
        // ---- Memory store ----
938
5
        case 0x36: case 0x37: case 0x38: case 0x39: // i32/i64/f32/f64.store
939
16
        case 0x3a: case 0x3b:                         // i32.store8/16
940
17
        case 0x3c: case 0x3d: case 0x3e:             // i64.store8/16/32
941
17
        {
942
17
            u32 align, memidx; u64 offset;
943
17
            r = ReadMemoryArg(&align, &memidx, &offset, &v->wasm, v->wasmEnd); if (r) return r;
944
17
            if (align > v_max_align(opcode)) return m3Err_invalidAlignment;
945
16
            if (not v_has_memory_idx(v, memidx)) return m3Err_unknownMemory;
946
16
            if (not v_offset_in_range(v, memidx, offset)) return m3Err_wasmMalformed;
947
16
            u8 valtype;
948
16
            if      (opcode == 0x36) valtype = c_m3Type_i32;
949
13
            else if (opcode == 0x37) valtype = c_m3Type_i64;
950
13
            else if (opcode == 0x38) valtype = c_m3Type_f32;
951
13
            else if (opcode == 0x39) valtype = c_m3Type_f64;
952
13
            else if (opcode <= 0x3b) valtype = c_m3Type_i32;
953
0
            else                     valtype = c_m3Type_i64;
954
16
            r = v_pop_expect(v, valtype, &a); if (r) return r;
955
15
            r = v_pop_expect(v, v_memory_addrtype(v, memidx), &a); if (r) return r;
956
15
            break;
957
15
        }
958
959
        // ---- Memory size/grow ----
960
15
        case 0x3f: // memory.size
961
2
        {
962
2
            u32 memidx;
963
2
            r = ReadLEB_u32(&memidx, &v->wasm, v->wasmEnd); if (r) return r;
964
2
            if (not v_has_memory_idx(v, memidx)) return m3Err_unknownMemory;
965
0
            r = v_push(v, v_memory_addrtype(v, memidx)); if (r) return r;
966
0
            break;
967
0
        }
968
2
        case 0x40: // memory.grow
969
2
        {
970
2
            u32 memidx;
971
2
            r = ReadLEB_u32(&memidx, &v->wasm, v->wasmEnd); if (r) return r;
972
2
            if (not v_has_memory_idx(v, memidx)) return m3Err_unknownMemory;
973
0
            r = v_pop_expect(v, v_memory_addrtype(v, memidx), &a); if (r) return r;
974
0
            r = v_push(v, v_memory_addrtype(v, memidx)); if (r) return r;
975
0
            break;
976
0
        }
977
978
        // ---- Constants ----
979
585
        case 0x41: { // i32.const
980
585
            i32 val;
981
585
            r = ReadLEB_i32(&val, &v->wasm, v->wasmEnd); if (r) return r;
982
585
            r = v_push(v, c_m3Type_i32); if (r) return r;
983
585
            break;
984
585
        }
985
585
        case 0x42: { // i64.const
986
186
            i64 val;
987
186
            r = ReadLEB_i64(&val, &v->wasm, v->wasmEnd); if (r) return r;
988
185
            r = v_push(v, c_m3Type_i64); if (r) return r;
989
185
            break;
990
185
        }
991
185
        case 0x43: { // f32.const
992
27
            if (v->wasm + 4 > v->wasmEnd) return m3Err_wasmUnderrun;
993
27
            v->wasm += 4;
994
27
            r = v_push(v, c_m3Type_f32); if (r) return r;
995
27
            break;
996
27
        }
997
27
        case 0x44: { // f64.const
998
10
            if (v->wasm + 8 > v->wasmEnd) return m3Err_wasmUnderrun;
999
9
            v->wasm += 8;
1000
9
            r = v_push(v, c_m3Type_f64); if (r) return r;
1001
9
            break;
1002
9
        }
1003
1004
1005
        // ---- i32 comparison ----
1006
15
        case 0x45: r = v_testop(v, c_m3Type_i32); break; // i32.eqz
1007
250
        case 0x46: case 0x47: case 0x48: case 0x49: case 0x4a:
1008
484
        case 0x4b: case 0x4c: case 0x4d: case 0x4e: case 0x4f:
1009
484
            r = v_relop(v, c_m3Type_i32); break;
1010
1011
        // ---- i64 comparison ----
1012
25
        case 0x50: r = v_testop(v, c_m3Type_i64); break; // i64.eqz
1013
87
        case 0x51: case 0x52: case 0x53: case 0x54: case 0x55:
1014
114
        case 0x56: case 0x57: case 0x58: case 0x59: case 0x5a:
1015
114
            r = v_relop(v, c_m3Type_i64); break;
1016
1017
        // ---- f32 comparison ----
1018
86
        case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f: case 0x60:
1019
86
            r = v_relop(v, c_m3Type_f32); break;
1020
1021
        // ---- f64 comparison ----
1022
44
        case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66:
1023
44
            r = v_relop(v, c_m3Type_f64); break;
1024
1025
        // ---- i32 unary ----
1026
25
        case 0x67: case 0x68: case 0x69: // clz, ctz, popcnt
1027
25
            r = v_unop(v, c_m3Type_i32, c_m3Type_i32); break;
1028
1029
        // ---- i32 binary ----
1030
137
        case 0x6a: case 0x6b: case 0x6c: case 0x6d: case 0x6e: case 0x6f:
1031
284
        case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75:
1032
348
        case 0x76: case 0x77: case 0x78: // add..rotr
1033
348
            r = v_binop(v, c_m3Type_i32); break;
1034
1035
        // ---- i64 unary ----
1036
97
        case 0x79: case 0x7a: case 0x7b: // clz, ctz, popcnt
1037
97
            r = v_unop(v, c_m3Type_i64, c_m3Type_i64); break;
1038
1039
        // ---- i64 binary ----
1040
159
        case 0x7c: case 0x7d: case 0x7e: case 0x7f: case 0x80: case 0x81:
1041
348
        case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87:
1042
388
        case 0x88: case 0x89: case 0x8a: // add..rotr
1043
388
            r = v_binop(v, c_m3Type_i64); break;
1044
1045
        // ---- f32 unary ----
1046
138
        case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f:
1047
229
        case 0x90: case 0x91: // abs, neg, ceil, floor, trunc, nearest, sqrt
1048
229
            r = v_unop(v, c_m3Type_f32, c_m3Type_f32); break;
1049
1050
        // ---- f32 binary ----
1051
161
        case 0x92: case 0x93: case 0x94: case 0x95: case 0x96:
1052
201
        case 0x97: case 0x98: // add, sub, mul, div, min, max, copysign
1053
201
            r = v_binop(v, c_m3Type_f32); break;
1054
1055
        // ---- f64 unary ----
1056
160
        case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d:
1057
167
        case 0x9e: case 0x9f: // abs, neg, ceil, floor, trunc, nearest, sqrt
1058
167
            r = v_unop(v, c_m3Type_f64, c_m3Type_f64); break;
1059
1060
        // ---- f64 binary ----
1061
166
        case 0xa0: case 0xa1: case 0xa2: case 0xa3: case 0xa4:
1062
259
        case 0xa5: case 0xa6: // add, sub, mul, div, min, max, copysign
1063
259
            r = v_binop(v, c_m3Type_f64); break;
1064
1065
        // ---- Conversions ----
1066
29
        case 0xa7: r = v_cvtop(v, c_m3Type_i64, c_m3Type_i32); break; // i32.wrap/i64
1067
125
        case 0xa8: case 0xa9:   // i32.trunc_s/f32, i32.trunc_u/f32
1068
125
            r = v_cvtop(v, c_m3Type_f32, c_m3Type_i32); break;
1069
13
        case 0xaa: case 0xab:   // i32.trunc_s/f64, i32.trunc_u/f64
1070
13
            r = v_cvtop(v, c_m3Type_f64, c_m3Type_i32); break;
1071
24
        case 0xac: case 0xad:   // i64.extend_s/i32, i64.extend_u/i32
1072
24
            r = v_cvtop(v, c_m3Type_i32, c_m3Type_i64); break;
1073
26
        case 0xae: case 0xaf:   // i64.trunc_s/f32, i64.trunc_u/f32
1074
26
            r = v_cvtop(v, c_m3Type_f32, c_m3Type_i64); break;
1075
66
        case 0xb0: case 0xb1:   // i64.trunc_s/f64, i64.trunc_u/f64
1076
66
            r = v_cvtop(v, c_m3Type_f64, c_m3Type_i64); break;
1077
30
        case 0xb2: case 0xb3:   // f32.convert_s/i32, f32.convert_u/i32
1078
30
            r = v_cvtop(v, c_m3Type_i32, c_m3Type_f32); break;
1079
57
        case 0xb4: case 0xb5:   // f32.convert_s/i64, f32.convert_u/i64
1080
57
            r = v_cvtop(v, c_m3Type_i64, c_m3Type_f32); break;
1081
5
        case 0xb6:              // f32.demote/f64
1082
5
            r = v_cvtop(v, c_m3Type_f64, c_m3Type_f32); break;
1083
39
        case 0xb7: case 0xb8:   // f64.convert_s/i32, f64.convert_u/i32
1084
39
            r = v_cvtop(v, c_m3Type_i32, c_m3Type_f64); break;
1085
43
        case 0xb9: case 0xba:   // f64.convert_s/i64, f64.convert_u/i64
1086
43
            r = v_cvtop(v, c_m3Type_i64, c_m3Type_f64); break;
1087
23
        case 0xbb:              // f64.promote/f32
1088
23
            r = v_cvtop(v, c_m3Type_f32, c_m3Type_f64); break;
1089
23
        case 0xbc:              // i32.reinterpret/f32
1090
23
            r = v_cvtop(v, c_m3Type_f32, c_m3Type_i32); break;
1091
9
        case 0xbd:              // i64.reinterpret/f64
1092
9
            r = v_cvtop(v, c_m3Type_f64, c_m3Type_i64); break;
1093
109
        case 0xbe:              // f32.reinterpret/i32
1094
109
            r = v_cvtop(v, c_m3Type_i32, c_m3Type_f32); break;
1095
4
        case 0xbf:              // f64.reinterpret/i64
1096
4
            r = v_cvtop(v, c_m3Type_i64, c_m3Type_f64); break;
1097
1098
        // ---- Sign-extension (MVP post) ----
1099
127
        case 0xc0: case 0xc1:   // i32.extend8_s, i32.extend16_s
1100
127
            r = v_unop(v, c_m3Type_i32, c_m3Type_i32); break;
1101
102
        case 0xc2: case 0xc3: case 0xc4: // i64.extend8/16/32_s
1102
102
            r = v_unop(v, c_m3Type_i64, c_m3Type_i64); break;
1103
1104
        // ---- 0xFC prefix (saturating truncations + bulk memory) ----
1105
255
        case 0xfc:
1106
255
        {
1107
255
            u32 sub;
1108
255
            r = ReadLEB_u32(&sub, &v->wasm, v->wasmEnd);
1109
255
            if (r) return r;
1110
255
            switch (sub) {
1111
18
            case 0x00: case 0x01: // i32.trunc_sat_f32_s/u
1112
18
                r = v_cvtop(v, c_m3Type_f32, c_m3Type_i32); break;
1113
1
            case 0x02: case 0x03: // i32.trunc_sat_f64_s/u
1114
1
                r = v_cvtop(v, c_m3Type_f64, c_m3Type_i32); break;
1115
26
            case 0x04: case 0x05: // i64.trunc_sat_f32_s/u
1116
26
                r = v_cvtop(v, c_m3Type_f32, c_m3Type_i64); break;
1117
0
            case 0x06: case 0x07: // i64.trunc_sat_f64_s/u
1118
0
                r = v_cvtop(v, c_m3Type_f64, c_m3Type_i64); break;
1119
0
            case 0x08: // memory.init
1120
0
            {
1121
0
                u32 dataidx, memidx;
1122
0
                r = ReadLEB_u32(&dataidx, &v->wasm, v->wasmEnd); if (r) return r;
1123
0
                r = ReadLEB_u32(&memidx, &v->wasm, v->wasmEnd); if (r) return r;
1124
0
                if (not v_has_memory_idx(v, memidx)) return m3Err_unknownMemory;
1125
                // the segments must have been declared up front by a data count section
1126
0
                if (not v->module->hasDataCount) return m3Err_dataCountRequired;
1127
0
                if (dataidx >= v->module->numDataSegments) return m3Err_unknownDataSegment;
1128
                // the segment is indexed as an i32 no matter how the memory is
1129
                // addressed; only the destination follows the address type
1130
0
                r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // n
1131
0
                r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // src
1132
0
                r = v_pop_expect(v, v_memory_addrtype(v, memidx), &a); if (r) return r; // dst
1133
0
                break;
1134
0
            }
1135
0
            case 0x09: // data.drop
1136
0
            {
1137
0
                u32 dataidx;
1138
0
                r = ReadLEB_u32(&dataidx, &v->wasm, v->wasmEnd); if (r) return r;
1139
                // needs the segment, but not a memory
1140
0
                if (not v->module->hasDataCount) return m3Err_dataCountRequired;
1141
0
                if (dataidx >= v->module->numDataSegments) return m3Err_unknownDataSegment;
1142
0
                break;
1143
0
            }
1144
0
#if d_m3HasRefTypes
1145
2
            case 0x0c: // table.init
1146
76
            case 0x0e: // table.copy
1147
76
            {
1148
76
                u32 a1, a2;
1149
76
                r = ReadLEB_u32(&a1, &v->wasm, v->wasmEnd); if (r) return r;
1150
76
                r = ReadLEB_u32(&a2, &v->wasm, v->wasmEnd); if (r) return r;
1151
1152
76
                if (sub == 0x0c) {                                          // elemidx, tableidx
1153
                    // the immediates are encoded elem-then-table, but the spec's
1154
                    // rule requires the table to be defined before the segment
1155
2
                    if (a2 >= v->module->numTables) return m3Err_unknownTable;
1156
1
                    if (a1 >= v->module->numElementSegments) return m3Err_unknownElemSegment;
1157
0
                    if (v->module->elementSegments[a1].type != v->module->tables[a2]->type)
1158
0
                        return m3Err_typeMismatch;
1159
74
                } else {                                                    // dst, src
1160
74
                    if (a1 >= v->module->numTables) return m3Err_unknownTable;
1161
74
                    if (a2 >= v->module->numTables) return m3Err_unknownTable;
1162
55
                    if (v->module->tables[a1]->type != v->module->tables[a2]->type)
1163
0
                        return m3Err_typeMismatch;
1164
55
                }
1165
1166
55
                if (sub == 0x0c) {
1167
                    // table.init indexes the segment as an i32 whatever the
1168
                    // table is; only the destination follows the table
1169
0
                    r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r;             // n
1170
0
                    r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r;             // s
1171
0
                    r = v_pop_expect(v, v_table_addrtype(v, a2), &a); if (r) return r;  // d
1172
55
                } else {
1173
                    // table.copy: each index follows its own table, and the
1174
                    // length the narrower of the two
1175
55
                    m3type_t dType = v_table_addrtype(v, a1);
1176
55
                    m3type_t sType = v_table_addrtype(v, a2);
1177
55
                    m3type_t nType = (dType == c_m3Type_i64 and sType == c_m3Type_i64)
1178
55
                                       ? c_m3Type_i64 : c_m3Type_i32;
1179
55
                    r = v_pop_expect(v, nType, &a); if (r) return r;  // n
1180
54
                    r = v_pop_expect(v, sType, &a); if (r) return r;  // s
1181
53
                    r = v_pop_expect(v, dType, &a); if (r) return r;  // d
1182
53
                }
1183
52
                break;
1184
55
            }
1185
52
            case 0x0d: // elem.drop
1186
0
            {
1187
0
                u32 elemIdx;
1188
0
                r = ReadLEB_u32(&elemIdx, &v->wasm, v->wasmEnd); if (r) return r;
1189
0
                if (elemIdx >= v->module->numElementSegments) return m3Err_unknownElemSegment;
1190
0
                break;
1191
0
            }
1192
19
            case 0x0f: // table.grow
1193
56
            case 0x10: // table.size
1194
118
            case 0x11: // table.fill
1195
118
            {
1196
118
                u32 tableIdx;
1197
118
                r = ReadLEB_u32(&tableIdx, &v->wasm, v->wasmEnd); if (r) return r;
1198
118
                if (tableIdx >= v->module->numTables) return m3Err_unknownTable;
1199
118
                u8 t = BaseTypeOf(v->module->tables[tableIdx]->type);
1200
118
                m3type_t at = v_table_addrtype(v, tableIdx);
1201
1202
118
                if (sub == 0x10) {                                          // table.size
1203
37
                    r = v_push(v, at); if (r) return r;
1204
81
                } else if (sub == 0x0f) {                                   // table.grow
1205
19
                    r = v_pop_expect(v, at, &a); if (r) return r;  // n
1206
19
                    r = v_pop_expect(v, t, &a);  if (r) return r;  // init
1207
18
                    r = v_push(v, at);           if (r) return r;
1208
62
                } else {                                                    // table.fill
1209
62
                    r = v_pop_expect(v, at, &a); if (r) return r;  // n
1210
62
                    r = v_pop_expect(v, t, &a);  if (r) return r;  // val
1211
62
                    r = v_pop_expect(v, at, &a); if (r) return r;  // i
1212
62
                }
1213
116
                break;
1214
118
            }
1215
116
#endif
1216
116
            case 0x0a: // memory.copy
1217
0
            {
1218
0
                u32 dst, src;
1219
0
                r = ReadLEB_u32(&dst, &v->wasm, v->wasmEnd); if (r) return r;
1220
0
                r = ReadLEB_u32(&src, &v->wasm, v->wasmEnd); if (r) return r;
1221
0
                if (not v_has_memory_idx(v, dst) or not v_has_memory_idx(v, src)) return m3Err_unknownMemory;
1222
                // Spec: each address follows its own memory, and the length is
1223
                // typed by the narrower of the two - so copying between an i32
1224
                // and an i64 memory takes an i32 length.
1225
0
                m3type_t dstType = v_memory_addrtype(v, dst);
1226
0
                m3type_t srcType = v_memory_addrtype(v, src);
1227
0
                m3type_t lenType = (dstType == c_m3Type_i64 and srcType == c_m3Type_i64)
1228
0
                                     ? c_m3Type_i64 : c_m3Type_i32;
1229
0
                r = v_pop_expect(v, lenType, &a); if (r) return r; // n
1230
0
                r = v_pop_expect(v, srcType, &a); if (r) return r; // src
1231
0
                r = v_pop_expect(v, dstType, &a); if (r) return r; // dst
1232
0
                break;
1233
0
            }
1234
0
            case 0x0b: // memory.fill
1235
0
            {
1236
0
                u32 memidx;
1237
0
                r = ReadLEB_u32(&memidx, &v->wasm, v->wasmEnd); if (r) return r;
1238
0
                if (not v_has_memory_idx(v, memidx)) return m3Err_unknownMemory;
1239
0
                m3type_t addrType = v_memory_addrtype(v, memidx);
1240
0
                r = v_pop_expect(v, addrType, &a); if (r) return r;      // n
1241
0
                r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r;  // val
1242
0
                r = v_pop_expect(v, addrType, &a); if (r) return r;      // dst
1243
0
                break;
1244
0
            }
1245
16
            default:
1246
                // Unknown FC sub-opcode: skip validation (allow forward compat)
1247
16
                break;
1248
255
            }
1249
229
            break;
1250
255
        }
1251
1252
341
        default:
1253
            // Unknown opcode - skip rather than fail for forward compat
1254
            // (the compiler will reject truly unsupported ops later)
1255
341
            break;
1256
1257
12.0k
        } // switch
1258
1259
11.5k
        if (r) return r;
1260
1261
11.5k
    } // while
1262
1263
    // If we ran out of bytes without hitting the final end
1264
29
    return m3Err_wasmMalformed;
1265
535
}
1266
1267
// ---------- Public entry point ----------
1268
1269
M3Result  ValidateFunction  (IM3Function i_function)
1270
538
{
1271
538
    if (!i_function->wasm) return m3Err_none;
1272
1273
538
    IM3FuncType funcType = i_function->funcType;
1274
538
    IM3Module   module   = i_function->module;
1275
1276
538
    IM3Runtime runtime = module->runtime;
1277
1278
538
    ValCtx* v = runtime->validator;
1279
538
    if (!v) {
1280
538
        v = m3_AllocStruct (ValCtx);
1281
538
        if (!v) return m3Err_mallocFailed;
1282
538
        runtime->validator = v;
1283
538
    }
1284
1285
538
    memset(v, 0, sizeof(*v));
1286
538
    v->module   = module;
1287
538
    v->function = i_function;
1288
538
    v->wasm     = i_function->wasm;
1289
538
    v->wasmEnd  = i_function->wasmEnd;
1290
1291
    // Skip code size LEB
1292
538
    u32 size;
1293
538
    M3Result r = ReadLEB_u32(&size, &v->wasm, v->wasmEnd);
1294
538
    if (r) return r;
1295
1296
    // Parse locals
1297
538
    u32 numLocalBlocks;
1298
538
    r = ReadLEB_u32(&numLocalBlocks, &v->wasm, v->wasmEnd);
1299
538
    if (r) return r;
1300
1301
    // First: params. Running out of room has to be an error, not a truncation:
1302
    // a short localTypes would make later local.get indices read as unknown
1303
538
    u16 numParams = funcType ? funcType->numArgs : 0;
1304
538
    if (numParams > d_m3ValStack) return m3Err_functionStackOverflow;
1305
684
    for (u16 i = 0; i < numParams; i++) {
1306
146
        v->localTypes[v->numLocals++] = BaseTypeOf(funcType->types[funcType->numRets + i]);
1307
146
    }
1308
1309
    // Then: declared locals
1310
868
    for (u32 b = 0; b < numLocalBlocks; b++) {
1311
333
        u32 count;
1312
333
        r = ReadLEB_u32(&count, &v->wasm, v->wasmEnd);
1313
333
        if (r) return r;
1314
333
        m3type_t localType;
1315
333
        r = ParseValueType(v->module, &localType, &v->wasm, v->wasmEnd);
1316
333
        if (r) return r;
1317
333
        u8 normalized = BaseTypeOf(localType);
1318
333
        if (count > (u32) (d_m3ValStack - v->numLocals)) return m3Err_functionStackOverflow;
1319
130k
        for (u32 c = 0; c < count; c++) {
1320
130k
            v->localTypes[v->numLocals++] = normalized;
1321
130k
        }
1322
330
    }
1323
1324
    // Push the function-level control frame
1325
535
    r = v_push_ctrl(v, 0x00, funcType); // opcode 0x00 marks function frame
1326
535
    if (r) return r;
1327
1328
    // Push params onto operand stack (they're part of the function body's initial stack)
1329
    // Actually per the spec, locals are indexed but not on the operand stack.
1330
    // The function frame's params are NOT pushed to the operand stack.
1331
    // Only block params would be pushed (and for the function frame there are no block params
1332
    // since the function body's "block type" has results = function returns, params = 0).
1333
    // The function frame's label_types = results (since it's not a loop).
1334
1335
    // Validate the body
1336
535
    r = v_validate_body(v);
1337
535
    if (r) return r;
1338
1339
    // After validation, control stack should be empty and all declared bytes consumed
1340
329
    if (v->ctrlTop != 0)
1341
0
        return m3Err_wasmMalformed;
1342
1343
329
    if (v->wasm != v->wasmEnd)
1344
122
        return m3Err_wasmSectionUnderrun;
1345
1346
207
    return m3Err_none;
1347
329
}
1348
1349
#else // !d_m3EnableValidation
1350
1351
M3Result  ValidateFunction  (IM3Function i_function)
1352
{
1353
    (void)i_function;
1354
    return m3Err_none;
1355
}
1356
1357
// clang-format on
1358
1359
#endif // d_m3EnableValidation