/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 | } |