Coverage Report

Created: 2026-10-17 06:58

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
650k
#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
14.0k
{
54
14.0k
    return v->module and i_memoryIdx < v->module->numMemories;
55
14.0k
}
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
24.9k
{
62
24.9k
    return Memory_AddrType (v->module->memories [i_memoryIdx]);
63
24.9k
}
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
10.3k
{
69
10.3k
    return v_memory_addrtype (v, i_memoryIdx) == c_m3Type_i64 or i_offset <= 0xFFFFFFFFull;
70
10.3k
}
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
1.81k
{
77
1.81k
    return Table_AddrType (v->module->tables [i_tableIdx]);
78
1.81k
}
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
9.55k
{
85
9.55k
    switch (opcode) {
86
847
    case 0x2c: case 0x2d:   // i32.load8_s, i32.load8_u
87
2.23k
    case 0x30: case 0x31:   // i64.load8_s, i64.load8_u
88
2.50k
    case 0x3a:              // i32.store8
89
2.70k
    case 0x3c:              // i64.store8
90
2.70k
        return 0;
91
1.13k
    case 0x2e: case 0x2f:   // i32.load16_s, i32.load16_u
92
2.19k
    case 0x32: case 0x33:   // i64.load16_s, i64.load16_u
93
2.51k
    case 0x3b:              // i32.store16
94
2.82k
    case 0x3d:              // i64.store16
95
2.82k
        return 1;
96
481
    case 0x29:              // i64.load
97
875
    case 0x2b:              // f64.load
98
1.12k
    case 0x37:              // i64.store
99
1.27k
    case 0x39:              // f64.store
100
1.27k
        return 3;
101
2.75k
    default:                // 32-bit accesses, and a safe fallback
102
2.75k
        return 2;
103
9.55k
    }
104
9.55k
}
105
106
#if d_m3HasAtomics
107
108
// What an instruction of the 0xFE prefix (other than atomic.fence) touches and
109
// takes: the width in bytes of the cell it addresses, how many operands it pops
110
// counting the address, the type of every operand but the address - a wait's
111
// timeout aside - and what it pushes. False for a sub-opcode that is not one.
112
//
113
// Outside notify and wait, the accesses come in runs of seven, one per pairing of
114
// value type and cell width: i32/32, i64/64, i32/8, i32/16, i64/8, i64/16, i64/32.
115
static bool v_atomic_shape (u32 sub, u32 * o_width, u32 * o_numOperands, m3type_t * o_valueType, m3type_t * o_resultType)
116
1.06k
{
117
1.06k
    static const u8 c_widths [7] = { 4, 8, 1, 2, 1, 2, 4 };
118
1.06k
    static const m3type_t c_types [7] = {
119
1.06k
        c_m3Type_i32, c_m3Type_i64,
120
1.06k
        c_m3Type_i32, c_m3Type_i32,
121
1.06k
        c_m3Type_i64, c_m3Type_i64, c_m3Type_i64
122
1.06k
    };
123
124
1.06k
    u32 variant;
125
126
1.06k
    if (sub <= 0x02) {              // memory.atomic.notify, wait32, wait64
127
161
        *o_width       = (sub == 0x02) ? 8 : 4;
128
161
        *o_numOperands = (sub == 0x00) ? 2 : 3;
129
161
        *o_valueType   = (sub == 0x02) ? c_m3Type_i64 : c_m3Type_i32;
130
161
        *o_resultType  = c_m3Type_i32;
131
161
        return true;
132
161
    }
133
134
899
    if (sub < 0x10 or sub > c_waOp_lastAtomic) return false;
135
136
845
    if (sub <= 0x16) {              // loads
137
149
        variant = sub - 0x10;       *o_numOperands = 1;
138
696
    } else if (sub <= 0x1d) {       // stores
139
96
        variant = sub - 0x17;       *o_numOperands = 2;
140
600
    } else if (sub <= 0x47) {       // add, sub, and, or, xor, xchg
141
448
        variant = (sub - 0x1e) % 7; *o_numOperands = 2;
142
448
    } else {                        // cmpxchg
143
152
        variant = sub - 0x48;       *o_numOperands = 3;
144
152
    }
145
146
845
    *o_width      = c_widths [variant];
147
845
    *o_valueType  = c_types [variant];
148
845
    *o_resultType = (sub >= 0x17 and sub <= 0x1d) ? c_m3Type_none : c_types [variant];
149
845
    return true;
150
899
}
151
152
#endif
153
154
// ---------- Operand stack ----------
155
156
static M3Result v_push (ValCtx * v, m3type_t type)
157
327k
{
158
327k
    if (M3_UNLIKELY(v->opdTop >= d_m3ValStack))
159
0
        return m3Err_functionStackOverflow;
160
327k
    v->opd[v->opdTop++] = type;
161
327k
    return m3Err_none;
162
327k
}
163
164
static M3Result v_pop (ValCtx * v, m3type_t * o_type)
165
279k
{
166
279k
    ValCtrlFrame * f = &v->ctrl[v->ctrlTop - 1];
167
279k
    if (M3_UNLIKELY(v->opdTop == f->height)) {
168
59.6k
        if (f->is_unreachable) { *o_type = c_valBottom; return m3Err_none; }
169
87
        return m3Err_functionStackUnderrun;
170
59.6k
    }
171
219k
    *o_type = v->opd[--v->opdTop];
172
219k
    return m3Err_none;
173
279k
}
174
175
static M3Result v_pop_expect (ValCtx * v, m3type_t expect, m3type_t * o_actual)
176
267k
{
177
267k
    m3type_t actual = c_valBottom;
178
267k
    M3Result r = v_pop(v, &actual);
179
267k
    if (M3_UNLIKELY(r)) return r;
180
267k
    if (M3_UNLIKELY(expect != c_valBottom && actual != c_valBottom && actual != expect))
181
334
        return m3Err_typeMismatch;
182
267k
    *o_actual = (actual == c_valBottom) ? expect : actual;
183
267k
    return m3Err_none;
184
267k
}
185
186
// ---------- Control stack ----------
187
188
static M3Result v_push_ctrl (ValCtx * v, m3opcode_t op, IM3FuncType type)
189
34.6k
{
190
34.6k
    if (M3_UNLIKELY(v->ctrlTop >= d_m3ValCtrlDepth))
191
0
        return m3Err_functionStackOverflow;
192
34.6k
    ValCtrlFrame * f = &v->ctrl[v->ctrlTop++];
193
34.6k
    f->opcode       = op;
194
34.6k
    f->type         = type;
195
34.6k
    f->param_count  = type ? type->numArgs : 0;
196
34.6k
    f->result_count = type ? type->numRets : 0;
197
34.6k
    f->height       = v->opdTop;
198
34.6k
    f->is_unreachable = false;
199
34.6k
    return m3Err_none;
200
34.6k
}
201
202
static M3Result v_pop_ctrl (ValCtx * v, ValCtrlFrame * o_frame)
203
31.3k
{
204
31.3k
    if (M3_UNLIKELY(v->ctrlTop == 0))
205
0
        return m3Err_wasmMalformed;
206
31.3k
    ValCtrlFrame * f = &v->ctrl[v->ctrlTop - 1];
207
    // pop result types
208
31.3k
    if (f->type) {
209
40.6k
        for (u16 i = f->result_count; i > 0; i--) {
210
9.44k
            m3type_t a;
211
9.44k
            M3Result r = v_pop_expect(v, BaseTypeOf(f->type->types[i - 1]), &a);
212
9.44k
            if (M3_UNLIKELY(r)) return r;
213
9.44k
        }
214
31.2k
    }
215
31.3k
    if (M3_UNLIKELY(v->opdTop != f->height))
216
68
        return m3Err_typeCountMismatch;
217
31.3k
    if (o_frame) *o_frame = *f;
218
31.3k
    v->ctrlTop--;
219
31.3k
    return m3Err_none;
220
31.3k
}
221
222
static void v_unreachable (ValCtx * v)
223
76.7k
{
224
76.7k
    ValCtrlFrame * f = &v->ctrl[v->ctrlTop - 1];
225
76.7k
    v->opdTop = f->height;
226
76.7k
    f->is_unreachable = true;
227
76.7k
}
228
229
// Label types: loop -> params, block/if/else/func -> results
230
static u16 v_label_n (ValCtrlFrame * f)
231
7.93k
{
232
7.93k
    return (f->opcode == 0x03) ? f->param_count : f->result_count;
233
7.93k
}
234
235
static m3type_t v_label_t (ValCtrlFrame * f, u16 i)
236
7.32k
{
237
7.32k
    if (!f->type) return c_m3Type_none;
238
7.32k
    if (f->opcode == 0x03)
239
3.13k
        return BaseTypeOf(f->type->types[f->type->numRets + i]); // params
240
4.18k
    return BaseTypeOf(f->type->types[i]); // results
241
7.32k
}
242
243
// The operand i_depth below the top, without popping. Anything at or below the
244
// current frame's height is implicitly bottom in an unreachable frame; reporting
245
// unknown there defers a genuine underrun to the pop that follows.
246
static m3type_t v_peek (ValCtx * v, u16 i_depth)
247
1.30k
{
248
1.30k
    ValCtrlFrame * f = &v->ctrl[v->ctrlTop - 1];
249
250
1.30k
    if (v->opdTop <= i_depth || (u16)(v->opdTop - i_depth - 1) < f->height)
251
1.16k
        return c_valBottom;
252
253
139
    return v->opd[v->opdTop - i_depth - 1];
254
1.30k
}
255
256
// Pop label types for branch target
257
static M3Result v_pop_labels (ValCtx * v, ValCtrlFrame * tgt)
258
3.73k
{
259
3.73k
    u16 n = v_label_n(tgt);
260
6.63k
    for (u16 i = n; i > 0; i--) {
261
2.90k
        m3type_t a;
262
2.90k
        M3Result r = v_pop_expect(v, v_label_t(tgt, i - 1), &a);
263
2.90k
        if (M3_UNLIKELY(r)) return r;
264
2.90k
    }
265
3.73k
    return m3Err_none;
266
3.73k
}
267
268
// Push label types back
269
static M3Result v_push_labels (ValCtx * v, ValCtrlFrame * tgt)
270
2.33k
{
271
2.33k
    u16 n = v_label_n(tgt);
272
3.61k
    for (u16 i = 0; i < n; i++) {
273
1.28k
        M3Result r = v_push(v, v_label_t(tgt, i));
274
1.28k
        if (M3_UNLIKELY(r)) return r;
275
1.28k
    }
276
2.33k
    return m3Err_none;
277
2.33k
}
278
279
// ---------- Block type resolution ----------
280
281
static M3Result v_read_blocktype (ValCtx * v, IM3FuncType * o_type)
282
7.43k
{
283
7.43k
    if (v->wasm >= v->wasmEnd)
284
14
        return m3Err_wasmUnderrun;
285
286
7.41k
#if d_m3HasTypedRefs
287
7.41k
    if (*v->wasm == d_waEncode_ref or *v->wasm == d_waEncode_refNull) {
288
85
        m3type_t refType;
289
85
        M3Result rr = ParseValueType(v->module, &refType, &v->wasm, v->wasmEnd);
290
85
        if (rr) return rr;
291
82
        IM3FuncType ftype = NULL;
292
82
        rr = AllocFuncType(&ftype, 1);
293
82
        if (rr) return rr;
294
82
        ftype->numRets = 1;
295
82
        ftype->types[0] = refType;
296
        // on failure this leaves ftype NULL, which would read downstream as a
297
        // block that returns nothing
298
82
        rr = Environment_AddFuncType(v->module->environment, &ftype);
299
82
        if (rr) return rr;
300
82
        *o_type = ftype;
301
82
        return m3Err_none;
302
82
    }
303
7.33k
#endif
304
305
7.33k
    i64 type;
306
7.33k
    M3Result r = ReadLebSigned(&type, 33, &v->wasm, v->wasmEnd);
307
7.33k
    if (r) return r;
308
309
7.33k
    if (type < 0) {
310
2.38k
        if (type < -64) return m3Err_invalidTypeId;
311
2.33k
        u8 valtype;
312
2.33k
        r = NormalizeType(&valtype, (i8)type);
313
2.33k
        if (r) return r;
314
2.33k
        IM3Environment env = v->module->environment;
315
2.33k
        *o_type = env->retFuncTypes[valtype];
316
4.95k
    } else {
317
4.95k
        if ((u32)type >= v->module->numFuncTypes)
318
58
            return m3Err_unknownType;
319
4.89k
        *o_type = v->module->funcTypes[(u32)type];
320
4.89k
    }
321
7.22k
    return m3Err_none;
322
7.33k
}
323
324
// ---------- Convenience ----------
325
326
static M3Result v_unop (ValCtx * v, m3type_t in, m3type_t out)
327
53.8k
{
328
53.8k
    m3type_t a; M3Result r = v_pop_expect(v, in, &a);
329
53.8k
    if (M3_UNLIKELY(r)) return r;
330
53.7k
    return v_push(v, out);
331
53.8k
}
332
333
// return_call/return_call_indirect return the callee's results straight to the enclosing
334
// function's caller, so the callee's result types must be the enclosing function's
335
static M3Result v_check_tail_results (ValCtx * v, IM3FuncType i_calleeType)
336
10.0k
{
337
10.0k
    IM3FuncType ft = v->function ? v->function->funcType : NULL;
338
339
10.0k
    u16 numCalleeRets = i_calleeType ? i_calleeType->numRets : 0;
340
10.0k
    u16 numFuncRets   = ft ? ft->numRets : 0;
341
342
10.0k
    if (numCalleeRets != numFuncRets)
343
0
        return m3Err_typeMismatch;
344
345
12.1k
    for (u16 i = 0; i < numCalleeRets; i++) {
346
2.18k
        if (i_calleeType->types[i] != ft->types[i])
347
0
            return m3Err_typeMismatch;
348
2.18k
    }
349
350
10.0k
    return m3Err_none;
351
10.0k
}
352
353
static M3Result v_binop (ValCtx * v, m3type_t t)
354
35.6k
{
355
35.6k
    m3type_t a; M3Result r;
356
35.6k
    r = v_pop_expect(v, t, &a); if (M3_UNLIKELY(r)) return r;
357
35.6k
    r = v_pop_expect(v, t, &a); if (M3_UNLIKELY(r)) return r;
358
35.6k
    return v_push(v, t);
359
35.6k
}
360
361
static M3Result v_relop (ValCtx * v, m3type_t t)
362
25.6k
{
363
25.6k
    m3type_t a; M3Result r;
364
25.6k
    r = v_pop_expect(v, t, &a); if (M3_UNLIKELY(r)) return r;
365
25.5k
    r = v_pop_expect(v, t, &a); if (M3_UNLIKELY(r)) return r;
366
25.5k
    return v_push(v, c_m3Type_i32);
367
25.5k
}
368
369
static M3Result v_testop (ValCtx * v, m3type_t t)
370
3.76k
{
371
3.76k
    return v_unop(v, t, c_m3Type_i32);
372
3.76k
}
373
374
static M3Result v_cvtop (ValCtx * v, m3type_t in, m3type_t out)
375
24.9k
{
376
24.9k
    return v_unop(v, in, out);
377
24.9k
}
378
379
380
// ---------- Main validation loop ----------
381
382
#if d_m3HasExceptionHandling
383
384
// One catch clause of a try_table. The label is resolved in the context the
385
// try_table itself appears in - the spec checks the clauses against C, not
386
// against C extended with the block's own label - so depth 0 names the block
387
// enclosing the try, not the try. What the clause hands the label is the tag's
388
// payload, optionally followed by the exnref that reifies the caught exception.
389
static M3Result v_catch_clause (ValCtx * v)
390
547
{
391
547
    u8 kind;
392
547
    M3Result r = Read_u8(&kind, &v->wasm, v->wasmEnd);
393
547
    if (r) return r;
394
541
    if (kind > 0x03) return m3Err_wasmMalformed;
395
396
541
    bool hasTag = (kind == 0x00 || kind == 0x01);
397
527
    bool hasRef = (kind == 0x01 || kind == 0x03);
398
399
527
    IM3FuncType tagType = NULL;
400
527
    if (hasTag) {
401
267
        u32 tagIndex;
402
267
        r = ReadLEB_u32(&tagIndex, &v->wasm, v->wasmEnd);
403
267
        if (r) return r;
404
264
        if (!v->module || tagIndex >= v->module->numTags) return m3Err_unknownTag;
405
224
        tagType = v->module->tags[tagIndex].type;
406
224
    }
407
408
484
    u32 depth;
409
484
    r = ReadLEB_u32(&depth, &v->wasm, v->wasmEnd);
410
484
    if (r) return r;
411
482
    if (depth >= v->ctrlTop) return m3Err_unknownLabel;
412
413
448
    ValCtrlFrame * tgt = &v->ctrl[v->ctrlTop - 1 - depth];
414
415
448
    u16 numPayload = tagType ? tagType->numArgs : 0;
416
448
    u16 numLabel   = v_label_n(tgt);
417
418
448
    if (numLabel != numPayload + (hasRef ? 1u : 0u))
419
4
        return m3Err_typeCountMismatch;
420
421
2.20k
    for (u16 i = 0; i < numPayload; i++) {
422
1.75k
        if (v_label_t(tgt, i) != BaseTypeOf(tagType->types[tagType->numRets + i]))
423
3
            return m3Err_typeMismatch;
424
1.75k
    }
425
426
441
    if (hasRef and v_label_t(tgt, numPayload) != c_m3Type_exnref)
427
4
        return m3Err_typeMismatch;
428
429
437
    return m3Err_none;
430
441
}
431
432
#endif // d_m3HasExceptionHandling
433
434
#if d_m3HasStackSwitching
435
436
static M3Result v_resume_clause (ValCtx * v, u8 kind, u32 tagIdx, IM3FuncType ct)
437
0
{
438
0
    if (kind > 0x01) return m3Err_wasmMalformed;
439
0
    if (!v->module || tagIdx >= v->module->numTags) return m3Err_unknownTag;
440
0
    IM3FuncType tagType = v->module->tags[tagIdx].type;
441
442
0
    if (kind == 0x00) {
443
0
        u32 depth;
444
0
        M3Result r = ReadLEB_u32(&depth, &v->wasm, v->wasmEnd); if (r) return r;
445
0
        if (depth >= v->ctrlTop) return m3Err_unknownLabel;
446
447
0
        ValCtrlFrame * tgt = &v->ctrl[v->ctrlTop - 1 - depth];
448
0
        u16 numPayload = tagType ? tagType->numArgs : 0;
449
0
        u16 numLabel   = v_label_n(tgt);
450
451
0
        if (numLabel != numPayload + 1u) return m3Err_typeCountMismatch;
452
453
0
        for (u16 j = 0; j < numPayload; j++) {
454
0
            if (v_label_t(tgt, j) != BaseTypeOf(tagType->types[tagType->numRets + j]))
455
0
                return m3Err_typeMismatch;
456
0
        }
457
0
        if (v_label_t(tgt, numPayload) != c_m3Type_contref)
458
0
            return m3Err_typeMismatch;
459
460
0
#if d_m3HasTypedRefs
461
0
        m3type_t labelContType = (tgt->opcode == 0x03)
462
0
            ? tgt->type->types[tgt->type->numRets + numPayload]
463
0
            : tgt->type->types[numPayload];
464
465
0
        if (HeapTypeOf(labelContType) == d_m3Type_heapAbstract ||
466
0
            HeapTypeOf(labelContType) == d_m3Type_heapNone) {
467
0
            return m3Err_typeMismatch;
468
0
        }
469
470
0
        u32 heapType = HeapTypeOf(labelContType);
471
0
        IM3FuncType labelCt = NULL;
472
0
        for (u32 idx = 0; idx < v->module->numFuncTypes; ++idx) {
473
0
            if (v->module->funcTypes[idx]->canonicalIndex == heapType &&
474
0
                v->module->funcTypes[idx]->isContinuation) {
475
0
                labelCt = v->module->funcTypes[idx];
476
0
                break;
477
0
            }
478
0
        }
479
0
        if (!labelCt) return m3Err_typeMismatch;
480
481
0
        IM3FuncType labelFt = labelCt->contFuncType ? labelCt->contFuncType : labelCt;
482
0
        IM3FuncType ft = ct->contFuncType ? ct->contFuncType : ct;
483
484
0
        u16 expectedNumParams = tagType ? tagType->numRets : 0;
485
0
        if (labelFt->numArgs != expectedNumParams) return m3Err_typeMismatch;
486
0
        for (u16 p = 0; p < expectedNumParams; ++p) {
487
0
            if (labelFt->types[labelFt->numRets + p] != tagType->types[p])
488
0
                return m3Err_typeMismatch;
489
0
        }
490
491
0
        u16 expectedNumRets = ft ? ft->numRets : 0;
492
0
        if (labelFt->numRets != expectedNumRets) return m3Err_typeMismatch;
493
0
        for (u16 r_idx = 0; r_idx < expectedNumRets; ++r_idx) {
494
0
            if (labelFt->types[r_idx] != ft->types[r_idx])
495
0
                return m3Err_typeMismatch;
496
0
        }
497
0
#endif
498
0
    } else {
499
0
        if (tagType && tagType->numArgs != 0) return m3Err_typeMismatch;
500
0
    }
501
0
    return m3Err_none;
502
0
}
503
504
#endif // d_m3HasStackSwitching
505
506
static M3Result v_validate_body (ValCtx * v)
507
27.3k
{
508
27.3k
    M3Result r = m3Err_none;
509
27.3k
    m3type_t a = c_valBottom;
510
511
442k
    while (v->wasm < v->wasmEnd)
512
442k
    {
513
442k
        m3opcode_t opcode;
514
442k
        r = Read_opcode(&opcode, &v->wasm, v->wasmEnd);
515
442k
        if (M3_UNLIKELY(r)) return r;
516
517
442k
        switch (opcode)
518
442k
        {
519
        // ---- Control ----
520
61.3k
        case 0x00: // unreachable
521
61.3k
            v_unreachable(v);
522
61.3k
            break;
523
524
18.3k
        case 0x01: // nop
525
18.3k
            break;
526
527
1.00k
        case 0x02: // block
528
2.50k
        case 0x03: // loop
529
6.83k
        case 0x04: // if
530
6.83k
        {
531
6.83k
            IM3FuncType bt = NULL;
532
6.83k
            r = v_read_blocktype(v, &bt);
533
6.83k
            if (r) return r;
534
6.71k
            if (opcode == 0x04) {
535
4.29k
                r = v_pop_expect(v, c_m3Type_i32, &a);
536
4.29k
                if (r) return r;
537
4.29k
            }
538
            // Pop block params from caller stack
539
6.71k
            if (bt) {
540
14.8k
                for (u16 i = bt->numArgs; i > 0; i--) {
541
8.31k
                    r = v_pop_expect(v, BaseTypeOf(bt->types[bt->numRets + i - 1]), &a);
542
8.31k
                    if (r) return r;
543
8.31k
                }
544
6.53k
            }
545
6.71k
            r = v_push_ctrl(v, opcode, bt);
546
6.71k
            if (r) return r;
547
            // Push params inside block
548
6.71k
            if (bt) {
549
14.8k
                for (u16 i = 0; i < bt->numArgs; i++) {
550
8.31k
                    r = v_push(v, BaseTypeOf(bt->types[bt->numRets + i]));
551
8.31k
                    if (r) return r;
552
8.31k
                }
553
6.53k
            }
554
6.71k
            break;
555
6.71k
        }
556
557
6.71k
#if d_m3HasExceptionHandling
558
6.71k
        case 0x1f: // try_table
559
596
        {
560
596
            IM3FuncType bt = NULL;
561
596
            r = v_read_blocktype(v, &bt);
562
596
            if (r) return r;
563
587
            if (bt) {
564
1.75k
                for (u16 i = bt->numArgs; i > 0; i--) {
565
1.21k
                    r = v_pop_expect(v, BaseTypeOf(bt->types[bt->numRets + i - 1]), &a);
566
1.21k
                    if (r) return r;
567
1.21k
                }
568
554
            }
569
            // the clauses are checked before the frame goes on, so their
570
            // labels count from outside the try block
571
582
            u32 numCatch;
572
582
            r = ReadLEB_u32(&numCatch, &v->wasm, v->wasmEnd);
573
582
            if (r) return r;
574
1.01k
            for (u32 i = 0; i < numCatch; i++) {
575
547
                r = v_catch_clause(v);
576
547
                if (r) return r;
577
547
            }
578
579
471
            r = v_push_ctrl(v, opcode, bt);
580
471
            if (r) return r;
581
582
471
            if (bt) {
583
1.47k
                for (u16 i = 0; i < bt->numArgs; i++) {
584
1.03k
                    r = v_push(v, BaseTypeOf(bt->types[bt->numRets + i]));
585
1.03k
                    if (r) return r;
586
1.03k
                }
587
440
            }
588
471
            break;
589
471
        }
590
591
471
        case 0x08: // throw
592
292
        {
593
292
            u32 tagIndex;
594
292
            r = ReadLEB_u32(&tagIndex, &v->wasm, v->wasmEnd);
595
292
            if (r) return r;
596
290
            if (!v->module || tagIndex >= v->module->numTags) return m3Err_unknownTag;
597
257
            IM3FuncType tt = v->module->tags[tagIndex].type;
598
257
            if (tt) {
599
1.05k
                for (u16 i = tt->numArgs; i > 0; i--) {
600
799
                    r = v_pop_expect(v, BaseTypeOf(tt->types[tt->numRets + i - 1]), &a);
601
799
                    if (r) return r;
602
799
                }
603
257
            }
604
254
            v_unreachable(v);
605
254
            break;
606
257
        }
607
608
809
        case 0x0a: // throw_ref
609
809
            r = v_pop_expect(v, c_m3Type_exnref, &a);
610
809
            if (r) return r;
611
801
            v_unreachable(v);
612
801
            break;
613
0
#endif // d_m3HasExceptionHandling
614
615
152
        case 0x05: // else
616
152
        {
617
152
            ValCtrlFrame frame;
618
152
            r = v_pop_ctrl(v, &frame);
619
152
            if (r) return r;
620
148
            if (frame.opcode != 0x04)
621
4
                return m3Err_wasmMalformed;
622
144
            r = v_push_ctrl(v, 0x05, frame.type);
623
144
            if (r) return r;
624
144
            if (frame.type) {
625
363
                for (u16 i = 0; i < frame.type->numArgs; i++) {
626
236
                    r = v_push(v, BaseTypeOf(frame.type->types[frame.type->numRets + i]));
627
236
                    if (r) return r;
628
236
                }
629
127
            }
630
144
            break;
631
144
        }
632
633
31.2k
        case 0x0b: // end
634
31.2k
        {
635
31.2k
            ValCtrlFrame frame;
636
31.2k
            r = v_pop_ctrl(v, &frame);
637
31.2k
            if (r) return r;
638
            // Push results
639
31.1k
            if (frame.type) {
640
40.2k
                for (u16 i = 0; i < frame.result_count; i++) {
641
9.20k
                    r = v_push(v, BaseTypeOf(frame.type->types[i]));
642
9.20k
                    if (r) return r;
643
9.20k
                }
644
31.0k
            }
645
            // If this was the outermost frame, we're done
646
31.1k
            if (v->ctrlTop == 0)
647
24.7k
                return m3Err_none;
648
6.41k
            break;
649
31.1k
        }
650
651
6.41k
        case 0x0c: // br
652
994
        {
653
994
            u32 depth;
654
994
            r = ReadLEB_u32(&depth, &v->wasm, v->wasmEnd);
655
994
            if (r) return r;
656
992
            if (depth >= v->ctrlTop) return m3Err_unknownLabel;
657
954
            ValCtrlFrame * tgt = &v->ctrl[v->ctrlTop - 1 - depth];
658
954
            r = v_pop_labels(v, tgt);
659
954
            if (r) return r;
660
950
            v_unreachable(v);
661
950
            break;
662
954
        }
663
664
2.35k
        case 0x0d: // br_if
665
2.35k
        {
666
2.35k
            u32 depth;
667
2.35k
            r = ReadLEB_u32(&depth, &v->wasm, v->wasmEnd);
668
2.35k
            if (r) return r;
669
2.35k
            if (depth >= v->ctrlTop) return m3Err_unknownLabel;
670
2.33k
            r = v_pop_expect(v, c_m3Type_i32, &a);
671
2.33k
            if (r) return r;
672
2.33k
            ValCtrlFrame * tgt = &v->ctrl[v->ctrlTop - 1 - depth];
673
2.33k
            r = v_pop_labels(v, tgt);
674
2.33k
            if (r) return r;
675
2.33k
            r = v_push_labels(v, tgt);
676
2.33k
            if (r) return r;
677
2.33k
            break;
678
2.33k
        }
679
680
2.33k
        case 0x0e: // br_table
681
517
        {
682
517
            u32 count;
683
517
            r = ReadLEB_u32(&count, &v->wasm, v->wasmEnd);
684
517
            if (r) return r;
685
515
            u32 defDepth = 0;
686
515
            u16 arity = 0;
687
            // First pass: read all depths and validate arity + types match default
688
515
            bytes_t savedPos = v->wasm;
689
            // Read all targets to find the default (last one)
690
1.61k
            for (u32 i = 0; i <= count; i++) {
691
1.15k
                u32 d;
692
1.15k
                r = ReadLEB_u32(&d, &v->wasm, v->wasmEnd);
693
1.15k
                if (r) return r;
694
1.15k
                if (d >= v->ctrlTop) return m3Err_unknownLabel;
695
1.10k
                if (i == count) defDepth = d;
696
1.10k
            }
697
            // Now validate all labels match the default's types
698
457
            ValCtrlFrame * defTgt = &v->ctrl[v->ctrlTop - 1 - defDepth];
699
457
            arity = v_label_n(defTgt);
700
457
            v->wasm = savedPos;
701
702
457
            r = v_pop_expect(v, c_m3Type_i32, &a);      // the index, before the labels are inspected
703
457
            if (r) return r;
704
705
1.41k
            for (u32 i = 0; i <= count; i++) {
706
961
                u32 d;
707
961
                r = ReadLEB_u32(&d, &v->wasm, v->wasmEnd);
708
961
                if (r) return r;
709
961
                ValCtrlFrame * t = &v->ctrl[v->ctrlTop - 1 - d];
710
961
                u16 n = v_label_n(t);
711
961
                if (n != arity) return m3Err_typeCountMismatch;
712
                // Spec: each target's types must be compatible with the operand
713
                // stack, not identical to the default's. In unreachable code the
714
                // operands are bottom, so the targets may legitimately differ.
715
2.26k
                for (u16 j = 0; j < n; j++) {
716
1.30k
                    m3type_t want = v_label_t(t, n - 1 - j);
717
1.30k
                    m3type_t have = v_peek(v, j);
718
1.30k
                    if (want != c_valBottom && have != c_valBottom && want != have)
719
4
                        return m3Err_typeMismatch;
720
1.30k
                }
721
959
            }
722
450
            ValCtrlFrame * dt = &v->ctrl[v->ctrlTop - 1 - defDepth];
723
450
            r = v_pop_labels(v, dt);
724
450
            if (r) return r;
725
449
            v_unreachable(v);
726
449
            break;
727
450
        }
728
729
2.90k
        case 0x0f: // return
730
2.90k
        {
731
2.90k
            IM3FuncType ft = v->function->funcType;
732
2.90k
            if (ft) {
733
12.1k
                for (u16 i = ft->numRets; i > 0; i--) {
734
9.21k
                    r = v_pop_expect(v, BaseTypeOf(ft->types[i - 1]), &a);
735
9.21k
                    if (r) return r;
736
9.21k
                }
737
2.90k
            }
738
2.90k
            v_unreachable(v);
739
2.90k
            break;
740
2.90k
        }
741
742
        // ---- Call ----
743
9.51k
        case 0x10: // call
744
9.51k
        {
745
9.51k
            u32 idx;
746
9.51k
            r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
747
9.51k
            if (r) return r;
748
9.50k
            if (idx >= v->module->numFunctions) return m3Err_unknownFunction;
749
9.47k
            IM3FuncType ft = v->module->functions[idx].funcType;
750
9.47k
            if (ft) {
751
11.8k
                for (u16 i = ft->numArgs; i > 0; i--) {
752
2.37k
                    r = v_pop_expect(v, BaseTypeOf(ft->types[ft->numRets + i - 1]), &a);
753
2.37k
                    if (r) return r;
754
2.37k
                }
755
37.0k
                for (u16 i = 0; i < ft->numRets; i++) {
756
27.5k
                    r = v_push(v, BaseTypeOf(ft->types[i]));
757
27.5k
                    if (r) return r;
758
27.5k
                }
759
9.46k
            }
760
9.46k
            break;
761
9.47k
        }
762
763
9.46k
        case 0x11: // call_indirect
764
264
        {
765
264
            u32 typeIdx;
766
264
            r = ReadLEB_u32(&typeIdx, &v->wasm, v->wasmEnd);
767
264
            if (r) return r;
768
260
            u32 tableIdx;
769
260
            r = ReadLEB_u32(&tableIdx, &v->wasm, v->wasmEnd);
770
260
            if (r) return r;
771
258
            if (typeIdx >= v->module->numFuncTypes) return m3Err_unknownType;
772
            // the table must exist and hold funcrefs
773
222
            if (tableIdx >= v->module->numTables) return m3Err_unknownTable;
774
195
            if (v->module->tables[tableIdx]->type != c_m3Type_funcref) return m3Err_typeMismatch;
775
194
            IM3FuncType ft = v->module->funcTypes[typeIdx];
776
194
            r = v_pop_expect(v, v_table_addrtype(v, tableIdx), &a); // table index operand
777
194
            if (r) return r;
778
192
            if (ft) {
779
473
                for (u16 i = ft->numArgs; i > 0; i--) {
780
282
                    r = v_pop_expect(v, BaseTypeOf(ft->types[ft->numRets + i - 1]), &a);
781
282
                    if (r) return r;
782
282
                }
783
811
                for (u16 i = 0; i < ft->numRets; i++) {
784
620
                    r = v_push(v, BaseTypeOf(ft->types[i]));
785
620
                    if (r) return r;
786
620
                }
787
191
            }
788
191
            break;
789
192
        }
790
791
9.25k
        case 0x12: // return_call
792
9.25k
        {
793
9.25k
            u32 idx;
794
9.25k
            r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
795
9.25k
            if (r) return r;
796
9.25k
            if (idx >= v->module->numFunctions) return m3Err_unknownFunction;
797
9.25k
            IM3FuncType ft = v->module->functions[idx].funcType;
798
9.25k
            r = v_check_tail_results(v, ft);
799
9.25k
            if (r) return r;
800
9.25k
            if (ft) {
801
11.5k
                for (u16 i = ft->numArgs; i > 0; i--) {
802
2.29k
                    r = v_pop_expect(v, BaseTypeOf(ft->types[ft->numRets + i - 1]), &a);
803
2.29k
                    if (r) return r;
804
2.29k
                }
805
9.25k
            }
806
9.24k
            v_unreachable(v);
807
9.24k
            break;
808
9.25k
        }
809
810
269
        case 0x13: // return_call_indirect
811
269
        {
812
269
            u32 typeIdx;
813
269
            r = ReadLEB_u32(&typeIdx, &v->wasm, v->wasmEnd);
814
269
            if (r) return r;
815
267
            u32 tableIdx;
816
267
            r = ReadLEB_u32(&tableIdx, &v->wasm, v->wasmEnd);
817
267
            if (r) return r;
818
266
            if (typeIdx >= v->module->numFuncTypes) return m3Err_unknownType;
819
            // the table must exist and hold funcrefs
820
241
            if (tableIdx >= v->module->numTables) return m3Err_unknownTable;
821
195
            if (v->module->tables[tableIdx]->type != c_m3Type_funcref) return m3Err_typeMismatch;
822
194
            IM3FuncType ft = v->module->funcTypes[typeIdx];
823
194
            r = v_check_tail_results(v, ft);
824
194
            if (r) return r;
825
194
            r = v_pop_expect(v, v_table_addrtype(v, tableIdx), &a); // table index operand
826
194
            if (r) return r;
827
193
            if (ft) {
828
532
                for (u16 i = ft->numArgs; i > 0; i--) {
829
340
                    r = v_pop_expect(v, BaseTypeOf(ft->types[ft->numRets + i - 1]), &a);
830
340
                    if (r) return r;
831
340
                }
832
193
            }
833
192
            v_unreachable(v);
834
192
            break;
835
193
        }
836
837
0
#if d_m3HasTypedRefs
838
619
        case 0x14: // call_ref
839
1.21k
        case 0x15: // return_call_ref
840
1.21k
        {
841
1.21k
            u32 typeIdx;
842
1.21k
            r = ReadLEB_u32(&typeIdx, &v->wasm, v->wasmEnd);
843
1.21k
            if (r) return r;
844
1.21k
            if (typeIdx >= v->module->numFuncTypes) return m3Err_unknownType;
845
1.17k
            IM3FuncType ft = v->module->funcTypes[typeIdx];
846
1.17k
            bool isTail = (opcode == 0x15);
847
1.17k
            if (isTail) {
848
573
                r = v_check_tail_results(v, ft);
849
573
                if (r) return r;
850
573
            }
851
            // the callee itself, as a reference to a function of this type
852
1.17k
            r = v_pop_expect(v, c_m3Type_funcref, &a);
853
1.17k
            if (r) return r;
854
1.17k
            if (ft) {
855
2.11k
                for (u16 i = ft->numArgs; i > 0; i--) {
856
953
                    r = v_pop_expect(v, BaseTypeOf(ft->types[ft->numRets + i - 1]), &a);
857
953
                    if (r) return r;
858
953
                }
859
1.16k
                if (not isTail) {
860
2.79k
                    for (u16 i = 0; i < ft->numRets; i++) {
861
2.19k
                        r = v_push(v, BaseTypeOf(ft->types[i]));
862
2.19k
                        if (r) return r;
863
2.19k
                    }
864
601
                }
865
1.16k
            }
866
1.16k
            if (isTail) v_unreachable(v);
867
1.16k
            break;
868
1.17k
        }
869
870
347
        case 0xd4: // ref.as_non_null
871
347
        {
872
347
            r = v_pop_expect(v, c_m3Type_funcref, &a);
873
347
            if (r) return r;
874
343
            r = v_push(v, (a == c_valBottom) ? c_m3Type_funcref : a);
875
343
            if (r) return r;
876
343
            break;
877
343
        }
878
343
#endif // d_m3HasTypedRefs
879
880
        // ---- Parametric ----
881
6.17k
        case 0x1a: // drop
882
6.17k
            r = v_pop(v, &a);
883
6.17k
            if (r) return r;
884
6.17k
            break;
885
886
6.17k
        case 0x1b: // select
887
5.38k
        {
888
5.38k
            r = v_pop_expect(v, c_m3Type_i32, &a);
889
5.38k
            if (r) return r;
890
5.37k
            m3type_t t2 = c_valBottom;
891
5.37k
            r = v_pop(v, &t2);
892
5.37k
            if (r) return r;
893
5.37k
            m3type_t t1;
894
5.37k
            r = v_pop_expect(v, t2, &t1);
895
5.37k
            if (r) return r;
896
            // untyped select is numeric only; references need the 0x1c form
897
5.37k
            if (t2 != c_valBottom && IsRefType(t2)) return m3Err_typeMismatch;
898
5.37k
            r = v_push(v, (t2 == c_valBottom) ? t1 : t2);
899
5.37k
            if (r) return r;
900
5.37k
            break;
901
5.37k
        }
902
903
5.37k
#if d_m3HasRefTypes
904
5.37k
        case 0x1c: // select with an explicit result type
905
48
        {
906
48
            u32 numTypes;
907
48
            r = ReadLEB_u32(&numTypes, &v->wasm, v->wasmEnd);
908
48
            if (r) return r;
909
46
            if (numTypes != 1) return m3Err_wasmMalformed;
910
911
21
            m3type_t selType;
912
21
            r = ParseValueType(v->module, &selType, &v->wasm, v->wasmEnd);  if (r) return r;
913
20
            u8 t = BaseTypeOf(selType);
914
915
20
            r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r;
916
19
            r = v_pop_expect(v, t, &a);            if (r) return r;
917
18
            r = v_pop_expect(v, t, &a);            if (r) return r;
918
17
            r = v_push(v, t);                      if (r) return r;
919
17
            break;
920
17
        }
921
922
444
        case 0x25: // table.get
923
537
        case 0x26: // table.set
924
537
        {
925
537
            u32 tableIdx;
926
537
            r = ReadLEB_u32(&tableIdx, &v->wasm, v->wasmEnd);
927
537
            if (r) return r;
928
535
            if (tableIdx >= v->module->numTables) return m3Err_unknownTable;
929
500
            u8 t = BaseTypeOf(v->module->tables[tableIdx]->type);
930
500
            m3type_t at = v_table_addrtype(v, tableIdx);
931
932
500
            if (opcode == 0x26) {
933
79
                r = v_pop_expect(v, t, &a);   if (r) return r;
934
78
                r = v_pop_expect(v, at, &a);  if (r) return r;
935
421
            } else {
936
421
                r = v_pop_expect(v, at, &a);  if (r) return r;
937
420
                r = v_push(v, t);             if (r) return r;
938
420
            }
939
497
            break;
940
500
        }
941
942
589
        case 0xd0: // ref.null
943
589
        {
944
589
#if d_m3HasTypedRefs
945
589
            m3type_t heapBits;
946
589
            r = ParseHeapType(v->module, &heapBits, &v->wasm, v->wasmEnd);  if (r) return r;
947
584
            u8 t = (heapBits & d_m3Type_refExtern) ? c_m3Type_externref :
948
584
# if d_m3HasStackSwitching
949
584
                   (heapBits & d_m3Type_refCont)   ? c_m3Type_contref :
950
487
# endif
951
487
                   c_m3Type_funcref;
952
#else
953
            i8 waType;
954
            u8 t;
955
            r = ReadLEB_i7(&waType, &v->wasm, v->wasmEnd);   if (r) return r;
956
            r = NormalizeType(&t, waType);                   if (r) return r;
957
            if (!IsRefType(t)) return m3Err_wasmMalformed;
958
#endif
959
584
            r = v_push(v, t);                                if (r) return r;
960
584
            break;
961
584
        }
962
963
584
        case 0xd1: // ref.is_null
964
254
        {
965
254
            m3type_t t = c_valBottom;
966
254
            r = v_pop(v, &t);
967
254
            if (r) return r;
968
253
            if (t != c_valBottom && !IsRefType(t)) return m3Err_typeMismatch;
969
251
            r = v_push(v, c_m3Type_i32);
970
251
            if (r) return r;
971
251
            break;
972
251
        }
973
974
251
        case 0xd2: // ref.func
975
131
        {
976
131
            u32 funcIdx;
977
131
            r = ReadLEB_u32(&funcIdx, &v->wasm, v->wasmEnd);
978
131
            if (r) return r;
979
130
            if (funcIdx >= v->module->numFunctions) return m3Err_unknownFunction;
980
123
            if (!Module_IsFunctionDeclared(v->module, funcIdx)) return m3Err_undeclaredFuncRef;
981
122
            r = v_push(v, c_m3Type_funcref);
982
122
            if (r) return r;
983
122
            break;
984
122
        }
985
122
#endif
986
987
        // ---- Variable ----
988
21.8k
        case 0x20: // local.get
989
21.8k
        {
990
21.8k
            u32 idx;
991
21.8k
            r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
992
21.8k
            if (r) return r;
993
21.8k
            if (idx >= v->numLocals) return m3Err_unknownLocal;
994
21.8k
            r = v_push(v, v->localTypes[idx]);
995
21.8k
            if (r) return r;
996
21.8k
            break;
997
21.8k
        }
998
999
21.8k
        case 0x21: // local.set
1000
4.03k
        {
1001
4.03k
            u32 idx;
1002
4.03k
            r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
1003
4.03k
            if (r) return r;
1004
4.03k
            if (idx >= v->numLocals) return m3Err_unknownLocal;
1005
4.00k
            r = v_pop_expect(v, v->localTypes[idx], &a);
1006
4.00k
            if (r) return r;
1007
4.00k
            break;
1008
4.00k
        }
1009
1010
7.12k
        case 0x22: // local.tee
1011
7.12k
        {
1012
7.12k
            u32 idx;
1013
7.12k
            r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
1014
7.12k
            if (r) return r;
1015
7.12k
            if (idx >= v->numLocals) return m3Err_unknownLocal;
1016
7.11k
            r = v_pop_expect(v, v->localTypes[idx], &a);
1017
7.11k
            if (r) return r;
1018
7.11k
            r = v_push(v, v->localTypes[idx]);
1019
7.11k
            if (r) return r;
1020
7.11k
            break;
1021
7.11k
        }
1022
1023
7.11k
        case 0x23: // global.get
1024
271
        {
1025
271
            u32 idx;
1026
271
            r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
1027
271
            if (r) return r;
1028
270
            if (idx >= v->module->numGlobals) return m3Err_unknownGlobal;
1029
236
            r = v_push(v, BaseTypeOf(v->module->globals[idx].type));
1030
236
            if (r) return r;
1031
236
            break;
1032
236
        }
1033
1034
289
        case 0x24: // global.set
1035
289
        {
1036
289
            u32 idx;
1037
289
            r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
1038
289
            if (r) return r;
1039
289
            if (idx >= v->module->numGlobals) return m3Err_unknownGlobal;
1040
256
            r = v_pop_expect(v, BaseTypeOf(v->module->globals[idx].type), &a);
1041
256
            if (r) return r;
1042
255
            break;
1043
256
        }
1044
1045
        // ---- Memory load ----
1046
1.69k
        case 0x28: case 0x29: case 0x2a: case 0x2b: // i32/i64/f32/f64.load
1047
3.70k
        case 0x2c: case 0x2d: case 0x2e: case 0x2f: // i32.load8/16 s/u
1048
6.17k
        case 0x30: case 0x31: case 0x32: case 0x33: // i64.load8/16 s/u
1049
7.29k
        case 0x34: case 0x35:                         // i64.load32 s/u
1050
7.29k
        {
1051
7.29k
            u32 align, memidx; u64 offset;
1052
7.29k
            r = ReadMemoryArg(&align, &memidx, &offset, &v->wasm, v->wasmEnd); if (r) return r;
1053
7.22k
            if (align > v_max_align(opcode)) return m3Err_invalidAlignment;
1054
7.17k
            if (not v_has_memory_idx(v, memidx)) return m3Err_unknownMemory;
1055
7.16k
            if (not v_offset_in_range(v, memidx, offset)) return m3Err_wasmMalformed;
1056
7.12k
            r = v_pop_expect(v, v_memory_addrtype(v, memidx), &a); if (r) return r;
1057
7.11k
            u8 result;
1058
7.11k
            if      (opcode == 0x28) result = c_m3Type_i32;
1059
6.68k
            else if (opcode == 0x29) result = c_m3Type_i64;
1060
6.20k
            else if (opcode == 0x2a) result = c_m3Type_f32;
1061
5.84k
            else if (opcode == 0x2b) result = c_m3Type_f64;
1062
5.45k
            else if (opcode <= 0x2f) result = c_m3Type_i32;
1063
3.50k
            else                     result = c_m3Type_i64;
1064
7.11k
            r = v_push(v, result);
1065
7.11k
            if (r) return r;
1066
7.11k
            break;
1067
7.11k
        }
1068
1069
        // ---- Memory store ----
1070
7.11k
        case 0x36: case 0x37: case 0x38: case 0x39: // i32/i64/f32/f64.store
1071
1.58k
        case 0x3a: case 0x3b:                         // i32.store8/16
1072
2.36k
        case 0x3c: case 0x3d: case 0x3e:             // i64.store8/16/32
1073
2.36k
        {
1074
2.36k
            u32 align, memidx; u64 offset;
1075
2.36k
            r = ReadMemoryArg(&align, &memidx, &offset, &v->wasm, v->wasmEnd); if (r) return r;
1076
2.32k
            if (align > v_max_align(opcode)) return m3Err_invalidAlignment;
1077
2.28k
            if (not v_has_memory_idx(v, memidx)) return m3Err_unknownMemory;
1078
2.26k
            if (not v_offset_in_range(v, memidx, offset)) return m3Err_wasmMalformed;
1079
2.21k
            u8 valtype;
1080
2.21k
            if      (opcode == 0x36) valtype = c_m3Type_i32;
1081
1.88k
            else if (opcode == 0x37) valtype = c_m3Type_i64;
1082
1.64k
            else if (opcode == 0x38) valtype = c_m3Type_f32;
1083
1.43k
            else if (opcode == 0x39) valtype = c_m3Type_f64;
1084
1.29k
            else if (opcode <= 0x3b) valtype = c_m3Type_i32;
1085
719
            else                     valtype = c_m3Type_i64;
1086
2.21k
            r = v_pop_expect(v, valtype, &a); if (r) return r;
1087
2.21k
            r = v_pop_expect(v, v_memory_addrtype(v, memidx), &a); if (r) return r;
1088
2.21k
            break;
1089
2.21k
        }
1090
1091
        // ---- Memory size/grow ----
1092
2.21k
        case 0x3f: // memory.size
1093
1.40k
        {
1094
1.40k
            u32 memidx;
1095
1.40k
            r = ReadLEB_u32(&memidx, &v->wasm, v->wasmEnd); if (r) return r;
1096
1.40k
            if (not v_has_memory_idx(v, memidx)) return m3Err_unknownMemory;
1097
1.37k
            r = v_push(v, v_memory_addrtype(v, memidx)); if (r) return r;
1098
1.37k
            break;
1099
1.37k
        }
1100
1.37k
        case 0x40: // memory.grow
1101
952
        {
1102
952
            u32 memidx;
1103
952
            r = ReadLEB_u32(&memidx, &v->wasm, v->wasmEnd); if (r) return r;
1104
949
            if (not v_has_memory_idx(v, memidx)) return m3Err_unknownMemory;
1105
908
            r = v_pop_expect(v, v_memory_addrtype(v, memidx), &a); if (r) return r;
1106
907
            r = v_push(v, v_memory_addrtype(v, memidx)); if (r) return r;
1107
907
            break;
1108
907
        }
1109
1110
        // ---- Constants ----
1111
53.7k
        case 0x41: { // i32.const
1112
53.7k
            i32 val;
1113
53.7k
            r = ReadLEB_i32(&val, &v->wasm, v->wasmEnd); if (r) return r;
1114
53.7k
            r = v_push(v, c_m3Type_i32); if (r) return r;
1115
53.7k
            break;
1116
53.7k
        }
1117
53.7k
        case 0x42: { // i64.const
1118
51.6k
            i64 val;
1119
51.6k
            r = ReadLEB_i64(&val, &v->wasm, v->wasmEnd); if (r) return r;
1120
51.6k
            r = v_push(v, c_m3Type_i64); if (r) return r;
1121
51.6k
            break;
1122
51.6k
        }
1123
51.6k
        case 0x43: { // f32.const
1124
6.24k
            if (v->wasm + 4 > v->wasmEnd) return m3Err_wasmUnderrun;
1125
6.23k
            v->wasm += 4;
1126
6.23k
            r = v_push(v, c_m3Type_f32); if (r) return r;
1127
6.23k
            break;
1128
6.23k
        }
1129
6.23k
        case 0x44: { // f64.const
1130
1.83k
            if (v->wasm + 8 > v->wasmEnd) return m3Err_wasmUnderrun;
1131
1.83k
            v->wasm += 8;
1132
1.83k
            r = v_push(v, c_m3Type_f64); if (r) return r;
1133
1.83k
            break;
1134
1.83k
        }
1135
1136
1137
        // ---- i32 comparison ----
1138
2.82k
        case 0x45: r = v_testop(v, c_m3Type_i32); break; // i32.eqz
1139
6.48k
        case 0x46: case 0x47: case 0x48: case 0x49: case 0x4a:
1140
14.0k
        case 0x4b: case 0x4c: case 0x4d: case 0x4e: case 0x4f:
1141
14.0k
            r = v_relop(v, c_m3Type_i32); break;
1142
1143
        // ---- i64 comparison ----
1144
947
        case 0x50: r = v_testop(v, c_m3Type_i64); break; // i64.eqz
1145
4.34k
        case 0x51: case 0x52: case 0x53: case 0x54: case 0x55:
1146
7.84k
        case 0x56: case 0x57: case 0x58: case 0x59: case 0x5a:
1147
7.84k
            r = v_relop(v, c_m3Type_i64); break;
1148
1149
        // ---- f32 comparison ----
1150
2.06k
        case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f: case 0x60:
1151
2.06k
            r = v_relop(v, c_m3Type_f32); break;
1152
1153
        // ---- f64 comparison ----
1154
1.66k
        case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66:
1155
1.66k
            r = v_relop(v, c_m3Type_f64); break;
1156
1157
        // ---- i32 unary ----
1158
5.19k
        case 0x67: case 0x68: case 0x69: // clz, ctz, popcnt
1159
5.19k
            r = v_unop(v, c_m3Type_i32, c_m3Type_i32); break;
1160
1161
        // ---- i32 binary ----
1162
6.24k
        case 0x6a: case 0x6b: case 0x6c: case 0x6d: case 0x6e: case 0x6f:
1163
12.2k
        case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75:
1164
14.7k
        case 0x76: case 0x77: case 0x78: // add..rotr
1165
14.7k
            r = v_binop(v, c_m3Type_i32); break;
1166
1167
        // ---- i64 unary ----
1168
6.18k
        case 0x79: case 0x7a: case 0x7b: // clz, ctz, popcnt
1169
6.18k
            r = v_unop(v, c_m3Type_i64, c_m3Type_i64); break;
1170
1171
        // ---- i64 binary ----
1172
7.94k
        case 0x7c: case 0x7d: case 0x7e: case 0x7f: case 0x80: case 0x81:
1173
12.2k
        case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87:
1174
14.7k
        case 0x88: case 0x89: case 0x8a: // add..rotr
1175
14.7k
            r = v_binop(v, c_m3Type_i64); break;
1176
1177
        // ---- f32 unary ----
1178
2.71k
        case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f:
1179
3.78k
        case 0x90: case 0x91: // abs, neg, ceil, floor, trunc, nearest, sqrt
1180
3.78k
            r = v_unop(v, c_m3Type_f32, c_m3Type_f32); break;
1181
1182
        // ---- f32 binary ----
1183
2.58k
        case 0x92: case 0x93: case 0x94: case 0x95: case 0x96:
1184
3.35k
        case 0x97: case 0x98: // add, sub, mul, div, min, max, copysign
1185
3.35k
            r = v_binop(v, c_m3Type_f32); break;
1186
1187
        // ---- f64 unary ----
1188
2.38k
        case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d:
1189
3.35k
        case 0x9e: case 0x9f: // abs, neg, ceil, floor, trunc, nearest, sqrt
1190
3.35k
            r = v_unop(v, c_m3Type_f64, c_m3Type_f64); break;
1191
1192
        // ---- f64 binary ----
1193
2.13k
        case 0xa0: case 0xa1: case 0xa2: case 0xa3: case 0xa4:
1194
2.84k
        case 0xa5: case 0xa6: // add, sub, mul, div, min, max, copysign
1195
2.84k
            r = v_binop(v, c_m3Type_f64); break;
1196
1197
        // ---- Conversions ----
1198
494
        case 0xa7: r = v_cvtop(v, c_m3Type_i64, c_m3Type_i32); break; // i32.wrap/i64
1199
914
        case 0xa8: case 0xa9:   // i32.trunc_s/f32, i32.trunc_u/f32
1200
914
            r = v_cvtop(v, c_m3Type_f32, c_m3Type_i32); break;
1201
957
        case 0xaa: case 0xab:   // i32.trunc_s/f64, i32.trunc_u/f64
1202
957
            r = v_cvtop(v, c_m3Type_f64, c_m3Type_i32); break;
1203
1.24k
        case 0xac: case 0xad:   // i64.extend_s/i32, i64.extend_u/i32
1204
1.24k
            r = v_cvtop(v, c_m3Type_i32, c_m3Type_i64); break;
1205
940
        case 0xae: case 0xaf:   // i64.trunc_s/f32, i64.trunc_u/f32
1206
940
            r = v_cvtop(v, c_m3Type_f32, c_m3Type_i64); break;
1207
1.37k
        case 0xb0: case 0xb1:   // i64.trunc_s/f64, i64.trunc_u/f64
1208
1.37k
            r = v_cvtop(v, c_m3Type_f64, c_m3Type_i64); break;
1209
1.93k
        case 0xb2: case 0xb3:   // f32.convert_s/i32, f32.convert_u/i32
1210
1.93k
            r = v_cvtop(v, c_m3Type_i32, c_m3Type_f32); break;
1211
2.15k
        case 0xb4: case 0xb5:   // f32.convert_s/i64, f32.convert_u/i64
1212
2.15k
            r = v_cvtop(v, c_m3Type_i64, c_m3Type_f32); break;
1213
271
        case 0xb6:              // f32.demote/f64
1214
271
            r = v_cvtop(v, c_m3Type_f64, c_m3Type_f32); break;
1215
2.73k
        case 0xb7: case 0xb8:   // f64.convert_s/i32, f64.convert_u/i32
1216
2.73k
            r = v_cvtop(v, c_m3Type_i32, c_m3Type_f64); break;
1217
3.14k
        case 0xb9: case 0xba:   // f64.convert_s/i64, f64.convert_u/i64
1218
3.14k
            r = v_cvtop(v, c_m3Type_i64, c_m3Type_f64); break;
1219
339
        case 0xbb:              // f64.promote/f32
1220
339
            r = v_cvtop(v, c_m3Type_f32, c_m3Type_f64); break;
1221
466
        case 0xbc:              // i32.reinterpret/f32
1222
466
            r = v_cvtop(v, c_m3Type_f32, c_m3Type_i32); break;
1223
906
        case 0xbd:              // i64.reinterpret/f64
1224
906
            r = v_cvtop(v, c_m3Type_f64, c_m3Type_i64); break;
1225
1.72k
        case 0xbe:              // f32.reinterpret/i32
1226
1.72k
            r = v_cvtop(v, c_m3Type_i32, c_m3Type_f32); break;
1227
1.76k
        case 0xbf:              // f64.reinterpret/i64
1228
1.76k
            r = v_cvtop(v, c_m3Type_i64, c_m3Type_f64); break;
1229
1230
        // ---- Sign-extension (MVP post) ----
1231
3.71k
        case 0xc0: case 0xc1:   // i32.extend8_s, i32.extend16_s
1232
3.71k
            r = v_unop(v, c_m3Type_i32, c_m3Type_i32); break;
1233
2.91k
        case 0xc2: case 0xc3: case 0xc4: // i64.extend8/16/32_s
1234
2.91k
            r = v_unop(v, c_m3Type_i64, c_m3Type_i64); break;
1235
1236
        // ---- 0xFC prefix (saturating truncations, bulk memory, wide arithmetic) ----
1237
6.03k
        case 0xfc:
1238
6.03k
        {
1239
6.03k
            u32 sub;
1240
6.03k
            r = ReadLEB_u32(&sub, &v->wasm, v->wasmEnd);
1241
6.03k
            if (r) return r;
1242
6.03k
            switch (sub) {
1243
1.13k
            case 0x00: case 0x01: // i32.trunc_sat_f32_s/u
1244
1.13k
                r = v_cvtop(v, c_m3Type_f32, c_m3Type_i32); break;
1245
827
            case 0x02: case 0x03: // i32.trunc_sat_f64_s/u
1246
827
                r = v_cvtop(v, c_m3Type_f64, c_m3Type_i32); break;
1247
901
            case 0x04: case 0x05: // i64.trunc_sat_f32_s/u
1248
901
                r = v_cvtop(v, c_m3Type_f32, c_m3Type_i64); break;
1249
759
            case 0x06: case 0x07: // i64.trunc_sat_f64_s/u
1250
759
                r = v_cvtop(v, c_m3Type_f64, c_m3Type_i64); break;
1251
45
            case 0x08: // memory.init
1252
45
            {
1253
45
                u32 dataidx, memidx;
1254
45
                r = ReadLEB_u32(&dataidx, &v->wasm, v->wasmEnd); if (r) return r;
1255
44
                r = ReadLEB_u32(&memidx, &v->wasm, v->wasmEnd); if (r) return r;
1256
43
                if (not v_has_memory_idx(v, memidx)) return m3Err_unknownMemory;
1257
                // the segments must have been declared up front by a data count section
1258
1
                if (not v->module->hasDataCount) return m3Err_dataCountRequired;
1259
0
                if (dataidx >= v->module->numDataSegments) return m3Err_unknownDataSegment;
1260
                // the segment is indexed as an i32 no matter how the memory is
1261
                // addressed; only the destination follows the address type
1262
0
                r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // n
1263
0
                r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // src
1264
0
                r = v_pop_expect(v, v_memory_addrtype(v, memidx), &a); if (r) return r; // dst
1265
0
                break;
1266
0
            }
1267
2
            case 0x09: // data.drop
1268
2
            {
1269
2
                u32 dataidx;
1270
2
                r = ReadLEB_u32(&dataidx, &v->wasm, v->wasmEnd); if (r) return r;
1271
                // needs the segment, but not a memory
1272
1
                if (not v->module->hasDataCount) return m3Err_dataCountRequired;
1273
0
                if (dataidx >= v->module->numDataSegments) return m3Err_unknownDataSegment;
1274
0
                break;
1275
0
            }
1276
0
#if d_m3HasRefTypes
1277
36
            case 0x0c: // table.init
1278
362
            case 0x0e: // table.copy
1279
362
            {
1280
362
                u32 a1, a2;
1281
362
                r = ReadLEB_u32(&a1, &v->wasm, v->wasmEnd); if (r) return r;
1282
361
                r = ReadLEB_u32(&a2, &v->wasm, v->wasmEnd); if (r) return r;
1283
1284
360
                if (sub == 0x0c) {                                          // elemidx, tableidx
1285
                    // the immediates are encoded elem-then-table, but the spec's
1286
                    // rule requires the table to be defined before the segment
1287
35
                    if (a2 >= v->module->numTables) return m3Err_unknownTable;
1288
1
                    if (a1 >= v->module->numElementSegments) return m3Err_unknownElemSegment;
1289
0
                    if (v->module->elementSegments[a1].type != v->module->tables[a2]->type)
1290
0
                        return m3Err_typeMismatch;
1291
325
                } else {                                                    // dst, src
1292
325
                    if (a1 >= v->module->numTables) return m3Err_unknownTable;
1293
298
                    if (a2 >= v->module->numTables) return m3Err_unknownTable;
1294
284
                    if (v->module->tables[a1]->type != v->module->tables[a2]->type)
1295
0
                        return m3Err_typeMismatch;
1296
284
                }
1297
1298
284
                if (sub == 0x0c) {
1299
                    // table.init indexes the segment as an i32 whatever the
1300
                    // table is; only the destination follows the table
1301
0
                    r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r;             // n
1302
0
                    r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r;             // s
1303
0
                    r = v_pop_expect(v, v_table_addrtype(v, a2), &a); if (r) return r;  // d
1304
284
                } else {
1305
                    // table.copy: each index follows its own table, and the
1306
                    // length the narrower of the two
1307
284
                    m3type_t dType = v_table_addrtype(v, a1);
1308
284
                    m3type_t sType = v_table_addrtype(v, a2);
1309
284
                    m3type_t nType = (dType == c_m3Type_i64 and sType == c_m3Type_i64)
1310
284
                                       ? c_m3Type_i64 : c_m3Type_i32;
1311
284
                    r = v_pop_expect(v, nType, &a); if (r) return r;  // n
1312
283
                    r = v_pop_expect(v, sType, &a); if (r) return r;  // s
1313
282
                    r = v_pop_expect(v, dType, &a); if (r) return r;  // d
1314
282
                }
1315
281
                break;
1316
284
            }
1317
281
            case 0x0d: // elem.drop
1318
19
            {
1319
19
                u32 elemIdx;
1320
19
                r = ReadLEB_u32(&elemIdx, &v->wasm, v->wasmEnd); if (r) return r;
1321
18
                if (elemIdx >= v->module->numElementSegments) return m3Err_unknownElemSegment;
1322
0
                break;
1323
18
            }
1324
214
            case 0x0f: // table.grow
1325
298
            case 0x10: // table.size
1326
404
            case 0x11: // table.fill
1327
404
            {
1328
404
                u32 tableIdx;
1329
404
                r = ReadLEB_u32(&tableIdx, &v->wasm, v->wasmEnd); if (r) return r;
1330
402
                if (tableIdx >= v->module->numTables) return m3Err_unknownTable;
1331
359
                u8 t = BaseTypeOf(v->module->tables[tableIdx]->type);
1332
359
                m3type_t at = v_table_addrtype(v, tableIdx);
1333
1334
359
                if (sub == 0x10) {                                          // table.size
1335
67
                    r = v_push(v, at); if (r) return r;
1336
292
                } else if (sub == 0x0f) {                                   // table.grow
1337
190
                    r = v_pop_expect(v, at, &a); if (r) return r;  // n
1338
189
                    r = v_pop_expect(v, t, &a);  if (r) return r;  // init
1339
188
                    r = v_push(v, at);           if (r) return r;
1340
188
                } else {                                                    // table.fill
1341
102
                    r = v_pop_expect(v, at, &a); if (r) return r;  // n
1342
101
                    r = v_pop_expect(v, t, &a);  if (r) return r;  // val
1343
100
                    r = v_pop_expect(v, at, &a); if (r) return r;  // i
1344
100
                }
1345
354
                break;
1346
359
            }
1347
354
#endif
1348
525
            case 0x0a: // memory.copy
1349
525
            {
1350
525
                u32 dst, src;
1351
525
                r = ReadLEB_u32(&dst, &v->wasm, v->wasmEnd); if (r) return r;
1352
524
                r = ReadLEB_u32(&src, &v->wasm, v->wasmEnd); if (r) return r;
1353
523
                if (not v_has_memory_idx(v, dst) or not v_has_memory_idx(v, src)) return m3Err_unknownMemory;
1354
                // Spec: each address follows its own memory, and the length is
1355
                // typed by the narrower of the two - so copying between an i32
1356
                // and an i64 memory takes an i32 length.
1357
485
                m3type_t dstType = v_memory_addrtype(v, dst);
1358
485
                m3type_t srcType = v_memory_addrtype(v, src);
1359
485
                m3type_t lenType = (dstType == c_m3Type_i64 and srcType == c_m3Type_i64)
1360
485
                                     ? c_m3Type_i64 : c_m3Type_i32;
1361
485
                r = v_pop_expect(v, lenType, &a); if (r) return r; // n
1362
484
                r = v_pop_expect(v, srcType, &a); if (r) return r; // src
1363
484
                r = v_pop_expect(v, dstType, &a); if (r) return r; // dst
1364
483
                break;
1365
484
            }
1366
483
            case 0x0b: // memory.fill
1367
160
            {
1368
160
                u32 memidx;
1369
160
                r = ReadLEB_u32(&memidx, &v->wasm, v->wasmEnd); if (r) return r;
1370
158
                if (not v_has_memory_idx(v, memidx)) return m3Err_unknownMemory;
1371
154
                m3type_t addrType = v_memory_addrtype(v, memidx);
1372
154
                r = v_pop_expect(v, addrType, &a); if (r) return r;      // n
1373
152
                r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r;  // val
1374
151
                r = v_pop_expect(v, addrType, &a); if (r) return r;      // dst
1375
150
                break;
1376
151
            }
1377
150
#if d_m3HasWideArithmetic
1378
270
            case 0x13: case 0x14: // i64.add128, i64.sub128
1379
756
            case 0x15: case 0x16: // i64.mul_wide_s, i64.mul_wide_u
1380
756
            {
1381
                // two 128-bit operands of two i64 halves each, or two i64 factors;
1382
                // either way a 128-bit result, the low half pushed first
1383
756
                u32 numOperands = (sub <= 0x14) ? 4 : 2;
1384
2.79k
                for (u32 i = 0; i < numOperands; ++i) {
1385
2.04k
                    r = v_pop_expect(v, c_m3Type_i64, &a); if (r) return r;
1386
2.04k
                }
1387
751
                r = v_push(v, c_m3Type_i64); if (r) return r;
1388
751
                r = v_push(v, c_m3Type_i64); if (r) return r;
1389
751
                break;
1390
751
            }
1391
751
#endif
1392
751
            default:
1393
                // Unknown FC sub-opcode: skip validation (allow forward compat)
1394
145
                break;
1395
6.03k
            }
1396
5.78k
            break;
1397
6.03k
        }
1398
1399
5.78k
#if d_m3HasAtomics
1400
        // ---- 0xFE prefix (atomics) ----
1401
5.78k
        case 0xfe:
1402
1.09k
        {
1403
1.09k
            u32 sub;
1404
1.09k
            r = ReadLEB_u32(&sub, &v->wasm, v->wasmEnd);
1405
1.09k
            if (r) return r;
1406
1407
1.09k
            if (sub == 0x03) { // atomic.fence
1408
31
                u8 reserved;
1409
31
                r = Read_u8(&reserved, &v->wasm, v->wasmEnd); if (r) return r;
1410
29
                if (reserved != 0) return m3Err_wasmMalformed;
1411
25
                break;
1412
29
            }
1413
1414
1.06k
            u32 width, numOperands; m3type_t valueType, resultType;
1415
1.06k
            if (not v_atomic_shape(sub, &width, &numOperands, &valueType, &resultType)) return m3Err_unknownOpcode;
1416
1417
1.00k
            u32 align, memidx; u64 offset;
1418
1.00k
            r = ReadMemoryArg(&align, &memidx, &offset, &v->wasm, v->wasmEnd); if (r) return r;
1419
            // Spec: unlike an ordinary access, an atomic one must be aligned exactly
1420
997
            if ((1u << align) != width) return m3Err_invalidAtomicAlignment;
1421
987
            if (not v_has_memory_idx(v, memidx)) return m3Err_unknownMemory;
1422
960
            if (not v_offset_in_range(v, memidx, offset)) return m3Err_wasmMalformed;
1423
1424
            // the operands above the address are all of the one type, except a
1425
            // wait, whose timeout is an i64 whatever it is waiting on
1426
954
            if (sub == 0x01 or sub == 0x02) {
1427
90
                r = v_pop_expect(v, c_m3Type_i64, &a); if (r) return r;
1428
87
                numOperands--;
1429
87
            }
1430
1.90k
            for (u32 i = 1; i < numOperands; ++i) {
1431
951
                r = v_pop_expect(v, valueType, &a); if (r) return r;
1432
951
            }
1433
950
            r = v_pop_expect(v, v_memory_addrtype(v, memidx), &a); if (r) return r;
1434
949
            if (resultType != c_m3Type_none) {
1435
856
                r = v_push(v, resultType); if (r) return r;
1436
856
            }
1437
949
            break;
1438
949
        }
1439
949
#endif
1440
1441
949
#if d_m3HasStackSwitching
1442
        // ---- Stack Switching ----
1443
949
        case 0xe0: // cont.new
1444
40
        {
1445
40
            u32 typeIdx;
1446
40
            r = ReadLEB_u32(&typeIdx, &v->wasm, v->wasmEnd); if (r) return r;
1447
39
            if (typeIdx >= v->module->numFuncTypes) return m3Err_unknownType;
1448
2
            IM3FuncType ft = v->module->funcTypes[typeIdx];
1449
2
            if (!ft || !ft->isContinuation) return m3Err_typeMismatch;
1450
0
            r = v_pop_expect(v, c_m3Type_funcref, &a); if (r) return r;
1451
0
            r = v_push(v, c_m3Type_contref); if (r) return r;
1452
0
            break;
1453
0
        }
1454
1455
55
        case 0xe1: // cont.bind
1456
55
        {
1457
55
            u32 typeIdx1, typeIdx2;
1458
55
            r = ReadLEB_u32(&typeIdx1, &v->wasm, v->wasmEnd); if (r) return r;
1459
54
            r = ReadLEB_u32(&typeIdx2, &v->wasm, v->wasmEnd); if (r) return r;
1460
52
            if (typeIdx1 >= v->module->numFuncTypes || typeIdx2 >= v->module->numFuncTypes) return m3Err_unknownType;
1461
1
            IM3FuncType ct1 = v->module->funcTypes[typeIdx1];
1462
1
            IM3FuncType ct2 = v->module->funcTypes[typeIdx2];
1463
1
            if (!ct1 || !ct2 || !ct1->isContinuation || !ct2->isContinuation) return m3Err_typeMismatch;
1464
1465
0
            IM3FuncType ft1 = ct1->contFuncType ? ct1->contFuncType : ct1;
1466
0
            IM3FuncType ft2 = ct2->contFuncType ? ct2->contFuncType : ct2;
1467
1468
0
            u16 numParams1 = ft1->numArgs;
1469
0
            u16 numParams2 = ft2->numArgs;
1470
0
            if (numParams1 < numParams2) return m3Err_typeMismatch;
1471
0
            u32 numBound = numParams1 - numParams2;
1472
1473
0
            if (ft1->numRets != ft2->numRets) return m3Err_typeMismatch;
1474
0
            for (u16 i = 0; i < ft1->numRets; ++i) {
1475
0
                if (ft1->types[i] != ft2->types[i]) return m3Err_typeMismatch;
1476
0
            }
1477
0
            for (u16 i = 0; i < numParams2; ++i) {
1478
0
                if (ft1->types[ft1->numRets + numBound + i] != ft2->types[ft2->numRets + i]) return m3Err_typeMismatch;
1479
0
            }
1480
1481
0
            r = v_pop_expect(v, c_m3Type_contref, &a); if (r) return r;
1482
1483
0
            for (u32 i = numBound; i > 0; i--) {
1484
0
                r = v_pop_expect(v, BaseTypeOf(ft1->types[ft1->numRets + i - 1]), &a);
1485
0
                if (r) return r;
1486
0
            }
1487
1488
0
            r = v_push(v, c_m3Type_contref); if (r) return r;
1489
0
            break;
1490
0
        }
1491
1492
193
        case 0xe2: // suspend
1493
193
        {
1494
193
            u32 tagIdx;
1495
193
            r = ReadLEB_u32(&tagIdx, &v->wasm, v->wasmEnd); if (r) return r;
1496
192
            if (!v->module || tagIdx >= v->module->numTags) return m3Err_unknownTag;
1497
158
            IM3FuncType tt = v->module->tags[tagIdx].type;
1498
158
            if (tt) {
1499
780
                for (u16 i = tt->numArgs; i > 0; i--) {
1500
625
                    r = v_pop_expect(v, BaseTypeOf(tt->types[tt->numRets + i - 1]), &a);
1501
625
                    if (r) return r;
1502
625
                }
1503
402
                for (u16 i = 0; i < tt->numRets; i++) {
1504
247
                    r = v_push(v, BaseTypeOf(tt->types[i]));
1505
247
                    if (r) return r;
1506
247
                }
1507
155
            }
1508
155
            break;
1509
158
        }
1510
1511
155
        case 0xe3: // resume
1512
39
        {
1513
39
            u32 typeIdx;
1514
39
            r = ReadLEB_u32(&typeIdx, &v->wasm, v->wasmEnd); if (r) return r;
1515
37
            if (typeIdx >= v->module->numFuncTypes) return m3Err_unknownType;
1516
1
            IM3FuncType ct = v->module->funcTypes[typeIdx];
1517
1
            if (!ct || !ct->isContinuation) return m3Err_typeMismatch;
1518
1519
0
            u32 numClauses;
1520
0
            r = ReadLEB_u32(&numClauses, &v->wasm, v->wasmEnd); if (r) return r;
1521
1522
0
            for (u32 i = 0; i < numClauses; i++) {
1523
0
                u8 kind;
1524
0
                r = Read_u8(&kind, &v->wasm, v->wasmEnd); if (r) return r;
1525
0
                u32 tagIdx;
1526
0
                r = ReadLEB_u32(&tagIdx, &v->wasm, v->wasmEnd); if (r) return r;
1527
0
                r = v_resume_clause(v, kind, tagIdx, ct); if (r) return r;
1528
0
            }
1529
1530
0
            r = v_pop_expect(v, c_m3Type_contref, &a); if (r) return r;
1531
1532
0
            IM3FuncType ft = ct->contFuncType ? ct->contFuncType : ct;
1533
0
            if (ft) {
1534
0
                for (u16 i = ft->numArgs; i > 0; i--) {
1535
0
                    r = v_pop_expect(v, BaseTypeOf(ft->types[ft->numRets + i - 1]), &a);
1536
0
                    if (r) return r;
1537
0
                }
1538
0
                for (u16 i = 0; i < ft->numRets; i++) {
1539
0
                    r = v_push(v, BaseTypeOf(ft->types[i]));
1540
0
                    if (r) return r;
1541
0
                }
1542
0
            }
1543
0
            break;
1544
0
        }
1545
1546
63
        case 0xe4: // resume_throw
1547
63
        {
1548
63
            u32 typeIdx, tagIdx, numClauses;
1549
63
            r = ReadLEB_u32(&typeIdx, &v->wasm, v->wasmEnd); if (r) return r;
1550
62
            r = ReadLEB_u32(&tagIdx, &v->wasm, v->wasmEnd); if (r) return r;
1551
61
            r = ReadLEB_u32(&numClauses, &v->wasm, v->wasmEnd); if (r) return r;
1552
1553
59
            if (typeIdx >= v->module->numFuncTypes) return m3Err_unknownType;
1554
31
            if (!v->module || tagIdx >= v->module->numTags) return m3Err_unknownTag;
1555
1
            IM3FuncType ct = v->module->funcTypes[typeIdx];
1556
1
            if (!ct || !ct->isContinuation) return m3Err_typeMismatch;
1557
0
            IM3FuncType tagType = v->module->tags[tagIdx].type;
1558
1559
            // it is an exception that gets raised, so the tag has to be one
1560
            // throw could have raised: no results
1561
0
            if (tagType && tagType->numRets != 0) return m3Err_typeMismatch;
1562
1563
0
            for (u32 i = 0; i < numClauses; i++) {
1564
0
                u8 kind;
1565
0
                r = Read_u8(&kind, &v->wasm, v->wasmEnd); if (r) return r;
1566
0
                u32 cTagIdx;
1567
0
                r = ReadLEB_u32(&cTagIdx, &v->wasm, v->wasmEnd); if (r) return r;
1568
0
                r = v_resume_clause(v, kind, cTagIdx, ct); if (r) return r;
1569
0
            }
1570
1571
0
            r = v_pop_expect(v, c_m3Type_contref, &a); if (r) return r;
1572
1573
0
            if (tagType) {
1574
0
                for (u16 i = tagType->numArgs; i > 0; i--) {
1575
0
                    r = v_pop_expect(v, BaseTypeOf(tagType->types[tagType->numRets + i - 1]), &a);
1576
0
                    if (r) return r;
1577
0
                }
1578
0
            }
1579
1580
0
            IM3FuncType ft = ct->contFuncType ? ct->contFuncType : ct;
1581
0
            if (ft) {
1582
0
                for (u16 i = 0; i < ft->numRets; i++) {
1583
0
                    r = v_push(v, BaseTypeOf(ft->types[i]));
1584
0
                    if (r) return r;
1585
0
                }
1586
0
            }
1587
0
            break;
1588
0
        }
1589
1590
32
        case 0xe5: // resume_throw_ref
1591
32
        {
1592
32
            u32 typeIdx, numClauses;
1593
32
            r = ReadLEB_u32(&typeIdx, &v->wasm, v->wasmEnd); if (r) return r;
1594
31
            r = ReadLEB_u32(&numClauses, &v->wasm, v->wasmEnd); if (r) return r;
1595
1596
30
            if (typeIdx >= v->module->numFuncTypes) return m3Err_unknownType;
1597
1
            IM3FuncType ct = v->module->funcTypes[typeIdx];
1598
1
            if (!ct || !ct->isContinuation) return m3Err_typeMismatch;
1599
1600
0
            for (u32 i = 0; i < numClauses; i++) {
1601
0
                u8 kind;
1602
0
                r = Read_u8(&kind, &v->wasm, v->wasmEnd); if (r) return r;
1603
0
                u32 cTagIdx;
1604
0
                r = ReadLEB_u32(&cTagIdx, &v->wasm, v->wasmEnd); if (r) return r;
1605
0
                r = v_resume_clause(v, kind, cTagIdx, ct); if (r) return r;
1606
0
            }
1607
1608
0
            r = v_pop_expect(v, c_m3Type_contref, &a); if (r) return r;
1609
0
            r = v_pop_expect(v, c_m3Type_exnref, &a); if (r) return r;
1610
1611
0
            IM3FuncType ft = ct->contFuncType ? ct->contFuncType : ct;
1612
0
            if (ft) {
1613
0
                for (u16 i = 0; i < ft->numRets; i++) {
1614
0
                    r = v_push(v, BaseTypeOf(ft->types[i]));
1615
0
                    if (r) return r;
1616
0
                }
1617
0
            }
1618
0
            break;
1619
0
        }
1620
1621
45
        case 0xe6: // switch
1622
45
        {
1623
45
            u32 typeIdx, tagIdx;
1624
45
            r = ReadLEB_u32(&typeIdx, &v->wasm, v->wasmEnd); if (r) return r;
1625
44
            r = ReadLEB_u32(&tagIdx, &v->wasm, v->wasmEnd); if (r) return r;
1626
1627
42
            if (typeIdx >= v->module->numFuncTypes) return m3Err_unknownType;
1628
5
            if (!v->module || tagIdx >= v->module->numTags) return m3Err_unknownTag;
1629
1
            IM3FuncType ct = v->module->funcTypes[typeIdx];
1630
1
            if (!ct || !ct->isContinuation) return m3Err_typeMismatch;
1631
1632
            // a switch tag carries no payload of its own
1633
0
            IM3FuncType tagType = v->module->tags[tagIdx].type;
1634
0
            if (tagType && tagType->numArgs != 0) return m3Err_typeMismatch;
1635
1636
            // switch $ct1 : [t1* (ref null $ct1)] -> [t2*], where $ct1's last
1637
            // parameter names $ct2 - the continuation this instruction
1638
            // suspends - and t2* is $ct2's parameters, not $ct1's results
1639
0
            IM3FuncType ft = ct->contFuncType;
1640
0
            if (!ft || ft->numArgs == 0) return m3Err_typeMismatch;
1641
1642
0
            IM3FuncType peer = Module_ContTypeOfRef(v->module, ft->types[ft->numRets + ft->numArgs - 1]);
1643
0
            if (!peer || !peer->contFuncType) return m3Err_typeMismatch;
1644
1645
0
            IM3FuncType self = peer->contFuncType;
1646
1647
0
            r = v_pop_expect(v, c_m3Type_contref, &a); if (r) return r;
1648
1649
0
            for (u16 i = (u16)(ft->numArgs - 1); i > 0; i--) {
1650
0
                r = v_pop_expect(v, BaseTypeOf(ft->types[ft->numRets + i - 1]), &a);
1651
0
                if (r) return r;
1652
0
            }
1653
1654
0
            for (u16 i = 0; i < self->numArgs; i++) {
1655
0
                r = v_push(v, BaseTypeOf(self->types[self->numRets + i]));
1656
0
                if (r) return r;
1657
0
            }
1658
0
            break;
1659
0
        }
1660
1661
0
#endif // d_m3HasStackSwitching
1662
1663
3.35k
        default:
1664
            // Unknown opcode - skip rather than fail for forward compat
1665
            // (the compiler will reject truly unsupported ops later)
1666
3.35k
            break;
1667
1668
442k
        } // switch
1669
1670
415k
        if (r) return r;
1671
1672
415k
    } // while
1673
1674
    // If we ran out of bytes without hitting the final end
1675
230
    return m3Err_wasmMalformed;
1676
27.3k
}
1677
1678
// ---------- Public entry point ----------
1679
1680
M3Result  ValidateFunction  (IM3Function i_function)
1681
27.4k
{
1682
27.4k
    if (!i_function->wasm) return m3Err_none;
1683
1684
27.4k
    IM3FuncType funcType = i_function->funcType;
1685
27.4k
    IM3Module   module   = i_function->module;
1686
1687
27.4k
    IM3Runtime runtime = module->runtime;
1688
1689
27.4k
    ValCtx* v = runtime->validator;
1690
27.4k
    if (!v) {
1691
27.4k
        v = m3_AllocStruct (ValCtx);
1692
27.4k
        if (!v) return m3Err_mallocFailed;
1693
27.4k
        runtime->validator = v;
1694
27.4k
    }
1695
1696
27.4k
    memset(v, 0, sizeof(*v));
1697
27.4k
    v->module   = module;
1698
27.4k
    v->function = i_function;
1699
27.4k
    v->wasm     = i_function->wasm;
1700
27.4k
    v->wasmEnd  = i_function->wasmEnd;
1701
1702
    // Skip code size LEB
1703
27.4k
    u32 size;
1704
27.4k
    M3Result r = ReadLEB_u32(&size, &v->wasm, v->wasmEnd);
1705
27.4k
    if (r) return r;
1706
1707
    // Parse locals
1708
27.4k
    u32 numLocalBlocks;
1709
27.4k
    r = ReadLEB_u32(&numLocalBlocks, &v->wasm, v->wasmEnd);
1710
27.4k
    if (r) return r;
1711
1712
    // First: params. Running out of room has to be an error, not a truncation:
1713
    // a short localTypes would make later local.get indices read as unknown
1714
27.4k
    u16 numParams = funcType ? funcType->numArgs : 0;
1715
27.4k
    if (numParams > d_m3ValStack) return m3Err_functionStackOverflow;
1716
32.1k
    for (u16 i = 0; i < numParams; i++) {
1717
4.66k
        v->localTypes[v->numLocals++] = BaseTypeOf(funcType->types[funcType->numRets + i]);
1718
4.66k
    }
1719
1720
    // Then: declared locals
1721
49.0k
    for (u32 b = 0; b < numLocalBlocks; b++) {
1722
21.6k
        u32 count;
1723
21.6k
        r = ReadLEB_u32(&count, &v->wasm, v->wasmEnd);
1724
21.6k
        if (r) return r;
1725
21.6k
        m3type_t localType;
1726
21.6k
        r = ParseValueType(v->module, &localType, &v->wasm, v->wasmEnd);
1727
21.6k
        if (r) return r;
1728
21.6k
        u8 normalized = BaseTypeOf(localType);
1729
21.6k
        if (count > (u32) (d_m3ValStack - v->numLocals)) return m3Err_functionStackOverflow;
1730
4.39M
        for (u32 c = 0; c < count; c++) {
1731
4.37M
            v->localTypes[v->numLocals++] = normalized;
1732
4.37M
        }
1733
21.6k
    }
1734
1735
    // Push the function-level control frame
1736
27.3k
    r = v_push_ctrl(v, 0x00, funcType); // opcode 0x00 marks function frame
1737
27.3k
    if (r) return r;
1738
1739
    // Push params onto operand stack (they're part of the function body's initial stack)
1740
    // Actually per the spec, locals are indexed but not on the operand stack.
1741
    // The function frame's params are NOT pushed to the operand stack.
1742
    // Only block params would be pushed (and for the function frame there are no block params
1743
    // since the function body's "block type" has results = function returns, params = 0).
1744
    // The function frame's label_types = results (since it's not a loop).
1745
1746
    // Validate the body
1747
27.3k
    r = v_validate_body(v);
1748
27.3k
    if (r) return r;
1749
1750
    // After validation, control stack should be empty and all declared bytes consumed
1751
24.7k
    if (v->ctrlTop != 0)
1752
0
        return m3Err_wasmMalformed;
1753
1754
24.7k
    if (v->wasm != v->wasmEnd)
1755
10
        return m3Err_wasmSectionUnderrun;
1756
1757
24.7k
    return m3Err_none;
1758
24.7k
}
1759
1760
#else // !d_m3EnableValidation
1761
1762
M3Result  ValidateFunction  (IM3Function i_function)
1763
{
1764
    (void)i_function;
1765
    return m3Err_none;
1766
}
1767
1768
// clang-format on
1769
1770
#endif // d_m3EnableValidation