Coverage Report

Created: 2026-09-04 06:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wasm3/source/m3_compile.c
Line
Count
Source
1
//
2
//  m3_compile.c
3
//
4
//  Created by Steven Massey on 4/17/19.
5
//  Copyright © 2019 Steven Massey. All rights reserved.
6
//
7
8
// Allow using opcodes for compilation process
9
#define M3_COMPILE_OPCODES
10
11
#include "m3_env.h"
12
#include "m3_compile.h"
13
#include "m3_exec.h"
14
#include "m3_exception.h"
15
#include "m3_info.h"
16
#include "m3_validate.h"
17
18
//----- EMIT --------------------------------------------------------------------------------------------------------------
19
20
static inline
21
pc_t GetPC (IM3Compilation o)
22
8.17k
{
23
8.17k
    return GetPagePC(o->page);
24
8.17k
}
25
26
static M3_NOINLINE
27
M3Result EnsureCodePageNumLines (IM3Compilation o, u32 i_numLines)
28
46.8k
{
29
46.8k
    M3Result result = m3Err_none;
30
31
46.8k
    i_numLines += 2; // room for Bridge
32
33
46.8k
    if (NumFreeLines(o->page) < i_numLines) {
34
7
        IM3CodePage page = AcquireCodePageWithCapacity(o->runtime, i_numLines);
35
36
7
        if (page) {
37
7
            m3log(emit, "bridging new code page from: %d %p (free slots: %d) to: %d", o->page->info.sequence, GetPC(o), NumFreeLines(o->page), page->info.sequence);
38
7
            d_m3Assert(NumFreeLines(o->page) >= 2);
39
40
7
            EmitWord(o->page, op_Branch);
41
7
            EmitWord(o->page, GetPagePC(page));
42
43
7
            ReleaseCodePage(o->runtime, o->page);
44
45
7
            o->page = page;
46
7
        } else {
47
0
            result = m3Err_mallocFailedCodePage;
48
0
        }
49
7
    }
50
51
46.8k
    return result;
52
46.8k
}
53
54
// invalidate the pending local.set fold candidate; called wherever code is emitted or a
55
// branch target captures the current pc (a fold appends an immediate, moving that position)
56
static inline
57
void InvalidateFold (IM3Compilation o)
58
62.3k
{
59
62.3k
#if d_m3FoldSetLocal
60
62.3k
    o->foldPatchPC = NULL;
61
#else
62
    (void)o;
63
#endif
64
62.3k
}
65
66
static M3_NOINLINE
67
M3Result EmitOp (IM3Compilation o, IM3Operation i_operation)
68
60.9k
{
69
60.9k
    M3Result result = m3Err_none;                                 d_m3Assert (i_operation or IsStackPolymorphic (o));
70
71
60.9k
    InvalidateFold(o);
72
73
    // it's OK for page to be null; when compile-walking the bytecode without emitting
74
60.9k
    if (o->page) {
75
#if d_m3EnableOpTracing
76
        if (i_operation != op_DumpStack) {
77
            o->numEmits++;
78
        }
79
#endif
80
81
        // have execution jump to a new page if slots are critically low
82
46.6k
        result = EnsureCodePageNumLines(o, d_m3CodePageFreeLinesThreshold);
83
84
46.6k
        if (not result) {
85
46.6k
            if (d_m3LogEmit) {
86
0
                log_emit(o, i_operation);
87
0
            }
88
#if d_m3RecordBacktraces
89
            EmitMappingEntry(o->page, (u32)(o->lastOpcodeStart - o->module->wasmStart));
90
#endif // d_m3RecordBacktraces
91
46.6k
            EmitWord(o->page, i_operation);
92
46.6k
        }
93
46.6k
    }
94
95
60.9k
    return result;
96
60.9k
}
97
98
// Push an immediate constant into the M3 codestream
99
static M3_NOINLINE
100
void EmitConstant32 (IM3Compilation o, const u32 i_immediate)
101
3.75k
{
102
3.75k
    if (o->page) {                                                          m3log (emit, "const32: %ud", i_immediate);
103
3.75k
        EmitWord32(o->page, i_immediate);
104
3.75k
    }
105
3.75k
}
106
107
#if d_m3HasMemory64
108
// Takes two lines of the code page where a pointer is 32 bits, so whatever
109
// reads it back has to step _pc by the same amount - see op_Const64.
110
static M3_NOINLINE
111
void EmitConstant64 (IM3Compilation o, const u64 i_immediate)
112
137
{
113
137
    if (o->page) {
114
137
        EmitWord64(o->page, i_immediate);
115
137
    }
116
137
}
117
#endif
118
119
static M3_NOINLINE
120
void EmitSlotOffset (IM3Compilation o, const i32 i_offset)
121
64.0k
{
122
64.0k
    if (o->page) {                                                          m3log (emit, "slot: [%d]", i_offset);
123
46.2k
        EmitWord32(o->page, i_offset);
124
46.2k
    }
125
64.0k
}
126
127
static M3_NOINLINE
128
pc_t EmitPointer (IM3Compilation o, const void* const i_pointer)
129
3.47k
{
130
3.47k
    pc_t ptr = GetPagePC(o->page);
131
132
3.47k
    if (o->page) {                                                          m3log (emit, "ptr: %p", i_pointer);
133
3.47k
        EmitWord(o->page, i_pointer);
134
3.47k
    }
135
136
3.47k
    return ptr;
137
3.47k
}
138
139
static M3_NOINLINE
140
void* ReservePointer (IM3Compilation o)
141
242
{
142
242
    pc_t ptr = GetPagePC(o->page);
143
242
    EmitPointer(o, NULL);
144
242
    return (void*)ptr;
145
242
}
146
147
148
//-------------------------------------------------------------------------------------------------------------------------
149
150
#define d_indent "     | %s"
151
152
// just want less letters and numbers to stare at down the way in the compiler table
153
#define i_32    c_m3Type_i32
154
#define i_64    c_m3Type_i64
155
#define f_32    c_m3Type_f32
156
#define f_64    c_m3Type_f64
157
#define none    c_m3Type_none
158
#define any     (u8)-1
159
160
#if d_m3HasFloat
161
#  define FPOP(x) x
162
#else
163
#  define FPOP(x) NULL
164
#endif
165
166
// These are indexed by M3ValueType, so every type up to c_m3Type_externref needs
167
// an entry. A reference is one pointer-sized word, so it moves with the integer
168
// operation of that width; v128 has no operations at all.
169
#if M3_SIZEOF_PTR == 8
170
#  define REFOP(NAME)  op_##NAME##_i64
171
#else
172
#  define REFOP(NAME)  op_##NAME##_i32
173
#endif
174
175
// clang-format off
176
177
static const IM3Operation c_preserveSetSlot[] = { NULL, op_PreserveSetSlot_i32,       op_PreserveSetSlot_i64,
178
                                                    FPOP(op_PreserveSetSlot_f32), FPOP(op_PreserveSetSlot_f64),
179
                                                    NULL, REFOP(PreserveSetSlot),  REFOP(PreserveSetSlot),
180
                                                    REFOP(PreserveSetSlot) };
181
static const IM3Operation c_setSetOps[] =       { NULL, op_SetSlot_i32,               op_SetSlot_i64,
182
                                                    FPOP(op_SetSlot_f32),         FPOP(op_SetSlot_f64),
183
                                                    NULL, REFOP(SetSlot),         REFOP(SetSlot),
184
                                                    REFOP(SetSlot) };
185
static const IM3Operation c_setGlobalOps[] =    { NULL, op_SetGlobal_i32,             op_SetGlobal_i64,
186
                                                    FPOP(op_SetGlobal_f32),       FPOP(op_SetGlobal_f64),
187
                                                    NULL, REFOP(SetGlobal),       REFOP(SetGlobal),
188
                                                    REFOP(SetGlobal) };
189
static const IM3Operation c_setRegisterOps[] =  { NULL, op_SetRegister_i32,           op_SetRegister_i64,
190
                                                    FPOP(op_SetRegister_f32),     FPOP(op_SetRegister_f64),
191
                                                    NULL, REFOP(SetRegister),     REFOP(SetRegister),
192
                                                    REFOP(SetRegister) };
