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