Coverage Report

Created: 2026-09-01 06:51

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