193
194
// A table shorter than the enum reads out of bounds, so tie the two together at
195
// build time: adding a value type must not silently outgrow these.
196
#define d_m3CheckTypeTable(TABLE) \
197
    M3_STATIC_ASSERT (sizeof (TABLE) / sizeof (*(TABLE)) == c_m3Type_count, TABLE##_needs_one_entry_per_type)
198
199
d_m3CheckTypeTable(c_preserveSetSlot);
200
d_m3CheckTypeTable(c_setSetOps);
201
d_m3CheckTypeTable(c_setGlobalOps);
202
d_m3CheckTypeTable(c_setRegisterOps);
203
204
#if d_m3FoldSetLocal
205
206
// destination-folded variants of the hot binops, indexed by the operand-form index
207
// recorded at emission: [0]=_rs [1]=_sr [2]=_ss [3]=(fp _rr, never folded)
208
#define d_foldOpList(TYPE, NAME)            { op_##TYPE##_##NAME##_rs_f, op_##TYPE##_##NAME##_sr_f, op_##TYPE##_##NAME##_ss_f, NULL }
209
#define d_foldCommutativeOpList(TYPE, NAME) { op_##TYPE##_##NAME##_rs_f, NULL,                      op_##TYPE##_##NAME##_ss_f, NULL }
210
211
static const IM3Operation c_fold_i32_Add[]         = d_foldCommutativeOpList (i32, Add);
212
static const IM3Operation c_fold_i32_Subtract[]    = d_foldOpList (i32, Subtract);
213
static const IM3Operation c_fold_i32_Multiply[]    = d_foldCommutativeOpList (i32, Multiply);
214
static const IM3Operation c_fold_u32_And[]         = d_foldCommutativeOpList (u32, And);
215
static const IM3Operation c_fold_u32_Or[]          = d_foldCommutativeOpList (u32, Or);
216
static const IM3Operation c_fold_u32_Xor[]         = d_foldCommutativeOpList (u32, Xor);
217
static const IM3Operation c_fold_u32_ShiftLeft[]   = d_foldOpList (u32, ShiftLeft);
218
static const IM3Operation c_fold_i32_ShiftRight[]  = d_foldOpList (i32, ShiftRight);
219
static const IM3Operation c_fold_u32_ShiftRight[]  = d_foldOpList (u32, ShiftRight);
220
221
static const IM3Operation c_fold_i64_Add[]         = d_foldCommutativeOpList (i64, Add);
222
static const IM3Operation c_fold_i64_Subtract[]    = d_foldOpList (i64, Subtract);
223
static const IM3Operation c_fold_i64_Multiply[]    = d_foldCommutativeOpList (i64, Multiply);
224
static const IM3Operation c_fold_u64_And[]         = d_foldCommutativeOpList (u64, And);
225
static const IM3Operation c_fold_u64_Or[]          = d_foldCommutativeOpList (u64, Or);
226
static const IM3Operation c_fold_u64_Xor[]         = d_foldCommutativeOpList (u64, Xor);
227
static const IM3Operation c_fold_u64_ShiftLeft[]   = d_foldOpList (u64, ShiftLeft);
228
static const IM3Operation c_fold_i64_ShiftRight[]  = d_foldOpList (i64, ShiftRight);
229
static const IM3Operation c_fold_u64_ShiftRight[]  = d_foldOpList (u64, ShiftRight);
230
231
#  if d_m3HasFloat
232
static const IM3Operation c_fold_f32_Add[]         = d_foldCommutativeOpList (f32, Add);
233
static const IM3Operation c_fold_f32_Subtract[]    = d_foldOpList (f32, Subtract);
234
static const IM3Operation c_fold_f32_Multiply[]    = d_foldCommutativeOpList (f32, Multiply);
235
static const IM3Operation c_fold_f32_Divide[]      = d_foldOpList (f32, Divide);
236
static const IM3Operation c_fold_f64_Add[]         = d_foldCommutativeOpList (f64, Add);
237
static const IM3Operation c_fold_f64_Subtract[]    = d_foldOpList (f64, Subtract);
238
static const IM3Operation c_fold_f64_Multiply[]    = d_foldCommutativeOpList (f64, Multiply);
239
static const IM3Operation c_fold_f64_Divide[]      = d_foldOpList (f64, Divide);
240
#  endif
241
242
// loads are unary: [0]= address in _r0, [1]= address in a slot
243
#define d_foldLoadList(DEST, SRC)       { op_##DEST##_Load_##SRC##_r_f, op_##DEST##_Load_##SRC##_s_f, NULL, NULL }
244
245
static const IM3Operation c_fold_i32_Load_i32[] = d_foldLoadList(i32, i32);
246
static const IM3Operation c_fold_i32_Load_i8[] = d_foldLoadList(i32, i8);
247
static const IM3Operation c_fold_i32_Load_u8[] = d_foldLoadList(i32, u8);
248
static const IM3Operation c_fold_i32_Load_i16[] = d_foldLoadList(i32, i16);
249
static const IM3Operation c_fold_i32_Load_u16[] = d_foldLoadList(i32, u16);
250
251
static const IM3Operation c_fold_i64_Load_i64[] = d_foldLoadList(i64, i64);
252
static const IM3Operation c_fold_i64_Load_i8[] = d_foldLoadList(i64, i8);
253
static const IM3Operation c_fold_i64_Load_u8[] = d_foldLoadList(i64, u8);
254
static const IM3Operation c_fold_i64_Load_i16[] = d_foldLoadList(i64, i16);
255
static const IM3Operation c_fold_i64_Load_u16[] = d_foldLoadList(i64, u16);
256
static const IM3Operation c_fold_i64_Load_i32[] = d_foldLoadList(i64, i32);
257
static const IM3Operation c_fold_i64_Load_u32[] = d_foldLoadList(i64, u32);
258
#  if d_m3HasFloat
259
static const IM3Operation c_fold_f32_Load_f32[] = d_foldLoadList(f32, f32);
260
static const IM3Operation c_fold_f64_Load_f64[] = d_foldLoadList(f64, f64);
261
#  endif
262
263
static
264
IM3Operation GetFoldOp (m3opcode_t i_opcode, u8 i_form)
265
136
{
266
136
    if (i_form >= 4) {
267
0
        return NULL;
268
0
    }
269
270
136
    switch (i_opcode) {
271
0
    case 0x6a:  return c_fold_i32_Add        [i_form];      // i32.add
272
4
    case 0x6b:  return c_fold_i32_Subtract   [i_form];      // i32.sub
273
0
    case 0x6c:  return c_fold_i32_Multiply   [i_form];      // i32.mul
274
0
    case 0x71:  return c_fold_u32_And        [i_form];      // i32.and
275
0
    case 0x72:  return c_fold_u32_Or         [i_form];      // i32.or
276
0
    case 0x73:  return c_fold_u32_Xor        [i_form];      // i32.xor
277
0
    case 0x74:  return c_fold_u32_ShiftLeft  [i_form];      // i32.shl
278
0
    case 0x75:  return c_fold_i32_ShiftRight [i_form];      // i32.shr_s
279
0
    case 0x76:  return c_fold_u32_ShiftRight [i_form];      // i32.shr_u
280
281
0
    case 0x7c:  return c_fold_i64_Add        [i_form];      // i64.add
282
1
    case 0x7d:  return c_fold_i64_Subtract   [i_form];      // i64.sub
283
1
    case 0x7e:  return c_fold_i64_Multiply   [i_form];      // i64.mul
284
0
    case 0x83:  return c_fold_u64_And        [i_form];      // i64.and
285
4
    case 0x84:  return c_fold_u64_Or         [i_form];      // i64.or
286
0
    case 0x85:  return c_fold_u64_Xor        [i_form];      // i64.xor
287
1
    case 0x86:  return c_fold_u64_ShiftLeft  [i_form];      // i64.shl
288
0
    case 0x87:  return c_fold_i64_ShiftRight [i_form];      // i64.shr_s
289
0
    case 0x88:  return c_fold_u64_ShiftRight [i_form];      // i64.shr_u
290
291
0
#  if d_m3HasFloat
292
0
    case 0x92:  return c_fold_f32_Add        [i_form];      // f32.add
293
0
    case 0x93:  return c_fold_f32_Subtract   [i_form];      // f32.sub
294
0
    case 0x94:  return c_fold_f32_Multiply   [i_form];      // f32.mul
295
0
    case 0x95:  return c_fold_f32_Divide     [i_form];      // f32.div
296
0
    case 0xa0:  return c_fold_f64_Add        [i_form];      // f64.add
297
0
    case 0xa1:  return c_fold_f64_Subtract   [i_form];      // f64.sub
298
0
    case 0xa2:  return c_fold_f64_Multiply   [i_form];      // f64.mul
299
0
    case 0xa3:  return c_fold_f64_Divide     [i_form];      // f64.div
300
0
#  endif
301
302
6
    case 0x28:  return c_fold_i32_Load_i32   [i_form];      // i32.load
303
6
    case 0x2c:  return c_fold_i32_Load_i8    [i_form];      // i32.load8_s
304
11
    case 0x2d:  return c_fold_i32_Load_u8    [i_form];      // i32.load8_u
305
9
    case 0x2e:  return c_fold_i32_Load_i16   [i_form];      // i32.load16_s
306
10
    case 0x2f:  return c_fold_i32_Load_u16   [i_form];      // i32.load16_u
307
308
16
    case 0x29:  return c_fold_i64_Load_i64   [i_form];      // i64.load
309
10
    case 0x30:  return c_fold_i64_Load_i8    [i_form];      // i64.load8_s
310
10
    case 0x31:  return c_fold_i64_Load_u8    [i_form];      // i64.load8_u
311
10
    case 0x32:  return c_fold_i64_Load_i16   [i_form];      // i64.load16_s
312
10
    case 0x33:  return c_fold_i64_Load_u16   [i_form];      // i64.load16_u
313
6
    case 0x34:  return c_fold_i64_Load_i32   [i_form];      // i64.load32_s
314
10
    case 0x35:  return c_fold_i64_Load_u32   [i_form];      // i64.load32_u
315
316
0
#  if d_m3HasFloat
317
10
    case 0x2a:  return c_fold_f32_Load_f32   [i_form];      // f32.load
318
0
    case 0x2b:  return c_fold_f64_Load_f64   [i_form];      // f64.load
319
0
#  endif
320
321
1
    default: return NULL;
322
136
    }
323
136
}
324
325
#endif // d_m3FoldSetLocal
326
327
// clang-format on
328
329
#if d_m3FuseBranch
330
331
// fused compare+branch/if variants, indexed like the fold lists: [0]=_rs [1]=_sr [2]=_ss [3]=unused.
332
// the [1] entries are NULL for commutative compares: the compiler never records the _sr form for them
333
#  define d_fuseCmpList(TYPE, NAME, KIND)             { op_##TYPE##_##KIND##_##NAME##_rs, op_##TYPE##_##KIND##_##NAME##_sr, op_##TYPE##_##KIND##_##NAME##_ss, NULL }
334
#  define d_fuseCommutativeCmpList(TYPE, NAME, KIND)  { op_##TYPE##_##KIND##_##NAME##_rs, NULL,                             op_##TYPE##_##KIND##_##NAME##_ss, NULL }
335
336
typedef struct M3FusedCmpOps {
337
    IM3Operation branchIf[4];
338
    IM3Operation ifOp[4];
339
} M3FusedCmpOps;
340
341
#  define d_fuseCmp(TYPE, NAME)               { d_fuseCmpList (TYPE, NAME, BranchIf),            d_fuseCmpList (TYPE, NAME, If) }
342
#  define d_fuseCommutativeCmp(TYPE, NAME)    { d_fuseCommutativeCmpList (TYPE, NAME, BranchIf), d_fuseCommutativeCmpList (TYPE, NAME, If) }
343
344
// clang-format off
345
static const M3FusedCmpOps c_fuse_i32_Equal              = d_fuseCommutativeCmp (i32, Equal);
346
static const M3FusedCmpOps c_fuse_i32_NotEqual           = d_fuseCommutativeCmp (i32, NotEqual);
347
static const M3FusedCmpOps c_fuse_i32_LessThan           = d_fuseCmp (i32, LessThan);
348
static const M3FusedCmpOps c_fuse_u32_LessThan           = d_fuseCmp (u32, LessThan);
349
static const M3FusedCmpOps c_fuse_i32_GreaterThan        = d_fuseCmp (i32, GreaterThan);
350
static const M3FusedCmpOps c_fuse_u32_GreaterThan        = d_fuseCmp (u32, GreaterThan);
351
static const M3FusedCmpOps c_fuse_i32_LessThanOrEqual    = d_fuseCmp (i32, LessThanOrEqual);
352
static const M3FusedCmpOps c_fuse_u32_LessThanOrEqual    = d_fuseCmp (u32, LessThanOrEqual);
353
static const M3FusedCmpOps c_fuse_i32_GreaterThanOrEqual = d_fuseCmp (i32, GreaterThanOrEqual);
354
static const M3FusedCmpOps c_fuse_u32_GreaterThanOrEqual = d_fuseCmp (u32, GreaterThanOrEqual);
355
// clang-format on
356
357
// i32.eqz is unary: [0]= operand in _r0, [1]= operand in a slot
358
static const M3FusedCmpOps c_fuse_i32_Eqz = {
359
    { op_i32_BranchIfEqz_r, op_i32_BranchIfEqz_s, NULL, NULL },
360
    { op_i32_IfEqz_r,       op_i32_IfEqz_s,       NULL, NULL }
361
};
362
363
static
364
const M3FusedCmpOps* GetFusedCmpOps (m3opcode_t i_opcode)
365
15
{
366
15
    switch (i_opcode) {
367
0
    case 0x45: return &c_fuse_i32_Eqz;                    // i32.eqz
368
0
    case 0x46: return &c_fuse_i32_Equal;                  // i32.eq
369
0
    case 0x47: return &c_fuse_i32_NotEqual;               // i32.ne
370
0
    case 0x48: return &c_fuse_i32_LessThan;               // i32.lt_s
371
2
    case 0x49: return &c_fuse_u32_LessThan;               // i32.lt_u
372
0
    case 0x4a: return &c_fuse_i32_GreaterThan;            // i32.gt_s
373
0
    case 0x4b: return &c_fuse_u32_GreaterThan;            // i32.gt_u
374
0
    case 0x4c: return &c_fuse_i32_LessThanOrEqual;        // i32.le_s
375
0
    case 0x4d: return &c_fuse_u32_LessThanOrEqual;        // i32.le_u
376
0
    case 0x4e: return &c_fuse_i32_GreaterThanOrEqual;     // i32.ge_s
377
0
    case 0x4f: return &c_fuse_u32_GreaterThanOrEqual;     // i32.ge_u
378
13
    default: return NULL;
379
15
    }
380
15
}
381
382
#endif // d_m3FuseBranch
383
384
static const IM3Operation c_intSelectOps[2][4] = {
385
    { op_Select_i32_rss, op_Select_i32_srs, op_Select_i32_ssr, op_Select_i32_sss },
386
    { op_Select_i64_rss, op_Select_i64_srs, op_Select_i64_ssr, op_Select_i64_sss }
387
};
388
#if d_m3HasFloat
389
static const IM3Operation c_fpSelectOps[2][2][3] = {
390
    { { op_Select_f32_sss, op_Select_f32_srs, op_Select_f32_ssr }, // selector in slot
391
      { op_Select_f32_rss, op_Select_f32_rrs, op_Select_f32_rsr } }, // selector in reg
392
    { { op_Select_f64_sss, op_Select_f64_srs, op_Select_f64_ssr }, // selector in slot
393
      { op_Select_f64_rss, op_Select_f64_rrs, op_Select_f64_rsr } }
394
};    // selector in reg
395
#endif
396
397
// all args & returns are 64-bit aligned, so use 2 slots for a d_m3Use32BitSlots=1 build
398
static const u16 c_ioSlotCount = sizeof(u64) / sizeof(m3slot_t);
399
400
static
401
M3Result AcquireCompilationCodePage (IM3Compilation o, IM3CodePage* o_codePage)
402
1.09k
{
403
1.09k
    M3Result result = m3Err_none;
404
405
1.09k
    IM3CodePage page = AcquireCodePage(o->runtime);
406
407
1.09k
    if (page) {
408
#if (d_m3EnableCodePageRefCounting)
409
        {
410
            if (o->function) {
411
                IM3Function func = o->function;
412
                page->info.usageCount++;
413
414
                u32 index = func->numCodePageRefs++;
415
_               (m3ReallocArray(&func->codePageRefs, IM3CodePage, func->numCodePageRefs, index));
416
                func->codePageRefs[index] = page;
417
            }
418
        }
419
#endif
420
1.09k
    } else {
421
0
        _throw(m3Err_mallocFailedCodePage);
422
0
    }
423
424
1.09k
    _catch:
425
426
1.09k
    *o_codePage = page;
427
428
1.09k
    return result;
429
1.09k
}
430
431
static inline
432
void ReleaseCompilationCodePage (IM3Compilation o)
433
1.09k
{
434
1.09k
    ReleaseCodePage(o->runtime, o->page);
435
1.09k
}
436
437
static inline
438
u16 GetTypeNumSlots (m3type_t i_type)
439
3.18M
{
440
3.18M
    i_type = BaseTypeOf(i_type);
441
442
    // v128 is 16 bytes - 4 slots in 32-bit-slot mode, 2 in 64-bit.
443
    // (Slot-allocator only; no v128 ops execute.)
444
3.18M
    if (i_type == c_m3Type_v128)
445
67.8k
#if d_m3Use32BitSlots
446
67.8k
        return 4;
447
#else
448
        return 2;
449
#endif
450
3.12M
#if d_m3Use32BitSlots
451
3.12M
    return Is64BitType(i_type) ? 2 : 1;
452
#else
453
    return 1;
454
#endif
455
3.18M
}
456
457
static inline
458
void AlignSlotToType (u16* io_slot, m3type_t i_type)
459
1.50M
{
460
    // align 64-bit words to even slots (if d_m3Use32BitSlots)
461
1.50M
    u16 numSlots = GetTypeNumSlots(i_type);
462
463
1.50M
    u16 mask = numSlots - 1;
464
1.50M
    *io_slot = (*io_slot + mask) & ~mask;
465
1.50M
}
466
467
// Deliberately unchecked. Validation rejects an empty operand stack before compilation.
468
static inline
469
i16 GetStackTopIndex (IM3Compilation o)
470
109k
{
471
109k
    return o->stackIndex - 1;
472
109k
}
473
474
static inline
475
M3Result GetStackTopIndexThrows (IM3Compilation o, i16* o_stackIndex)
476
1.54k
{
477
1.54k
    *o_stackIndex = o->stackIndex - 1;
478
479
1.54k
    if (o->stackIndex > o->stackFirstDynamicIndex or IsStackPolymorphic(o)) {
480
1.54k
        return m3Err_none;
481
1.54k
    } else {
482
0
        return m3Err_functionStackUnderrun;
483
0
    }
484
1.54k
}
485
486
487
// Items in the static portion of the stack (args/locals) are hidden from GetStackTypeFromTop ()
488
// In other words, only "real" Wasm stack items can be inspected.  This is important when
489
// returning values, etc. and you need an accurate wasm-view of the stack.
490
static
491
m3type_t GetStackTypeFromTop (IM3Compilation o, u16 i_offset)
492
31.2k
{
493
31.2k
    m3type_t type = c_m3Type_none;
494
495
31.2k
    ++i_offset;
496
31.2k
    if (o->stackIndex >= i_offset) {
497
29.3k
        u16 index = o->stackIndex - i_offset;
498
499
29.3k
        if (index >= o->stackFirstDynamicIndex) {
500
27.0k
            type = o->typeStack[index];
501
27.0k
        }
502
29.3k
    }
503
504
31.2k
    return type;
505
31.2k
}
506
507
static inline
508
m3type_t GetStackTopType (IM3Compilation o)
509
23.2k
{
510
23.2k
    return GetStackTypeFromTop(o, 0);
511
23.2k
}
512
513
static inline
514
m3type_t GetStackTypeFromBottom (IM3Compilation o, u16 i_offset)
515
42.4k
{
516
42.4k
    m3type_t type = c_m3Type_none;
517
518
42.4k
    if (i_offset < o->stackIndex) {
519
42.4k
        type = o->typeStack[i_offset];
520
42.4k
    }
521
522
42.4k
    return type;
523
42.4k
}
524
525
526
static inline
527
bool IsConstantSlot (IM3Compilation o, u16 i_slot)
528
675
{
529
675
    return (i_slot >= o->slotFirstConstIndex and i_slot < o->slotMaxConstIndex);
530
675
}
531
static inline
532
bool IsSlotAllocated (IM3Compilation o, u16 i_slot)
533
11.2k
{
534
11.2k
    return o->m3Slots[i_slot];
535
11.2k
}
536
537
static inline
538
bool IsStackIndexInRegister (IM3Compilation o, i32 i_stackIndex)
539
81.3k
{                                                                           d_m3Assert (i_stackIndex < o->stackIndex or IsStackPolymorphic (o));
540
81.3k
    if (i_stackIndex >= 0 and i_stackIndex < o->stackIndex) {
541
77.2k
        return (o->wasmStack[i_stackIndex] >= d_m3Reg0SlotAlias);
542
77.2k
    } else {
543
4.09k
        return false;
544
4.09k
    }
545
81.3k
}
546
547
static inline
548
u16 GetNumBlockValuesOnStack (IM3Compilation o)
549
2.29k
{
550
2.29k
    return o->stackIndex - o->block.blockStackIndex;
551
2.29k
}
552
553
static inline
554
bool IsStackTopInRegister (IM3Compilation o)
555
56.9k
{
556
56.9k
    return IsStackIndexInRegister(o, (i32)GetStackTopIndex(o));
557
56.9k
}
558
static inline
559
bool IsStackTopMinus1InRegister (IM3Compilation o)
560
17.1k
{
561
17.1k
    return IsStackIndexInRegister(o, (i32)GetStackTopIndex(o) - 1);
562
17.1k
}
563
static inline
564
bool IsStackTopMinus2InRegister (IM3Compilation o)
565
44
{
566
44
    return IsStackIndexInRegister(o, (i32)GetStackTopIndex(o) - 2);
567
44
}
568
569
static inline
570
bool IsStackTopInSlot (IM3Compilation o)
571
37.6k
{
572
37.6k
    return not IsStackTopInRegister(o);
573
37.6k
}
574
575
static inline
576
bool IsValidSlot (u16 i_slot)
577
1.65k
{
578
1.65k
    return (i_slot < d_m3MaxFunctionSlots);
579
1.65k
}
580
581
static inline
582
u16 GetStackTopSlotNumber (IM3Compilation o)
583
26.9k
{
584
26.9k
    i16 i = GetStackTopIndex(o);
585
586
26.9k
    u16 slot = c_slotUnused;
587
588
26.9k
    if (i >= 0) {
589
24.5k
        slot = o->wasmStack[i];
590
24.5k
    }
591
592
26.9k
    return slot;
593
26.9k
}
594
595
596
// from bottom
597
static inline
598
u16 GetSlotForStackIndex (IM3Compilation o, u16 i_stackIndex)
599
965k
{                                                                   d_m3Assert (i_stackIndex < o->stackIndex or IsStackPolymorphic (o));
600
965k
    u16 slot = c_slotUnused;
601
602
965k
    if (i_stackIndex < o->stackIndex) {
603
964k
        slot = o->wasmStack[i_stackIndex];
604
964k
    }
605
606
965k
    return slot;
607
965k
}
608
609
static inline
610
u16 GetExtraSlotForStackIndex (IM3Compilation o, u16 i_stackIndex)
611
24.6k
{
612
24.6k
    u16 baseSlot = GetSlotForStackIndex(o, i_stackIndex);
613
614
24.6k
    if (baseSlot != c_slotUnused) {
615
24.6k
        u16 extraSlot = GetTypeNumSlots(GetStackTypeFromBottom(o, i_stackIndex)) - 1;
616
24.6k
        baseSlot += extraSlot;
617
24.6k
    }
618
619
24.6k
    return baseSlot;
620
24.6k
}
621
622
623
static inline
624
void TouchSlot (IM3Compilation o, u16 i_slot)
625
2.41M
{
626
    // op_Entry uses this value to track and detect stack overflow
627
2.41M
    o->maxStackSlots = M3_MAX(o->maxStackSlots, i_slot + 1);
628
2.41M
}
629
630
static inline
631
void MarkSlotAllocated (IM3Compilation o, u16 i_slot)
632
2.41M
{                                                                   d_m3Assert (o->m3Slots [i_slot] == 0); // shouldn't be already allocated
633
2.41M
    o->m3Slots[i_slot] = 1;
634
635
2.41M
    o->slotMaxAllocatedIndexPlusOne = M3_MAX(o->slotMaxAllocatedIndexPlusOne, i_slot + 1);
636
637
2.41M
    TouchSlot(o, i_slot);
638
2.41M
}
639
640
static inline
641
M3Result MarkSlotsAllocated (IM3Compilation o, u16 i_slot, u16 i_numSlots)
642
1.49M
{
643
1.49M
    if (i_slot + i_numSlots > d_m3MaxFunctionSlots) {
644
1
        return m3Err_functionStackOverflow;
645
1
    }
646
647
3.90M
    while (i_numSlots--) {
648
2.41M
        MarkSlotAllocated(o, i_slot++);
649
2.41M
    }
650
651
1.49M
    return m3Err_none;
652
1.49M
}
653
654
static inline
655
M3Result MarkSlotsAllocatedByType (IM3Compilation o, u16 i_slot, m3type_t i_type)
656
11.5k
{
657
11.5k
    u16 numSlots = GetTypeNumSlots(i_type);
658
11.5k
    return MarkSlotsAllocated(o, i_slot, numSlots);
659
11.5k
}
660
661
662
static
663
M3Result AllocateSlotsWithinRange (IM3Compilation o, u16* o_slot, m3type_t i_type, u16 i_startSlot, u16 i_endSlot)
664
1.49M
{
665
1.49M
    M3Result result = m3Err_functionStackOverflow;
666
667
1.49M
    u16 numSlots     = GetTypeNumSlots(i_type);
668
1.49M
    u16 searchOffset = numSlots - 1;
669
670
1.49M
    AlignSlotToType(&i_startSlot, i_type);
671
672
    // search for 1 or 2 consecutive slots in the execution stack
673
1.49M
    u16 i = i_startSlot;
674
4.68G
    while (i + searchOffset < i_endSlot) {
675
4.68G
        if (i + searchOffset < d_m3MaxFunctionSlots and o->m3Slots[i] == 0 and o->m3Slots[i + searchOffset] == 0) {
676
1.48M
            MarkSlotsAllocated(o, i, numSlots);
677
678
1.48M
            *o_slot = i;
679
1.48M
            result  = m3Err_none;
680
1.48M
            break;
681
1.48M
        }
682
683
        // keep 2-slot allocations even-aligned
684
4.67G
        i += numSlots;
685
4.67G
    }
686
687
1.49M
    return result;
688
1.49M
}
689
690
static inline
691
M3Result AllocateSlots (IM3Compilation o, u16* o_slot, m3type_t i_type)
692
1.48M
{
693
1.48M
    return AllocateSlotsWithinRange(o, o_slot, i_type, o->slotFirstDynamicIndex, d_m3MaxFunctionSlots);
694
1.48M
}
695
696
static inline
697
M3Result AllocateConstantSlots (IM3Compilation o, u16* o_slot, m3type_t i_type)
698
17.0k
{
699
17.0k
    u16 maxTableIndex = o->slotFirstConstIndex + d_m3MaxConstantTableSize;
700
17.0k
    return AllocateSlotsWithinRange(o, o_slot, i_type, o->slotFirstConstIndex, M3_MIN(o->slotFirstDynamicIndex, maxTableIndex));
701
17.0k
}
702
703
704
// TOQUE: this usage count system could be eliminated. real world code doesn't frequently trigger it.  just copy to multiple
705
// unique slots.
706
static inline
707
M3Result IncrementSlotUsageCount (IM3Compilation o, u16 i_slot)
708
8
{                                                                                       d_m3Assert (i_slot < d_m3MaxFunctionSlots);
709
8
    M3Result result = m3Err_none;                                                       d_m3Assert (o->m3Slots [i_slot] > 0);
710
711
    // OPTZ (memory): 'm3Slots' could still be fused with 'typeStack' if 4 bits were used to indicate: [0,1,2,many]. The many-case
712
    // would scan 'wasmStack' to determine the actual usage count
713
8
    if (o->m3Slots[i_slot] < 0xFF) {
714
8
        o->m3Slots[i_slot]++;
715
8
    } else {
716
0
        result = "slot usage count overflow";
717
0
    }
718
719
8
    return result;
720
8
}
721
722
static inline
723
void DeallocateSlot (IM3Compilation o, i16 i_slot, m3type_t i_type)
724
47.7k
{                                                                                       d_m3Assert (i_slot >= o->slotFirstDynamicIndex);
725
47.7k
                                                                                        d_m3Assert (i_slot < o->slotMaxAllocatedIndexPlusOne);
726
131k
    for (u16 i = 0; i < GetTypeNumSlots(i_type); ++i, ++i_slot) {                       d_m3Assert (o->m3Slots [i_slot]);
727
83.4k
        --o->m3Slots[i_slot];
728
83.4k
    }
729
47.7k
}
730
731
732
static inline
733
bool IsRegisterTypeAllocated (IM3Compilation o, u8 i_type)
734
192
{
735
192
    return IsRegisterAllocated(o, IsFpType(i_type));
736
192
}
737
738
static inline
739
void AllocateRegister (IM3Compilation o, u32 i_register, u16 i_stackIndex)
740
19.2k
{                                                                                       d_m3Assert (not IsRegisterAllocated (o, i_register));
741
19.2k
    o->regStackIndexPlusOne[i_register] = i_stackIndex + 1;
742
19.2k
}
743
744
static inline
745
void DeallocateRegister (IM3Compilation o, u32 i_register)
746
18.9k
{                                                                                       d_m3Assert (IsRegisterAllocated (o, i_register));
747
18.9k
    o->regStackIndexPlusOne[i_register] = c_m3RegisterUnallocated;
748
18.9k
}
749
750
static inline
751
u16 GetRegisterStackIndex (IM3Compilation o, u32 i_register)
752
5.84k
{                                                                                       d_m3Assert (IsRegisterAllocated (o, i_register));
753
5.84k
    return o->regStackIndexPlusOne[i_register] - 1;
754
5.84k
}
755
756
u16 GetMaxUsedSlotPlusOne (IM3Compilation o)
757
1.79k
{
758
8.23k
    while (o->slotMaxAllocatedIndexPlusOne > o->slotFirstDynamicIndex) {
759
7.20k
        if (IsSlotAllocated(o, o->slotMaxAllocatedIndexPlusOne - 1)) {
760
763
            break;
761
763
        }
762
763
6.44k
        o->slotMaxAllocatedIndexPlusOne--;
764
6.44k
    }
765
766
#ifdef DEBUG
767
    u16 maxSlot = o->slotMaxAllocatedIndexPlusOne;
768
    while (maxSlot < d_m3MaxFunctionSlots) {
769
        d_m3Assert(o->m3Slots[maxSlot] == 0);
770
        maxSlot++;
771
    }
772
#endif
773
774
1.79k
    return o->slotMaxAllocatedIndexPlusOne;
775
1.79k
}
776
777
static
778
M3Result PreserveRegisterIfOccupied (IM3Compilation o, m3type_t i_registerType)
779
11.8k
{
780
11.8k
    M3Result result = m3Err_none;
781
782
11.8k
    u32 regSelect = IsFpType(i_registerType);
783
784
11.8k
    if (IsRegisterAllocated(o, regSelect)) {
785
5.81k
        u16 stackIndex = GetRegisterStackIndex(o, regSelect);
786
5.81k
        DeallocateRegister(o, regSelect);
787
788
5.81k
        m3type_t type = GetStackTypeFromBottom(o, stackIndex);
789
790
        // and point to a exec slot
791
5.81k
        u16 slot = c_slotUnused;
792
5.81k
_       (AllocateSlots(o, &slot, type));
793
5.81k
        o->wasmStack[stackIndex] = slot;
794
795
        // Ensure type is within the valid range
796
5.81k
        if (BaseTypeOf(type) < c_m3Type_count) {
797
5.81k
_           (EmitOp(o, c_setSetOps[BaseTypeOf(type)]));
798
5.81k
        } else {
799
0
            _throw(m3Err_unknownType);
800
0
        }
801
802
5.81k
        EmitSlotOffset(o, slot);
803
5.81k
    }
804
805
11.8k
    _catch: return result;
806
11.8k
}
807
808
809
// all values must be in slots before entering loop, if, and else blocks
810
// otherwise they'd end up preserve-copied in the block to probably different locations (if/else)
811
static inline
812
M3Result PreserveRegisters (IM3Compilation o)
813
618
{
814
618
    M3Result result;
815
816
618
_   (PreserveRegisterIfOccupied(o, c_m3Type_f64));
817
618
_   (PreserveRegisterIfOccupied(o, c_m3Type_i64));
818
819
618
    _catch: return result;
820
618
}
821
822
static
823
M3Result PreserveNonTopRegisters (IM3Compilation o)
824
161
{
825
161
    M3Result result = m3Err_none;
826
827
161
    i16 stackTop = GetStackTopIndex(o);
828
829
161
    if (stackTop >= 0) {
830
137
        if (IsRegisterAllocated(o, 0))     // r0
831
29
        {
832
29
            if (GetRegisterStackIndex(o, 0) != stackTop) {
833
8
_               (PreserveRegisterIfOccupied(o, c_m3Type_i64));
834
8
            }
835
29
        }
836
837
137
        if (IsRegisterAllocated(o, 1))     // fp0
838
0
        {
839
0
            if (GetRegisterStackIndex(o, 1) != stackTop) {
840
0
_               (PreserveRegisterIfOccupied(o, c_m3Type_f64));
841
0
            }
842
0
        }
843
137
    }
844
845
161
    _catch: return result;
846
161
}
847
848
849
//----------------------------------------------------------------------------------------------------------------------
850
851
static
852
M3Result Push (IM3Compilation o, m3type_t i_type, u16 i_slot)
853
1.50M
{
854
1.50M
    M3Result result = m3Err_none;
855
856
#if !d_m3HasFloat
857
    if (i_type == c_m3Type_f32 || i_type == c_m3Type_f64) {
858
        return m3Err_unknownOpcode;
859
    }
860
#endif
861
862
1.50M
    if (M3_UNLIKELY(o->stackIndex >= d_m3MaxFunctionStackHeight)) {
863
399
        return m3Err_functionStackOverflow;
864
399
    }
865
866
1.50M
    u16 stackIndex = o->stackIndex++;                                       // printf ("push: %d\n", (i32) i);
867
868
1.50M
    o->wasmStack[stackIndex] = i_slot;
869
1.50M
    o->typeStack[stackIndex] = i_type;
870
871
1.50M
    if (IsRegisterSlotAlias(i_slot)) {
872
19.2k
        u32 regSelect = IsFpRegisterSlotAlias(i_slot);
873
19.2k
        AllocateRegister(o, regSelect, stackIndex);
874
19.2k
    }
875
876
1.50M
    if (d_m3LogWasmStack) {
877
0
        dump_type_stack(o);
878
0
    }
879
880
1.50M
    return result;
881
1.50M
}
882
883
static inline
884
M3Result PushRegister (IM3Compilation o, m3type_t i_type)
885
19.2k
{
886
19.2k
    M3Result result = m3Err_none;                                                       d_m3Assert ((u16) d_m3Reg0SlotAlias > (u16) d_m3MaxFunctionSlots);
887
19.2k
    u16      slot   = IsFpType(i_type) ? d_m3Fp0SlotAlias : d_m3Reg0SlotAlias;          d_m3Assert (i_type or IsStackPolymorphic (o));
888
889
19.2k
_   (Push(o, i_type, slot));
890
891
19.2k
    _catch: return result;
892
19.2k
}
893
894
static
895
M3Result Pop (IM3Compilation o)
896
71.9k
{
897
71.9k
    M3Result result = m3Err_none;
898
899
71.9k
    if (o->stackIndex > o->block.blockStackIndex) {
900
62.7k
        o->stackIndex--;                                                //  printf ("pop: %d\n", (i32) o->stackIndex);
901
902
62.7k
        u16      slot = o->wasmStack[o->stackIndex];
903
62.7k
        m3type_t type = o->typeStack[o->stackIndex];
904
905
62.7k
        if (IsRegisterSlotAlias(slot)) {
906
13.1k
            u32 regSelect = IsFpRegisterSlotAlias(slot);
907
13.1k
            DeallocateRegister(o, regSelect);
908
49.6k
        } else if (slot >= o->slotMaxAllocatedIndexPlusOne) {
909
37
            return m3Err_functionStackUnderrun; // Return error for invalid slot indices
910
49.5k
        } else if (slot >= o->slotFirstDynamicIndex) {
911
47.6k
            DeallocateSlot(o, slot, type);
912
47.6k
        }
913
62.7k
    } else if (not IsStackPolymorphic(o)) {
914
5
        result = m3Err_functionStackUnderrun;
915
5
    }
916
917
71.8k
    return result;
918
71.9k
}
919
920
static
921
M3Result PopType (IM3Compilation o, m3type_t i_type)
922
4.10k
{
923
4.10k
    M3Result result = m3Err_none;
924
925
4.10k
    m3type_t topType = GetStackTopType(o);
926
927
4.10k
    if (IsSubTypeOf(topType, i_type) or o->block.isPolymorphic) {
928
4.10k
_       (Pop(o));
929
4.09k
    } else {
930
0
        _throw(m3Err_typeMismatch);
931
0
    }
932
933
4.10k
    _catch:
934
4.10k
    return result;
935
4.10k
}
936
937
static
938
M3Result _PushAllocatedSlotAndEmit (IM3Compilation o, m3type_t i_type, bool i_doEmit)
939
1.47M
{
940
1.47M
    M3Result result = m3Err_none;
941
942
1.47M
    u16 slot = c_slotUnused;
943
944
1.47M
_   (AllocateSlots(o, &slot, i_type));
945
1.47M
_   (Push(o, i_type, slot));
946
947
1.47M
    if (i_doEmit) {
948
15.9k
        EmitSlotOffset(o, slot);
949
15.9k
    }
950
951
    //    printf ("push: %d\n", (u32) slot);
952
953
1.47M
    _catch: return result;
954
1.47M
}
955
956
static inline
957
M3Result PushAllocatedSlotAndEmit (IM3Compilation o, m3type_t i_type)
958
15.9k
{
959
15.9k
    return _PushAllocatedSlotAndEmit(o, i_type, true);
960
15.9k
}
961
962
static inline
963
M3Result PushAllocatedSlot (IM3Compilation o, m3type_t i_type)
964
1.45M
{
965
1.45M
    return _PushAllocatedSlotAndEmit(o, i_type, false);
966
1.45M
}
967
968
static
969
M3Result PushConst (IM3Compilation o, u64 i_word, m3type_t i_type)
970
54.2k
{
971
54.2k
    M3Result result = m3Err_none;
972
973
    // When compile-walking a constant expression without emitting there is no
974
    // constant table to place the value in, but the type still has to land on
975
    // the stack: extended-const arithmetic downstream consumes it.
976
54.2k
    if (!o->page) {
977
36.9k
        return PushAllocatedSlot(o, i_type);
978
36.9k
    }
979
980
54.2k
    bool matchFound  = false;
981
17.3k
    bool is64BitType = Is64BitType(i_type);
982
983
17.3k
    u16 numRequiredSlots  = GetTypeNumSlots(i_type);
984
17.3k
    u16 numUsedConstSlots = o->slotMaxConstIndex - o->slotFirstConstIndex;
985
986
    // search for duplicate matching constant slot to reuse
987
17.3k
    if (numRequiredSlots == 2 and numUsedConstSlots >= 2) {
988
281
        u16 firstConstSlot = o->slotFirstConstIndex;
989
281
        AlignSlotToType(&firstConstSlot, c_m3Type_i64);
990
991
1.02k
        for (u16 slot = firstConstSlot; slot < o->slotMaxConstIndex - 1; slot += 2) {
992
873
            if (IsSlotAllocated(o, slot) and IsSlotAllocated(o, slot + 1)) {
993
829
                u64 constant;
994
829
                memcpy(&constant, &o->constants[slot - o->slotFirstConstIndex], sizeof(constant));
995
996
829
                if (constant == i_word) {
997
130
                    matchFound = true;
998
130
_                   (Push(o, i_type, slot));
999
130
                    break;
1000
130
                }
1001
829
            }
1002
873
        }
1003
17.0k
    } else if (numRequiredSlots == 1) {
1004
8.48k
        for (u16 i = 0; i < numUsedConstSlots; ++i) {
1005
2.31k
            u16 slot = o->slotFirstConstIndex + i;
1006
1007
2.31k
            if (IsSlotAllocated(o, slot)) {
1008
2.30k
                bool matches;
1009
2.30k
                if (is64BitType) {
1010
0
                    u64 constant;
1011
0
                    memcpy(&constant, &o->constants[i], sizeof(constant));
1012
0
                    matches = (constant == i_word);
1013
2.30k
                } else {
1014
2.30k
                    u32 constant;
1015
2.30k
                    memcpy(&constant, &o->constants[i], sizeof(constant));
1016
2.30k
                    matches = (constant == i_word);
1017
2.30k
                }
1018
2.30k
                if (matches) {
1019
129
                    matchFound = true;
1020
129
_                   (Push(o, i_type, slot));
1021
129
                    break;
1022
129
                }
1023
2.30k
            }
1024
2.31k
        }
1025
6.29k
    }
1026
1027
17.3k
    if (not matchFound) {
1028
17.0k
        u16 slot = c_slotUnused;
1029
17.0k
        result   = AllocateConstantSlots(o, &slot, i_type);
1030
1031
17.0k
        if (result || slot == c_slotUnused) // no more constant table space; use inline constants
1032
15.8k
        {
1033
15.8k
            result = m3Err_none;
1034
1035
15.8k
            if (is64BitType) {
1036
10.5k
_               (EmitOp(o, op_Const64));
1037
10.5k
                EmitWord64(o->page, i_word);
1038
10.5k
            } else {
1039
5.27k
_               (EmitOp(o, op_Const32));
1040
5.27k
                EmitWord32(o->page, (u32)i_word);
1041
5.27k
            }
1042
1043
15.8k
_           (PushAllocatedSlotAndEmit(o, i_type));
1044
15.8k
        } else {
1045
1.19k
            u16 constTableIndex = slot - o->slotFirstConstIndex;
1046
1047
1.19k
            d_m3Assert(constTableIndex < d_m3MaxConstantTableSize);
1048
1049
1.19k
            if (is64BitType) {
1050
307
                memcpy(&o->constants[constTableIndex], &i_word, sizeof(i_word));
1051
891
            } else {
1052
891
                u32 word32 = (u32)i_word;
1053
891
                memcpy(&o->constants[constTableIndex], &word32, sizeof(word32));
1054
891
            }
1055
1056
1.19k
_           (Push(o, i_type, slot));
1057
1058
1.19k
            o->slotMaxConstIndex = M3_MAX(slot + numRequiredSlots, o->slotMaxConstIndex);
1059
1.19k
        }
1060
17.0k
    }
1061
1062
17.3k
    _catch: return result;
1063
17.3k
}
1064
1065
static inline
1066
M3Result EmitSlotNumOfStackTopAndPop (IM3Compilation o)
1067
36.8k
{
1068
    // no emit if value is in register
1069
36.8k
    if (IsStackTopInSlot(o)) {
1070
26.0k
        EmitSlotOffset(o, GetStackTopSlotNumber(o));
1071
26.0k
    }
1072
1073
36.8k
    return Pop(o);
1074
36.8k
}
1075
1076
1077
// Or, maybe: EmitTrappingOp
1078
M3Result AddTrapRecord (IM3Compilation o)
1079
6.01k
{
1080
6.01k
    M3Result result = m3Err_none;
1081
1082
6.01k
    if (o->function) {
1083
6.01k
    }
1084
1085
6.01k
    return result;
1086
6.01k
}
1087
1088
static
1089
M3Result UnwindBlockStack (IM3Compilation o)
1090
7.08k
{
1091
7.08k
    M3Result result = m3Err_none;
1092
1093
7.08k
    u32 popCount = 0;
1094
24.3k
    while (o->stackIndex > o->block.blockStackIndex) {
1095
17.2k
_       (Pop(o));
1096
17.2k
        ++popCount;
1097
17.2k
    }
1098
1099
7.06k
    if (popCount) {
1100
2.41k
        m3log(compile, "unwound stack top: %d", popCount);
1101
2.41k
    }
1102
1103
7.08k
    _catch: return result;
1104
7.06k
}
1105
1106
static inline
1107
M3Result SetStackPolymorphic (IM3Compilation o)
1108
6.54k
{
1109
6.54k
    o->block.isPolymorphic = true;                              m3log (compile, "stack set polymorphic");
1110
6.54k
    return UnwindBlockStack(o);
1111
6.54k
}
1112
1113
static
1114
void PatchBranches (IM3Compilation o)
1115
537
{
1116
537
    InvalidateFold(o);         // patched branches land at the current pc
1117
1118
537
    pc_t pc = GetPC(o);
1119
1120
537
    pc_t patches     = o->block.patches;
1121
537
    o->block.patches = NULL;
1122
1123
577
    while (patches) {                                           m3log (compile, "patching location: %p to pc: %p", patches, pc);
1124
40
        pc_t next       = *(pc_t*)patches;
1125
40
        *(pc_t*)patches = pc;
1126
40
        patches         = next;
1127
40
    }
1128
537
}
1129
1130
//-------------------------------------------------------------------------------------------------------------------------
1131
1132
static
1133
M3Result CopyStackIndexToSlot (IM3Compilation o, u16 i_destSlot, u16 i_stackIndex)  // NoPushPop
1134
7.15k
{
1135
7.15k
    M3Result result = m3Err_none;
1136
1137
7.15k
    IM3Operation op;
1138
1139
7.15k
    m3type_t type       = GetStackTypeFromBottom(o, i_stackIndex);
1140
7.15k
    bool     inRegister = IsStackIndexInRegister(o, i_stackIndex);
1141
1142
7.15k
    if (inRegister) {
1143
476
        op = c_setSetOps[BaseTypeOf(type)];
1144
6.68k
    } else {
1145
6.68k
        op = Is64BitType(type) ? op_CopySlot_64 : op_CopySlot_32;
1146
6.68k
    }
1147
1148
7.15k
_   (EmitOp(o, op));
1149
7.15k
    EmitSlotOffset(o, i_destSlot);
1150
1151
7.15k
    if (not inRegister) {
1152
6.68k
        u16 srcSlot = GetSlotForStackIndex(o, i_stackIndex);
1153
6.68k
        EmitSlotOffset(o, srcSlot);
1154
6.68k
    }
1155
1156
7.15k
    _catch: return result;
1157
7.15k
}
1158
1159
static
1160
M3Result CopyStackTopToSlot (IM3Compilation o, u16 i_destSlot)  // NoPushPop
1161
1.54k
{
1162
1.54k
    M3Result result;
1163
1164
1.54k
    i16 stackTop;
1165
1.54k
_   (GetStackTopIndexThrows(o, &stackTop));
1166
1.54k
_   (CopyStackIndexToSlot(o, i_destSlot, (u16)stackTop));
1167
1168
1.54k
    _catch: return result;
1169
1.54k
}
1170
1171
1172
// a copy-on-write strategy is used with locals. when a get local occurs, it's not copied anywhere. the stack
1173
// entry just has a index pointer to that local memory slot.
1174
// then, when a previously referenced local is set, the current value needs to be preserved for those references
1175
1176
// TODO: consider getting rid of these specialized operations: PreserveSetSlot & PreserveCopySlot.
1177
// They likely just take up space (which seems to reduce performance) without improving performance.
1178
static
1179
M3Result PreservedCopyTopSlot (IM3Compilation o, u16 i_destSlot, u16 i_preserveSlot)
1180
6
{
1181
6
    M3Result result = m3Err_none;             d_m3Assert (i_destSlot != i_preserveSlot);
1182
1183
6
    IM3Operation op;
1184
1185
6
    m3type_t type = GetStackTopType(o);
1186
1187
6
    if (IsStackTopInRegister(o)) {
1188
0
        op = c_preserveSetSlot[BaseTypeOf(type)];
1189
6
    } else {
1190
6
        op = Is64BitType(type) ? op_PreserveCopySlot_64 : op_PreserveCopySlot_32;
1191
6
    }
1192
1193
6
_   (EmitOp(o, op));
1194
6
    EmitSlotOffset(o, i_destSlot);
1195
1196
6
    if (IsStackTopInSlot(o)) {
1197
6
        EmitSlotOffset(o, GetStackTopSlotNumber(o));
1198
6
    }
1199
1200
6
    EmitSlotOffset(o, i_preserveSlot);
1201
1202
6
    _catch: return result;
1203
6
}
1204
1205
static
1206
M3Result CopyStackTopToRegister (IM3Compilation o, bool i_updateStack)
1207
469
{
1208
469
    M3Result result = m3Err_none;
1209
1210
469
    if (IsStackTopInSlot(o)) {
1211
299
        m3type_t type = GetStackTopType(o);
1212
1213
299
_       (PreserveRegisterIfOccupied(o, type));
1214
1215
299
        IM3Operation op = c_setRegisterOps[BaseTypeOf(type)];
1216
1217
299
_       (EmitOp(o, op));
1218
299
        EmitSlotOffset(o, GetStackTopSlotNumber(o));
1219
1220
299
        if (i_updateStack) {
1221
0
_           (PopType(o, type));
1222
0
_           (PushRegister(o, type));
1223
0
        }
1224
299
    }
1225
1226
469
    _catch: return result;
1227
469
}
1228
1229
1230
// if local is unreferenced, o_preservedSlotNumber will be equal to localIndex on return
1231
static
1232
M3Result FindReferencedLocalWithinCurrentBlock (IM3Compilation o, u16* o_preservedSlotNumber, u32 i_localSlot)
1233
897k
{
1234
897k
    M3Result result = m3Err_none;
1235
1236
897k
    IM3CompilationScope scope = &o->block;
1237
1238
897k
    u16 startIndex = scope->blockStackIndex;
1239
1240
1.13M
    while (scope->opcode == c_waOp_block) {
1241
237k
        scope = scope->outer;
1242
237k
        if (not scope) {
1243
0
            break;
1244
0
        }
1245
1246
237k
        startIndex = scope->blockStackIndex;
1247
237k
    }
1248
1249
897k
    *o_preservedSlotNumber = (u16)i_localSlot;
1250
1251
10.4M
    for (u32 i = startIndex; i < o->stackIndex; ++i) {
1252
9.59M
        if (o->wasmStack[i] == i_localSlot) {
1253
35
            m3type_t type = GetStackTypeFromBottom(o, i);                         d_m3Assert (type != c_m3Type_none)
1254
1255
35
            if (*o_preservedSlotNumber == i_localSlot) {
1256
33
_               (AllocateSlots(o, o_preservedSlotNumber, type));
1257
32
            } else {
1258
                // A 64-bit value spans two slots, and allocating or releasing it
1259
                // counts both, so a second reference has to count both as well.
1260
10
                for (u16 s = 0; s < GetTypeNumSlots(type); ++s) {
1261
8
_                   (IncrementSlotUsageCount(o, (u16)(*o_preservedSlotNumber + s)));
1262
8
                }
1263
2
            }
1264
1265
34
            o->wasmStack[i] = *o_preservedSlotNumber;
1266
34
        }
1267
9.59M
    }
1268
1269
897k
    _catch: return result;
1270
897k
}
1271
1272
static
1273
M3Result GetBlockScope (IM3Compilation o, IM3CompilationScope* o_scope, u32 i_depth)
1274
507
{
1275
507
    M3Result result = m3Err_none;
1276
1277
507
    IM3CompilationScope scope = &o->block;
1278
1279
722
    while (i_depth--) {
1280
215
        scope = scope->outer;
1281
215
        _throwif("invalid block depth", not scope);
1282
215
    }
1283
1284
507
    *o_scope = scope;
1285
1286
507
    _catch:
1287
507
    return result;
1288
507
}
1289
1290
static
1291
M3Result CopyStackSlotsR (IM3Compilation o, u16 i_targetSlotStackIndex, u16 i_stackIndex, u16 i_endStackIndex, u16 i_tempSlot)
1292
4.02k
{
1293
4.02k
    M3Result result = m3Err_none;
1294
1295
4.02k
    if (i_stackIndex < i_endStackIndex) {
1296
3.86k
        u16 srcSlot = GetSlotForStackIndex(o, i_stackIndex);
1297
1298
3.86k
        m3type_t type      = GetStackTypeFromBottom(o, i_stackIndex);
1299
3.86k
        u16      numSlots  = GetTypeNumSlots(type);
1300
3.86k
        u16      extraSlot = numSlots - 1;
1301
1302
3.86k
        u16 targetSlot = GetSlotForStackIndex(o, i_targetSlotStackIndex);
1303
1304
3.86k
        u16 preserveIndex = i_stackIndex;
1305
3.86k
        u16 collisionSlot = srcSlot;
1306
1307
3.86k
        if (targetSlot != srcSlot) {
1308
            // search for collisions
1309
2.79k
            u16 checkIndex = i_stackIndex + 1;
1310
26.8k
            while (checkIndex < i_endStackIndex) {
1311
24.6k
                u16 otherSlot1 = GetSlotForStackIndex(o, checkIndex);
1312
24.6k
                u16 otherSlot2 = GetExtraSlotForStackIndex(o, checkIndex);
1313
1314
24.6k
                if (targetSlot == otherSlot1 or
1315
24.2k
                    targetSlot == otherSlot2 or
1316
24.2k
                    targetSlot + extraSlot == otherSlot1) {
1317
576
                    _throwif(m3Err_functionStackOverflow, i_tempSlot >= d_m3MaxFunctionSlots);
1318
1319
568
_                   (CopyStackIndexToSlot(o, i_tempSlot, checkIndex));
1320
568
                    o->wasmStack[checkIndex] = i_tempSlot;
1321
568
                    i_tempSlot += GetTypeNumSlots(c_m3Type_i64);
1322
568
                    TouchSlot(o, i_tempSlot - 1);
1323
1324
                    // restore this on the way back down
1325
568
                    preserveIndex = checkIndex;
1326
568
                    collisionSlot = otherSlot1;
1327
1328
568
                    break;
1329
568
                }
1330
1331
24.0k
                ++checkIndex;
1332
24.0k
            }
1333
1334
2.78k
_           (CopyStackIndexToSlot(o, targetSlot, i_stackIndex));                                                m3log (compile, " copying slot: %d to slot: %d", srcSlot, targetSlot);
1335
2.78k
            o->wasmStack[i_stackIndex] = targetSlot;
1336
2.78k
        }
1337
1338
3.85k
_       (CopyStackSlotsR(o, i_targetSlotStackIndex + 1, i_stackIndex + 1, i_endStackIndex, i_tempSlot));
1339
1340
        // restore the stack state
1341
3.80k
        o->wasmStack[i_stackIndex]  = srcSlot;
1342
3.80k
        o->wasmStack[preserveIndex] = collisionSlot;
1343
3.80k
    }
1344
1345
4.02k
    _catch:
1346
4.02k
    return result;
1347
4.02k
}
1348
1349
static
1350
M3Result ResolveBlockResults (IM3Compilation o, IM3CompilationScope i_targetBlock, bool i_isBranch)
1351
185
{
1352
185
    M3Result result = m3Err_none;
1353
185
    if (d_m3LogWasmStack) {
1354
0
        dump_type_stack(o);
1355
0
    }
1356
1357
185
    bool isLoop = (i_targetBlock->opcode == c_waOp_loop and i_isBranch);
1358
1359
185
    u16 numParams  = GetFuncTypeNumParams(i_targetBlock->type);
1360
185
    u16 numResults = GetFuncTypeNumResults(i_targetBlock->type);
1361
1362
185
    u16 slotRecords = i_targetBlock->exitStackIndex;
1363
1364
185
    u16 numValues;
1365
1366
185
    if (not isLoop) {
1367
184
        numValues = numResults;
1368
184
        slotRecords += numParams;
1369
184
    } else {
1370
1
        numValues = numParams;
1371
1
    }
1372
1373
185
    u16 blockHeight = GetNumBlockValuesOnStack(o);
1374
1375
185
    _throwif(m3Err_typeCountMismatch, i_isBranch ? (blockHeight < numValues) : (blockHeight != numValues));
1376
1377
185
    if (numValues) {
1378
175
        u16 endIndex     = GetStackTopIndex(o) + 1;
1379
175
        u16 numRemValues = numValues;
1380
1381
        // The last result is taken from _fp0. See PushBlockResults.
1382
175
        if (not isLoop and IsFpType(GetStackTopType(o))) {
1383
63
_           (CopyStackTopToRegister(o, false));
1384
63
            --endIndex;
1385
63
            --numRemValues;
1386
63
        }
1387
1388
        // TODO: tempslot affects maxStackSlots, so can grow unnecess each time.
1389
175
        u16 tempSlot = o->maxStackSlots;// GetMaxUsedSlotPlusOne (o); doesn't work cause can collide with slotRecords
1390
175
        AlignSlotToType(&tempSlot, c_m3Type_i64);
1391
1392
175
_       (CopyStackSlotsR(o, slotRecords, endIndex - numRemValues, endIndex, tempSlot));
1393
1394
167
        if (d_m3LogWasmStack) {
1395
0
            dump_type_stack(o);
1396
0
        }
1397
167
    }
1398
1399
185
    _catch: return result;
1400
185
}
1401
1402
1403
static
1404
M3Result ReturnValues (IM3Compilation o, IM3CompilationScope i_functionBlock, bool i_isBranch)
1405
682
{
1406
682
    M3Result result = m3Err_none;
1407
682
    if (d_m3LogWasmStack) {
1408
0
        dump_type_stack(o);
1409
0
    }
1410
1411
682
    u16 numReturns  = GetFuncTypeNumResults(i_functionBlock->type);     // could just o->function too...
1412
682
    u16 blockHeight = GetNumBlockValuesOnStack(o);
1413
1414
682
    if (not IsStackPolymorphic(o)) {
1415
245
        _throwif(m3Err_typeCountMismatch, i_isBranch ? (blockHeight < numReturns) : (blockHeight != numReturns));
1416
214
    }
1417
1418
651
    if (numReturns) {
1419
        // return slots like args are 64-bit aligned
1420
266
        u16 returnSlot = numReturns * c_ioSlotCount;
1421
266
        u16 stackTop   = GetStackTopIndex(o);
1422
1423
3.37k
        for (u16 i = 0; i < numReturns; ++i) {
1424
3.10k
            m3type_t returnType = GetFuncTypeResultType(i_functionBlock->type, numReturns - 1 - i);
1425
1426
3.10k
            m3type_t stackType = GetStackTypeFromTop(o, i);  // using FromTop so that only dynamic items are checked
1427
1428
3.10k
            if (IsStackPolymorphic(o) and stackType == c_m3Type_none) {
1429
830
                stackType = returnType;
1430
830
            }
1431
1432
3.10k
            _throwif(m3Err_typeMismatch, not IsSubTypeOf(stackType, returnType));
1433
1434
3.10k
            if (not IsStackPolymorphic(o)) {
1435
1.98k
                returnSlot -= c_ioSlotCount;
1436
1.98k
_               (CopyStackIndexToSlot(o, returnSlot, stackTop--));
1437
1.98k
            }
1438
3.10k
        }
1439
1440
265
        if (not i_isBranch) {
1441
1.81k
            while (numReturns--) {
1442
1.61k
_               (Pop(o));
1443
1.61k
            }
1444
195
        }
1445
265
    }
1446
1447
682
    _catch: return result;
1448
651
}
1449
1450
1451
//-------------------------------------------------------------------------------------------------------------------------
1452
1453
static
1454
M3Result Compile_Const_i32 (IM3Compilation o, m3opcode_t i_opcode)
1455
11.8k
{
1456
11.8k
    M3Result result;
1457
1458
11.8k
    i32 value;
1459
11.8k
_   (ReadLEB_i32(&value, &o->wasm, o->wasmEnd));
1460
11.8k
_   (PushConst(o, value, c_m3Type_i32));                        m3log (compile, d_indent " (const i32 = %" PRIi32 ")", get_indention_string (o), value);
1461
11.8k
    _catch: return result;
1462
11.8k
}
1463
1464
static
1465
M3Result Compile_Const_i64 (IM3Compilation o, m3opcode_t i_opcode)
1466
38.8k
{
1467
38.8k
    M3Result result;
1468
1469
38.8k
    i64 value;
1470
38.8k
_   (ReadLEB_i64(&value, &o->wasm, o->wasmEnd));
1471
38.8k
_   (PushConst(o, value, c_m3Type_i64));                        m3log (compile, d_indent " (const i64 = %" PRIi64 ")", get_indention_string (o), value);
1472
38.8k
    _catch: return result;
1473
38.8k
}
1474
1475
1476
#if d_m3ImplementFloat
1477
static
1478
M3Result Compile_Const_f32 (IM3Compilation o, m3opcode_t i_opcode)
1479
2.88k
{
1480
2.88k
    M3Result result;
1481
1482
2.88k
    union {
1483
2.88k
        u32 u;
1484
2.88k
        f32 f;
1485
2.88k
    } value = { 0 };
1486
1487
2.88k
_   (Read_f32(&value.f, &o->wasm, o->wasmEnd));                 m3log (compile, d_indent " (const f32 = %" PRIf32 ")", get_indention_string (o), value.f);
1488
2.88k
_   (PushConst(o, value.u, c_m3Type_f32));
1489
1490
2.88k
    _catch: return result;
1491
2.88k
}
1492
1493
static
1494
M3Result Compile_Const_f64 (IM3Compilation o, m3opcode_t i_opcode)
1495
534
{
1496
534
    M3Result result;
1497
1498
534
    union {
1499
534
        u64 u;
1500
534
        f64 f;
1501
534
    } value = { 0 };
1502
1503
534
_   (Read_f64(&value.f, &o->wasm, o->wasmEnd));                 m3log (compile, d_indent " (const f64 = %" PRIf64 ")", get_indention_string (o), value.f);
1504
534
_   (PushConst(o, value.u, c_m3Type_f64));
1505
1506
534
    _catch: return result;
1507
534
}
1508
#endif
1509
1510
#if d_m3CascadedOpcodes
1511
1512
static
1513
M3Result Compile_ExtendedOpcode (IM3Compilation o, m3opcode_t i_opcode)
1514
506
{
1515
506
_try {
1516
506
    u8 opcode;
1517
506
_   (Read_u8(&opcode, &o->wasm, o->wasmEnd));                   m3log (compile, d_indent " (FC: %" PRIi32 ")", get_indention_string (o), opcode);
1518
1519
506
    i_opcode = (i_opcode << 8) | opcode;
1520
1521
    //printf("Extended opcode: 0x%x\n", i_opcode);
1522
1523
506
    IM3OpInfo opInfo = GetOpInfo(i_opcode);
1524
506
    _throwif(m3Err_unknownOpcode, not opInfo);
1525
1526
493
    M3Compiler compiler = opInfo->compiler;
1527
493
    _throwif(m3Err_noCompiler, not compiler);
1528
1529
493
_   ((*compiler)(o, i_opcode));
1530
1531
492
    o->previousOpcode = i_opcode;
1532
1533
506
    } _catch: return result;
1534
492
}
1535
#endif
1536
1537
static
1538
M3Result Compile_Return (IM3Compilation o, m3opcode_t i_opcode)
1539
237
{
1540
237
    M3Result result = m3Err_none;
1541
1542
237
    if (not IsStackPolymorphic(o)) {
1543
54
        IM3CompilationScope functionScope;
1544
54
_       (GetBlockScope(o, &functionScope, o->block.depth));
1545
1546
54
_       (ReturnValues(o, functionScope, true));
1547
1548
54
_       (EmitOp(o, op_Return));
1549
1550
54
_       (SetStackPolymorphic(o));
1551
54
    }
1552
1553
237
    _catch: return result;
1554
237
}
1555
1556
static
1557
M3Result ValidateBlockEnd (IM3Compilation o)
1558
1.31k
{
1559
1.31k
    M3Result result = m3Err_none;
1560
1561
1.31k
    u16 numResults  = GetFuncTypeNumResults(o->block.type);
1562
1.31k
    u16 blockHeight = GetNumBlockValuesOnStack(o);
1563
1564
1.31k
    if (not IsStackPolymorphic(o)) {
1565
        // Spec: at block end, stack height must match the number of results
1566
477
        _throwif(m3Err_typeCountMismatch, blockHeight != numResults);
1567
1568
        // Spec: result types must match expected types
1569
4.40k
        for (u16 i = 0; i < numResults; ++i) {
1570
4.13k
            m3type_t expectedType = GetFuncTypeResultType(o->block.type, numResults - 1 - i);
1571
4.13k
            m3type_t actualType   = GetStackTypeFromTop(o, i);
1572
4.13k
            _throwif(m3Err_typeMismatch, not IsSubTypeOf(actualType, expectedType));
1573
4.13k
        }
1574
271
    }
1575
1576
1.31k
    _catch: return result;
1577
1.31k
}
1578
1579
static
1580
M3Result Compile_End (IM3Compilation o, m3opcode_t i_opcode)
1581
1.30k
{
1582
1.30k
    M3Result result = m3Err_none;                   //dump_type_stack (o);
1583
1584
    // function end:
1585
1.30k
    if (o->block.depth == 0) {
1586
748
        ValidateBlockEnd(o);
1587
1588
        //      if (not IsStackPolymorphic (o))
1589
748
        {
1590
            // A constant expression has no function frame, but its value still
1591
            // has to be copied down to slot 0, where EvaluateExpression reads it
1592
            // back. The non-emitting init-expr walk is skipped: it has no block
1593
            // type to return against.
1594
748
            if (o->function or o->page) {
1595
593
_               (ReturnValues(o, &o->block, false));
1596
561
            }
1597
1598
716
_           (EmitOp(o, op_Return));
1599
716
        }
1600
716
    }
1601
1602
1.30k
    _catch: return result;
1603
1.30k
}
1604
1605
1606
static
1607
M3Result Compile_SetLocal (IM3Compilation o, m3opcode_t i_opcode)
1608
212
{
1609
212
    M3Result result;
1610
1611
212
    u32 localIndex;
1612
212
_   (ReadLEB_u32(&localIndex, &o->wasm, o->wasmEnd));                           m3log (compile, d_indent " (index = %u)", get_indention_string (o), localIndex);
1613
1614
212
    if (localIndex < GetFunctionNumArgsAndLocals(o->function)) {
1615
        // Spec: value type must match local type
1616
212
        if (not IsStackPolymorphic(o)) {
1617
19
            m3type_t localType    = GetStackTypeFromBottom(o, localIndex);
1618
19
            m3type_t stackTopType = GetStackTopType(o);
1619
19
            _throwif(m3Err_typeMismatch, stackTopType != c_m3Type_none and localType != c_m3Type_none and
1620
19
                                           not IsSubTypeOf(stackTopType, localType));
1621
19
        }
1622
1623
212
        u16 localSlot = GetSlotForStackIndex(o, localIndex);
1624
1625
212
        u16 preserveSlot;
1626
212
_       (FindReferencedLocalWithinCurrentBlock(o, &preserveSlot, localSlot));  // preserve will be different than local, if referenced
1627
1628
212
        bool folded = false;
1629
1630
212
#if d_m3FoldSetLocal
1631
        // destination folding: when the value was produced by the immediately preceding op (nothing
1632
        // emitted since, result on top in _r0, no copy-on-write preservation needed), retro-patch the
1633
        // producer to a variant that writes straight to the local's slot and skip the SetSlot op
1634
212
        if (o->foldPatchPC and preserveSlot == localSlot and IsStackTopInRegister(o) and GetStackTopIndex(o) == (i16)o->foldStackIndex) {
1635
136
            IM3Operation foldOp = GetFoldOp(o->foldOpcode, o->foldForm);
1636
1637
136
            if (foldOp) {
1638
135
                *(IM3Operation*)o->foldPatchPC = foldOp;     // retro-patch the producer
1639
135
                EmitSlotOffset(o, localSlot);                  // append its destination
1640
135
                InvalidateFold(o);
1641
135
                folded = true;
1642
135
            }
1643
136
        }
1644
212
#endif
1645
1646
212
        if (folded) {
1647
135
            m3type_t type = GetStackTopType(o);
1648
135
_           (Pop(o));                              // the value now lives in the local's slot
1649
1650
            // A folded op writes its result through to the register as well, so a tee
1651
            // can keep it there. Pushing the slot instead would make the next use that
1652
            // wants a register reload the slot this op just stored to.
1653
135
            if (i_opcode == c_waOp_teeLocal) {
1654
66
_               (PushRegister(o, type));
1655
66
            }
1656
135
        } else {
1657
77
            if (preserveSlot == localSlot) {
1658
71
_               (CopyStackTopToSlot(o, localSlot))
1659
71
            } else {
1660
6
_               (PreservedCopyTopSlot(o, localSlot, preserveSlot))
1661
6
            }
1662
1663
77
            if (i_opcode != c_waOp_teeLocal) {
1664
48
_               (Pop(o));
1665
48
            }
1666
77
        }
1667
212
    } else {
1668
0
        _throw("local index out of bounds");
1669
0
    }
1670
1671
212
    _catch: return result;
1672
212
}
1673
1674
static
1675
M3Result Compile_GetLocal (IM3Compilation o, m3opcode_t i_opcode)
1676
219
{
1677
219
_try {
1678
1679
219
    u32 localIndex;
1680
219
_   (ReadLEB_u32(&localIndex, &o->wasm, o->wasmEnd));                           m3log (compile, d_indent " (index = %u)", get_indention_string (o), localIndex);
1681
1682
219
    if (localIndex >= GetFunctionNumArgsAndLocals(o->function)) {
1683
0
        _throw("local index out of bounds");
1684
0
    }
1685
1686
219
    m3type_t type = GetStackTypeFromBottom(o, localIndex);
1687
219
    u16      slot = GetSlotForStackIndex(o, localIndex);
1688
1689
219
_   (Push(o, type, slot));
1690
1691
219
    } _catch: return result;
1692
218
}
1693
1694
static
1695
M3Result Compile_GetGlobal (IM3Compilation o, M3Global* i_global)
1696
88
{
1697
88
    M3Result result;
1698
1699
88
    IM3Operation op = Is64BitType(i_global->type) ? op_GetGlobal_s64 : op_GetGlobal_s32;
1700
88
_   (EmitOp(o, op));
1701
88
    EmitPointer(o, &i_global->i64Value);
1702
88
_   (PushAllocatedSlotAndEmit(o, i_global->type));
1703
1704
88
    _catch: return result;
1705
88
}
1706
1707
static
1708
M3Result Compile_SetGlobal (IM3Compilation o, M3Global* i_global)
1709
138
{
1710
138
    M3Result result = m3Err_none;
1711
1712
138
    if (i_global->isMutable) {
1713
        // Spec: value type must match global type
1714
137
        if (not IsStackPolymorphic(o)) {
1715
27
            m3type_t stackTopType = GetStackTopType(o);
1716
27
            _throwif(m3Err_typeMismatch, stackTopType != c_m3Type_none and
1717
27
                                           not IsSubTypeOf(stackTopType, i_global->type));
1718
27
        }
1719
1720
137
        IM3Operation op;
1721
137
        m3type_t     type = GetStackTopType(o);
1722
1723
137
        if (IsStackTopInRegister(o)) {
1724
18
            op = c_setGlobalOps[BaseTypeOf(type)];
1725
119
        } else {
1726
119
            op = Is64BitType(type) ? op_SetGlobal_s64 : op_SetGlobal_s32;
1727
119
        }
1728
1729
137
_       (EmitOp(o, op));
1730
137
        EmitPointer(o, &i_global->i64Value);
1731
1732
137
        if (IsStackTopInSlot(o)) {
1733
119
            EmitSlotOffset(o, GetStackTopSlotNumber(o));
1734
119
        }
1735
1736
137
_       (Pop(o));
1737
137
    } else {
1738
1
        _throw(m3Err_settingImmutableGlobal);
1739
0
    }
1740
1741
138
    _catch: return result;
1742
138
}
1743
1744
static
1745
M3Result Compile_GetSetGlobal (IM3Compilation o, m3opcode_t i_opcode)
1746
227
{
1747
227
    M3Result result = m3Err_none;
1748
1749
227
    u32 globalIndex;
1750
227
_   (ReadLEB_u32(&globalIndex, &o->wasm, o->wasmEnd));
1751
1752
227
    if (globalIndex < o->module->numGlobals) {
1753
227
        if (o->module->globals) {
1754
227
            M3Global* global = &o->module->globals[globalIndex];
1755
1756
            // Spec: a constant expression may only read an imported immutable
1757
            // global. The module's own globals are counted before their
1758
            // initializer is walked, so a bare index check would let one
1759
            // reference itself. These describe how the module declared the
1760
            // global, so they are checked before following any link.
1761
227
            _throwif(m3Err_globaIndexOutOfBounds, o->isInitExpr and not global->imported);
1762
226
            _throwif(m3Err_wasmMalformed, o->isInitExpr and global->isMutable);
1763
1764
            // an import linked to another module reads and writes that module's
1765
            // cell, not the placeholder standing in for it here
1766
226
            if (global->resolved) {
1767
0
                global = global->resolved;
1768
0
            }
1769
1770
226
_           ((i_opcode == c_waOp_getGlobal) ? Compile_GetGlobal(o, global) : Compile_SetGlobal(o, global));
1771
225
        } else {
1772
0
            _throw(ErrorCompile(m3Err_globalMemoryNotAllocated, o, "module '%s' is missing global memory", o->module->name));
1773
0
        }
1774
227
    } else {
1775
0
        _throw(m3Err_globaIndexOutOfBounds);
1776
0
    }
1777
1778
227
    _catch: return result;
1779
227
}
1780
1781
static
1782
void EmitPatchingBranchPointer (IM3Compilation o, IM3CompilationScope i_scope)
1783
45
{
1784
45
    pc_t patch       = EmitPointer(o, i_scope->patches);                m3log (compile, "branch patch required at: %p", patch);
1785
45
    i_scope->patches = patch;
1786
45
}
1787
1788
static
1789
M3Result EmitPatchingBranch (IM3Compilation o, IM3CompilationScope i_scope)
1790
41
{
1791
41
    M3Result result = m3Err_none;
1792
1793
41
_   (EmitOp(o, op_Branch));
1794
41
    EmitPatchingBranchPointer(o, i_scope);
1795
1796
41
    _catch: return result;
1797
41
}
1798
1799
#if d_m3HasExceptionHandling
1800
1801
// How many try_table handler records an exit from i_from out to i_target takes
1802
// down with it. Walking outward from the block the branch sits in, every
1803
// try_table scope up to and including the target is left behind - the target
1804
// too, because landing on a block's label means that block is finished.
1805
//
1806
// A loop label is the exception, but a loop is never a try_table, so the count
1807
// is the same either way: the loop itself is not on the list.
1808
static
1809
u32 CountTryFramesToExit (IM3CompilationScope i_from, IM3CompilationScope i_target)
1810
108
{
1811
108
    u32 count = 0;
1812
1813
134
    for (IM3CompilationScope scope = i_from; scope; scope = scope->outer) {
1814
134
        if (scope->opcode == c_waOp_tryTable) {
1815
14
            ++count;
1816
14
        }
1817
1818
134
        if (scope == i_target) {
1819
108
            break;
1820
108
        }
1821
134
    }
1822
1823
108
    return count;
1824
108
}
1825
1826
1827
// The handler records a branch out of i_from to i_target has to drop.
1828
//
1829
// A branch to the function's own label compiles to op_Return, and returning
1830
// unwinds op_TryTable's native frame, which drops that record on its own, so
1831
// those need no pop at the branch site.
1832
//
1833
// i_from is where the walk starts, which is not always the block the branch is
1834
// written in: a catch stub passes the scope outside its try, because
1835
// op_TryTable takes its own record off before jumping to the stub.
1836
static
1837
u32 NumTryFramesToPop (IM3CompilationScope i_from, IM3CompilationScope i_target)
1838
227
{
1839
227
    if (i_target->depth == 0) {
1840
119
        return 0;
1841
119
    }
1842
1843
    // the walk only ever runs inward-to-outward; a target that is already
1844
    // outside i_from has nothing between them
1845
108
    if (not i_from or i_from->depth < i_target->depth) {
1846
0
        return 0;
1847
0
    }
1848
1849
108
    return CountTryFramesToExit(i_from, i_target);
1850
108
}
1851
1852
1853
static
1854
M3Result EmitPopTryFrames (IM3Compilation o, u32 i_numFrames)
1855
311
{
1856
311
    M3Result result = m3Err_none;
1857
1858
311
    if (i_numFrames) {
1859
29
_       (EmitOp(o, op_PopHandlers));
1860
29
        EmitConstant32(o, i_numFrames);
1861
29
    }
1862
1863
311
    _catch: return result;
1864
311
}
1865
1866
1867
// A return_call leaves the enclosing function before the callee runs, so no
1868
// try_table in this function may catch what the callee throws. Neither shape of
1869
// the instruction unwinds the native stack in time to arrange that on its own:
1870
// op_ReturnCall tail-jumps with op_TryTable's frames still standing, and the
1871
// fallback shape is an ordinary call sitting inside them. Either way the
1872
// handler records have to come off first.
1873
static
1874
M3Result EmitPopTryFramesForReturnCall (IM3Compilation o)
1875
238
{
1876
238
    u32 count = 0;
1877
1878
642
    for (IM3CompilationScope scope = &o->block; scope; scope = scope->outer) {
1879
404
        if (scope->opcode == c_waOp_tryTable) {
1880
19
            ++count;
1881
19
        }
1882
404
    }
1883
1884
238
    return EmitPopTryFrames(o, count);
1885
238
}
1886
1887
#else
1888
1889
static
1890
M3Result EmitPopTryFrames (IM3Compilation o, u32 i_numFrames)
1891
{
1892
    (void)o;
1893
    (void)i_numFrames;
1894
    return m3Err_none;
1895
}
1896
1897
#endif // d_m3HasExceptionHandling
1898
1899
#if d_m3FuseBranch
1900
// when the pending fold candidate is a compare whose result sits on top in _r0, return the fused
1901
// variant that absorbs an immediately following br_if (i_isIf false) or if (i_isIf true)
1902
static
1903
IM3Operation GetBranchFusionOp (IM3Compilation o, bool i_isIf)
1904
165
{
1905
165
    if (o->foldPatchPC and o->foldForm < 4 and IsStackTopInRegister(o) and GetStackTopIndex(o) == (i16)o->foldStackIndex) {
1906
15
        const M3FusedCmpOps* ops = GetFusedCmpOps(o->foldOpcode);
1907
1908
15
        if (ops) {
1909
2
            return i_isIf ? ops->ifOp[o->foldForm] : ops->branchIf[o->foldForm];
1910
2
        }
1911
15
    }
1912
1913
163
    return NULL;
1914
165
}
1915
#endif // d_m3FuseBranch
1916
1917
static
1918
M3Result Compile_Branch (IM3Compilation o, m3opcode_t i_opcode)
1919
137
{
1920
137
    M3Result result;
1921
137
    u32      numTryFrames = 0;
1922
1923
137
    u32 depth;
1924
137
_   (ReadLEB_u32(&depth, &o->wasm, o->wasmEnd));
1925
1926
    // Spec: br_if condition must be i32
1927
137
    if (i_opcode == c_waOp_branchIf and not IsStackPolymorphic(o)) {
1928
14
        m3type_t condType = GetStackTopType(o);
1929
14
        _throwif(m3Err_typeMismatch, condType != c_m3Type_none and condType != c_m3Type_i32);
1930
14
    }
1931
1932
137
    IM3CompilationScope scope;
1933
137
_   (GetBlockScope(o, &scope, depth));
1934
1935
137
#if d_m3HasExceptionHandling
1936
137
    numTryFrames = NumTryFramesToPop(&o->block, scope);
1937
137
#endif
1938
1939
    // branch target is a loop (continue)
1940
137
    if (scope->opcode == c_waOp_loop) {
1941
21
        if (i_opcode == c_waOp_branchIf) {
1942
            // a handler pop belongs on the taken path only, so it forces the
1943
            // jump-over form even where the target takes no parameters
1944
17
            if (GetFuncTypeNumParams(scope->type) or numTryFrames) {
1945
9
                IM3Operation op = IsStackTopInRegister(o) ? op_BranchIfPrologue_r : op_BranchIfPrologue_s;
1946
1947
9
_               (EmitOp(o, op));
1948
9
_               (EmitSlotNumOfStackTopAndPop(o));
1949
1950
9
                pc_t* jumpTo = (pc_t*)ReservePointer(o);
1951
1952
9
_               (EmitPopTryFrames(o, numTryFrames));
1953
1954
9
                if (not IsStackPolymorphic(o)) {
1955
0
_                   (ResolveBlockResults(o, scope, /* isBranch: */ true));
1956
0
                }
1957
1958
9
_               (EmitOp(o, op_ContinueLoop));
1959
9
                EmitPointer(o, scope->pc);
1960
1961
9
                *jumpTo = GetPC(o);
1962
9
            } else {
1963
                // move the condition to a register
1964
8
_               (CopyStackTopToRegister(o, false));
1965
8
_               (PopType(o, c_m3Type_i32));
1966
1967
7
_               (EmitOp(o, op_ContinueLoopIf));
1968
7
                EmitPointer(o, scope->pc);
1969
7
            }
1970
1971
            //          dump_type_stack(o);
1972
17
        } else // is c_waOp_branch
1973
4
        {
1974
4
_           (EmitPopTryFrames(o, numTryFrames));
1975
1976
            // the branch operands become the loop's parameters on the next iteration
1977
4
            if (not IsStackPolymorphic(o)) {
1978
0
_               (ResolveBlockResults(o, scope, /* isBranch: */ true));
1979
0
            }
1980
1981
4
_           (EmitOp(o, op_ContinueLoop));
1982
4
            EmitPointer(o, scope->pc);
1983
4
            o->block.isPolymorphic = true;
1984
4
        }
1985
21
    } else // forward branch
1986
116
    {
1987
116
        pc_t* jumpTo = NULL;
1988
1989
116
        bool isReturn         = (scope->depth == 0);
1990
116
        bool targetHasResults = GetFuncTypeNumResults(scope->type);
1991
1992
116
        if (i_opcode == c_waOp_branchIf) {
1993
59
            if (targetHasResults or isReturn or numTryFrames) {
1994
55
                IM3Operation op = IsStackTopInRegister(o) ? op_BranchIfPrologue_r : op_BranchIfPrologue_s;
1995
1996
55
_               (EmitOp(o, op));
1997
55
_               (EmitSlotNumOfStackTopAndPop(o)); // condition
1998
1999
                // this is continuation point, if the branch isn't taken
2000
54
                jumpTo = (pc_t*)ReservePointer(o);
2001
54
            } else {
2002
4
#if d_m3FuseBranch
2003
4
                IM3Operation fuseOp = GetBranchFusionOp(o, false);
2004
2005
4
                if (fuseOp) {
2006
                    // absorb the branch into the compare that produced the condition
2007
0
                    *(IM3Operation*)o->foldPatchPC = fuseOp;
2008
0
                    InvalidateFold(o);
2009
2010
0
_                   (Pop(o));                      // the condition is consumed inside the fused op
2011
2012
0
                    EmitPatchingBranchPointer(o, scope);
2013
0
                    goto _catch;
2014
0
                }
2015
4
#endif
2016
4
                IM3Operation op = IsStackTopInRegister(o) ? op_BranchIf_r : op_BranchIf_s;
2017
2018
4
_               (EmitOp(o, op));
2019
4
_               (EmitSlotNumOfStackTopAndPop(o)); // condition
2020
2021
4
                EmitPatchingBranchPointer(o, scope);
2022
4
                goto _catch;
2023
4
            }
2024
59
        }
2025
2026
111
        if (not IsStackPolymorphic(o)) {
2027
27
            if (isReturn) {
2028
16
_               (ReturnValues(o, scope, true));
2029
16
_               (EmitOp(o, op_Return));
2030
16
            } else {
2031
11
_               (EmitPopTryFrames(o, numTryFrames));
2032
2033
11
_               (ResolveBlockResults(o, scope, true));
2034
11
_               (EmitPatchingBranch(o, scope));
2035
11
            }
2036
27
        }
2037
2038
111
        if (jumpTo) {
2039
54
            *jumpTo = GetPC(o);
2040
54
        }
2041
2042
111
        if (i_opcode == c_waOp_branch) {
2043
57
_           (SetStackPolymorphic(o));
2044
57
        }
2045
111
    }
2046
2047
137
    _catch: return result;
2048
137
}
2049
2050
static
2051
M3Result Compile_BranchTable (IM3Compilation o, m3opcode_t i_opcode)
2052
67
{
2053
    // the page a continue-op page is standing in for; non-NULL only while that
2054
    // page is installed in o->page, so the catch below knows to put it back
2055
67
    IM3CodePage displacedPage = NULL;
2056
    // indexed by label depth: the continuation stub already emitted for that target
2057
    // within this table, or NULL if this table has not reached it yet
2058
67
    pc_t* targetStubs = NULL;
2059
67
_try {
2060
67
    u32 targetCount;
2061
67
_   (ReadLEB_u32(&targetCount, &o->wasm, o->wasmEnd));
2062
2063
    // Spec: validate that the branch index operand is i32
2064
67
    if (not IsStackPolymorphic(o)) {
2065
27
        m3type_t indexType = GetStackTopType(o);
2066
27
        _throwif(m3Err_typeMismatch, indexType != c_m3Type_none and indexType != c_m3Type_i32);
2067
27
    }
2068
2069
67
_   (PreserveRegisterIfOccupied(o, c_m3Type_i64));         // move branch operand to a slot
2070
66
    u16 slot = GetStackTopSlotNumber(o);
2071
66
_   (Pop(o));
2072
2073
    // OPTZ: according to spec: "forward branches that target a control instruction with a non-empty
2074
    // result type consume matching operands first and push them back on the operand stack after unwinding"
2075
    // So, this move-to-reg is only necessary if the target scopes have a type.
2076
2077
64
    u32 numCodeLines = targetCount + 4; // 3 => IM3Operation + slot + target_count + default_target
2078
64
_   (EnsureCodePageNumLines(o, numCodeLines));
2079
2080
64
_   (EmitOp(o, op_BranchTable));
2081
64
    EmitSlotOffset(o, slot);
2082
64
    EmitConstant32(o, targetCount);
2083
2084
64
    IM3CodePage continueOpPage = NULL;
2085
2086
    // The label types are checked by ValidateFunction, which runs under the same
2087
    // d_m3EnableValidation guard just before this compilation pass. Comparing each
2088
    // target against the default here would be wrong anyway: the spec matches every
2089
    // target against the operand stack, so in unreachable code the operands are the
2090
    // bottom type and the targets may legitimately differ (unreached-valid.wast).
2091
2092
64
    ++targetCount; // include default
2093
2094
    // Every entry of one br_table is resolved from the same compilation state - each
2095
    // stub restores whatever it touched - so two entries naming the same label emit
2096
    // byte-identical epilogues. Emit one and point both table entries at it. A label
2097
    // deeper than the current block is rejected by GetBlockScope below, which is what
2098
    // bounds this array.
2099
64
    targetStubs = m3_AllocArray(pc_t, (size_t)o->block.depth + 1);
2100
64
    _throwifnull(targetStubs);
2101
2102
362
    for (u32 i = 0; i < targetCount; ++i) {
2103
298
        u32 target;
2104
298
_       (ReadLEB_u32(&target, &o->wasm, o->wasmEnd));
2105
2106
298
        IM3CompilationScope scope;
2107
298
_       (GetBlockScope(o, &scope, target));
2108
2109
298
        if (targetStubs[target]) {
2110
226
            EmitPointer(o, targetStubs[target]);
2111
226
            continue;
2112
226
        }
2113
2114
        // TODO: don't need codepage rigmarole for
2115
        // no-param forward-branch targets
2116
2117
72
_       (AcquireCompilationCodePage(o, &continueOpPage));
2118
2119
72
        pc_t startPC  = GetPagePC(continueOpPage);
2120
72
        displacedPage = o->page;
2121
72
        o->page       = continueOpPage;
2122
2123
72
        u32 numTryFrames = 0;
2124
72
#if d_m3HasExceptionHandling
2125
72
        numTryFrames = NumTryFramesToPop(&o->block, scope);
2126
72
#endif
2127
2128
72
        if (scope->opcode == c_waOp_loop) {
2129
13
_           (EmitPopTryFrames(o, numTryFrames));
2130
2131
13
            if (not IsStackPolymorphic(o)) {
2132
1
_               (ResolveBlockResults(o, scope, true));
2133
1
            }
2134
2135
13
_           (EmitOp(o, op_ContinueLoop));
2136
13
            EmitPointer(o, scope->pc);
2137
59
        } else {
2138
59
            if (not IsStackPolymorphic(o)) {
2139
31
                if (scope->depth == 0) {
2140
13
_                   (ReturnValues(o, scope, true));
2141
13
_                   (EmitOp(o, op_Return));
2142
18
                } else {
2143
18
_                   (EmitPopTryFrames(o, numTryFrames));
2144
2145
18
_                   (ResolveBlockResults(o, scope, true));
2146
2147
18
_                   (EmitPatchingBranch(o, scope));
2148
18
                }
2149
31
            }
2150
59
        }
2151
2152
72
        ReleaseCompilationCodePage(o);
2153
72
        o->page       = displacedPage;
2154
72
        displacedPage = NULL;
2155
2156
72
        EmitPointer(o, startPC);
2157
72
        targetStubs[target] = startPC;
2158
72
    }
2159
2160
64
_   (SetStackPolymorphic(o));
2161
67
} _catch:
2162
2163
    // thrown out of the loop above with a continue-op page installed: release it
2164
    // and restore the one it displaced, which the caller's catch then releases.
2165
    // Otherwise the displaced page is on no list and simply leaks.
2166
67
    if (displacedPage) {
2167
0
        ReleaseCompilationCodePage(o);
2168
0
        o->page = displacedPage;
2169
0
    }
2170
2171
67
    m3_Free(targetStubs);
2172
2173
67
    return result;
2174
61
}
2175
2176
static
2177
M3Result CompileCallArgsAndReturn (IM3Compilation o, u16* o_stackOffset, IM3FuncType i_type, bool i_isIndirect)
2178
624
{
2179
624
_try {
2180
2181
624
    u16 topSlot = GetMaxUsedSlotPlusOne(o);
2182
2183
    // force use of at least one stack slot; this is to help ensure
2184
    // the m3 stack overflows (and traps) before the native stack can overflow.
2185
    // e.g. see Wasm spec test 'runaway' in call.wast
2186
624
    topSlot = M3_MAX(1, topSlot);
2187
2188
    // stack frame is 64-bit aligned
2189
624
    AlignSlotToType(&topSlot, c_m3Type_i64);
2190
2191
624
    *o_stackOffset = topSlot;
2192
2193
    // wait to pop this here so that topSlot search is correct
2194
624
    if (i_isIndirect) {
2195
80
_       (Pop(o));
2196
79
    }
2197
2198
623
    u16 numArgs = GetFuncTypeNumParams(i_type);
2199
623
    u16 numRets = GetFuncTypeNumResults(i_type);
2200
2201
623
    u16 argTop = topSlot + (numArgs + numRets) * c_ioSlotCount;
2202
2203
623
    TouchSlot(o, argTop - 1);
2204
2205
1.29k
    while (numArgs--) {
2206
676
_       (CopyStackTopToSlot(o, argTop -= c_ioSlotCount));
2207
676
_       (Pop(o));
2208
676
    }
2209
2210
623
    u16 i = 0;
2211
10.2k
    while (numRets--) {
2212
9.60k
        m3type_t type = GetFuncTypeResultType(i_type, i++);
2213
2214
9.60k
_       (Push(o, type, topSlot));
2215
9.60k
_       (MarkSlotsAllocatedByType(o, topSlot, type));
2216
2217
9.60k
        topSlot += c_ioSlotCount;
2218
9.60k
    }
2219
2220
624
    } _catch: return result;
2221
623
}
2222
2223
// A tail call hands the current frame over to the callee instead of stacking a new one.
2224
// The callee's results have to be the enclosing function's results, so both use the same
2225
// return slots and only the arguments move: they're staged above the caller's stack here
2226
// (the argument expressions can still be reading the args/locals they're about to land on)
2227
// and slid down onto the frame base at runtime, by op_ReturnCall[Indirect].
2228
static
2229
M3Result CompileTailCallArgs (IM3Compilation o, u16* o_stackOffset, u16* o_numArgSlots, IM3FuncType i_type, bool i_isIndirect)
2230
239
{
2231
239
_try {
2232
239
    IM3FuncType funcType = o->function->funcType;
2233
2234
239
    u16 numRets = GetFuncTypeNumResults(i_type);
2235
239
    _throwif(m3Err_typeMismatch, numRets != GetFuncTypeNumResults(funcType));
2236
2237
1.60k
    for (u16 i = 0; i < numRets; ++i) {
2238
1.36k
        _throwif(m3Err_typeMismatch, GetFuncTypeResultType(i_type, i) != GetFuncTypeResultType(funcType, i));
2239
1.36k
    }
2240
2241
239
    u16 topSlot = GetMaxUsedSlotPlusOne(o);
2242
2243
239
    topSlot = M3_MAX(1, topSlot);
2244
2245
    // stack frame is 64-bit aligned
2246
239
    AlignSlotToType(&topSlot, c_m3Type_i64);
2247
2248
239
    *o_stackOffset = topSlot;
2249
2250
    // wait to pop this here so that topSlot search is correct
2251
239
    if (i_isIndirect) {
2252
84
_       (Pop(o));
2253
83
    }
2254
2255
238
    u16 numArgs     = GetFuncTypeNumParams(i_type);
2256
238
    u16 numArgSlots = numArgs * c_ioSlotCount;
2257
2258
238
    *o_numArgSlots = numArgSlots;
2259
2260
238
    u16 argTop = topSlot + numArgSlots;
2261
238
    _throwif(m3Err_functionStackOverflow, argTop > d_m3MaxFunctionSlots);
2262
2263
    // the staging area is written by the caller, so op_Entry has to account for it
2264
238
    TouchSlot(o, argTop - 1);
2265
2266
1.03k
    while (numArgs--) {
2267
793
_       (CopyStackTopToSlot(o, argTop -= c_ioSlotCount));
2268
793
_       (Pop(o));
2269
793
    }
2270
2271
239
    } _catch: return result;
2272
238
}
2273
2274
2275
#if d_m3HasTypedRefs
2276
2277
// call_ref $t: the callee is the reference on top of the stack, so the operand
2278
// order is the same as call_indirect's, minus the table.
2279
static
2280
M3Result Compile_CallRef (IM3Compilation o, m3opcode_t i_opcode)
2281
{
2282
_try {
2283
    u32 typeIndex;
2284
_   (ReadLEB_u32(&typeIndex, &o->wasm, o->wasmEnd));
2285
2286
    _throwif("function call type index out of range", typeIndex >= o->module->numFuncTypes);
2287
2288
    IM3FuncType type = o->module->funcTypes[typeIndex];
2289
2290
    if (not IsStackPolymorphic(o)) {
2291
        // whatever shape the reference has, it must be a function of this type
2292
        m3type_t refType = GetStackTopType(o);
2293
        _throwif(m3Err_typeMismatch, not IsSubTypeOf(refType, RefTypeOfFuncType(type, false)));
2294
    }
2295
2296
    if (IsStackTopInRegister(o)) {
2297
_       (PreserveRegisterIfOccupied(o, c_m3Type_funcref));
2298
    }
2299
2300
    u16 functionSlot = GetStackTopSlotNumber(o);
2301
2302
    bool isReturnCall = (i_opcode == c_waOp_returnCallRef);
2303
    bool useTailCall  = isReturnCall and d_m3CanTailCall;
2304
2305
    u16 execTop, numArgSlots = 0;
2306
2307
    if (useTailCall) {
2308
_       (CompileTailCallArgs(o, &execTop, &numArgSlots, type, true));
2309
    } else {
2310
_       (CompileCallArgsAndReturn(o, &execTop, type, true));
2311
    }
2312
2313
#  if d_m3HasExceptionHandling
2314
    if (isReturnCall) {
2315
_       (EmitPopTryFramesForReturnCall(o));
2316
    }
2317
#  endif
2318
2319
_   (EmitOp(o, useTailCall ? op_ReturnCallRef : op_CallRef));
2320
    EmitSlotOffset(o, functionSlot);
2321
    EmitPointer(o, type);
2322
    EmitSlotOffset(o, execTop);
2323
2324
    if (useTailCall) {
2325
        EmitSlotOffset(o, o->function->numRetSlots);
2326
        EmitConstant32(o, numArgSlots);
2327
2328
_       (SetStackPolymorphic(o));
2329
    } else if (isReturnCall) {
2330
_       (Compile_Return(o, i_opcode));
2331
    }
2332
2333
} _catch:
2334
    return result;
2335
}
2336
2337
2338
// ref.as_non_null only sharpens the type; the value is unchanged, so the stack
2339
// entry is re-pushed with the null stripped out of its type.
2340
static
2341
M3Result Compile_Ref_AsNonNull (IM3Compilation o, m3opcode_t i_opcode)
2342
{
2343
    M3Result result = m3Err_none;
2344
2345
    if (not IsStackPolymorphic(o)) {
2346
        m3type_t type = GetStackTopType(o);
2347
        _throwif(m3Err_typeMismatch, not IsRefType(type));
2348
2349
        u16 slot = GetStackTopSlotNumber(o);
2350
2351
_       (EmitOp(o, op_RefAsNonNull));
2352
        EmitSlotOffset(o, slot);
2353
2354
        m3type_t nonNull = IsSpelledRefType(type)
2355
                             ? (m3type_t)(type | d_m3Type_refNonNull)
2356
                             : (m3type_t)(d_m3Type_ref | d_m3Type_refNonNull | d_m3Type_heapAbstract |
2357
                                          ((BaseTypeOf(type) == c_m3Type_externref) ? d_m3Type_refExtern : 0));
2358
2359
_       (Pop(o));
2360
_       (Push(o, nonNull, slot));
2361
    }
2362
2363
    _catch: return result;
2364
}
2365
2366
#endif // d_m3HasTypedRefs
2367
2368
2369
// How wide a table operand is read at comes from the table it names, so a slot
2370
// holding a narrower type would be read past what was allocated for it. The
2371
// validator refuses that already; this keeps a build without one from reaching
2372
// the interpreter with it. c_m3Type_none means the operand is not on the
2373
// dynamic stack to inspect, which is not this check's business.
2374
static
2375
M3Result CheckOperandType (IM3Compilation o, u16 i_depthFromTop, m3type_t i_type)
2376
827
{
2377
827
    m3type_t actual;
2378
2379
827
    if (IsStackPolymorphic(o)) {
2380
680
        return m3Err_none;
2381
680
    }
2382
2383
147
    actual = GetStackTypeFromTop(o, i_depthFromTop);
2384
2385
147
    return (actual == c_m3Type_none or BaseTypeOf(actual) == i_type) ? m3Err_none
2386
147
                                                                     : m3Err_typeMismatch;
2387
827
}
2388
2389
2390
// Swaps the _mem register onto a given memory. Only ever emitted in pairs --
2391
// see op_SetMemory.
2392
static
2393
M3Result EmitSetMemoryPtr (IM3Compilation o, IM3Memory i_memory)
2394
0
{
2395
0
    M3Result result = m3Err_none;
2396
2397
0
_   (EmitOp(o, op_SetMemory));
2398
0
    EmitPointer(o, i_memory);
2399
2400
0
    _catch: return result;
2401
0
}
2402
2403
2404
// ...onto one of the compiling module's own memories, by index. Only reached
2405
// with a non-zero index under multi-memory, but the index has already been
2406
// bounds-checked by ReadMemoryIndex either way.
2407
static inline
2408
M3Result EmitSetMemory (IM3Compilation o, u32 i_memoryIdx)
2409
0
{
2410
0
#if d_m3HasMultiMemory
2411
0
    return EmitSetMemoryPtr(o, o->module->memories[i_memoryIdx]);
2412
#else
2413
    (void)o;
2414
    (void)i_memoryIdx;
2415
    return m3Err_unknownMemory;
2416
#endif
2417
0
}
2418
2419
static
2420
M3Result Compile_Call (IM3Compilation o, m3opcode_t i_opcode)
2421
699
{
2422
699
_try {
2423
699
    u32 functionIndex;
2424
699
_   (ReadLEB_u32(&functionIndex, &o->wasm, o->wasmEnd));
2425
2426
699
    IM3Function function = Module_GetFunction(o->module, functionIndex);
2427
2428
699
    if (function) {                                                     m3log (compile, d_indent " (func= [%d] '%s'; args= %d)",
2429
699
                                                                                get_indention_string (o), functionIndex, m3_GetFunctionName (function), function->funcType->numArgs);
2430
        // an import linked to another module's export runs that module's body
2431
699
        IM3Function target = Function_Implementation(function);
2432
2433
699
        if (target->module) {
2434
699
            bool isReturnCall = (i_opcode == c_waOp_returnCall);
2435
2436
            // The callee's body runs against its own module's memory, so a call
2437
            // that crosses modules is bracketed by a pair of op_SetMemory. A
2438
            // tail call never returns to run the second one, so those compile
2439
            // as a plain call followed by a return instead.
2440
699
            bool crossModule = (target->module != o->module);
2441
699
            bool useTailCall = isReturnCall and d_m3CanTailCall and not crossModule;
2442
2443
699
            u16 slotTop, numArgSlots = 0;
2444
2445
699
            if (useTailCall) {
2446
155
_               (CompileTailCallArgs(o, &slotTop, &numArgSlots, target->funcType, false));
2447
544
            } else {
2448
544
_               (CompileCallArgsAndReturn(o, &slotTop, target->funcType, false));
2449
543
            }
2450
2451
698
            IM3Operation op;
2452
698
            const void*  operand;
2453
2454
698
            if (target->compiled) {
2455
0
                op      = useTailCall ? op_ReturnCall : op_Call;
2456
0
                operand = target->compiled;
2457
698
            } else {
2458
698
                op      = useTailCall ? op_CompileReturnCall : op_Compile;
2459
698
                operand = target;
2460
698
            }
2461
2462
698
#if d_m3HasExceptionHandling
2463
698
            if (isReturnCall) {
2464
155
_               (EmitPopTryFramesForReturnCall(o));
2465
155
            }
2466
698
#endif
2467
2468
698
            if (crossModule) {
2469
0
_               (EmitSetMemoryPtr(o, Module_Memory0(target->module)));
2470
0
            }
2471
2472
698
_           (EmitOp(o, op));
2473
698
            EmitPointer(o, operand);
2474
698
            EmitSlotOffset(o, slotTop);
2475
2476
698
            if (useTailCall) {
2477
155
                EmitSlotOffset(o, o->function->numRetSlots);
2478
155
                EmitConstant32(o, numArgSlots);
2479
2480
155
_               (SetStackPolymorphic(o));
2481
543
            } else {
2482
543
                if (crossModule) {
2483
0
_                   (EmitSetMemoryPtr(o, Module_Memory0(o->module)));
2484
0
                }
2485
2486
543
                if (isReturnCall) {
2487
0
_                   (Compile_Return(o, i_opcode));
2488
0
                }
2489
543
            }
2490
698
        } else {
2491
0
            _throw(ErrorCompile(m3Err_functionImportMissing, o, "'%s.%s'", GetFunctionImportModuleName(function), m3_GetFunctionName(function)));
2492
0
        }
2493
699
    } else {
2494
0
        _throw(ErrorCompile(m3Err_functionLookupFailed, o, "index: %d", functionIndex));
2495
0
    }
2496
2497
699
    } _catch: return result;
2498
699
}
2499
2500
static
2501
M3Result Compile_CallIndirect (IM3Compilation o, m3opcode_t i_opcode)
2502
164
{
2503
164
_try {
2504
164
    u32 typeIndex;
2505
164
_   (ReadLEB_u32(&typeIndex, &o->wasm, o->wasmEnd));
2506
2507
164
    u32 tableIndex;
2508
164
_   (ReadLEB_u32(&tableIndex, &o->wasm, o->wasmEnd));
2509
2510
164
    _throwif("function call type index out of range", typeIndex >= o->module->numFuncTypes);
2511
164
    _throwif("table index out of range", tableIndex >= o->module->numTables);
2512
164
    _throwif(m3Err_typeMismatch, BaseTypeOf(o->module->tables[tableIndex]->type) != c_m3Type_funcref);
2513
2514
164
_   (CheckOperandType(o, 0, Table_AddrType(o->module->tables[tableIndex])));
2515
2516
164
    if (IsStackTopInRegister(o)) {
2517
48
_       (PreserveRegisterIfOccupied(o, c_m3Type_i32));
2518
48
    }
2519
2520
164
    u16 tableIndexSlot = GetStackTopSlotNumber(o);
2521
2522
164
    bool isReturnCall = (i_opcode == c_waOp_returnCallIndirect);
2523
164
    bool useTailCall  = isReturnCall and d_m3CanTailCall;
2524
2525
164
    u16         execTop, numArgSlots = 0;
2526
164
    IM3FuncType type = o->module->funcTypes[typeIndex];
2527
2528
164
    if (useTailCall) {
2529
84
_       (CompileTailCallArgs(o, &execTop, &numArgSlots, type, true));
2530
83
    } else {
2531
80
_       (CompileCallArgsAndReturn(o, &execTop, type, true));
2532
79
    }
2533
2534
162
#if d_m3HasExceptionHandling
2535
162
    if (isReturnCall) {
2536
83
_       (EmitPopTryFramesForReturnCall(o));
2537
83
    }
2538
162
#endif
2539
2540
    // the table comes first: how wide its indexes are is what says how much of
2541
    // the slot below holds one
2542
162
_   (EmitOp(o, useTailCall ? op_ReturnCallIndirect : op_CallIndirect));
2543
162
    EmitPointer(o, o->module->tables[tableIndex]);
2544
162
    EmitSlotOffset(o, tableIndexSlot);
2545
162
    EmitPointer(o, type);
2546
162
    EmitSlotOffset(o, execTop);
2547
2548
162
    if (useTailCall) {
2549
83
        EmitSlotOffset(o, o->function->numRetSlots);
2550
83
        EmitConstant32(o, numArgSlots);
2551
2552
83
_       (SetStackPolymorphic(o));
2553
83
    } else if (isReturnCall) {
2554
0
_       (Compile_Return(o, i_opcode));
2555
0
    }
2556
2557
164
} _catch:
2558
164
    return result;
2559
162
}
2560
2561
// Reads the memory index a memory instruction names, and checks it against the
2562
// module's index space. Without multi-memory the immediate is a reserved byte
2563
// that has to be zero, which is the same check.
2564
static
2565
M3Result ReadMemoryIndex (IM3Compilation o, u32* o_memoryIdx)
2566
520
{
2567
520
    M3Result result;
2568
2569
520
_   (ReadLEB_u32(o_memoryIdx, &o->wasm, o->wasmEnd));
2570
2571
520
    _throwif(m3Err_unknownMemory, *o_memoryIdx >= o->module->numMemories);
2572
2573
520
    _catch: return result;
2574
520
}
2575
2576
static
2577
M3Result Compile_Memory_Size (IM3Compilation o, m3opcode_t i_opcode)
2578
55
{
2579
55
    M3Result result;
2580
2581
55
    u32 memoryIdx;
2582
2583
    // A page count is given in the memory's own address type. Declared up here
2584
    // so the throws below don't jump over its initialization.
2585
55
    m3type_t addrType = c_m3Type_i32;
2586
2587
55
_   (ReadMemoryIndex(o, &memoryIdx));
2588
2589
55
    addrType = Memory_AddrType(o->module->memories[memoryIdx]);
2590
2591
55
_   (PreserveRegisterIfOccupied(o, addrType));
2592
2593
55
    if (memoryIdx) {
2594
0
_       (EmitSetMemory(o, memoryIdx));
2595
0
    }
2596
2597
    // op_MemSize writes the whole register either way; the slot it is pushed
2598
    // to is what decides how much of it the module gets to see
2599
55
_   (EmitOp(o, op_MemSize));
2600
2601
55
    if (memoryIdx) {
2602
0
_       (EmitSetMemory(o, 0));
2603
0
    }
2604
2605
55
_   (PushRegister(o, addrType));
2606
2607
55
    _catch: return result;
2608
54
}
2609
2610
static
2611
M3Result Compile_Memory_Grow (IM3Compilation o, m3opcode_t i_opcode)
2612
207
{
2613
207
    M3Result result;
2614
2615
207
    u32 memoryIdx;
2616
2617
    // see Compile_Memory_Size
2618
207
    m3type_t addrType = c_m3Type_i32;
2619
2620
207
_   (ReadMemoryIndex(o, &memoryIdx));
2621
2622
207
    addrType = Memory_AddrType(o->module->memories[memoryIdx]);
2623
2624
207
_   (CopyStackTopToRegister(o, false));
2625
207
_   (PopType(o, addrType));
2626
2627
206
    if (memoryIdx) {
2628
0
_       (EmitSetMemory(o, memoryIdx));
2629
0
    }
2630
2631
206
#if d_m3HasMemory64
2632
206
_   (EmitOp(o, (addrType == c_m3Type_i64) ? op_MemGrow64 : op_MemGrow));
2633
#else
2634
_   (EmitOp(o, op_MemGrow));
2635
#endif
2636
2637
206
    if (memoryIdx) {
2638
0
_       (EmitSetMemory(o, 0));
2639
0
    }
2640
2641
206
_   (PushRegister(o, addrType));
2642
2643
207
    _catch: return result;
2644
205
}
2645
2646
static
2647
M3Result Compile_Memory_CopyFill (IM3Compilation o, m3opcode_t i_opcode)
2648
191
{
2649
191
    M3Result result = m3Err_none;
2650
2651
    // memory.copy names the destination first and then the source; memory.fill
2652
    // names only the one it writes.
2653
191
    u32  sourceMemoryIdx = 0, targetMemoryIdx = 0;
2654
191
    bool isCopy = (i_opcode == c_waOp_memoryCopy);
2655
2656
    // Each address is typed by the memory it belongs to. The length follows
2657
    // the narrower of the two, so it is 64-bit only when both are - which for
2658
    // memory.fill, naming one memory twice over, means whenever that one is.
2659
    // Declared up here so the throws below don't jump over them.
2660
191
    m3type_t targetType = c_m3Type_i32;
2661
191
    m3type_t sourceType = c_m3Type_i32;
2662
191
    m3type_t lengthType = c_m3Type_i32;
2663
2664
191
    _throwif(m3Err_wasmMalformed, o->module->numMemories == 0);
2665
2666
191
_   (ReadMemoryIndex(o, &targetMemoryIdx));
2667
2668
191
    if (isCopy) {
2669
67
_       (ReadMemoryIndex(o, &sourceMemoryIdx));
2670
67
    }
2671
2672
191
    targetType = Memory_AddrType(o->module->memories[targetMemoryIdx]);
2673
191
    sourceType = isCopy ? Memory_AddrType(o->module->memories[sourceMemoryIdx])
2674
191
                        : targetType;
2675
191
    lengthType = (targetType == c_m3Type_i64 and sourceType == c_m3Type_i64)
2676
191
                   ? c_m3Type_i64
2677
191
                   : c_m3Type_i32;
2678
2679
191
_   (CopyStackTopToRegister(o, false));
2680
2681
191
#if d_m3HasMultiMemory
2682
191
    if (isCopy and sourceMemoryIdx != targetMemoryIdx) {
2683
        // two memories at once: _mem can only name one, so the op takes both
2684
0
_       (EmitOp(o, op_MemCopy_x));
2685
0
        EmitPointer(o, o->module->memories[targetMemoryIdx]);
2686
0
        EmitPointer(o, o->module->memories[sourceMemoryIdx]);
2687
2688
        // ...and, since they need not be addressed alike, how wide to read
2689
        // each of the operands: bit 0 the destination, bit 1 the source
2690
0
        EmitConstant32(o, ((targetType == c_m3Type_i64) ? 0x1u : 0u) |
2691
0
                            ((sourceType == c_m3Type_i64) ? 0x2u : 0u));
2692
0
    } else
2693
191
#endif
2694
191
    {
2695
191
        IM3Operation op = isCopy ? op_MemCopy : op_MemFill;
2696
2697
191
#if d_m3HasMemory64
2698
191
        if (targetType == c_m3Type_i64) {
2699
60
            op = isCopy ? op_MemCopy64 : op_MemFill64;
2700
60
        }
2701
191
#endif
2702
2703
191
        if (targetMemoryIdx) {
2704
0
_           (EmitSetMemory(o, targetMemoryIdx));
2705
0
        }
2706
2707
191
_       (EmitOp(o, op));
2708
191
    }
2709
2710
    // the length is in the register; the other two are named by slot, at
2711
    // whatever width their stack entries were pushed with
2712
191
_   (PopType(o, lengthType));
2713
191
_   (EmitSlotNumOfStackTopAndPop(o));      // the source, or the byte to fill with
2714
191
_   (EmitSlotNumOfStackTopAndPop(o));      // the destination
2715
2716
191
    if (targetMemoryIdx and not (isCopy and sourceMemoryIdx != targetMemoryIdx)) {
2717
0
_       (EmitSetMemory(o, 0));
2718
0
    }
2719
2720
191
    _catch: return result;
2721
191
}
2722
2723
2724
// memory.init and data.drop address a data segment by index. Both are only valid
2725
// when a data count section declared the segments up front.
2726
static
2727
M3Result ReadDataSegment (IM3Compilation o, M3DataSegment** o_segment)
2728
0
{
2729
0
    M3Result result = m3Err_none;
2730
2731
0
    u32 index;
2732
0
_   (ReadLEB_u32(&index, &o->wasm, o->wasmEnd));
2733
2734
0
    _throwif("data count section required", not o->module->hasDataCount);
2735
0
    _throwif(m3Err_wasmMalformed, index >= o->module->numDataSegments);
2736
2737
0
    *o_segment = &o->module->dataSegments[index];
2738
2739
0
    _catch: return result;
2740
0
}
2741
2742
2743
M3Result Compile_Memory_Init (IM3Compilation o, m3opcode_t i_opcode)
2744
0
{
2745
0
    M3Result result = m3Err_none;
2746
2747
0
    M3DataSegment* segment = NULL;
2748
0
    u32            memoryIdx;
2749
2750
0
    _throwif(m3Err_wasmMalformed, o->module->numMemories == 0);
2751
2752
0
_   (ReadDataSegment(o, &segment));
2753
0
_   (ReadMemoryIndex(o, &memoryIdx));
2754
2755
0
_   (CopyStackTopToRegister(o, false));
2756
2757
0
    if (memoryIdx) {
2758
0
_       (EmitSetMemory(o, memoryIdx));
2759
0
    }
2760
2761
0
#if d_m3HasMemory64
2762
    // only the destination follows the memory's address type: a data segment
2763
    // is indexed as an i32 however the memory it is copied into is addressed
2764
0
_   (EmitOp(o, o->module->memories[memoryIdx]->isMemory64 ? op_MemInit64 : op_MemInit));
2765
#else
2766
_   (EmitOp(o, op_MemInit));
2767
#endif
2768
0
    EmitPointer(o, segment);
2769
0
_   (PopType(o, c_m3Type_i32));
2770
0
_   (EmitSlotNumOfStackTopAndPop(o));
2771
0
_   (EmitSlotNumOfStackTopAndPop(o));
2772
2773
0
    if (memoryIdx) {
2774
0
_       (EmitSetMemory(o, 0));
2775
0
    }
2776
2777
0
    _catch: return result;
2778
0
}
2779
2780
2781
#if d_m3HasRefTypes
2782
2783
static
2784
M3Result Compile_Select (IM3Compilation o, m3opcode_t i_opcode);
2785
2786
// select with an explicit result type vector. The types only matter to the
2787
// validator; the operands are laid out exactly as for the untyped select.
2788
M3Result Compile_Select_Typed (IM3Compilation o, m3opcode_t i_opcode)
2789
0
{
2790
0
    M3Result result = m3Err_none;
2791
2792
0
    u32 numTypes;
2793
0
_   (ReadLEB_u32(&numTypes, &o->wasm, o->wasmEnd));
2794
0
    _throwif(m3Err_wasmMalformed, numTypes != 1);
2795
2796
0
    i8 waType;
2797
0
    u8 type;
2798
0
_   (ReadLEB_i7(&waType, &o->wasm, o->wasmEnd));
2799
0
_   (NormalizeType(&type, waType));
2800
2801
0
_   (Compile_Select(o, i_opcode));
2802
2803
0
    _catch: return result;
2804
0
}
2805
2806
2807
// A reference is one pointer-sized word and null is 0, so null-testing it is
2808
// just an integer eqz of the matching width.
2809
#  if M3_SIZEOF_PTR == 8
2810
5
#    define d_m3RefIsNull_r   op_i64_EqualToZero_r
2811
12
#    define d_m3RefIsNull_s   op_i64_EqualToZero_s
2812
#  else
2813
#    define d_m3RefIsNull_r   op_i32_EqualToZero_r
2814
#    define d_m3RefIsNull_s   op_i32_EqualToZero_s
2815
#  endif
2816
2817
static
2818
M3Result ReadRefType (IM3Compilation o, m3type_t* o_type)
2819
4
{
2820
4
    M3Result result = m3Err_none;
2821
2822
#  if d_m3HasTypedRefs
2823
    // ref.null names a heap type: func, extern, or a function type index
2824
    m3type_t heapBits;
2825
_   (ParseHeapType(o->module, &heapBits, &o->wasm, o->wasmEnd));
2826
2827
    *o_type = d_m3Type_ref | heapBits;
2828
#  else
2829
4
    i8 waType;
2830
4
    u8 plainType;
2831
4
_   (ReadLEB_i7(&waType, &o->wasm, o->wasmEnd));
2832
4
_   (NormalizeType(&plainType, waType));
2833
3
    *o_type = plainType;
2834
3
    _throwif(m3Err_wasmMalformed, not IsRefType(*o_type));
2835
3
#  endif
2836
2837
4
    _catch: return result;
2838
3
}
2839
2840
2841
M3Result Compile_Ref_Null (IM3Compilation o, m3opcode_t i_opcode)
2842
4
{
2843
4
    M3Result result = m3Err_none;
2844
2845
4
    m3type_t type;
2846
4
_   (ReadRefType(o, &type));
2847
3
_   (PushConst(o, 0, type));
2848
2849
4
    _catch: return result;
2850
3
}
2851
2852
2853
M3Result Compile_Ref_IsNull (IM3Compilation o, m3opcode_t i_opcode)
2854
17
{
2855
17
    M3Result result = m3Err_none;
2856
2857
17
    IM3Operation op;
2858
2859
17
    if (not IsStackPolymorphic(o)) {
2860
5
        _throwif(m3Err_typeMismatch, not IsRefType(GetStackTopType(o)));
2861
5
    }
2862
2863
17
    if (IsStackTopInRegister(o)) {
2864
5
        op = d_m3RefIsNull_r;
2865
12
    } else {
2866
12
_       (PreserveRegisterIfOccupied(o, c_m3Type_i32));
2867
12
        op = d_m3RefIsNull_s;
2868
12
    }
2869
2870
17
_   (EmitOp(o, op));
2871
17
_   (EmitSlotNumOfStackTopAndPop(o));
2872
17
_   (PushRegister(o, c_m3Type_i32));
2873
2874
17
    _catch: return result;
2875
17
}
2876
2877
2878
M3Result Compile_Ref_Func (IM3Compilation o, m3opcode_t i_opcode)
2879
102
{
2880
102
    M3Result result = m3Err_none;
2881
2882
102
    u32 funcIndex;
2883
2884
    // declared before the throws below, which jump past this point to _catch
2885
102
    m3type_t    refType   = c_m3Type_funcref;
2886
102
    IM3Function reference = NULL;
2887
2888
102
_   (ReadLEB_u32(&funcIndex, &o->wasm, o->wasmEnd));
2889
102
    _throwif("function index out of range", funcIndex >= o->module->numFunctions);
2890
2891
    // Inside a constant expression ref.func is itself a declaration; inside a
2892
    // function body the function must already have been declared elsewhere.
2893
102
    if (o->function) {
2894
66
        _throwif("undeclared function reference", not Module_IsFunctionDeclared(o->module, funcIndex));
2895
66
    } else {
2896
36
_       (Module_DeclareFunction(o->module, funcIndex));
2897
36
    }
2898
2899
#  if d_m3HasTypedRefs
2900
    refType = RefTypeOfFuncType(o->module->functions[funcIndex].funcType, true);
2901
#  endif
2902
2903
    // A reference denotes the function that actually runs, so a ref.func naming
2904
    // an import linked to another module is the same reference that module's
2905
    // own ref.func would produce - and carries the defining module with it.
2906
102
    reference = Function_Implementation(&o->module->functions[funcIndex]);
2907
2908
102
_   (PushConst(o, (u64)(uintptr_t)reference, refType));
2909
2910
102
    _catch: return result;
2911
102
}
2912
2913
2914
// table.get/set/size/grow/fill all name a table by index; the table struct goes
2915
// into the codestream so the operation doesn't have to walk the module.
2916
static
2917
M3Result ReadTable (IM3Compilation o, M3Table** o_table)
2918
537
{
2919
537
    M3Result result = m3Err_none;
2920
2921
537
    u32 index;
2922
537
_   (ReadLEB_u32(&index, &o->wasm, o->wasmEnd));
2923
537
    _throwif("table index out of range", index >= o->module->numTables);
2924
2925
537
    *o_table = o->module->tables[index];
2926
2927
537
    _catch: return result;
2928
537
}
2929
2930
2931
// The table operations take every operand from a slot, so spill the integer
2932
// register first: after this each EmitSlotNumOfStackTopAndPop () emits a real
2933
// slot offset, and the register is free for the result. Operands are emitted
2934
// from the top of the stack downwards, which is the order the ops read them.
2935
static
2936
M3Result Compile_Table_Op (IM3Compilation o, IM3Operation i_op, M3Table* i_table, u32 i_numOperands, m3type_t i_retType)
2937
303
{
2938
303
    M3Result result = m3Err_none;
2939
2940
303
_   (PreserveRegisterIfOccupied(o, c_m3Type_i64));
2941
2942
303
_   (EmitOp(o, i_op));
2943
303
    EmitPointer(o, i_table);
2944
2945
792
    for (u32 i = 0; i < i_numOperands; ++i) {
2946
490
_       (EmitSlotNumOfStackTopAndPop(o));
2947
489
    }
2948
2949
302
    if (i_retType != c_m3Type_none) {
2950
173
_       (PushRegister(o, i_retType));
2951
172
    }
2952
2953
303
    _catch: return result;
2954
302
}
2955
2956
2957
M3Result Compile_Table_GetSet (IM3Compilation o, m3opcode_t i_opcode)
2958
163
{
2959
163
    M3Result result = m3Err_none;
2960
2961
163
    M3Table* table;
2962
163
_   (ReadTable(o, &table));
2963
2964
163
    if (i_opcode == c_waOp_tableGet) {
2965
84
_       (CheckOperandType(o, 0, Table_AddrType(table)));
2966
84
_       (Compile_Table_Op(o, op_TableGet, table, 1, table->type));
2967
83
    } else {
2968
79
_       (CheckOperandType(o, 1, Table_AddrType(table)));
2969
79
_       (Compile_Table_Op(o, op_TableSet, table, 2, c_m3Type_none));
2970
79
    }
2971
2972
163
    _catch: return result;
2973
163
}
2974
2975
2976
M3Result Compile_Table_Init (IM3Compilation o, m3opcode_t i_opcode)
2977
0
{
2978
0
    M3Result result = m3Err_none;
2979
2980
0
    u32      elemIndex;
2981
0
    M3Table* table;
2982
2983
0
_   (ReadLEB_u32(&elemIndex, &o->wasm, o->wasmEnd));
2984
0
    _throwif("element segment index out of range", elemIndex >= o->module->numElementSegments);
2985
0
_   (ReadTable(o, &table));
2986
2987
0
_   (CheckOperandType(o, 2, Table_AddrType(table)));
2988
2989
0
_   (PreserveRegisterIfOccupied(o, c_m3Type_i64));
2990
0
_   (EmitOp(o, op_TableInit));
2991
0
    EmitPointer(o, table);
2992
0
    EmitPointer(o, &o->module->elementSegments[elemIndex]);
2993
2994
0
    for (u32 i = 0; i < 3; ++i) {
2995
0
_       (EmitSlotNumOfStackTopAndPop(o));
2996
0
    }
2997
2998
0
    _catch: return result;
2999
0
}
3000
3001
3002
M3Result Compile_Elem_Drop (IM3Compilation o, m3opcode_t i_opcode)
3003
0
{
3004
0
    M3Result result = m3Err_none;
3005
3006
0
    u32 elemIndex;
3007
0
_   (ReadLEB_u32(&elemIndex, &o->wasm, o->wasmEnd));
3008
0
    _throwif("element segment index out of range", elemIndex >= o->module->numElementSegments);
3009
3010
0
_   (EmitOp(o, op_ElemDrop));
3011
0
    EmitPointer(o, &o->module->elementSegments[elemIndex]);
3012
3013
0
    _catch: return result;
3014
0
}
3015
3016
3017
M3Result Compile_Table_Copy (IM3Compilation o, m3opcode_t i_opcode)
3018
117
{
3019
117
    M3Result result = m3Err_none;
3020
3021
117
    M3Table* dst;
3022
117
    M3Table* src;
3023
3024
117
_   (ReadTable(o, &dst));
3025
117
_   (ReadTable(o, &src));
3026
117
    _throwif(m3Err_typeMismatch, not IsSubTypeOf(src->type, dst->type));
3027
3028
117
_   (CheckOperandType(o, 2, Table_AddrType(dst)));
3029
117
_   (CheckOperandType(o, 1, Table_AddrType(src)));
3030
117
_   (CheckOperandType(o, 0, (dst->isTable64 and src->isTable64) ? c_m3Type_i64 : c_m3Type_i32));
3031
3032
117
_   (PreserveRegisterIfOccupied(o, c_m3Type_i64));
3033
117
_   (EmitOp(o, op_TableCopy));
3034
117
    EmitPointer(o, dst);
3035
117
    EmitPointer(o, src);
3036
3037
468
    for (u32 i = 0; i < 3; ++i) {
3038
351
_       (EmitSlotNumOfStackTopAndPop(o));
3039
351
    }
3040
3041
117
    _catch: return result;
3042
117
}
3043
3044
3045
M3Result Compile_Table_Size (IM3Compilation o, m3opcode_t i_opcode)
3046
41
{
3047
41
    M3Result result = m3Err_none;
3048
3049
41
    M3Table* table;
3050
41
_   (ReadTable(o, &table));
3051
3052
    // a table size is given in the table's own index type
3053
41
_   (Compile_Table_Op(o, op_TableSize, table, 0, Table_AddrType(table)));
3054
3055
41
    _catch: return result;
3056
40
}
3057
3058
3059
M3Result Compile_Table_GrowFill (IM3Compilation o, m3opcode_t i_opcode)
3060
99
{
3061
99
    M3Result result = m3Err_none;
3062
3063
99
    M3Table* table;
3064
99
_   (ReadTable(o, &table));
3065
3066
99
    if (i_opcode == c_waOp_tableGrow) {
3067
49
_       (CheckOperandType(o, 0, Table_AddrType(table)));
3068
49
_       (Compile_Table_Op(o, op_TableGrow, table, 2, Table_AddrType(table)));
3069
50
    } else {
3070
50
_       (CheckOperandType(o, 0, Table_AddrType(table)));
3071
50
_       (CheckOperandType(o, 2, Table_AddrType(table)));
3072
50
_       (Compile_Table_Op(o, op_TableFill, table, 3, c_m3Type_none));
3073
50
    }
3074
3075
99
    _catch: return result;
3076
99
}
3077
3078
#endif // d_m3HasRefTypes
3079
3080
3081
M3Result Compile_Data_Drop (IM3Compilation o, m3opcode_t i_opcode)
3082
0
{
3083
0
    M3Result result = m3Err_none;
3084
3085
0
    M3DataSegment* segment = NULL;
3086
0
_   (ReadDataSegment(o, &segment));
3087
3088
0
_   (EmitOp(o, op_DataDrop));
3089
0
    EmitPointer(o, segment);
3090
3091
0
    _catch: return result;
3092
0
}
3093
3094
3095
static
3096
M3Result ReadBlockType (IM3Compilation o, IM3FuncType* o_blockType)
3097
716
{
3098
716
    M3Result result = m3Err_none;
3099
3100
#if d_m3HasTypedRefs
3101
    // a spelled-out reference type is two bytes, so it cannot be told apart
3102
    // from a type index by the s33 that block types otherwise use
3103
    if (o->wasm < o->wasmEnd and (*o->wasm == d_waEncode_ref or *o->wasm == d_waEncode_refNull)) {
3104
        m3type_t refType;
3105
_       (ParseValueType(o->module, &refType, &o->wasm, o->wasmEnd));
3106
        *o_blockType = o->module->environment->retFuncTypes[BaseTypeOf(refType)];
3107
        return result;
3108
    }
3109
#endif
3110
3111
716
    i64 type;
3112
716
_   (ReadLebSigned(&type, 33, &o->wasm, o->wasmEnd));
3113
3114
716
    if (type < 0) {
3115
61
        u8 valueType;
3116
61
_       (NormalizeType(&valueType, (i8)type));                                 m3log (compile, d_indent " (type: %s)", get_indention_string (o), c_waTypes [valueType]);
3117
61
        *o_blockType = o->module->environment->retFuncTypes[valueType];
3118
655
    } else {
3119
655
        _throwif("func type out of bounds", type >= o->module->numFuncTypes);
3120
655
        *o_blockType = o->module->funcTypes[type];                         m3log (compile, d_indent " (type: %s)", get_indention_string (o), SPrintFuncTypeSignature (*o_blockType));
3121
655
    }
3122
716
    _catch: return result;
3123
716
}
3124
3125
static
3126
M3Result PreserveArgsAndLocals (IM3Compilation o)
3127
717
{
3128
717
    M3Result result = m3Err_none;
3129
3130
717
    if (o->stackIndex > o->stackFirstDynamicIndex) {
3131
541
        u32 numArgsAndLocals = GetFunctionNumArgsAndLocals(o->function);
3132
3133
897k
        for (u32 i = 0; i < numArgsAndLocals; ++i) {
3134
897k
            u16 slot = GetSlotForStackIndex(o, i);
3135
3136
897k
            u16 preservedSlotNumber;
3137
897k
_           (FindReferencedLocalWithinCurrentBlock(o, &preservedSlotNumber, slot));
3138
3139
897k
            if (preservedSlotNumber != slot) {
3140
26
                m3type_t     type = GetStackTypeFromBottom(o, i);                 d_m3Assert (type != c_m3Type_none)
3141
26
                IM3Operation op   = Is64BitType(type) ? op_CopySlot_64 : op_CopySlot_32;
3142
3143
26
                EmitOp(o, op);
3144
26
                EmitSlotOffset(o, preservedSlotNumber);
3145
26
                EmitSlotOffset(o, slot);
3146
26
            }
3147
897k
        }
3148
541
    }
3149
3150
717
    _catch:
3151
717
    return result;
3152
717
}
3153
3154
static
3155
M3Result Compile_LoopOrBlock (IM3Compilation o, m3opcode_t i_opcode)
3156
534
{
3157
534
    M3Result result;
3158
3159
534
_   (PreserveRegisters(o));
3160
534
_   (PreserveArgsAndLocals(o));
3161
3162
534
    IM3FuncType blockType;
3163
534
_   (ReadBlockType(o, &blockType));
3164
3165
534
    if (i_opcode == c_waOp_loop) {
3166
241
        u16 numParams = GetFuncTypeNumParams(blockType);
3167
241
        if (numParams) {
3168
            // instantiate constants
3169
104
            u16 numValues = GetNumBlockValuesOnStack(o);
3170
3171
104
            if (numValues >= numParams) {
3172
46
                u16 stackTop = GetStackTopIndex(o) + 1;
3173
3174
721
                for (u16 i = stackTop - numParams; i < stackTop; ++i) {
3175
675
                    u16      slot = GetSlotForStackIndex(o, i);
3176
675
                    m3type_t type = GetStackTypeFromBottom(o, i);
3177
3178
675
                    if (IsConstantSlot(o, slot)) {
3179
284
                        u16 newSlot = c_slotUnused;
3180
284
_                       (AllocateSlots(o, &newSlot, type));
3181
284
_                       (CopyStackIndexToSlot(o, newSlot, i));
3182
284
                        o->wasmStack[i] = newSlot;
3183
284
                    }
3184
675
                }
3185
46
            }
3186
104
        }
3187
3188
241
_       (EmitOp(o, op_Loop));
3189
293
    } else {
3190
293
    }
3191
3192
534
_   (CompileBlock(o, blockType, i_opcode));
3193
3194
534
    _catch: return result;
3195
353
}
3196
3197
#if d_m3HasExceptionHandling
3198
3199
// Emits one out-of-line stub per catch clause, and writes each stub's address
3200
// into the slot op_TryTable reserved for it.
3201
//
3202
// Called from CompileBlock with the try block's scope already on the stack, so
3203
// the clause labels - which count from outside the try - are one deeper here.
3204
// The payload values are pushed on top of whatever the block entry left there:
3205
// only their position relative to the stack top matters, since
3206
// ResolveBlockResults copies the topmost values into the label's landing pads.
3207
// Popping them again afterwards puts the compiler's stack back where the body
3208
// expects to find it.
3209
static
3210
M3Result EmitCatchStubs (IM3Compilation o)
3211
21
{
3212
21
    IM3CodePage displacedPage = NULL;
3213
21
_try {
3214
21
    M3CatchClause* clauses    = o->tryClauses;
3215
21
    u32            numClauses = o->numTryClauses;
3216
3217
    // the body must not see these: a nested try_table sets up its own
3218
21
    o->tryClauses    = NULL;
3219
21
    o->numTryClauses = 0;
3220
3221
39
    for (u32 i = 0; i < numClauses; ++i) {
3222
18
        M3CatchClause* clause = &clauses[i];
3223
3224
18
        IM3CompilationScope scope;
3225
18
_       (GetBlockScope(o, &scope, clause->labelDepth + 1));
3226
3227
18
        IM3FuncType payload = clause->tag ? clause->tag->type : NULL;
3228
18
        u16         numArgs = GetFuncTypeNumParams(payload);
3229
3230
18
        IM3CodePage stubPage;
3231
18
_       (AcquireCompilationCodePage(o, &stubPage));
3232
3233
18
        pc_t startPC  = GetPagePC(stubPage);
3234
18
        displacedPage = o->page;
3235
18
        o->page       = stubPage;
3236
3237
18
        u16 firstIndex = o->stackIndex;
3238
3239
18
        for (u16 a = 0; a < numArgs; ++a) {
3240
0
_           (PushAllocatedSlot(o, GetFuncTypeParamType(payload, a)));
3241
0
        }
3242
3243
18
        if (clause->hasRef) {
3244
8
_           (PushAllocatedSlot(o, c_m3Type_exnref));
3245
8
        }
3246
3247
        // emitted even with nothing to move: this is also where an exception
3248
        // no clause will reify gets released
3249
18
_       (EnsureCodePageNumLines(o, 2 * numArgs + 3 + d_m3CodePageFreeLinesThreshold));
3250
3251
18
_       (EmitOp(o, op_CatchPayload));
3252
18
        EmitConstant32(o, numArgs);
3253
3254
18
        for (u16 a = 0; a < numArgs; ++a) {
3255
0
            u16 index = firstIndex + a;
3256
0
            EmitSlotOffset(o, GetSlotForStackIndex(o, index));
3257
0
            EmitConstant32(o, Is64BitType(GetStackTypeFromBottom(o, index)));
3258
0
        }
3259
3260
18
        EmitSlotOffset(o, clause->hasRef ? (i32)GetSlotForStackIndex(o, firstIndex + numArgs) : -1);
3261
3262
18
_       (EmitPopTryFrames(o, NumTryFramesToPop(o->block.outer, scope)));
3263
3264
18
        if (scope->opcode == c_waOp_loop) {
3265
0
_           (ResolveBlockResults(o, scope, /* isBranch: */ true));
3266
3267
0
_           (EmitOp(o, op_ContinueLoop));
3268
0
            EmitPointer(o, scope->pc);
3269
18
        } else if (scope->depth == 0) {
3270
6
_           (ReturnValues(o, scope, /* isBranch: */ true));
3271
6
_           (EmitOp(o, op_Return));
3272
12
        } else {
3273
12
_           (ResolveBlockResults(o, scope, /* isBranch: */ true));
3274
12
_           (EmitPatchingBranch(o, scope));
3275
12
        }
3276
3277
18
        ReleaseCompilationCodePage(o);
3278
18
        o->page       = displacedPage;
3279
18
        displacedPage = NULL;
3280
3281
18
        *(pc_t*)clause->stubSlot = startPC;
3282
3283
26
        while (o->stackIndex > firstIndex) {
3284
8
_           (Pop(o));
3285
8
        }
3286
18
    }
3287
3288
21
} _catch:
3289
3290
    // thrown with a stub page installed: release it and put back the one it
3291
    // displaced, which the caller's catch then releases
3292
21
    if (displacedPage) {
3293
0
        ReleaseCompilationCodePage(o);
3294
0
        o->page = displacedPage;
3295
0
    }
3296
3297
21
    return result;
3298
21
}
3299
3300
3301
static
3302
M3Result Compile_TryTable (IM3Compilation o, m3opcode_t i_opcode)
3303
22
{
3304
22
    M3CatchClause* clauses = NULL;
3305
22
_try {
3306
    // a catch entry has to be able to assume the operand stack is entirely in
3307
    // slots: an unwind leaves nothing in the registers
3308
22
_   (PreserveRegisters(o));
3309
22
_   (PreserveArgsAndLocals(o));
3310
3311
21
    IM3FuncType blockType;
3312
21
_   (ReadBlockType(o, &blockType));
3313
3314
21
    u32 numClauses;
3315
21
_   (ReadLEB_u32(&numClauses, &o->wasm, o->wasmEnd));
3316
3317
21
    _throwif("too many catch clauses", numClauses > d_m3MaxSaneTagsCount);
3318
3319
21
    if (numClauses) {
3320
13
        clauses = m3_AllocArray(M3CatchClause, numClauses);
3321
13
        _throwifnull(clauses);
3322
13
    }
3323
3324
39
    for (u32 i = 0; i < numClauses; ++i) {
3325
18
        u8 kind;
3326
18
_       (Read_u8(&kind, &o->wasm, o->wasmEnd));
3327
18
        _throwif(m3Err_wasmMalformed, kind > 0x03);
3328
3329
18
        clauses[i].tag    = NULL;
3330
18
        clauses[i].hasRef = (kind == 0x01 or kind == 0x03);
3331
3332
18
        if (kind == 0x00 or kind == 0x01) {
3333
10
            u32 tagIndex;
3334
10
_           (ReadLEB_u32(&tagIndex, &o->wasm, o->wasmEnd));
3335
10
            _throwif(m3Err_unknownTag, tagIndex >= o->module->numTags);
3336
3337
10
            clauses[i].tag = &o->module->tags[tagIndex];
3338
10
        }
3339
3340
18
_       (ReadLEB_u32(&clauses[i].labelDepth, &o->wasm, o->wasmEnd));
3341
18
    }
3342
3343
    // op_TryTable, the clause count, and two words per clause, all of which have
3344
    // to land on one page: the op reads the table by offset from its own pc
3345
21
_   (EnsureCodePageNumLines(o, 2 * numClauses + 2 + d_m3CodePageFreeLinesThreshold));
3346
3347
21
_   (EmitOp(o, op_TryTable));
3348
21
    EmitConstant32(o, numClauses);
3349
3350
39
    for (u32 i = 0; i < numClauses; ++i) {
3351
18
        EmitPointer(o, clauses[i].tag);
3352
18
        clauses[i].stubSlot = ReservePointer(o);
3353
18
    }
3354
3355
21
    o->tryClauses    = clauses;
3356
21
    o->numTryClauses = numClauses;
3357
3358
21
_   (CompileBlock(o, blockType, c_waOp_tryTable));
3359
3360
22
} _catch:
3361
3362
22
    o->tryClauses    = NULL;
3363
22
    o->numTryClauses = 0;
3364
3365
22
    m3_Free(clauses);
3366
3367
22
    return result;
3368
15
}
3369
3370
3371
static
3372
M3Result Compile_Throw (IM3Compilation o, m3opcode_t i_opcode)
3373
62
{
3374
62
_try {
3375
62
    u32 tagIndex;
3376
62
_   (ReadLEB_u32(&tagIndex, &o->wasm, o->wasmEnd));
3377
62
    _throwif(m3Err_unknownTag, tagIndex >= o->module->numTags);
3378
3379
62
    IM3Tag tag = &o->module->tags[tagIndex];
3380
3381
62
    u16 numArgs = GetFuncTypeNumParams(tag->type);
3382
3383
    // the payload is read out of slots, so nothing may still be in a register
3384
62
_   (PreserveRegisters(o));
3385
3386
    // check the payload before emitting anything, so a mistyped throw does not
3387
    // leave half an operation behind
3388
62
    bool isPolymorphic = IsStackPolymorphic(o);
3389
3390
62
    if (not isPolymorphic) {
3391
8
        _throwif(m3Err_typeCountMismatch, GetNumBlockValuesOnStack(o) < numArgs);
3392
3393
8
        for (u16 i = 0; i < numArgs; ++i) {
3394
0
            u16 index = (u16)(o->stackIndex - numArgs + i);
3395
3396
0
            _throwif(m3Err_typeMismatch, not IsSubTypeOf(GetStackTypeFromBottom(o, index),
3397
0
                                                         GetFuncTypeParamType(tag->type, i)));
3398
0
        }
3399
8
    }
3400
3401
62
_   (EnsureCodePageNumLines(o, 2 * numArgs + 3 + d_m3CodePageFreeLinesThreshold));
3402
3403
62
_   (EmitOp(o, op_Throw));
3404
62
    EmitPointer(o, tag);
3405
62
    EmitConstant32(o, numArgs);
3406
3407
    // the payload is named in the order the tag declares it, so the deepest of
3408
    // the arguments on the stack comes first. In unreachable code there is
3409
    // nothing on the stack to name and nothing will run this, so slot 0 stands
3410
    // in for an operand that isn't there.
3411
62
    for (u16 i = 0; i < numArgs; ++i) {
3412
0
        m3type_t type = GetFuncTypeParamType(tag->type, i);
3413
3414
0
        EmitSlotOffset(o, isPolymorphic ? 0 : GetSlotForStackIndex(o, (u16)(o->stackIndex - numArgs + i)));
3415
0
        EmitConstant32(o, Is64BitType(type));
3416
0
    }
3417
3418
62
    if (not isPolymorphic) {
3419
8
        for (u16 i = 0; i < numArgs; ++i) {
3420
0
_           (Pop(o));
3421
0
        }
3422
8
    }
3423
3424
62
_   (SetStackPolymorphic(o));
3425
3426
62
} _catch: return result;
3427
62
}
3428
3429
3430
static
3431
M3Result Compile_ThrowRef (IM3Compilation o, m3opcode_t i_opcode)
3432
51
{
3433
51
_try {
3434
51
    if (not IsStackPolymorphic(o)) {
3435
1
_       (PreserveRegisterIfOccupied(o, c_m3Type_i64));
3436
3437
1
        _throwif(m3Err_typeMismatch, BaseTypeOf(GetStackTopType(o)) != c_m3Type_exnref);
3438
3439
1
_       (EmitOp(o, op_ThrowRef));
3440
1
        EmitSlotOffset(o, GetStackTopSlotNumber(o));
3441
3442
1
_       (Pop(o));
3443
1
    }
3444
3445
51
_   (SetStackPolymorphic(o));
3446
3447
51
} _catch: return result;
3448
51
}
3449
3450
#endif // d_m3HasExceptionHandling
3451
3452
3453
static
3454
M3Result CompileElseBlock (IM3Compilation o, pc_t* o_startPC, IM3FuncType i_blockType)
3455
80
{
3456
80
    IM3CodePage savedPage = o->page;
3457
80
_try {
3458
3459
80
    IM3CodePage elsePage;
3460
80
_   (AcquireCompilationCodePage(o, &elsePage));
3461
3462
80
    *o_startPC = GetPagePC(elsePage);
3463
3464
80
    o->page = elsePage;
3465
3466
80
_   (CompileBlock(o, i_blockType, c_waOp_else));
3467
3468
60
_   (EmitOp(o, op_Branch));
3469
60
    EmitPointer(o, GetPagePC(savedPage));
3470
80
} _catch:
3471
3472
80
    if (o->page != savedPage) {
3473
80
        ReleaseCompilationCodePage(o);
3474
80
    }
3475
80
    o->page = savedPage;
3476
80
    return result;
3477
60
}
3478
3479
static
3480
M3Result Compile_If (IM3Compilation o, m3opcode_t i_opcode)
3481
161
{
3482
    /*      [   op_If   ]
3483
            [ <else-pc> ]   ---->   [ ..else..  ]
3484
            [  ..if..   ]           [ ..block.. ]
3485
            [ ..block.. ]           [ op_Branch ]
3486
            [    end    ]  <-----   [  <end-pc> ]       */
3487
3488
161
_try {
3489
3490
    // Spec: if condition must be i32
3491
161
    if (not IsStackPolymorphic(o)) {
3492
42
        m3type_t condType = GetStackTopType(o);
3493
42
        _throwif(m3Err_typeMismatch, condType != c_m3Type_none and condType != c_m3Type_i32);
3494
42
    }
3495
3496
161
_   (PreserveNonTopRegisters(o));
3497
161
_   (PreserveArgsAndLocals(o));
3498
3499
161
#if d_m3FuseBranch
3500
161
    IM3Operation fuseOp = GetBranchFusionOp(o, true);
3501
3502
161
    if (fuseOp) {
3503
        // absorb the if into the compare that produced the condition
3504
2
        *(IM3Operation*)o->foldPatchPC = fuseOp;
3505
2
        InvalidateFold(o);
3506
3507
2
_       (Pop(o));                          // the condition is consumed inside the fused op
3508
2
    } else
3509
159
#endif
3510
159
    {
3511
159
        IM3Operation op = IsStackTopInRegister(o) ? op_If_r : op_If_s;
3512
3513
159
_       (EmitOp(o, op));
3514
159
_       (EmitSlotNumOfStackTopAndPop(o));
3515
159
    }
3516
3517
161
    pc_t* pc = (pc_t*)ReservePointer(o);
3518
3519
161
    IM3FuncType blockType;
3520
161
_   (ReadBlockType(o, &blockType));     //  dump_type_stack (o);
3521
3522
161
    u16 stackIndex = o->stackIndex;
3523
3524
161
_   (CompileBlock(o, blockType, i_opcode));
3525
3526
109
    if (o->previousOpcode == c_waOp_else) {
3527
14
        o->stackIndex = stackIndex;
3528
14
_       (CompileElseBlock(o, pc, blockType));
3529
95
    } else {
3530
        // if block produces values and there isn't a defined else
3531
        // case, then we need to make one up so that the pass-through
3532
        // results end up in the right place
3533
95
        if (GetFuncTypeNumResults(blockType)) {
3534
            // rewind to the if's end to create a fake else block
3535
66
            o->wasm--;
3536
66
            o->stackIndex = stackIndex;     // dump_type_stack (o);
3537
3538
66
_           (CompileElseBlock(o, pc, blockType));
3539
46
        } else {
3540
29
            *pc = GetPC(o);
3541
29
        }
3542
95
    }
3543
3544
161
    } _catch: return result;
3545
109
}
3546
3547
static
3548
M3Result Compile_Select (IM3Compilation o, m3opcode_t i_opcode)
3549
556
{
3550
556
    M3Result result = m3Err_none;
3551
3552
556
    u16 slots[3] = { c_slotUnused, c_slotUnused, c_slotUnused };
3553
3554
556
    IM3Operation op = NULL;
3555
3556
556
    m3type_t type = GetStackTypeFromTop(o, 1); // get type of selection
3557
3558
556
    if (not IsStackPolymorphic(o)) {
3559
        // Spec: the condition operand (top) must be i32
3560
43
        m3type_t condType = GetStackTypeFromTop(o, 0);
3561
43
        _throwif(m3Err_typeMismatch, condType != c_m3Type_none and condType != c_m3Type_i32);
3562
3563
        // Spec: the two value operands (below condition) must have matching types
3564
43
        m3type_t type2 = GetStackTypeFromTop(o, 2);
3565
43
        _throwif(m3Err_typeMismatch, type != c_m3Type_none and type2 != c_m3Type_none and
3566
43
                                       not (IsSubTypeOf(type, type2) or IsSubTypeOf(type2, type)));
3567
43
    }
3568
3569
556
    if (IsFpType(type)) {
3570
17
#if d_m3HasFloat
3571
        // not consuming a fp reg, so preserve
3572
17
        if (not IsStackTopMinus1InRegister(o) and
3573
16
            not IsStackTopMinus2InRegister(o)) {
3574
16
_           (PreserveRegisterIfOccupied(o, type));
3575
16
        }
3576
3577
17
        bool selectorInReg = IsStackTopInRegister(o);
3578
17
        slots[0]           = GetStackTopSlotNumber(o);
3579
17
_       (Pop(o));
3580
3581
17
        u32 opIndex = 0;
3582
3583
49
        for (u32 i = 1; i <= 2; ++i) {
3584
33
            if (IsStackTopInRegister(o)) {
3585
13
                opIndex = i;
3586
20
            } else {
3587
20
                slots[i] = GetStackTopSlotNumber(o);
3588
20
            }
3589
3590
33
_           (Pop(o));
3591
32
        }
3592
3593
16
        op = c_fpSelectOps[type - c_m3Type_f32][selectorInReg][opIndex];
3594
#else
3595
        _throw(m3Err_unknownOpcode);
3596
#endif
3597
539
    } else if (IsIntType(type) or IsRefType(type)) {
3598
        // 'sss' operation doesn't consume a register, so might have to protected its contents
3599
96
        if (not IsStackTopInRegister(o) and
3600
32
            not IsStackTopMinus1InRegister(o) and
3601
28
            not IsStackTopMinus2InRegister(o)) {
3602
27
_           (PreserveRegisterIfOccupied(o, type));
3603
26
        }
3604
3605
95
        u32 opIndex = 3;  // op_Select_*_sss
3606
3607
380
        for (u32 i = 0; i < 3; ++i) {
3608
285
            if (IsStackTopInRegister(o)) {
3609
98
                opIndex = i;
3610
187
            } else {
3611
187
                slots[i] = GetStackTopSlotNumber(o);
3612
187
            }
3613
3614
285
_           (Pop(o));
3615
285
        }
3616
3617
        // a reference is a pointer-sized integer as far as select is concerned
3618
95
        u32 typeIndex = IsRefType(type) ? ((M3_SIZEOF_PTR == 8) ? 1 : 0)
3619
95
                                        : (u32)(type - c_m3Type_i32);
3620
3621
95
        op = c_intSelectOps[typeIndex][opIndex];
3622
443
    } else if (not IsStackPolymorphic(o)) {
3623
2
        _throw(m3Err_functionStackUnderrun);
3624
0
    }
3625
3626
552
    EmitOp(o, op);
3627
2.20k
    for (u32 i = 0; i < 3; i++) {
3628
1.65k
        if (IsValidSlot(slots[i])) {
3629
189
            EmitSlotOffset(o, slots[i]);
3630
189
        }
3631
1.65k
    }
3632
552
_   (PushRegister(o, type));
3633
3634
556
    _catch: return result;
3635
552
}
3636
3637
static
3638
M3Result Compile_Drop (IM3Compilation o, m3opcode_t i_opcode)
3639
97
{
3640
97
    M3Result result = Pop(o);
3641
97
    if (d_m3LogWasmStack) {
3642
0
        dump_type_stack(o);
3643
0
    }
3644
97
    return result;
3645
97
}
3646
3647
static
3648
M3Result Compile_Nop (IM3Compilation o, m3opcode_t i_opcode)
3649
673
{
3650
673
    return m3Err_none;
3651
673
}
3652
3653
static
3654
M3Result Compile_Unreachable (IM3Compilation o, m3opcode_t i_opcode)
3655
6.01k
{
3656
6.01k
    M3Result result;
3657
3658
6.01k
_   (AddTrapRecord(o));
3659
3660
6.01k
_   (EmitOp(o, op_Unreachable));
3661
6.01k
_   (SetStackPolymorphic(o));
3662
3663
6.01k
    _catch:
3664
6.01k
    return result;
3665
6.00k
}
3666
3667
3668
// OPTZ: currently all stack slot indices take up a full word, but
3669
// dual stack source operands could be packed together
3670
static
3671
M3Result Compile_Operator (IM3Compilation o, m3opcode_t i_opcode)
3672
18.1k
{
3673
18.1k
    M3Result result;
3674
3675
    // Declared before the first throw below, which jumps past this point to
3676
    // _catch and so must not skip an initialization.
3677
18.1k
    IM3Operation op = NULL;
3678
3679
18.1k
    IM3OpInfo opInfo = GetOpInfo(i_opcode);
3680
18.1k
    _throwif(m3Err_unknownOpcode, not opInfo);
3681
3682
    // Spec: validate operand types for load/store operations
3683
18.1k
    if (not IsStackPolymorphic(o)) {
3684
        // For load ops (stackOffset == 0, unary), the operand is always i32 (address)
3685
15.9k
        if (i_opcode >= 0x28 and i_opcode <= 0x35) {
3686
97
            m3type_t topType = GetStackTopType(o);
3687
97
            _throwif(m3Err_typeMismatch, topType != c_m3Type_none and topType != c_m3Type_i32);
3688
97
        }
3689
3690
        // For store ops (stackOffset == -2), the address operand must be i32
3691
15.9k
        if (i_opcode >= 0x36 and i_opcode <= 0x3e) {
3692
34
            m3type_t addrType = GetStackTypeFromTop(o, 1);
3693
34
            _throwif(m3Err_typeMismatch, addrType != c_m3Type_none and addrType != c_m3Type_i32);
3694
34
        }
3695
15.9k
    }
3696
3697
    // This preserve is for for FP compare operations.
3698
    // either need additional slot destination operations or the
3699
    // easy fix, move _r0 out of the way.
3700
    // moving out the way might be the optimal solution most often?
3701
    // otherwise, the _r0 reg can get buried down in the stack
3702
    // and be idle & wasted for a moment.
3703
18.1k
    if (IsFpType(GetStackTopType(o)) and IsIntType(opInfo->type)) {
3704
1.60k
_       (PreserveRegisterIfOccupied(o, opInfo->type));
3705
1.60k
    }
3706
3707
18.1k
    if (opInfo->stackOffset == 0) {
3708
971
        if (IsStackTopInRegister(o)) {
3709
161
            op = opInfo->operations[0]; // _s
3710
810
        } else {
3711
810
_           (PreserveRegisterIfOccupied(o, opInfo->type));
3712
809
            op = opInfo->operations[1]; // _r
3713
809
        }
3714
17.1k
    } else {
3715
17.1k
        if (IsStackTopInRegister(o)) {
3716
7.43k
            op = opInfo->operations[0];  // _rs
3717
3718
7.43k
            if (IsStackTopMinus1InRegister(o)) {    d_m3Assert (i_opcode == c_waOp_store_f32 or i_opcode == c_waOp_store_f64);
3719
12
                op = opInfo->operations[3]; // _rr for fp.store
3720
12
            }
3721
9.70k
        } else if (IsStackTopMinus1InRegister(o)) {
3722
3.04k
            op = opInfo->operations[1]; // _sr
3723
3724
3.04k
            if (not op) {  // must be commutative, then
3725
1.58k
                op = opInfo->operations[0];
3726
1.58k
            }
3727
6.65k
        } else {
3728
6.65k
_           (PreserveRegisterIfOccupied(o, opInfo->type));     // _ss
3729
6.65k
            op = opInfo->operations[2];
3730
6.65k
        }
3731
17.1k
    }
3732
3733
18.1k
    if (op) {
3734
18.1k
_       (EmitOp(o, op));
3735
3736
18.1k
#if d_m3FoldSetLocal
3737
        // the op word just written (EmitOp may have bridged pages); NULL in the page-less compile-walk mode
3738
18.1k
        pc_t patchPC = o->page ? GetPC(o) - 1 : NULL;
3739
18.1k
#endif
3740
3741
18.1k
_       (EmitSlotNumOfStackTopAndPop(o));
3742
3743
18.1k
        if (opInfo->stackOffset < 0) {
3744
17.1k
_           (EmitSlotNumOfStackTopAndPop(o));
3745
17.1k
        }
3746
3747
18.0k
        if (opInfo->type != c_m3Type_none) {
3748
17.7k
_           (PushRegister(o, opInfo->type));
3749
3750
17.7k
#if d_m3FoldSetLocal
3751
            // record this op as a fold candidate for an immediately following local.set;
3752
            // the operand-form index is recovered by matching against the variant table
3753
17.7k
            if (patchPC) {
3754
13.7k
                for (u8 form = 0; form < 4; ++form) {
3755
13.7k
                    if (opInfo->operations[form] == op) {
3756
7.16k
                        o->foldPatchPC    = patchPC;
3757
7.16k
                        o->foldOpcode     = i_opcode;
3758
7.16k
                        o->foldForm       = form;
3759
7.16k
                        o->foldStackIndex = (u16)GetStackTopIndex(o);
3760
7.16k
                        break;
3761
7.16k
                    }
3762
13.7k
                }
3763
7.16k
            }
3764
17.7k
#endif
3765
17.7k
        }
3766
18.0k
    } else {
3767
#ifdef DEBUG
3768
        result = ErrorCompile("no operation found for opcode", o, "'%s'", opInfo->name);
3769
#else
3770
3
        result = ErrorCompile("no operation found for opcode", o, "%x", i_opcode);
3771
3
#endif
3772
3
        _throw(result);
3773
0
    }
3774
3775
18.1k
    _catch: return result;
3776
18.1k
}
3777
3778
static
3779
M3Result Compile_Convert (IM3Compilation o, m3opcode_t i_opcode)
3780
192
{
3781
192
_try {
3782
192
    IM3OpInfo opInfo = GetOpInfo(i_opcode);
3783
192
    _throwif(m3Err_unknownOpcode, not opInfo);
3784
3785
    // Spec: validate source operand type for conversion instructions
3786
192
    if (not IsStackPolymorphic(o)) {
3787
7
        u8 sourceType = c_m3Type_none;
3788
7
        switch (i_opcode)
3789
        // clang-format off
3790
7
        {
3791
0
        case 0xa7:              // i32.wrap/i64
3792
0
            sourceType = c_m3Type_i64; break;
3793
0
        case 0xa8: case 0xa9:   // i32.trunc_s/f32, i32.trunc_u/f32
3794
0
        case 0xae: case 0xaf:   // i64.trunc_s/f32, i64.trunc_u/f32
3795
0
        case 0xbb:              // f64.promote/f32
3796
0
        case 0xbc:              // i32.reinterpret/f32
3797
0
            sourceType = c_m3Type_f32; break;
3798
0
        case 0xaa: case 0xab:   // i32.trunc_s/f64, i32.trunc_u/f64
3799
1
        case 0xb0: case 0xb1:   // i64.trunc_s/f64, i64.trunc_u/f64
3800
1
        case 0xb6:              // f32.demote/f64
3801
1
        case 0xbd:              // i64.reinterpret/f64
3802
1
            sourceType = c_m3Type_f64; break;
3803
0
        case 0xac: case 0xad:   // i64.extend_s/i32, i64.extend_u/i32
3804
4
        case 0xb2: case 0xb3:   // f32.convert_s/i32, f32.convert_u/i32
3805
4
        case 0xb7: case 0xb8:   // f64.convert_s/i32, f64.convert_u/i32
3806
4
        case 0xbe:              // f32.reinterpret/i32
3807
4
            sourceType = c_m3Type_i32; break;
3808
0
        case 0xb4: case 0xb5:   // f32.convert_s/i64, f32.convert_u/i64
3809
1
        case 0xb9: case 0xba:   // f64.convert_s/i64, f64.convert_u/i64
3810
2
        case 0xbf:              // f64.reinterpret/i64
3811
2
            sourceType = c_m3Type_i64; break;
3812
0
        default: break;
3813
7
        }
3814
        // clang-format on
3815
3816
7
        if (sourceType != c_m3Type_none) {
3817
7
            m3type_t topType = GetStackTopType(o);
3818
7
            _throwif(m3Err_typeMismatch, topType != c_m3Type_none and not IsSubTypeOf(topType, sourceType));
3819
7
        }
3820
7
    }
3821
3822
192
    bool destInSlot   = IsRegisterTypeAllocated(o, opInfo->type);
3823
192
    bool sourceInSlot = IsStackTopInSlot(o);
3824
3825
192
    IM3Operation op = opInfo->operations[destInSlot * 2 + sourceInSlot];
3826
3827
192
_   (EmitOp(o, op));
3828
192
_   (EmitSlotNumOfStackTopAndPop(o));
3829
3830
192
    if (destInSlot) {
3831
18
_       (PushAllocatedSlotAndEmit(o, opInfo->type))
3832
174
    } else {
3833
174
_       (PushRegister(o, opInfo->type))
3834
174
    }
3835
192
}
3836
192
    _catch: return result;
3837
192
}
3838
3839
#if d_m3HasMemory64
3840
3841
// Replaces the 64-bit address operand of a load or store with the checked
3842
// 32-bit effective address op_CheckAddr64 leaves behind, so that the access
3843
// itself compiles exactly as it would against a 32-bit memory: one slot to read
3844
// the address from, and an offset of zero folded in already.
3845
//
3846
// For a store the address sits under the value, so it is rewritten where it
3847
// stands rather than popped - its stack entry is repointed at the scratch slot
3848
// and retyped, and the slot it used to occupy released.
3849
static
3850
M3Result EmitCheckAddr64 (IM3Compilation o, u64 i_offset, bool i_isStore)
3851
495
{
3852
495
    M3Result result = m3Err_none;
3853
3854
495
    u16 numOperands = i_isStore ? 2 : 1;
3855
3856
    // The address has to be in a slot for op_CheckAddr64 to read it. Spilling
3857
    // r0 wholesale also keeps the value of a store out of it, which is what
3858
    // leaves the address as the one operand still in a register to worry about.
3859
495
_   (PreserveRegisterIfOccupied(o, c_m3Type_i64));
3860
3861
    // unreachable code: the operands the instruction names may not be there
3862
495
    if (o->stackIndex < o->block.blockStackIndex + numOperands) {
3863
352
        return result;
3864
352
    }
3865
3866
143
    {
3867
143
        u16      stackIndex = (u16)(o->stackIndex - numOperands);
3868
143
        u16      srcSlot    = o->wasmStack[stackIndex];
3869
143
        m3type_t srcType    = o->typeStack[stackIndex];
3870
3871
143
        u16 dstSlot = c_slotUnused;
3872
3873
        // Checked here rather than left to the validator, which a build may
3874
        // have switched off: reading a slot as an i64 that only holds an i32
3875
        // would reach past what was allocated for it.
3876
143
        _throwif(m3Err_typeMismatch, BaseTypeOf(srcType) != c_m3Type_i64);
3877
3878
        // an address is an integer, so nothing should be left in a register
3879
        // once r0 has been preserved
3880
138
        _throwif(m3Err_typeMismatch, IsRegisterSlotAlias(srcSlot));
3881
137
        _throwif(m3Err_functionStackUnderrun, srcSlot >= o->slotMaxAllocatedIndexPlusOne);
3882
3883
        // allocated before the source is released, so the two cannot be the
3884
        // same slot - op_CheckAddr64 reads one and writes the other
3885
137
_       (AllocateSlots(o, &dstSlot, c_m3Type_i32));
3886
3887
137
_       (EmitOp(o, op_CheckAddr64));
3888
137
        EmitSlotOffset(o, srcSlot);
3889
137
        EmitSlotOffset(o, dstSlot);
3890
137
        EmitConstant64(o, i_offset);
3891
3892
137
        o->wasmStack[stackIndex] = dstSlot;
3893
137
        o->typeStack[stackIndex] = c_m3Type_i32;
3894
3895
        // locals and the constant table outlive the instruction; a dynamic
3896
        // slot the address was computed into does not
3897
137
        if (srcSlot >= o->slotFirstDynamicIndex) {
3898
112
            DeallocateSlot(o, srcSlot, srcType);
3899
112
        }
3900
137
    }
3901
3902
143
    _catch: return result;
3903
137
}
3904
3905
#endif // d_m3HasMemory64
3906
3907
3908
static
3909
M3Result Compile_Load_Store (IM3Compilation o, m3opcode_t i_opcode)
3910
1.05k
{
3911
1.05k
_try {
3912
1.05k
    u32 alignHint, memoryIdx;
3913
1.05k
    u64 memoryOffset;
3914
3915
    // alignHint is checked by the validator
3916
1.05k
_   (ReadMemoryArg(&alignHint, &memoryIdx, &memoryOffset, &o->wasm, o->wasmEnd));
3917
1.05k
                                                                        m3log (compile, d_indent " (memory = %d; offset = %llu)", get_indention_string (o), memoryIdx, (unsigned long long) memoryOffset);
3918
1.05k
    _throwif(m3Err_unknownMemory, memoryIdx >= o->module->numMemories);
3919
3920
1.05k
    IM3OpInfo opInfo = GetOpInfo(i_opcode);
3921
1.05k
    _throwif(m3Err_unknownOpcode, not opInfo);
3922
3923
1.05k
    bool isMemory64 = o->module->memories[memoryIdx]->isMemory64;
3924
3925
    // Spec: the static offset has to be in range of the address type. Checked
3926
    // here as well as in the validator, so that a build without one still
3927
    // refuses the module rather than silently truncating the offset.
3928
1.05k
    _throwif(m3Err_wasmMalformed, not isMemory64 and memoryOffset > 0xFFFFFFFFull);
3929
3930
1.05k
    if (IsFpType(opInfo->type)) {
3931
126
_       (PreserveRegisterIfOccupied(o, c_m3Type_f64));
3932
126
    }
3933
3934
1.05k
    if (memoryIdx) {
3935
0
_       (EmitSetMemory(o, memoryIdx));
3936
0
    }
3937
3938
1.05k
#if d_m3HasMemory64
3939
1.05k
    if (isMemory64) {
3940
        // An offset this far out puts every address out of bounds, so rather
3941
        // than carrying it, clamp it to the limit: op_CheckAddr64 then traps
3942
        // on whatever address it is given, which is the outcome either way.
3943
495
        if (memoryOffset > d_m3AddressLimit) {
3944
56
            memoryOffset = d_m3AddressLimit;
3945
56
        }
3946
3947
495
_       (EmitCheckAddr64(o, memoryOffset, opInfo->stackOffset < 0));
3948
3949
        // op_CheckAddr64 has folded the offset in already
3950
489
        memoryOffset = 0;
3951
489
    }
3952
1.05k
#endif
3953
3954
1.05k
_   (Compile_Operator(o, i_opcode));
3955
3956
1.05k
    EmitConstant32(o, (u32)memoryOffset);
3957
3958
1.05k
    if (memoryIdx) {
3959
0
_       (EmitSetMemory(o, 0));
3960
0
    }
3961
1.05k
}
3962
1.05k
    _catch: return result;
3963
1.05k
}
3964
3965
3966
M3Result CompileRawFunction (IM3Module io_module, IM3Function io_function, const void* i_function, const void* i_userdata)
3967
0
{
3968
0
    d_m3Assert(io_module->runtime);
3969
3970
0
    IM3CodePage page = AcquireCodePageWithCapacity(io_module->runtime, 4);
3971
3972
0
    if (page) {
3973
0
        io_function->compiled = GetPagePC(page);
3974
0
        io_function->module   = io_module;
3975
3976
        // Unless a host module's ABI names a memory (m3_BindImportMemory), a
3977
        // host function addresses memory 0 - what a bare i32 guest pointer means
3978
0
        io_function->hostMemory = io_module->memory0;
3979
3980
0
        EmitWord(page, op_CallRawFunction);
3981
0
        EmitWord(page, i_function);
3982
0
        EmitWord(page, io_function);
3983
0
        EmitWord(page, i_userdata);
3984
3985
0
        ReleaseCodePage(io_module->runtime, page);
3986
0
        return m3Err_none;
3987
0
    } else {
3988
0
        return m3Err_mallocFailedCodePage;
3989
0
    }
3990
0
}
3991
3992
3993
// d_logOp, d_logOp2 macros aren't actually used by the compiler, just codepage decoding (d_m3LogCodePages = 1)
3994
#define d_logOp(OP)                         { op_##OP,                  NULL,                       NULL,                       NULL }
3995
#define d_logOp2(OP1,OP2)                   { op_##OP1,                 op_##OP2,                   NULL,                       NULL }
3996
3997
#define d_emptyOpList                       { NULL,                     NULL,                       NULL,                       NULL }
3998
#define d_unaryOpList(TYPE, NAME)           { op_##TYPE##_##NAME##_r,   op_##TYPE##_##NAME##_s,     NULL,                       NULL }
3999
#define d_binOpList(TYPE, NAME)             { op_##TYPE##_##NAME##_rs,  op_##TYPE##_##NAME##_sr,    op_##TYPE##_##NAME##_ss,    NULL }
4000
#define d_storeFpOpList(TYPE, NAME)         { op_##TYPE##_##NAME##_rs,  op_##TYPE##_##NAME##_sr,    op_##TYPE##_##NAME##_ss,    op_##TYPE##_##NAME##_rr }
4001
#define d_commutativeBinOpList(TYPE, NAME)  { op_##TYPE##_##NAME##_rs,  NULL,                       op_##TYPE##_##NAME##_ss,    NULL }
4002
4003
#define d_convertOpList(OP)                 { op_##OP##_r_r,            op_##OP##_r_s,              op_##OP##_s_r,              op_##OP##_s_s }
4004
4005
// clang-format off
4006
const M3OpInfo c_operations[] =
4007
{
4008
    M3OP( "unreachable",         0, none,   d_logOp (Unreachable),              Compile_Unreachable ),  // 0x00
4009
    M3OP( "nop",                 0, none,   d_emptyOpList,                      Compile_Nop ),          // 0x01 .
4010
    M3OP( "block",               0, none,   d_emptyOpList,                      Compile_LoopOrBlock ),  // 0x02
4011
    M3OP( "loop",                0, none,   d_logOp (Loop),                     Compile_LoopOrBlock ),  // 0x03
4012
    M3OP( "if",                 -1, none,   d_emptyOpList,                      Compile_If ),           // 0x04
4013
    M3OP( "else",                0, none,   d_emptyOpList,                      Compile_Nop ),          // 0x05
4014
4015
    M3OP_RESERVED,  M3OP_RESERVED,                                                                      // 0x06...0x07
4016
4017
#if d_m3HasExceptionHandling
4018
    M3OP( "throw",               0, none,   d_logOp (Throw),                    Compile_Throw ),        // 0x08
4019
    M3OP_RESERVED,                                                                                      // 0x09
4020
    M3OP( "throw_ref",           0, none,   d_logOp (ThrowRef),                 Compile_ThrowRef ),     // 0x0a
4021
#else
4022
    M3OP_RESERVED,  M3OP_RESERVED, M3OP_RESERVED,                                                       // 0x08...0x0a
4023
#endif
4024
4025
    M3OP( "end",                 0, none,   d_emptyOpList,                      Compile_End ),          // 0x0b
4026
    M3OP( "br",                  0, none,   d_logOp (Branch),                   Compile_Branch ),       // 0x0c
4027
    M3OP( "br_if",              -1, none,   d_logOp2 (BranchIf_r, BranchIf_s),  Compile_Branch ),       // 0x0d
4028
    M3OP( "br_table",           -1, none,   d_logOp (BranchTable),              Compile_BranchTable ),  // 0x0e
4029
    M3OP( "return",              0, any,    d_logOp (Return),                   Compile_Return ),       // 0x0f
4030
    M3OP( "call",                0, any,    d_logOp (Call),                     Compile_Call ),         // 0x10
4031
    M3OP( "call_indirect",       0, any,    d_logOp (CallIndirect),             Compile_CallIndirect ), // 0x11
4032
    M3OP( "return_call",         0, any,    d_logOp (ReturnCall),               Compile_Call ),         // 0x12
4033
    M3OP( "return_call_indirect",0, any,    d_logOp (ReturnCallIndirect),       Compile_CallIndirect ), // 0x13
4034
4035
#if d_m3HasTypedRefs
4036
    M3OP( "call_ref",            0, any,    d_logOp (CallRef),                  Compile_CallRef ),      // 0x14
4037
    M3OP( "return_call_ref",     0, any,    d_logOp (ReturnCallRef),            Compile_CallRef ),      // 0x15
4038
#else
4039
    M3OP_RESERVED,  M3OP_RESERVED,                                                                      // 0x14...
4040
#endif
4041
    M3OP_RESERVED,  M3OP_RESERVED, M3OP_RESERVED, M3OP_RESERVED,                                        // ...0x19
4042
4043
    M3OP( "drop",               -1, none,   d_emptyOpList,                      Compile_Drop ),         // 0x1a
4044
    M3OP( "select",             -2, any,    d_emptyOpList,                      Compile_Select  ),      // 0x1b
4045
4046
#if d_m3HasRefTypes
4047
    M3OP( "select.t",           -2, any,    d_emptyOpList,                      Compile_Select_Typed ), // 0x1c
4048
#else
4049
    M3OP_RESERVED,                                                                                      // 0x1c
4050
#endif
4051
    M3OP_RESERVED,  M3OP_RESERVED,                                                                      // 0x1d...0x1e
4052
4053
#if d_m3HasExceptionHandling
4054
    M3OP( "try_table",           0, none,   d_logOp (TryTable),                 Compile_TryTable ),     // 0x1f
4055
#else
4056
    M3OP_RESERVED,                                                                                      // 0x1f
4057
#endif
4058
4059
    M3OP( "local.get",          1,  any,    d_emptyOpList,                      Compile_GetLocal ),     // 0x20
4060
    M3OP( "local.set",          1,  none,   d_emptyOpList,                      Compile_SetLocal ),     // 0x21
4061
    M3OP( "local.tee",          0,  any,    d_emptyOpList,                      Compile_SetLocal ),     // 0x22
4062
    M3OP( "global.get",         1,  none,   d_emptyOpList,                      Compile_GetSetGlobal ), // 0x23
4063
    M3OP( "global.set",         1,  none,   d_emptyOpList,                      Compile_GetSetGlobal ), // 0x24
4064
4065
#if d_m3HasRefTypes
4066
    M3OP( "table.get",           0,  any,    d_emptyOpList,                     Compile_Table_GetSet ), // 0x25
4067
    M3OP( "table.set",          -2, none,    d_emptyOpList,                     Compile_Table_GetSet ), // 0x26
4068
#else
4069
    M3OP_RESERVED,  M3OP_RESERVED,                                                                      // 0x25...0x26
4070
#endif
4071
    M3OP_RESERVED,                                                                                      // 0x27
4072
4073
    M3OP( "i32.load",           0,  i_32,   d_unaryOpList (i32, Load_i32),      Compile_Load_Store ),   // 0x28
4074
    M3OP( "i64.load",           0,  i_64,   d_unaryOpList (i64, Load_i64),      Compile_Load_Store ),   // 0x29
4075
    M3OP_F( "f32.load",         0,  f_32,   d_unaryOpList (f32, Load_f32),      Compile_Load_Store ),   // 0x2a
4076
    M3OP_F( "f64.load",         0,  f_64,   d_unaryOpList (f64, Load_f64),      Compile_Load_Store ),   // 0x2b
4077
4078
    M3OP( "i32.load8_s",        0,  i_32,   d_unaryOpList (i32, Load_i8),       Compile_Load_Store ),   // 0x2c
4079
    M3OP( "i32.load8_u",        0,  i_32,   d_unaryOpList (i32, Load_u8),       Compile_Load_Store ),   // 0x2d
4080
    M3OP( "i32.load16_s",       0,  i_32,   d_unaryOpList (i32, Load_i16),      Compile_Load_Store ),   // 0x2e
4081
    M3OP( "i32.load16_u",       0,  i_32,   d_unaryOpList (i32, Load_u16),      Compile_Load_Store ),   // 0x2f
4082
4083
    M3OP( "i64.load8_s",        0,  i_64,   d_unaryOpList (i64, Load_i8),       Compile_Load_Store ),   // 0x30
4084
    M3OP( "i64.load8_u",        0,  i_64,   d_unaryOpList (i64, Load_u8),       Compile_Load_Store ),   // 0x31
4085
    M3OP( "i64.load16_s",       0,  i_64,   d_unaryOpList (i64, Load_i16),      Compile_Load_Store ),   // 0x32
4086
    M3OP( "i64.load16_u",       0,  i_64,   d_unaryOpList (i64, Load_u16),      Compile_Load_Store ),   // 0x33
4087
    M3OP( "i64.load32_s",       0,  i_64,   d_unaryOpList (i64, Load_i32),      Compile_Load_Store ),   // 0x34
4088
    M3OP( "i64.load32_u",       0,  i_64,   d_unaryOpList (i64, Load_u32),      Compile_Load_Store ),   // 0x35
4089
4090
    M3OP( "i32.store",          -2, none,   d_binOpList (i32, Store_i32),       Compile_Load_Store ),   // 0x36
4091
    M3OP( "i64.store",          -2, none,   d_binOpList (i64, Store_i64),       Compile_Load_Store ),   // 0x37
4092
    M3OP_F( "f32.store",        -2, none,   d_storeFpOpList (f32, Store_f32),   Compile_Load_Store ),   // 0x38
4093
    M3OP_F( "f64.store",        -2, none,   d_storeFpOpList (f64, Store_f64),   Compile_Load_Store ),   // 0x39
4094
4095
    M3OP( "i32.store8",         -2, none,   d_binOpList (i32, Store_u8),        Compile_Load_Store ),   // 0x3a
4096
    M3OP( "i32.store16",        -2, none,   d_binOpList (i32, Store_i16),       Compile_Load_Store ),   // 0x3b
4097
4098
    M3OP( "i64.store8",         -2, none,   d_binOpList (i64, Store_u8),        Compile_Load_Store ),   // 0x3c
4099
    M3OP( "i64.store16",        -2, none,   d_binOpList (i64, Store_i16),       Compile_Load_Store ),   // 0x3d
4100
    M3OP( "i64.store32",        -2, none,   d_binOpList (i64, Store_i32),       Compile_Load_Store ),   // 0x3e
4101
4102
    M3OP( "memory.size",        1,  i_32,   d_logOp (MemSize),                  Compile_Memory_Size ),  // 0x3f
4103
    M3OP( "memory.grow",        1,  i_32,   d_logOp (MemGrow),                  Compile_Memory_Grow ),  // 0x40
4104
4105
    M3OP( "i32.const",          1,  i_32,   d_logOp (Const32),                  Compile_Const_i32 ),    // 0x41
4106
    M3OP( "i64.const",          1,  i_64,   d_logOp (Const64),                  Compile_Const_i64 ),    // 0x42
4107
    M3OP_F( "f32.const",        1,  f_32,   d_emptyOpList,                      Compile_Const_f32 ),    // 0x43
4108
    M3OP_F( "f64.const",        1,  f_64,   d_emptyOpList,                      Compile_Const_f64 ),    // 0x44
4109
4110
    M3OP( "i32.eqz",            0,  i_32,   d_unaryOpList (i32, EqualToZero)        , NULL  ),          // 0x45
4111
    M3OP( "i32.eq",             -1, i_32,   d_commutativeBinOpList (i32, Equal)     , NULL  ),          // 0x46
4112
    M3OP( "i32.ne",             -1, i_32,   d_commutativeBinOpList (i32, NotEqual)  , NULL  ),          // 0x47
4113
    M3OP( "i32.lt_s",           -1, i_32,   d_binOpList (i32, LessThan)             , NULL  ),          // 0x48
4114
    M3OP( "i32.lt_u",           -1, i_32,   d_binOpList (u32, LessThan)             , NULL  ),          // 0x49
4115
    M3OP( "i32.gt_s",           -1, i_32,   d_binOpList (i32, GreaterThan)          , NULL  ),          // 0x4a
4116
    M3OP( "i32.gt_u",           -1, i_32,   d_binOpList (u32, GreaterThan)          , NULL  ),          // 0x4b
4117
    M3OP( "i32.le_s",           -1, i_32,   d_binOpList (i32, LessThanOrEqual)      , NULL  ),          // 0x4c
4118
    M3OP( "i32.le_u",           -1, i_32,   d_binOpList (u32, LessThanOrEqual)      , NULL  ),          // 0x4d
4119
    M3OP( "i32.ge_s",           -1, i_32,   d_binOpList (i32, GreaterThanOrEqual)   , NULL  ),          // 0x4e
4120
    M3OP( "i32.ge_u",           -1, i_32,   d_binOpList (u32, GreaterThanOrEqual)   , NULL  ),          // 0x4f
4121
4122
    M3OP( "i64.eqz",            0,  i_32,   d_unaryOpList (i64, EqualToZero)        , NULL  ),          // 0x50
4123
    M3OP( "i64.eq",             -1, i_32,   d_commutativeBinOpList (i64, Equal)     , NULL  ),          // 0x51
4124
    M3OP( "i64.ne",             -1, i_32,   d_commutativeBinOpList (i64, NotEqual)  , NULL  ),          // 0x52
4125
    M3OP( "i64.lt_s",           -1, i_32,   d_binOpList (i64, LessThan)             , NULL  ),          // 0x53
4126
    M3OP( "i64.lt_u",           -1, i_32,   d_binOpList (u64, LessThan)             , NULL  ),          // 0x54
4127
    M3OP( "i64.gt_s",           -1, i_32,   d_binOpList (i64, GreaterThan)          , NULL  ),          // 0x55
4128
    M3OP( "i64.gt_u",           -1, i_32,   d_binOpList (u64, GreaterThan)          , NULL  ),          // 0x56
4129
    M3OP( "i64.le_s",           -1, i_32,   d_binOpList (i64, LessThanOrEqual)      , NULL  ),          // 0x57
4130
    M3OP( "i64.le_u",           -1, i_32,   d_binOpList (u64, LessThanOrEqual)      , NULL  ),          // 0x58
4131
    M3OP( "i64.ge_s",           -1, i_32,   d_binOpList (i64, GreaterThanOrEqual)   , NULL  ),          // 0x59
4132
    M3OP( "i64.ge_u",           -1, i_32,   d_binOpList (u64, GreaterThanOrEqual)   , NULL  ),          // 0x5a
4133
4134
    M3OP_F( "f32.eq",           -1, i_32,   d_commutativeBinOpList (f32, Equal)     , NULL  ),          // 0x5b
4135
    M3OP_F( "f32.ne",           -1, i_32,   d_commutativeBinOpList (f32, NotEqual)  , NULL  ),          // 0x5c
4136
    M3OP_F( "f32.lt",           -1, i_32,   d_binOpList (f32, LessThan)             , NULL  ),          // 0x5d
4137
    M3OP_F( "f32.gt",           -1, i_32,   d_binOpList (f32, GreaterThan)          , NULL  ),          // 0x5e
4138
    M3OP_F( "f32.le",           -1, i_32,   d_binOpList (f32, LessThanOrEqual)      , NULL  ),          // 0x5f
4139
    M3OP_F( "f32.ge",           -1, i_32,   d_binOpList (f32, GreaterThanOrEqual)   , NULL  ),          // 0x60
4140
4141
    M3OP_F( "f64.eq",           -1, i_32,   d_commutativeBinOpList (f64, Equal)     , NULL  ),          // 0x61
4142
    M3OP_F( "f64.ne",           -1, i_32,   d_commutativeBinOpList (f64, NotEqual)  , NULL  ),          // 0x62
4143
    M3OP_F( "f64.lt",           -1, i_32,   d_binOpList (f64, LessThan)             , NULL  ),          // 0x63
4144
    M3OP_F( "f64.gt",           -1, i_32,   d_binOpList (f64, GreaterThan)          , NULL  ),          // 0x64
4145
    M3OP_F( "f64.le",           -1, i_32,   d_binOpList (f64, LessThanOrEqual)      , NULL  ),          // 0x65
4146
    M3OP_F( "f64.ge",           -1, i_32,   d_binOpList (f64, GreaterThanOrEqual)   , NULL  ),          // 0x66
4147
4148
    M3OP( "i32.clz",            0,  i_32,   d_unaryOpList (u32, Clz)                , NULL  ),          // 0x67
4149
    M3OP( "i32.ctz",            0,  i_32,   d_unaryOpList (u32, Ctz)                , NULL  ),          // 0x68
4150
    M3OP( "i32.popcnt",         0,  i_32,   d_unaryOpList (u32, Popcnt)             , NULL  ),          // 0x69
4151
4152
    M3OP( "i32.add",            -1, i_32,   d_commutativeBinOpList (i32, Add)       , NULL  ),          // 0x6a
4153
    M3OP( "i32.sub",            -1, i_32,   d_binOpList (i32, Subtract)             , NULL  ),          // 0x6b
4154
    M3OP( "i32.mul",            -1, i_32,   d_commutativeBinOpList (i32, Multiply)  , NULL  ),          // 0x6c
4155
    M3OP( "i32.div_s",          -1, i_32,   d_binOpList (i32, Divide)               , NULL  ),          // 0x6d
4156
    M3OP( "i32.div_u",          -1, i_32,   d_binOpList (u32, Divide)               , NULL  ),          // 0x6e
4157
    M3OP( "i32.rem_s",          -1, i_32,   d_binOpList (i32, Remainder)            , NULL  ),          // 0x6f
4158
    M3OP( "i32.rem_u",          -1, i_32,   d_binOpList (u32, Remainder)            , NULL  ),          // 0x70
4159
    M3OP( "i32.and",            -1, i_32,   d_commutativeBinOpList (u32, And)       , NULL  ),          // 0x71
4160
    M3OP( "i32.or",             -1, i_32,   d_commutativeBinOpList (u32, Or)        , NULL  ),          // 0x72
4161
    M3OP( "i32.xor",            -1, i_32,   d_commutativeBinOpList (u32, Xor)       , NULL  ),          // 0x73
4162
    M3OP( "i32.shl",            -1, i_32,   d_binOpList (u32, ShiftLeft)            , NULL  ),          // 0x74
4163
    M3OP( "i32.shr_s",          -1, i_32,   d_binOpList (i32, ShiftRight)           , NULL  ),          // 0x75
4164
    M3OP( "i32.shr_u",          -1, i_32,   d_binOpList (u32, ShiftRight)           , NULL  ),          // 0x76
4165
    M3OP( "i32.rotl",           -1, i_32,   d_binOpList (u32, Rotl)                 , NULL  ),          // 0x77
4166
    M3OP( "i32.rotr",           -1, i_32,   d_binOpList (u32, Rotr)                 , NULL  ),          // 0x78
4167
4168
    M3OP( "i64.clz",            0,  i_64,   d_unaryOpList (u64, Clz)                , NULL  ),          // 0x79
4169
    M3OP( "i64.ctz",            0,  i_64,   d_unaryOpList (u64, Ctz)                , NULL  ),          // 0x7a
4170
    M3OP( "i64.popcnt",         0,  i_64,   d_unaryOpList (u64, Popcnt)             , NULL  ),          // 0x7b
4171
4172
    M3OP( "i64.add",            -1, i_64,   d_commutativeBinOpList (i64, Add)       , NULL  ),          // 0x7c
4173
    M3OP( "i64.sub",            -1, i_64,   d_binOpList (i64, Subtract)             , NULL  ),          // 0x7d
4174
    M3OP( "i64.mul",            -1, i_64,   d_commutativeBinOpList (i64, Multiply)  , NULL  ),          // 0x7e
4175
    M3OP( "i64.div_s",          -1, i_64,   d_binOpList (i64, Divide)               , NULL  ),          // 0x7f
4176
    M3OP( "i64.div_u",          -1, i_64,   d_binOpList (u64, Divide)               , NULL  ),          // 0x80
4177
    M3OP( "i64.rem_s",          -1, i_64,   d_binOpList (i64, Remainder)            , NULL  ),          // 0x81
4178
    M3OP( "i64.rem_u",          -1, i_64,   d_binOpList (u64, Remainder)            , NULL  ),          // 0x82
4179
    M3OP( "i64.and",            -1, i_64,   d_commutativeBinOpList (u64, And)       , NULL  ),          // 0x83
4180
    M3OP( "i64.or",             -1, i_64,   d_commutativeBinOpList (u64, Or)        , NULL  ),          // 0x84
4181
    M3OP( "i64.xor",            -1, i_64,   d_commutativeBinOpList (u64, Xor)       , NULL  ),          // 0x85
4182
    M3OP( "i64.shl",            -1, i_64,   d_binOpList (u64, ShiftLeft)            , NULL  ),          // 0x86
4183
    M3OP( "i64.shr_s",          -1, i_64,   d_binOpList (i64, ShiftRight)           , NULL  ),          // 0x87
4184
    M3OP( "i64.shr_u",          -1, i_64,   d_binOpList (u64, ShiftRight)           , NULL  ),          // 0x88
4185
    M3OP( "i64.rotl",           -1, i_64,   d_binOpList (u64, Rotl)                 , NULL  ),          // 0x89
4186
    M3OP( "i64.rotr",           -1, i_64,   d_binOpList (u64, Rotr)                 , NULL  ),          // 0x8a
4187
4188
    M3OP_F( "f32.abs",          0,  f_32,   d_unaryOpList(f32, Abs)                 , NULL  ),          // 0x8b
4189
    M3OP_F( "f32.neg",          0,  f_32,   d_unaryOpList(f32, Negate)              , NULL  ),          // 0x8c
4190
    M3OP_F( "f32.ceil",         0,  f_32,   d_unaryOpList(f32, Ceil)                , NULL  ),          // 0x8d
4191
    M3OP_F( "f32.floor",        0,  f_32,   d_unaryOpList(f32, Floor)               , NULL  ),          // 0x8e
4192
    M3OP_F( "f32.trunc",        0,  f_32,   d_unaryOpList(f32, Trunc)               , NULL  ),          // 0x8f
4193
    M3OP_F( "f32.nearest",      0,  f_32,   d_unaryOpList(f32, Nearest)             , NULL  ),          // 0x90
4194
    M3OP_F( "f32.sqrt",         0,  f_32,   d_unaryOpList(f32, Sqrt)                , NULL  ),          // 0x91
4195
4196
    M3OP_F( "f32.add",          -1, f_32,   d_commutativeBinOpList (f32, Add)       , NULL  ),          // 0x92
4197
    M3OP_F( "f32.sub",          -1, f_32,   d_binOpList (f32, Subtract)             , NULL  ),          // 0x93
4198
    M3OP_F( "f32.mul",          -1, f_32,   d_commutativeBinOpList (f32, Multiply)  , NULL  ),          // 0x94
4199
    M3OP_F( "f32.div",          -1, f_32,   d_binOpList (f32, Divide)               , NULL  ),          // 0x95
4200
    M3OP_F( "f32.min",          -1, f_32,   d_commutativeBinOpList (f32, Min)       , NULL  ),          // 0x96
4201
    M3OP_F( "f32.max",          -1, f_32,   d_commutativeBinOpList (f32, Max)       , NULL  ),          // 0x97
4202
    M3OP_F( "f32.copysign",     -1, f_32,   d_binOpList (f32, CopySign)             , NULL  ),          // 0x98
4203
4204
    M3OP_F( "f64.abs",          0,  f_64,   d_unaryOpList(f64, Abs)                 , NULL  ),          // 0x99
4205
    M3OP_F( "f64.neg",          0,  f_64,   d_unaryOpList(f64, Negate)              , NULL  ),          // 0x9a
4206
    M3OP_F( "f64.ceil",         0,  f_64,   d_unaryOpList(f64, Ceil)                , NULL  ),          // 0x9b
4207
    M3OP_F( "f64.floor",        0,  f_64,   d_unaryOpList(f64, Floor)               , NULL  ),          // 0x9c
4208
    M3OP_F( "f64.trunc",        0,  f_64,   d_unaryOpList(f64, Trunc)               , NULL  ),          // 0x9d
4209
    M3OP_F( "f64.nearest",      0,  f_64,   d_unaryOpList(f64, Nearest)             , NULL  ),          // 0x9e
4210
    M3OP_F( "f64.sqrt",         0,  f_64,   d_unaryOpList(f64, Sqrt)                , NULL  ),          // 0x9f
4211
4212
    M3OP_F( "f64.add",          -1, f_64,   d_commutativeBinOpList (f64, Add)       , NULL  ),          // 0xa0
4213
    M3OP_F( "f64.sub",          -1, f_64,   d_binOpList (f64, Subtract)             , NULL  ),          // 0xa1
4214
    M3OP_F( "f64.mul",          -1, f_64,   d_commutativeBinOpList (f64, Multiply)  , NULL  ),          // 0xa2
4215
    M3OP_F( "f64.div",          -1, f_64,   d_binOpList (f64, Divide)               , NULL  ),          // 0xa3
4216
    M3OP_F( "f64.min",          -1, f_64,   d_commutativeBinOpList (f64, Min)       , NULL  ),          // 0xa4
4217
    M3OP_F( "f64.max",          -1, f_64,   d_commutativeBinOpList (f64, Max)       , NULL  ),          // 0xa5
4218
    M3OP_F( "f64.copysign",     -1, f_64,   d_binOpList (f64, CopySign)             , NULL  ),          // 0xa6
4219
4220
    M3OP( "i32.wrap/i64",       0,  i_32,   d_unaryOpList (i32, Wrap_i64),          NULL    ),          // 0xa7
4221
    M3OP_F( "i32.trunc_s/f32",  0,  i_32,   d_convertOpList (i32_Trunc_f32),        Compile_Convert ),  // 0xa8
4222
    M3OP_F( "i32.trunc_u/f32",  0,  i_32,   d_convertOpList (u32_Trunc_f32),        Compile_Convert ),  // 0xa9
4223
    M3OP_F( "i32.trunc_s/f64",  0,  i_32,   d_convertOpList (i32_Trunc_f64),        Compile_Convert ),  // 0xaa
4224
    M3OP_F( "i32.trunc_u/f64",  0,  i_32,   d_convertOpList (u32_Trunc_f64),        Compile_Convert ),  // 0xab
4225
4226
    M3OP( "i64.extend_s/i32",   0,  i_64,   d_unaryOpList (i64, Extend_i32),        NULL    ),          // 0xac
4227
    M3OP( "i64.extend_u/i32",   0,  i_64,   d_unaryOpList (i64, Extend_u32),        NULL    ),          // 0xad
4228
4229
    M3OP_F( "i64.trunc_s/f32",  0,  i_64,   d_convertOpList (i64_Trunc_f32),        Compile_Convert ),  // 0xae
4230
    M3OP_F( "i64.trunc_u/f32",  0,  i_64,   d_convertOpList (u64_Trunc_f32),        Compile_Convert ),  // 0xaf
4231
    M3OP_F( "i64.trunc_s/f64",  0,  i_64,   d_convertOpList (i64_Trunc_f64),        Compile_Convert ),  // 0xb0
4232
    M3OP_F( "i64.trunc_u/f64",  0,  i_64,   d_convertOpList (u64_Trunc_f64),        Compile_Convert ),  // 0xb1
4233
4234
    M3OP_F( "f32.convert_s/i32",0,  f_32,   d_convertOpList (f32_Convert_i32),      Compile_Convert ),  // 0xb2
4235
    M3OP_F( "f32.convert_u/i32",0,  f_32,   d_convertOpList (f32_Convert_u32),      Compile_Convert ),  // 0xb3
4236
    M3OP_F( "f32.convert_s/i64",0,  f_32,   d_convertOpList (f32_Convert_i64),      Compile_Convert ),  // 0xb4
4237
    M3OP_F( "f32.convert_u/i64",0,  f_32,   d_convertOpList (f32_Convert_u64),      Compile_Convert ),  // 0xb5
4238
4239
    M3OP_F( "f32.demote/f64",   0,  f_32,   d_unaryOpList (f32, Demote_f64),        NULL    ),          // 0xb6
4240
4241
    M3OP_F( "f64.convert_s/i32",0,  f_64,   d_convertOpList (f64_Convert_i32),      Compile_Convert ),  // 0xb7
4242
    M3OP_F( "f64.convert_u/i32",0,  f_64,   d_convertOpList (f64_Convert_u32),      Compile_Convert ),  // 0xb8
4243
    M3OP_F( "f64.convert_s/i64",0,  f_64,   d_convertOpList (f64_Convert_i64),      Compile_Convert ),  // 0xb9
4244
    M3OP_F( "f64.convert_u/i64",0,  f_64,   d_convertOpList (f64_Convert_u64),      Compile_Convert ),  // 0xba
4245
4246
    M3OP_F( "f64.promote/f32",  0,  f_64,   d_unaryOpList (f64, Promote_f32),       NULL    ),          // 0xbb
4247
4248
    M3OP_F( "i32.reinterpret/f32",0,i_32,   d_convertOpList (i32_Reinterpret_f32),  Compile_Convert ),  // 0xbc
4249
    M3OP_F( "i64.reinterpret/f64",0,i_64,   d_convertOpList (i64_Reinterpret_f64),  Compile_Convert ),  // 0xbd
4250
    M3OP_F( "f32.reinterpret/i32",0,f_32,   d_convertOpList (f32_Reinterpret_i32),  Compile_Convert ),  // 0xbe
4251
    M3OP_F( "f64.reinterpret/i64",0,f_64,   d_convertOpList (f64_Reinterpret_i64),  Compile_Convert ),  // 0xbf
4252
4253
    M3OP( "i32.extend8_s",       0,  i_32,   d_unaryOpList (i32, Extend8_s),        NULL    ),          // 0xc0
4254
    M3OP( "i32.extend16_s",      0,  i_32,   d_unaryOpList (i32, Extend16_s),       NULL    ),          // 0xc1
4255
    M3OP( "i64.extend8_s",       0,  i_64,   d_unaryOpList (i64, Extend8_s),        NULL    ),          // 0xc2
4256
    M3OP( "i64.extend16_s",      0,  i_64,   d_unaryOpList (i64, Extend16_s),       NULL    ),          // 0xc3
4257
    M3OP( "i64.extend32_s",      0,  i_64,   d_unaryOpList (i64, Extend32_s),       NULL    ),          // 0xc4
4258
4259
4260
#if d_m3HasRefTypes
4261
    [c_waOp_refNull]   = M3OP( "ref.null",    1, any,  d_emptyOpList,   Compile_Ref_Null ),
4262
    [c_waOp_refIsNull] = M3OP( "ref.is_null", 0, i_32, d_emptyOpList,   Compile_Ref_IsNull ),
4263
    [c_waOp_refFunc]   = M3OP( "ref.func",    1, any,  d_emptyOpList,   Compile_Ref_Func ),
4264
#if d_m3HasTypedRefs
4265
    [c_waOp_refAsNonNull] = M3OP( "ref.as_non_null", 0, any, d_emptyOpList, Compile_Ref_AsNonNull ),
4266
#endif
4267
#endif
4268
4269
# if d_m3CascadedOpcodes
4270
    [c_waOp_extended] = M3OP( "0xFC", 0, c_m3Type_unknown,   d_emptyOpList,  Compile_ExtendedOpcode ),
4271
# endif
4272
4273
// Internal operations, for codepage logging only. They sit past every opcode the
4274
// designated entries above claim, so GetOpInfo () can never reach them by opcode.
4275
# ifdef DEBUG // for codepage logging. the order doesn't matter:
4276
#define d_m3DebugOp(OP) M3OP (#OP, 0, none, { op_##OP })
4277
4278
# if d_m3HasFloat
4279
#define d_m3DebugTypedOp(OP) M3OP (#OP, 0, none, { op_##OP##_i32, op_##OP##_i64, op_##OP##_f32, op_##OP##_f64, })
4280
# else
4281
#define d_m3DebugTypedOp(OP) M3OP (#OP, 0, none, { op_##OP##_i32, op_##OP##_i64 })
4282
# endif
4283
4284
    d_m3DebugOp (Compile),          d_m3DebugOp (Entry),            d_m3DebugOp (End),
4285
    d_m3DebugOp (Unsupported),      d_m3DebugOp (CallRawFunction),
4286
4287
    d_m3DebugOp (GetGlobal_s32),    d_m3DebugOp (GetGlobal_s64),    d_m3DebugOp (ContinueLoop),     d_m3DebugOp (ContinueLoopIf),
4288
4289
    d_m3DebugOp (CopySlot_32),      d_m3DebugOp (PreserveCopySlot_32), d_m3DebugOp (If_s),          d_m3DebugOp (BranchIfPrologue_s),
4290
    d_m3DebugOp (CopySlot_64),      d_m3DebugOp (PreserveCopySlot_64), d_m3DebugOp (If_r),          d_m3DebugOp (BranchIfPrologue_r),
4291
4292
    d_m3DebugOp (Select_i32_rss),   d_m3DebugOp (Select_i32_srs),   d_m3DebugOp (Select_i32_ssr),   d_m3DebugOp (Select_i32_sss),
4293
    d_m3DebugOp (Select_i64_rss),   d_m3DebugOp (Select_i64_srs),   d_m3DebugOp (Select_i64_ssr),   d_m3DebugOp (Select_i64_sss),
4294
4295
# if d_m3HasFloat
4296
    d_m3DebugOp (Select_f32_sss),   d_m3DebugOp (Select_f32_srs),   d_m3DebugOp (Select_f32_ssr),
4297
    d_m3DebugOp (Select_f32_rss),   d_m3DebugOp (Select_f32_rrs),   d_m3DebugOp (Select_f32_rsr),
4298
4299
    d_m3DebugOp (Select_f64_sss),   d_m3DebugOp (Select_f64_srs),   d_m3DebugOp (Select_f64_ssr),
4300
    d_m3DebugOp (Select_f64_rss),   d_m3DebugOp (Select_f64_rrs),   d_m3DebugOp (Select_f64_rsr),
4301
# endif
4302
4303
    d_m3DebugOp (MemFill),          d_m3DebugOp (MemCopy),          d_m3DebugOp (MemInit),          d_m3DebugOp (DataDrop),
4304
4305
# if d_m3HasGasMetering
4306
    d_m3DebugOp (UseGas),
4307
# endif
4308
4309
# if d_m3HasRefTypes
4310
    d_m3DebugOp (TableGet),         d_m3DebugOp (TableSet),         d_m3DebugOp (TableSize),
4311
    d_m3DebugOp (TableGrow),        d_m3DebugOp (TableFill),        d_m3DebugOp (TableInit),
4312
    d_m3DebugOp (ElemDrop),         d_m3DebugOp (TableCopy),
4313
# endif
4314
4315
    d_m3DebugTypedOp (SetGlobal),   d_m3DebugOp (SetGlobal_s32),    d_m3DebugOp (SetGlobal_s64),
4316
4317
    d_m3DebugTypedOp (SetRegister), d_m3DebugTypedOp (SetSlot),     d_m3DebugTypedOp (PreserveSetSlot),
4318
# endif
4319
4320
# ifdef DEBUG
4321
    M3OP( "termination", 0, c_m3Type_unknown ) // for find_operation_info
4322
# endif
4323
};
4324
4325
const M3OpInfo c_operationsFC[] =
4326
{
4327
    M3OP_F( "i32.trunc_s:sat/f32",0,  i_32,   d_convertOpList (i32_TruncSat_f32),        Compile_Convert ),  // 0x00
4328
    M3OP_F( "i32.trunc_u:sat/f32",0,  i_32,   d_convertOpList (u32_TruncSat_f32),        Compile_Convert ),  // 0x01
4329
    M3OP_F( "i32.trunc_s:sat/f64",0,  i_32,   d_convertOpList (i32_TruncSat_f64),        Compile_Convert ),  // 0x02
4330
    M3OP_F( "i32.trunc_u:sat/f64",0,  i_32,   d_convertOpList (u32_TruncSat_f64),        Compile_Convert ),  // 0x03
4331
    M3OP_F( "i64.trunc_s:sat/f32",0,  i_64,   d_convertOpList (i64_TruncSat_f32),        Compile_Convert ),  // 0x04
4332
    M3OP_F( "i64.trunc_u:sat/f32",0,  i_64,   d_convertOpList (u64_TruncSat_f32),        Compile_Convert ),  // 0x05
4333
    M3OP_F( "i64.trunc_s:sat/f64",0,  i_64,   d_convertOpList (i64_TruncSat_f64),        Compile_Convert ),  // 0x06
4334
    M3OP_F( "i64.trunc_u:sat/f64",0,  i_64,   d_convertOpList (u64_TruncSat_f64),        Compile_Convert ),  // 0x07
4335
4336
    M3OP( "memory.init",            0,  none,   d_emptyOpList,                           Compile_Memory_Init ),     // 0x08
4337
    M3OP( "data.drop",              0,  none,   d_emptyOpList,                           Compile_Data_Drop ),       // 0x09
4338
4339
    M3OP( "memory.copy",            0,  none,   d_emptyOpList,                           Compile_Memory_CopyFill ), // 0x0a
4340
    M3OP( "memory.fill",            0,  none,   d_emptyOpList,                           Compile_Memory_CopyFill ), // 0x0b
4341
4342
#if d_m3HasRefTypes
4343
    M3OP( "table.init",             0,  none,   d_emptyOpList,                           Compile_Table_Init ),      // 0x0c
4344
    M3OP( "elem.drop",              0,  none,   d_emptyOpList,                           Compile_Elem_Drop ),       // 0x0d
4345
    M3OP( "table.copy",             0,  none,   d_emptyOpList,                           Compile_Table_Copy ),      // 0x0e
4346
#else
4347
    M3OP_RESERVED, M3OP_RESERVED, M3OP_RESERVED,                                                                    // 0x0c...0x0e
4348
#endif
4349
4350
#if d_m3HasRefTypes
4351
    M3OP( "table.grow",             0,  i_32,   d_emptyOpList,                           Compile_Table_GrowFill ),  // 0x0f
4352
    M3OP( "table.size",             1,  i_32,   d_emptyOpList,                           Compile_Table_Size ),      // 0x10
4353
    M3OP( "table.fill",             0,  none,   d_emptyOpList,                           Compile_Table_GrowFill ),  // 0x11
4354
#else
4355
    M3OP_RESERVED, M3OP_RESERVED, M3OP_RESERVED,                                                                    // 0x0f...0x11
4356
#endif
4357
4358
4359
# ifdef DEBUG
4360
    M3OP( "termination", 0, c_m3Type_unknown ) // for find_operation_info
4361
# endif
4362
};
4363
4364
// clang-format on
4365
4366
4367
// Opcodes the spec reserves leave zeroed holes in the tables above: no compiler
4368
// and no operations. Every implemented op has at least one of the two.
4369
static inline
4370
bool IsImplementedOp (IM3OpInfo i_info)
4371
104k
{
4372
104k
    return (i_info->compiler != NULL or i_info->operations[0] != NULL);
4373
104k
}
4374
4375
const u32 c_numOperations   = M3_COUNT_OF(c_operations);
4376
const u32 c_numOperationsFC = M3_COUNT_OF(c_operationsFC);
4377
4378
// c_operations is indexed by opcode only up to c_waOp_lastCore; past that it
4379
// holds internal operations (DEBUG builds) plus a few designated entries.
4380
static inline
4381
bool IsCoreOpcode (m3opcode_t opcode)
4382
104k
{
4383
104k
    return (opcode <= c_waOp_lastCore
4384
927
#if d_m3HasRefTypes
4385
927
            or (opcode >= c_waOp_refNull and opcode <= c_waOp_refFunc)
4386
#  if d_m3HasTypedRefs
4387
            or opcode == c_waOp_refAsNonNull
4388
#  endif
4389
804
#endif
4390
804
            or opcode == c_waOp_extended);
4391
104k
}
4392
4393
IM3OpInfo GetOpInfo (m3opcode_t opcode)
4394
105k
{
4395
105k
    IM3OpInfo info = NULL;
4396
4397
105k
    switch (opcode >> 8) {
4398
104k
    case 0x00:
4399
104k
        if (M3_LIKELY(IsCoreOpcode(opcode))) {
4400
104k
            info = &c_operations[opcode];
4401
104k
        }
4402
104k
        break;
4403
551
    case c_waOp_extended:
4404
551
        opcode &= 0xFF;
4405
551
        if (M3_LIKELY(opcode <= c_waOp_lastExtended)) {
4406
538
            info = &c_operationsFC[opcode];
4407
538
        }
4408
551
        break;
4409
105k
    }
4410
4411
105k
    return (info and IsImplementedOp(info)) ? info : NULL;
4412
105k
}
4413
4414
#if d_m3HasGasMetering
4415
4416
//----- GAS METERING ------------------------------------------------------------------------------------------------------
4417
//
4418
// Instrumentation the engine applies to a function body as it compiles it,
4419
// following ewasm's metering design, which does the same thing to the Wasm ahead
4420
// of time: https://github.com/ewasm/design/blob/master/metering.md
4421
//
4422
// The body is cut into segments at the instructions that can transfer control,
4423
// and each segment is charged in full, before any of it runs, by an op_UseGas
4424
// carrying the total as an immediate. Prepaying is what makes the accounting
4425
// hold: a call, or a loop iteration that never comes back round, has already
4426
// been paid for by the time it leaves.
4427
4428
// The cost table's unit is a ten-thousandth of a gas, which is why these numbers
4429
// are as large as they are. Two runs of instructions it does not price had to be
4430
// filled in. It has no floating point at all, ewasm having banned it, so float
4431
// instructions are priced as their integer counterparts - division and square
4432
// root as an integer divide, the rest as integer arithmetic. And it predates the
4433
// reference, bulk memory and exception instructions, which are priced as the
4434
// nearest thing it does list: a table access as a memory access, an operation
4435
// over a whole region as memory.grow, a throw as a branch.
4436
enum {
4437
    c_gasLocal   = 1,          // charged once per declared local, at function entry
4438
    c_gasNominal = 1,          // nop, block, loop, if, const
4439
    c_gasArith   = 45,
4440
    c_gasShift   = 67,
4441
    c_gasBranch  = 90,
4442
    c_gasMemSize = 100,
4443
    c_gasAccess  = 120,        // a local, global, table or memory access
4444
    c_gasHeavy   = 10000,      // an indirect call, or anything that works over a whole region
4445
    c_gasDivide  = 36000
4446
};
4447
4448
// The most one op_UseGas can carry. A segment that reaches it is split into
4449
// consecutive charges, which prepays exactly the same amount.
4450
13.8k
#  define d_m3MaxSegmentGas   0xFFFFFFFF
4451
4452
static
4453
u32 GetGasCost (m3opcode_t i_opcode)
4454
16.1k
{
4455
16.1k
    switch (i_opcode) {
4456
6.01k
    case 0x00:                        // unreachable
4457
6.67k
    case 0x01:                        // nop
4458
6.96k
    case c_waOp_block:
4459
7.21k
    case c_waOp_loop:
4460
7.37k
    case c_waOp_if:
4461
7.39k
    case c_waOp_tryTable:             // a block that also installs handlers
4462
7.39k
    case c_waOp_refNull:              // a constant, by another name
4463
7.46k
    case c_waOp_refFunc:
4464
7.46k
        return c_gasNominal;
4465
4466
1.02k
    case c_waOp_end:
4467
1.02k
        return 0;                     // the one control instruction the table prices at nothing
4468
4469
14
    case c_waOp_else:
4470
75
    case c_waOp_branch:
4471
151
    case c_waOp_branchIf:
4472
388
    case c_waOp_return:
4473
932
    case c_waOp_call:
4474
1.08k
    case c_waOp_returnCall:
4475
1.14k
    case c_waOp_throw:
4476
1.20k
    case c_waOp_throwRef:
4477
1.20k
        return c_gasBranch;
4478
4479
67
    case c_waOp_branchTable:
4480
164
    case 0x1a:                        // drop
4481
720
    case 0x1b:                        // select
4482
720
    case c_waOp_selectTyped:
4483
720
        return c_gasAccess;
4484
4485
80
    case c_waOp_callIndirect:
4486
164
    case c_waOp_returnCallIndirect:
4487
164
    case c_waOp_callRef:
4488
164
    case c_waOp_returnCallRef:
4489
164
    case c_waOp_memoryInit:
4490
231
    case c_waOp_memoryCopy:
4491
355
    case c_waOp_memoryFill:
4492
355
    case c_waOp_tableInit:
4493
472
    case c_waOp_tableCopy:
4494
521
    case c_waOp_tableGrow:
4495
571
    case c_waOp_tableFill:
4496
778
    case 0x40:                        // memory.grow
4497
778
        return c_gasHeavy;
4498
4499
0
    case c_waOp_dataDrop:
4500
0
    case c_waOp_elemDrop:
4501
41
    case c_waOp_tableSize:
4502
96
    case 0x3f:                        // memory.size
4503
96
        return c_gasMemSize;
4504
4505
17
    case c_waOp_refIsNull:
4506
17
    case c_waOp_refAsNonNull:
4507
17
        return c_gasArith;
4508
4509
4.82k
    default:
4510
4.82k
        break;
4511
16.1k
    }
4512
4513
    // The two integer arithmetic runs are priced by position, i32 at 0x6a and
4514
    // i64 at 0x7c, the table giving both the same shape
4515
4.82k
    if ((i_opcode >= 0x6a and i_opcode <= 0x78) or (i_opcode >= 0x7c and i_opcode <= 0x8a)) {
4516
        // clang-format off
4517
408
        static const u16 c_arithmeticCosts [] = {
4518
408
            c_gasArith,  c_gasArith,  c_gasArith,                   // add, sub, mul
4519
408
            c_gasDivide, c_gasDivide, c_gasDivide, c_gasDivide,     // div_s, div_u, rem_s, rem_u
4520
408
            c_gasArith,  c_gasArith,  c_gasArith,                   // and, or, xor
4521
408
            c_gasShift,  c_gasShift,  c_gasShift,                   // shl, shr_s, shr_u
4522
408
            c_gasBranch, c_gasBranch                                // rotl, rotr
4523
408
        };
4524
        // clang-format on
4525
4526
408
        return c_arithmeticCosts[i_opcode - (i_opcode <= 0x78 ? 0x6a : 0x7c)];
4527
408
    }
4528
4529
4.41k
    if (i_opcode >= 0x20 and i_opcode <= 0x3e) {
4530
1.87k
        return c_gasAccess;           // locals, globals, table accesses, loads and stores
4531
1.87k
    }
4532
2.53k
    if (i_opcode >= 0x41 and i_opcode <= 0x44) {
4533
1.41k
        return c_gasNominal;          // i32/i64/f32/f64.const
4534
1.41k
    }
4535
1.12k
    if (i_opcode >= 0x8b and i_opcode <= 0xa6) {
4536
        // the float run, priced as integer arithmetic except where it divides
4537
76
        bool divides = (i_opcode == 0x91 or i_opcode == 0x95 or     // f32.sqrt, f32.div
4538
76
                        i_opcode == 0x9f or i_opcode == 0xa3);      // f64.sqrt, f64.div
4539
4540
76
        return divides ? c_gasDivide : c_gasArith;
4541
76
    }
4542
4543
    // comparisons and clz/ctz/popcnt (0x45..0x69, 0x79..0x7b), the conversions
4544
    // and sign extensions (0xa7..0xc4), the saturating truncations, and anything
4545
    // else that reaches here: plain arithmetic
4546
1.04k
    return c_gasArith;
4547
1.12k
}
4548
4549
// The instructions ewasm's design segments a body at. A segment runs up to and
4550
// including the next one of these, so every cycle a body can execute - a loop's
4551
// back edge, a branch out of a block, falling off the end of a function - passes
4552
// through at least one, and no path can run unmetered however it is entered.
4553
static
4554
bool IsGasSegmentEnd (m3opcode_t i_opcode)
4555
16.1k
{
4556
16.1k
    switch (i_opcode) {
4557
241
    case c_waOp_loop:
4558
402
    case c_waOp_if:
4559
416
    case c_waOp_else:
4560
1.43k
    case c_waOp_end:
4561
1.49k
    case c_waOp_branch:
4562
1.57k
    case c_waOp_branchIf:
4563
1.64k
    case c_waOp_branchTable:
4564
1.87k
    case c_waOp_return:
4565
1.87k
        return true;
4566
14.2k
    default:
4567
14.2k
        return false;
4568
16.1k
    }
4569
16.1k
}
4570
4571
// Ends the open segment by writing what it accumulated into the immediate its
4572
// op_UseGas is still waiting on. Emitting onto a later code page does not move
4573
// an earlier one, so the reservation is still where it was left.
4574
static
4575
void CloseGasSegment (IM3Compilation o)
4576
1.87k
{
4577
1.87k
    if (o->gasPatch) {
4578
1.87k
        u32 cost = o->gasCost;
4579
1.87k
        memcpy(o->gasPatch, &cost, sizeof(cost));
4580
4581
1.87k
        o->gasPatch = NULL;
4582
1.87k
        o->gasCost  = 0;
4583
1.87k
    }
4584
1.87k
}
4585
4586
// Charges one instruction to the segment being compiled, opening a segment at
4587
// this point in the code stream if none is open, and settling the segment when
4588
// the instruction is one that ends it. Called before the instruction is
4589
// compiled, which is what both halves need: the op_UseGas lands ahead of what it
4590
// pays for, and the cost is final before a block-structured instruction - loop,
4591
// if, else - compiles its body from inside its own compiler. The body then opens
4592
// a segment of its own, inside the loop or the arm, where it is charged on every
4593
// pass rather than once on the way in.
4594
static
4595
M3Result MeterOpcode (IM3Compilation o, m3opcode_t i_opcode)
4596
84.8k
{
4597
84.8k
    M3Result result = m3Err_none;
4598
4599
    // constant expressions are not metered: they run once at instantiation,
4600
    // before the module is anything a gas budget was handed out for
4601
84.8k
    if (o->function and o->page and o->runtime->gasLimit) {
4602
        // with cascaded opcodes only the 0xFC prefix has been read so far; the
4603
        // instruction it names is the byte the compiler is about to take
4604
16.1k
        if (i_opcode == c_waOp_extended and o->wasm < o->wasmEnd) {
4605
506
            i_opcode = (m3opcode_t)((i_opcode << 8) | *o->wasm);
4606
506
        }
4607
4608
16.1k
        u32 cost = GetGasCost(i_opcode);
4609
4610
16.1k
        if (o->gasPatch and o->gasCost > d_m3MaxSegmentGas - cost) {
4611
0
            CloseGasSegment(o);
4612
0
        }
4613
4614
16.1k
        if (not o->gasPatch) {
4615
2.25k
_           (EmitOp(o, op_UseGas));
4616
2.25k
            o->gasPatch = (void*)GetPagePC(o->page);
4617
2.25k
            EmitConstant32(o, 0);
4618
2.25k
        }
4619
4620
16.1k
        o->gasCost += cost;
4621
4622
16.1k
        if (IsGasSegmentEnd(i_opcode)) {
4623
1.87k
            CloseGasSegment(o);
4624
1.87k
        }
4625
16.1k
    }
4626
4627
84.8k
    _catch: return result;
4628
84.8k
}
4629
4630
#endif // d_m3HasGasMetering
4631
4632
4633
M3Result CompileBlockStatements (IM3Compilation o)
4634
2.03k
{
4635
2.03k
    M3Result result   = m3Err_none;
4636
2.03k
    bool     validEnd = false;
4637
4638
85.2k
    while (o->wasm < o->wasmEnd) {
4639
#if d_m3EnableOpTracing
4640
        if (o->numEmits) {
4641
            EmitOp(o, op_DumpStack);
4642
            EmitConstant32(o, o->numOpcodes);
4643
            EmitConstant32(o, GetMaxUsedSlotPlusOne(o));
4644
            EmitPointer(o, o->function);
4645
4646
            o->numEmits = 0;
4647
        }
4648
#endif
4649
4650
85.2k
        m3opcode_t opcode;
4651
85.2k
        o->lastOpcodeStart = o->wasm;
4652
85.2k
_       (Read_opcode(&opcode, &o->wasm, o->wasmEnd));
4653
85.2k
        log_opcode(o, opcode);
4654
4655
        // Restrict opcodes when evaluating expressions
4656
85.2k
        if (not o->function) {
4657
            // clang-format off
4658
68.7k
            switch (opcode) {
4659
49.3k
            case c_waOp_i32_const: case c_waOp_i64_const:
4660
52.7k
            case c_waOp_f32_const: case c_waOp_f64_const:
4661
52.9k
            case c_waOp_getGlobal: case c_waOp_end:
4662
52.9k
#if d_m3HasRefTypes
4663
53.0k
            case c_waOp_refNull:   case c_waOp_refFunc:
4664
53.0k
#endif
4665
53.0k
#if d_m3HasExtendedConst
4666
58.2k
            case c_waOp_i32_add:   case c_waOp_i32_sub:   case c_waOp_i32_mul:
4667
68.7k
            case c_waOp_i64_add:   case c_waOp_i64_sub:   case c_waOp_i64_mul:
4668
68.7k
#endif
4669
68.7k
                break;
4670
22
            default:
4671
22
                _throw(m3Err_restrictedOpcode);
4672
68.7k
            }
4673
            // clang-format on
4674
68.7k
        }
4675
4676
85.2k
        IM3OpInfo opinfo = GetOpInfo(opcode);
4677
4678
85.2k
        if (opinfo == NULL) {
4679
360
            _throw(ErrorCompile(m3Err_unknownOpcode, o, "opcode '%x' not available", opcode));
4680
0
        }
4681
4682
84.8k
#if d_m3HasGasMetering
4683
84.8k
_       (MeterOpcode(o, opcode));
4684
84.8k
#endif
4685
4686
84.8k
        if (opinfo->compiler) {
4687
67.8k
_           ((*opinfo->compiler)(o, opcode))
4688
67.8k
        } else {
4689
17.0k
_           (Compile_Operator(o, opcode));
4690
17.0k
        }
4691
4692
84.4k
        o->previousOpcode = opcode;
4693
4694
84.4k
        if (opcode == c_waOp_else) {
4695
14
            _throwif(m3Err_wasmMalformed, o->block.opcode != c_waOp_if);
4696
14
            validEnd = true;
4697
14
            break;
4698
84.4k
        } else if (opcode == c_waOp_end) {
4699
1.26k
            validEnd = true;
4700
1.26k
            break;
4701
1.26k
        }
4702
84.4k
    }
4703
1.28k
    _throwif(m3Err_wasmMalformed, not validEnd);
4704
4705
2.03k
_catch:
4706
2.03k
    return result;
4707
1.28k
}
4708
4709
static
4710
M3Result PushBlockResults (IM3Compilation o)
4711
1.21k
{
4712
1.21k
    M3Result result = m3Err_none;
4713
4714
1.21k
    u16 numResults = GetFuncTypeNumResults(o->block.type);
4715
4716
17.4k
    for (u16 i = 0; i < numResults; ++i) {
4717
16.2k
        m3type_t type = GetFuncTypeResultType(o->block.type, i);
4718
4719
16.2k
        if (i == numResults - 1 and IsFpType(type)) {
4720
310
_           (PushRegister(o, type));
4721
15.9k
        } else {
4722
15.9k
_           (PushAllocatedSlot(o, type));
4723
15.9k
        }
4724
16.2k
    }
4725
4726
1.21k
    _catch: return result;
4727
1.21k
}
4728
4729
4730
M3Result CompileBlock (IM3Compilation o, IM3FuncType i_blockType, m3opcode_t i_blockOpcode)
4731
796
{
4732
796
                                                                                        d_m3Assert (not IsRegisterAllocated (o, 0));
4733
796
                                                                                        d_m3Assert (not IsRegisterAllocated (o, 1));
4734
796
    InvalidateFold(o);
4735
4736
796
    M3CompilationScope  outerScope = o->block;
4737
796
    M3CompilationScope* block      = &o->block;
4738
4739
796
    block->outer   = &outerScope;
4740
796
    block->pc      = GetPagePC(o->page);
4741
796
    block->patches = NULL;
4742
796
    block->type    = i_blockType;
4743
796
    block->opcode  = i_blockOpcode;
4744
796
    block->depth++;
4745
4746
    /*
4747
     The block stack frame is a little strange but for good reasons.  Because blocks need to be restarted to
4748
     compile different pathways (if/else), the incoming params must be saved.  The parameters are popped
4749
     and validated.  But, then the stack top is readjusted so they aren't subsequently overwritten.
4750
     Next, the result are preallocated to find destination slots.  But again these are immediately popped
4751
     (deallocated) and the stack top is readjusted to keep these records in pace. This allows branch instructions
4752
     to find their result landing pads.  Finally, the params are copied from the "dead" records and pushed back
4753
     onto the stack as active stack items for the CompileBlockStatements () call.
4754
4755
    [     block      ]
4756
    [     params     ]
4757
    ------------------
4758
    [     result     ]  <---- blockStackIndex
4759
    [      slots     ]
4760
    ------------------
4761
    [   saved param  ]
4762
    [     records    ]
4763
                        <----- exitStackIndex
4764
    */
4765
4766
796
_try {
4767
    // validate and dealloc params ----------------------------
4768
4769
796
    u16 stackIndex = o->stackIndex;
4770
4771
796
    u16 numParams = GetFuncTypeNumParams(i_blockType);
4772
4773
796
    if (i_blockOpcode != c_waOp_else) {
4774
4.41k
        for (u16 i = 0; i < numParams; ++i) {
4775
3.70k
            m3type_t type = GetFuncTypeParamType(i_blockType, numParams - 1 - i);
4776
3.70k
_           (PopType(o, type));
4777
3.69k
        }
4778
716
    } else {
4779
80
        if (IsStackPolymorphic(o) && o->block.blockStackIndex + numParams > o->stackIndex) {
4780
18
            o->stackIndex = o->block.blockStackIndex;
4781
62
        } else {
4782
62
            o->stackIndex -= numParams;
4783
62
        }
4784
80
    }
4785
4786
790
    u16 paramIndex        = o->stackIndex;
4787
790
    block->exitStackIndex = paramIndex; // consume the params at block exit
4788
4789
    // keep copies of param slots in the stack
4790
790
    o->stackIndex = stackIndex;
4791
4792
    // find slots for the results ----------------------------
4793
790
_   (PushBlockResults(o));
4794
4795
790
    stackIndex = o->stackIndex;
4796
4797
    // dealloc but keep record of the result slots in the stack
4798
790
    u16 numResults = GetFuncTypeNumResults(i_blockType);
4799
10.4k
    while (numResults--) {
4800
9.61k
        Pop(o);
4801
9.61k
    }
4802
4803
790
    block->blockStackIndex = o->stackIndex = stackIndex;
4804
4805
    // push the params back onto the stack -------------------
4806
4.51k
    for (u16 i = 0; i < numParams; ++i) {
4807
3.72k
        m3type_t type = GetFuncTypeParamType(i_blockType, i);
4808
4809
3.72k
        u16 slot = GetSlotForStackIndex(o, paramIndex + i);
4810
3.72k
        Push(o, type, slot);
4811
4812
3.72k
        if (slot >= o->slotFirstDynamicIndex && slot != c_slotUnused) {
4813
1.90k
_           (MarkSlotsAllocatedByType(o, slot, type));
4814
1.90k
        }
4815
3.72k
    }
4816
4817
    //--------------------------------------------------------
4818
4819
790
#if d_m3HasExceptionHandling
4820
    // with the scope pushed and the params back on, a catch label of 0 names
4821
    // this block - which is what the proposal says it should
4822
790
    if (i_blockOpcode == c_waOp_tryTable) {
4823
21
_       (EmitCatchStubs(o));
4824
21
    }
4825
790
#endif
4826
4827
790
_   (CompileBlockStatements(o));
4828
4829
567
_   (ValidateBlockEnd(o));
4830
4831
547
    if (o->function)    // skip for expressions
4832
547
    {
4833
547
        if (not IsStackPolymorphic(o)) {
4834
143
_           (ResolveBlockResults(o, &o->block, /* isBranch: */ false));
4835
135
        }
4836
4837
539
#if d_m3HasExceptionHandling
4838
        // Falling out of the end of a try region retires its handler. This sits
4839
        // ahead of PatchBranches so branches into the block's exit land past it:
4840
        // a branch out of the try popped its own handler at the branch site.
4841
539
        if (i_blockOpcode == c_waOp_tryTable) {
4842
15
_           (EmitOp(o, op_PopHandlers));
4843
15
            EmitConstant32(o, 1);
4844
15
        }
4845
539
#endif
4846
4847
539
_       (UnwindBlockStack(o))
4848
4849
538
        if (not ((i_blockOpcode == c_waOp_if and numResults) or o->previousOpcode == c_waOp_else)) {
4850
429
            o->stackIndex = o->block.exitStackIndex;
4851
429
_           (PushBlockResults(o));
4852
428
        }
4853
538
    }
4854
4855
537
    PatchBranches(o);
4856
4857
537
    o->block = outerScope;
4858
4859
796
} _catch: return result;
4860
537
}
4861
4862
static
4863
M3Result CompileLocals (IM3Compilation o)
4864
928
{
4865
928
    M3Result result;
4866
4867
928
    u32 numLocals = 0;
4868
928
    u32 numLocalBlocks;
4869
928
_   (ReadLEB_u32(&numLocalBlocks, &o->wasm, o->wasmEnd));
4870
4871
1.94k
    for (u32 l = 0; l < numLocalBlocks; ++l) {
4872
1.01k
        u32      varCount;
4873
1.01k
        m3type_t localType;
4874
4875
1.01k
_       (ReadLEB_u32(&varCount, &o->wasm, o->wasmEnd));
4876
1.01k
_       (ParseValueType(o->module, &localType, &o->wasm, o->wasmEnd));
4877
1.01k
        numLocals += varCount;                                                          m3log (compile, "pushing locals. count: %d; type: %s", varCount, c_waTypes [BaseTypeOf(localType)]);
4878
1.40M
        while (varCount--) {
4879
1.40M
_           (PushAllocatedSlot(o, localType));
4880
1.40M
        }
4881
1.01k
    }
4882
4883
928
    if (o->function) {
4884
928
        o->function->numLocals = numLocals;
4885
928
    }
4886
4887
928
    _catch: return result;
4888
928
}
4889
4890
static
4891
M3Result ReserveConstants (IM3Compilation o)
4892
928
{
4893
928
    M3Result result = m3Err_none;
4894
4895
    // in the interest of speed, this blindly scans the Wasm code looking for any byte
4896
    // that looks like an const opcode.
4897
928
    u16 numConstantSlots = 0;
4898
4899
928
    bytes_t wa = o->wasm;
4900
35.4k
    while (wa < o->wasmEnd) {
4901
34.5k
        u8  code     = *wa++;
4902
34.5k
        u16 addSlots = 0;
4903
4904
34.5k
        if (code == c_waOp_i32_const or code == c_waOp_f32_const) {
4905
1.45k
            addSlots = 1;
4906
33.0k
        } else if (code == c_waOp_i64_const or code == c_waOp_f64_const) {
4907
671
            addSlots = GetTypeNumSlots(c_m3Type_i64);
4908
671
        }
4909
4910
34.5k
        if (numConstantSlots + addSlots >= d_m3MaxConstantTableSize) {
4911
0
            break;
4912
0
        }
4913
4914
34.5k
        numConstantSlots += addSlots;
4915
34.5k
    }
4916
4917
    // if constants overflow their reserved stack space, the compiler simply emits op_Const
4918
    // operations as needed. Compiled expressions (global inits) don't pass through this
4919
    // ReserveConstants function and thus always produce inline constants.
4920
4921
928
    AlignSlotToType(&numConstantSlots, c_m3Type_i64);                                           m3log (compile, "reserved constant slots: %d", numConstantSlots);
4922
4923
928
    o->slotFirstDynamicIndex = o->slotFirstConstIndex + numConstantSlots;
4924
4925
928
    if (o->slotFirstDynamicIndex >= d_m3MaxFunctionSlots) {
4926
0
        _throw(m3Err_functionStackOverflow);
4927
0
    }
4928
4929
928
    _catch:
4930
928
    return result;
4931
928
}
4932
4933
4934
// A constant expression compiles like a miniature function root block: no args,
4935
// no locals and a single result, which Compile_End copies down into slot 0 for
4936
// EvaluateExpression to read back.
4937
M3Result CompileExpression (IM3Compilation o, IM3FuncType i_resultType)
4938
125
{
4939
125
    M3Result result = m3Err_none;
4940
4941
125
    o->block.type = i_resultType;
4942
4943
125
    u16 numRetSlots = GetFuncTypeNumResults(i_resultType) * c_ioSlotCount;
4944
4945
367
    for (u16 i = 0; i < numRetSlots; ++i) {
4946
242
        MarkSlotAllocated(o, i);
4947
242
    }
4948
4949
125
    o->maxStackSlots = o->slotMaxAllocatedIndexPlusOne = o->slotFirstDynamicIndex = numRetSlots;
4950
4951
125
_   (CompileBlockStatements(o));
4952
4953
125
    _catch: return result;
4954
94
}
4955
4956
4957
M3Result CompileFunction (IM3Function io_function)
4958
1.51k
{
4959
1.51k
    if (!io_function->wasm) {
4960
        // An import. Either linking pointed it at another module's function -
4961
        // in which case that one carries the body - or a host function was
4962
        // bound to it, or it is simply unsatisfied.
4963
0
        IM3Function impl = Function_Implementation(io_function);
4964
4965
0
        if (impl != io_function) {
4966
0
            if (not impl->compiled) {
4967
0
                M3Result r = CompileFunction(impl);
4968
0
                if (r) {
4969
0
                    return r;
4970
0
                }
4971
0
            }
4972
4973
            // keep the placeholder callable too, for anything still holding it
4974
0
            io_function->compiled = impl->compiled;
4975
0
            return m3Err_none;
4976
0
        }
4977
4978
0
        if (io_function->compiled) {
4979
0
            return m3Err_none;
4980
0
        }
4981
4982
0
        return ErrorModule(m3Err_functionImportMissing, io_function->module, "'%s.%s'",
4983
0
                           GetFunctionImportModuleName(io_function),
4984
0
                           m3_GetFunctionName(io_function));
4985
0
    }
4986
4987
1.51k
#if d_m3EnableValidation
4988
1.51k
    if (not io_function->module->runtime->skipValidation) {
4989
1.51k
        M3Result vr = ValidateFunction(io_function);
4990
1.51k
        if (vr) {
4991
589
            return vr;
4992
589
        }
4993
1.51k
    }
4994
928
#endif
4995
4996
928
    IM3FuncType funcType = io_function->funcType;                   m3log (compile, "compiling: [%d] %s %s; wasm-size: %d",
4997
928
                                                                        io_function->index, m3_GetFunctionName (io_function), SPrintFuncTypeSignature (funcType), (u32) (io_function->wasmEnd - io_function->wasm));
4998
928
    IM3Runtime runtime = io_function->module->runtime;
4999
5000
928
    IM3Compilation o = &runtime->compilation;                       d_m3Assert (d_m3MaxFunctionSlots >= d_m3MaxFunctionStackHeight * (d_m3Use32BitSlots + 1))  // need twice as many slots in 32-bit mode
5001
928
    memset(o, 0x0, sizeof(M3Compilation));
5002
5003
928
    o->runtime    = runtime;
5004
928
    o->module     = io_function->module;
5005
928
    o->function   = io_function;
5006
928
    o->wasm       = io_function->wasm;
5007
928
    o->wasmEnd    = io_function->wasmEnd;
5008
928
    o->block.type = funcType;
5009
5010
928
_try {
5011
    // skip over code size. the end was already calculated during parse phase
5012
928
    u32 size;
5013
928
_   (ReadLEB_u32(&size, &o->wasm, o->wasmEnd));                     d_m3Assert (size == (o->wasmEnd - o->wasm))
5014
5015
928
_   (AcquireCompilationCodePage(o, &o->page));
5016
5017
928
    pc_t pc = GetPagePC(o->page);
5018
5019
928
    u16 numRetSlots = GetFunctionNumReturns(o->function) * c_ioSlotCount;
5020
5021
8.11k
    for (u16 i = 0; i < numRetSlots; ++i) {
5022
7.18k
        MarkSlotAllocated(o, i);
5023
7.18k
    }
5024
5025
928
    o->function->numRetSlots = o->slotFirstDynamicIndex = numRetSlots;
5026
5027
928
    u16 numArgs = GetFunctionNumArgs(o->function);
5028
5029
    // push the arg types to the type stack
5030
2.32k
    for (u16 i = 0; i < numArgs; ++i) {
5031
1.40k
        m3type_t type = GetFunctionArgType(o->function, i);
5032
1.40k
_       (PushAllocatedSlot(o, type));
5033
5034
        // prevent allocator fill-in
5035
1.40k
        o->slotFirstDynamicIndex += c_ioSlotCount;
5036
1.40k
    }
5037
5038
928
    o->slotMaxAllocatedIndexPlusOne = o->function->numRetAndArgSlots = o->slotFirstLocalIndex = o->slotFirstDynamicIndex;
5039
5040
928
_   (CompileLocals(o));
5041
5042
928
#if d_m3HasGasMetering
5043
    // the table charges for a function's locals as well as for its instructions.
5044
    // op_Entry is what sets them up, so the charge rides on the body's first
5045
    // segment, which is emitted right after it
5046
928
    o->gasCost = o->function->numLocals * c_gasLocal;
5047
928
#endif
5048
5049
928
    u16 maxSlot = GetMaxUsedSlotPlusOne(o);
5050
5051
928
    o->function->numLocalBytes = (maxSlot - o->slotFirstLocalIndex) * sizeof(m3slot_t);
5052
5053
928
    o->slotFirstConstIndex = o->slotMaxConstIndex = maxSlot;
5054
5055
    // ReserveConstants initializes o->firstDynamicSlotNumber
5056
928
_   (ReserveConstants(o));
5057
5058
    // start tracking the max stack used (Push() also updates this value) so that op_Entry can precisely detect stack overflow
5059
928
    o->maxStackSlots = o->slotMaxAllocatedIndexPlusOne = o->slotFirstDynamicIndex;
5060
5061
928
    o->block.blockStackIndex = o->stackFirstDynamicIndex = o->stackIndex;                           m3log (compile, "start stack index: %u; max stack slots: %u",
5062
928
                                                                                                           (u32) o->stackFirstDynamicIndex, (u32) o->maxStackSlots);
5063
928
_   (EmitOp(o, op_Entry));
5064
928
    EmitPointer(o, io_function);
5065
5066
928
_   (CompileBlockStatements(o));
5067
5068
467
    _throwif(m3Err_wasmMalformed, o->previousOpcode != c_waOp_end);
5069
5070
467
    io_function->compiled      = pc;
5071
467
    io_function->maxStackSlots = o->maxStackSlots;
5072
5073
467
    u16 numConstantSlots = o->slotMaxConstIndex - o->slotFirstConstIndex;                           m3log (compile, "unique constant slots: %u; unused slots: %u",
5074
467
                                                                                                           numConstantSlots, o->slotFirstDynamicIndex - o->slotMaxConstIndex);
5075
467
    io_function->numConstantBytes = numConstantSlots * sizeof(m3slot_t);
5076
5077
467
    if (numConstantSlots) {
5078
264
        io_function->constants = m3_CopyMem((cbytes_t)o->constants, io_function->numConstantBytes);
5079
264
        _throwifnull(io_function->constants);
5080
264
    }
5081
5082
928
} _catch:
5083
5084
928
    ReleaseCompilationCodePage(o);
5085
5086
928
    return result;
5087
467
}