Coverage Report

Created: 2026-08-14 06:12

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