/src/wasm3/source/m3_compile.c
Line | Count | Source (jump to first uncovered line) |
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 | | |
17 | | //----- EMIT -------------------------------------------------------------------------------------------------------------- |
18 | | |
19 | | static inline |
20 | | pc_t GetPC (IM3Compilation o) |
21 | 356 | { |
22 | 356 | return GetPagePC (o->page); |
23 | 356 | } |
24 | | |
25 | | static M3_NOINLINE |
26 | | M3Result EnsureCodePageNumLines (IM3Compilation o, u32 i_numLines) |
27 | 14.6k | { |
28 | 14.6k | M3Result result = m3Err_none; |
29 | | |
30 | 14.6k | i_numLines += 2; // room for Bridge |
31 | | |
32 | 14.6k | if (NumFreeLines (o->page) < i_numLines) |
33 | 10 | { |
34 | 10 | IM3CodePage page = AcquireCodePageWithCapacity (o->runtime, i_numLines); |
35 | | |
36 | 10 | if (page) |
37 | 10 | { |
38 | 10 | 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); |
39 | 10 | d_m3Assert (NumFreeLines (o->page) >= 2); |
40 | | |
41 | 10 | EmitWord (o->page, op_Branch); |
42 | 10 | EmitWord (o->page, GetPagePC (page)); |
43 | | |
44 | 10 | ReleaseCodePage (o->runtime, o->page); |
45 | | |
46 | 10 | o->page = page; |
47 | 10 | } |
48 | 0 | else result = m3Err_mallocFailedCodePage; |
49 | 10 | } |
50 | | |
51 | 14.6k | return result; |
52 | 14.6k | } |
53 | | |
54 | | static M3_NOINLINE |
55 | | M3Result EmitOp (IM3Compilation o, IM3Operation i_operation) |
56 | 16.1k | { |
57 | 16.1k | M3Result result = m3Err_none; d_m3Assert (i_operation or IsStackPolymorphic (o)); |
58 | | |
59 | | // it's OK for page to be null; when compile-walking the bytecode without emitting |
60 | 16.1k | if (o->page) |
61 | 14.6k | { |
62 | | # if d_m3EnableOpTracing |
63 | | if (i_operation != op_DumpStack) |
64 | | o->numEmits++; |
65 | | # endif |
66 | | |
67 | | // have execution jump to a new page if slots are critically low |
68 | 14.6k | result = EnsureCodePageNumLines (o, d_m3CodePageFreeLinesThreshold); |
69 | | |
70 | 14.6k | if (not result) |
71 | 14.6k | { if (d_m3LogEmit) log_emit (o, i_operation); |
72 | | # if d_m3RecordBacktraces |
73 | | EmitMappingEntry (o->page, o->lastOpcodeStart - o->module->wasmStart); |
74 | | # endif // d_m3RecordBacktraces |
75 | 14.6k | EmitWord (o->page, i_operation); |
76 | 14.6k | } |
77 | 14.6k | } |
78 | | |
79 | 16.1k | return result; |
80 | 16.1k | } |
81 | | |
82 | | // Push an immediate constant into the M3 codestream |
83 | | static M3_NOINLINE |
84 | | void EmitConstant32 (IM3Compilation o, const u32 i_immediate) |
85 | 22 | { |
86 | 22 | if (o->page) |
87 | 22 | EmitWord32 (o->page, i_immediate); |
88 | 22 | } |
89 | | |
90 | | static M3_NOINLINE |
91 | | void EmitSlotOffset (IM3Compilation o, const i32 i_offset) |
92 | 25.4k | { |
93 | 25.4k | if (o->page) |
94 | 24.5k | EmitWord32 (o->page, i_offset); |
95 | 25.4k | } |
96 | | |
97 | | static M3_NOINLINE |
98 | | pc_t EmitPointer (IM3Compilation o, const void * const i_pointer) |
99 | 1.83k | { |
100 | 1.83k | pc_t ptr = GetPagePC (o->page); |
101 | | |
102 | 1.83k | if (o->page) |
103 | 910 | EmitWord (o->page, i_pointer); |
104 | | |
105 | 1.83k | return ptr; |
106 | 1.83k | } |
107 | | |
108 | | static M3_NOINLINE |
109 | | void * ReservePointer (IM3Compilation o) |
110 | 13 | { |
111 | 13 | pc_t ptr = GetPagePC (o->page); |
112 | 13 | EmitPointer (o, NULL); |
113 | 13 | return (void *) ptr; |
114 | 13 | } |
115 | | |
116 | | |
117 | | //------------------------------------------------------------------------------------------------------------------------- |
118 | | |
119 | | #define d_indent " | %s" |
120 | | |
121 | | // just want less letters and numbers to stare at down the way in the compiler table |
122 | | #define i_32 c_m3Type_i32 |
123 | | #define i_64 c_m3Type_i64 |
124 | | #define f_32 c_m3Type_f32 |
125 | | #define f_64 c_m3Type_f64 |
126 | | #define none c_m3Type_none |
127 | | #define any (u8)-1 |
128 | | |
129 | | #if d_m3HasFloat |
130 | | # define FPOP(x) x |
131 | | #else |
132 | | # define FPOP(x) NULL |
133 | | #endif |
134 | | |
135 | | static const IM3Operation c_preserveSetSlot [] = { NULL, op_PreserveSetSlot_i32, op_PreserveSetSlot_i64, |
136 | | FPOP(op_PreserveSetSlot_f32), FPOP(op_PreserveSetSlot_f64) }; |
137 | | static const IM3Operation c_setSetOps [] = { NULL, op_SetSlot_i32, op_SetSlot_i64, |
138 | | FPOP(op_SetSlot_f32), FPOP(op_SetSlot_f64) }; |
139 | | static const IM3Operation c_setGlobalOps [] = { NULL, op_SetGlobal_i32, op_SetGlobal_i64, |
140 | | FPOP(op_SetGlobal_f32), FPOP(op_SetGlobal_f64) }; |
141 | | static const IM3Operation c_setRegisterOps [] = { NULL, op_SetRegister_i32, op_SetRegister_i64, |
142 | | FPOP(op_SetRegister_f32), FPOP(op_SetRegister_f64) }; |
143 | | |
144 | | static const IM3Operation c_intSelectOps [2] [4] = { { op_Select_i32_rss, op_Select_i32_srs, op_Select_i32_ssr, op_Select_i32_sss }, |
145 | | { op_Select_i64_rss, op_Select_i64_srs, op_Select_i64_ssr, op_Select_i64_sss } }; |
146 | | #if d_m3HasFloat |
147 | | static const IM3Operation c_fpSelectOps [2] [2] [3] = { { { op_Select_f32_sss, op_Select_f32_srs, op_Select_f32_ssr }, // selector in slot |
148 | | { op_Select_f32_rss, op_Select_f32_rrs, op_Select_f32_rsr } }, // selector in reg |
149 | | { { op_Select_f64_sss, op_Select_f64_srs, op_Select_f64_ssr }, // selector in slot |
150 | | { op_Select_f64_rss, op_Select_f64_rrs, op_Select_f64_rsr } } }; // selector in reg |
151 | | #endif |
152 | | |
153 | | // all args & returns are 64-bit aligned, so use 2 slots for a d_m3Use32BitSlots=1 build |
154 | | static const u16 c_ioSlotCount = sizeof (u64) / sizeof (m3slot_t); |
155 | | |
156 | | static |
157 | | M3Result AcquireCompilationCodePage (IM3Compilation o, IM3CodePage * o_codePage) |
158 | 96 | { |
159 | 96 | M3Result result = m3Err_none; |
160 | | |
161 | 96 | IM3CodePage page = AcquireCodePage (o->runtime); |
162 | | |
163 | 96 | if (page) |
164 | 96 | { |
165 | | # if (d_m3EnableCodePageRefCounting) |
166 | | { |
167 | | if (o->function) |
168 | | { |
169 | | IM3Function func = o->function; |
170 | | page->info.usageCount++; |
171 | | |
172 | | u32 index = func->numCodePageRefs++; |
173 | | _ (m3ReallocArray (& func->codePageRefs, IM3CodePage, func->numCodePageRefs, index)); |
174 | | func->codePageRefs [index] = page; |
175 | | } |
176 | | } |
177 | | # endif |
178 | 96 | } |
179 | 96 | else _throw (m3Err_mallocFailedCodePage); |
180 | | |
181 | 96 | _catch: |
182 | | |
183 | 96 | * o_codePage = page; |
184 | | |
185 | 96 | return result; |
186 | 96 | } |
187 | | |
188 | | static inline |
189 | | void ReleaseCompilationCodePage (IM3Compilation o) |
190 | 94 | { |
191 | 94 | ReleaseCodePage (o->runtime, o->page); |
192 | 94 | } |
193 | | |
194 | | static inline |
195 | | u16 GetTypeNumSlots (u8 i_type) |
196 | 145k | { |
197 | 145k | # if d_m3Use32BitSlots |
198 | 145k | return Is64BitType (i_type) ? 2 : 1; |
199 | | # else |
200 | | return 1; |
201 | | # endif |
202 | 145k | } |
203 | | |
204 | | static inline |
205 | | void AlignSlotToType (u16 * io_slot, u8 i_type) |
206 | 12.6k | { |
207 | | // align 64-bit words to even slots (if d_m3Use32BitSlots) |
208 | 12.6k | u16 numSlots = GetTypeNumSlots (i_type); |
209 | | |
210 | 12.6k | u16 mask = numSlots - 1; |
211 | 12.6k | * io_slot = (* io_slot + mask) & ~mask; |
212 | 12.6k | } |
213 | | |
214 | | static inline |
215 | | i16 GetStackTopIndex (IM3Compilation o) |
216 | 605 | { d_m3Assert (o->stackIndex > o->stackFirstDynamicIndex or IsStackPolymorphic (o)); |
217 | 605 | return o->stackIndex - 1; |
218 | 605 | } |
219 | | |
220 | | |
221 | | // Items in the static portion of the stack (args/locals) are hidden from GetStackTypeFromTop () |
222 | | // In other words, only "real" Wasm stack items can be inspected. This is important when |
223 | | // returning values, etc. and you need an accurate wasm-view of the stack. |
224 | | static |
225 | | u8 GetStackTypeFromTop (IM3Compilation o, u16 i_offset) |
226 | 3.55k | { |
227 | 3.55k | u8 type = c_m3Type_none; |
228 | | |
229 | 3.55k | ++i_offset; |
230 | 3.55k | if (o->stackIndex >= i_offset) |
231 | 3.55k | { |
232 | 3.55k | u16 index = o->stackIndex - i_offset; |
233 | | |
234 | 3.55k | if (index >= o->stackFirstDynamicIndex) |
235 | 3.42k | type = o->typeStack [index]; |
236 | 3.55k | } |
237 | | |
238 | 3.55k | return type; |
239 | 3.55k | } |
240 | | |
241 | | static inline |
242 | | u8 GetStackTopType (IM3Compilation o) |
243 | 2.84k | { |
244 | 2.84k | return GetStackTypeFromTop (o, 0); |
245 | 2.84k | } |
246 | | |
247 | | static inline |
248 | | u8 GetStackTypeFromBottom (IM3Compilation o, u16 i_offset) |
249 | 109k | { |
250 | 109k | u8 type = c_m3Type_none; |
251 | | |
252 | 109k | if (i_offset < o->stackIndex) |
253 | 109k | type = o->typeStack [i_offset]; |
254 | | |
255 | 109k | return type; |
256 | 109k | } |
257 | | |
258 | | |
259 | 1.66k | static inline bool IsConstantSlot (IM3Compilation o, u16 i_slot) { return (i_slot >= o->slotFirstConstIndex and i_slot < o->slotMaxConstIndex); } |
260 | 146 | static inline bool IsSlotAllocated (IM3Compilation o, u16 i_slot) { return o->m3Slots [i_slot]; } |
261 | | |
262 | | static inline |
263 | | bool IsStackIndexInRegister (IM3Compilation o, i32 i_stackIndex) |
264 | 10.7k | { d_m3Assert (i_stackIndex < o->stackIndex or IsStackPolymorphic (o)); |
265 | 10.7k | if (i_stackIndex >= 0 and i_stackIndex < o->stackIndex) |
266 | 10.7k | return (o->wasmStack [i_stackIndex] >= d_m3Reg0SlotAlias); |
267 | 0 | else |
268 | 0 | return false; |
269 | 10.7k | } |
270 | | |
271 | 155 | static inline u16 GetNumBlockValuesOnStack (IM3Compilation o) { return o->stackIndex - o->block.blockStackIndex; } |
272 | | |
273 | 240 | static inline bool IsStackTopInRegister (IM3Compilation o) { return IsStackIndexInRegister (o, (i32) GetStackTopIndex (o)); } |
274 | 51 | static inline bool IsStackTopMinus1InRegister (IM3Compilation o) { return IsStackIndexInRegister (o, (i32) GetStackTopIndex (o) - 1); } |
275 | 0 | static inline bool IsStackTopMinus2InRegister (IM3Compilation o) { return IsStackIndexInRegister (o, (i32) GetStackTopIndex (o) - 2); } |
276 | | |
277 | 156 | static inline bool IsStackTopInSlot (IM3Compilation o) { return not IsStackTopInRegister (o); } |
278 | | |
279 | 0 | static inline bool IsValidSlot (u16 i_slot) { return (i_slot < d_m3MaxFunctionSlots); } |
280 | | |
281 | | static inline |
282 | | u16 GetStackTopSlotNumber (IM3Compilation o) |
283 | 96 | { |
284 | 96 | i16 i = GetStackTopIndex (o); |
285 | | |
286 | 96 | u16 slot = c_slotUnused; |
287 | | |
288 | 96 | if (i >= 0) |
289 | 96 | slot = o->wasmStack [i]; |
290 | | |
291 | 96 | return slot; |
292 | 96 | } |
293 | | |
294 | | |
295 | | // from bottom |
296 | | static inline |
297 | | u16 GetSlotForStackIndex (IM3Compilation o, u16 i_stackIndex) |
298 | 245k | { d_m3Assert (i_stackIndex < o->stackIndex or IsStackPolymorphic (o)); |
299 | 245k | u16 slot = c_slotUnused; |
300 | | |
301 | 245k | if (i_stackIndex < o->stackIndex) |
302 | 245k | slot = o->wasmStack [i_stackIndex]; |
303 | | |
304 | 245k | return slot; |
305 | 245k | } |
306 | | |
307 | | static inline |
308 | | u16 GetExtraSlotForStackIndex (IM3Compilation o, u16 i_stackIndex) |
309 | 90.5k | { |
310 | 90.5k | u16 baseSlot = GetSlotForStackIndex (o, i_stackIndex); |
311 | | |
312 | 90.5k | if (baseSlot != c_slotUnused) |
313 | 90.5k | { |
314 | 90.5k | u16 extraSlot = GetTypeNumSlots (GetStackTypeFromBottom (o, i_stackIndex)) - 1; |
315 | 90.5k | baseSlot += extraSlot; |
316 | 90.5k | } |
317 | | |
318 | 90.5k | return baseSlot; |
319 | 90.5k | } |
320 | | |
321 | | |
322 | | static inline |
323 | | void TouchSlot (IM3Compilation o, u16 i_slot) |
324 | 21.6k | { |
325 | | // op_Entry uses this value to track and detect stack overflow |
326 | 21.6k | o->maxStackSlots = M3_MAX (o->maxStackSlots, i_slot + 1); |
327 | 21.6k | } |
328 | | |
329 | | static inline |
330 | | void MarkSlotAllocated (IM3Compilation o, u16 i_slot) |
331 | 17.9k | { d_m3Assert (o->m3Slots [i_slot] == 0); // shouldn't be already allocated |
332 | 17.9k | o->m3Slots [i_slot] = 1; |
333 | | |
334 | 17.9k | o->slotMaxAllocatedIndexPlusOne = M3_MAX (o->slotMaxAllocatedIndexPlusOne, i_slot + 1); |
335 | | |
336 | 17.9k | TouchSlot (o, i_slot); |
337 | 17.9k | } |
338 | | |
339 | | static inline |
340 | | void MarkSlotsAllocated (IM3Compilation o, u16 i_slot, u16 i_numSlots) |
341 | 12.2k | { |
342 | 29.3k | while (i_numSlots--) |
343 | 17.0k | MarkSlotAllocated (o, i_slot++); |
344 | 12.2k | } |
345 | | |
346 | | static inline |
347 | | void MarkSlotsAllocatedByType (IM3Compilation o, u16 i_slot, u8 i_type) |
348 | 2.53k | { |
349 | 2.53k | u16 numSlots = GetTypeNumSlots (i_type); |
350 | 2.53k | MarkSlotsAllocated (o, i_slot, numSlots); |
351 | 2.53k | } |
352 | | |
353 | | |
354 | | static |
355 | | M3Result AllocateSlotsWithinRange (IM3Compilation o, u16 * o_slot, u8 i_type, u16 i_startSlot, u16 i_endSlot) |
356 | 12.5k | { |
357 | 12.5k | M3Result result = m3Err_functionStackOverflow; |
358 | | |
359 | 12.5k | u16 numSlots = GetTypeNumSlots (i_type); |
360 | 12.5k | u16 searchOffset = numSlots - 1; |
361 | | |
362 | 12.5k | AlignSlotToType (& i_startSlot, i_type); |
363 | | |
364 | | // search for 1 or 2 consecutive slots in the execution stack |
365 | 12.5k | u16 i = i_startSlot; |
366 | 1.59M | while (i + searchOffset < i_endSlot) |
367 | 1.58M | { |
368 | 1.58M | if (o->m3Slots [i] == 0 and o->m3Slots [i + searchOffset] == 0) |
369 | 9.71k | { |
370 | 9.71k | MarkSlotsAllocated (o, i, numSlots); |
371 | | |
372 | 9.71k | * o_slot = i; |
373 | 9.71k | result = m3Err_none; |
374 | 9.71k | break; |
375 | 9.71k | } |
376 | | |
377 | | // keep 2-slot allocations even-aligned |
378 | 1.57M | i += numSlots; |
379 | 1.57M | } |
380 | | |
381 | 12.5k | return result; |
382 | 12.5k | } |
383 | | |
384 | | static inline |
385 | | M3Result AllocateSlots (IM3Compilation o, u16 * o_slot, u8 i_type) |
386 | 9.71k | { |
387 | 9.71k | return AllocateSlotsWithinRange (o, o_slot, i_type, o->slotFirstDynamicIndex, d_m3MaxFunctionSlots); |
388 | 9.71k | } |
389 | | |
390 | | static inline |
391 | | M3Result AllocateConstantSlots (IM3Compilation o, u16 * o_slot, u8 i_type) |
392 | 2.80k | { |
393 | 2.80k | u16 maxTableIndex = o->slotFirstConstIndex + d_m3MaxConstantTableSize; |
394 | 2.80k | return AllocateSlotsWithinRange (o, o_slot, i_type, o->slotFirstConstIndex, M3_MIN(o->slotFirstDynamicIndex, maxTableIndex)); |
395 | 2.80k | } |
396 | | |
397 | | |
398 | | // TOQUE: this usage count system could be eliminated. real world code doesn't frequently trigger it. just copy to multiple |
399 | | // unique slots. |
400 | | static inline |
401 | | M3Result IncrementSlotUsageCount (IM3Compilation o, u16 i_slot) |
402 | 33 | { d_m3Assert (i_slot < d_m3MaxFunctionSlots); |
403 | 33 | M3Result result = m3Err_none; d_m3Assert (o->m3Slots [i_slot] > 0); |
404 | | |
405 | | // OPTZ (memory): 'm3Slots' could still be fused with 'typeStack' if 4 bits were used to indicate: [0,1,2,many]. The many-case |
406 | | // would scan 'wasmStack' to determine the actual usage count |
407 | 33 | if (o->m3Slots [i_slot] < 0xFF) |
408 | 33 | { |
409 | 33 | o->m3Slots [i_slot]++; |
410 | 33 | } |
411 | 0 | else result = "slot usage count overflow"; |
412 | | |
413 | 33 | return result; |
414 | 33 | } |
415 | | |
416 | | static inline |
417 | | void DeallocateSlot (IM3Compilation o, i16 i_slot, u8 i_type) |
418 | 5.87k | { d_m3Assert (i_slot >= o->slotFirstDynamicIndex); |
419 | 5.87k | d_m3Assert (i_slot < o->slotMaxAllocatedIndexPlusOne); |
420 | 13.7k | for (u16 i = 0; i < GetTypeNumSlots (i_type); ++i, ++i_slot) |
421 | 7.86k | { d_m3Assert (o->m3Slots [i_slot]); |
422 | 7.86k | -- o->m3Slots [i_slot]; |
423 | 7.86k | } |
424 | 5.87k | } |
425 | | |
426 | | |
427 | | static inline |
428 | | bool IsRegisterTypeAllocated (IM3Compilation o, u8 i_type) |
429 | 7 | { |
430 | 7 | return IsRegisterAllocated (o, IsFpType (i_type)); |
431 | 7 | } |
432 | | |
433 | | static inline |
434 | | void AllocateRegister (IM3Compilation o, u32 i_register, u16 i_stackIndex) |
435 | 127 | { d_m3Assert (not IsRegisterAllocated (o, i_register)); |
436 | 127 | o->regStackIndexPlusOne [i_register] = i_stackIndex + 1; |
437 | 127 | } |
438 | | |
439 | | static inline |
440 | | void DeallocateRegister (IM3Compilation o, u32 i_register) |
441 | 126 | { d_m3Assert (IsRegisterAllocated (o, i_register)); |
442 | 126 | o->regStackIndexPlusOne [i_register] = c_m3RegisterUnallocated; |
443 | 126 | } |
444 | | |
445 | | static inline |
446 | | u16 GetRegisterStackIndex (IM3Compilation o, u32 i_register) |
447 | 21 | { d_m3Assert (IsRegisterAllocated (o, i_register)); |
448 | 21 | return o->regStackIndexPlusOne [i_register] - 1; |
449 | 21 | } |
450 | | |
451 | | u16 GetMaxUsedSlotPlusOne (IM3Compilation o) |
452 | 17 | { |
453 | 156 | while (o->slotMaxAllocatedIndexPlusOne > o->slotFirstDynamicIndex) |
454 | 146 | { |
455 | 146 | if (IsSlotAllocated (o, o->slotMaxAllocatedIndexPlusOne - 1)) |
456 | 7 | break; |
457 | | |
458 | 139 | o->slotMaxAllocatedIndexPlusOne--; |
459 | 139 | } |
460 | | |
461 | | # ifdef DEBUG |
462 | | u16 maxSlot = o->slotMaxAllocatedIndexPlusOne; |
463 | | while (maxSlot < d_m3MaxFunctionSlots) |
464 | | { |
465 | | d_m3Assert (o->m3Slots [maxSlot] == 0); |
466 | | maxSlot++; |
467 | | } |
468 | | # endif |
469 | | |
470 | 17 | return o->slotMaxAllocatedIndexPlusOne; |
471 | 17 | } |
472 | | |
473 | | static |
474 | | M3Result PreserveRegisterIfOccupied (IM3Compilation o, u8 i_registerType) |
475 | 219 | { |
476 | 219 | M3Result result = m3Err_none; |
477 | | |
478 | 219 | u32 regSelect = IsFpType (i_registerType); |
479 | | |
480 | 219 | if (IsRegisterAllocated (o, regSelect)) |
481 | 17 | { |
482 | 17 | u16 stackIndex = GetRegisterStackIndex (o, regSelect); |
483 | 17 | DeallocateRegister (o, regSelect); |
484 | | |
485 | 17 | u8 type = GetStackTypeFromBottom (o, stackIndex); |
486 | | |
487 | | // and point to a exec slot |
488 | 17 | u16 slot = c_slotUnused; |
489 | 17 | _ (AllocateSlots (o, & slot, type)); |
490 | 17 | o->wasmStack [stackIndex] = slot; |
491 | | |
492 | 17 | _ (EmitOp (o, c_setSetOps [type])); |
493 | 17 | EmitSlotOffset (o, slot); |
494 | 17 | } |
495 | | |
496 | 219 | _catch: return result; |
497 | 219 | } |
498 | | |
499 | | |
500 | | // all values must be in slots before entering loop, if, and else blocks |
501 | | // otherwise they'd end up preserve-copied in the block to probably different locations (if/else) |
502 | | static inline |
503 | | M3Result PreserveRegisters (IM3Compilation o) |
504 | 80 | { |
505 | 80 | M3Result result; |
506 | | |
507 | 80 | _ (PreserveRegisterIfOccupied (o, c_m3Type_f64)); |
508 | 80 | _ (PreserveRegisterIfOccupied (o, c_m3Type_i64)); |
509 | | |
510 | 80 | _catch: return result; |
511 | 80 | } |
512 | | |
513 | | static |
514 | | M3Result PreserveNonTopRegisters (IM3Compilation o) |
515 | 13 | { |
516 | 13 | M3Result result = m3Err_none; |
517 | | |
518 | 13 | i16 stackTop = GetStackTopIndex (o); |
519 | | |
520 | 13 | if (stackTop >= 0) |
521 | 13 | { |
522 | 13 | if (IsRegisterAllocated (o, 0)) // r0 |
523 | 2 | { |
524 | 2 | if (GetRegisterStackIndex (o, 0) != stackTop) |
525 | 2 | _ (PreserveRegisterIfOccupied (o, c_m3Type_i64)); |
526 | 2 | } |
527 | | |
528 | 13 | if (IsRegisterAllocated (o, 1)) // fp0 |
529 | 2 | { |
530 | 2 | if (GetRegisterStackIndex (o, 1) != stackTop) |
531 | 2 | _ (PreserveRegisterIfOccupied (o, c_m3Type_f64)); |
532 | 2 | } |
533 | 13 | } |
534 | | |
535 | 13 | _catch: return result; |
536 | 13 | } |
537 | | |
538 | | |
539 | | //---------------------------------------------------------------------------------------------------------------------- |
540 | | |
541 | | static |
542 | | M3Result Push (IM3Compilation o, u8 i_type, u16 i_slot) |
543 | 13.1k | { |
544 | 13.1k | M3Result result = m3Err_none; |
545 | | |
546 | | #if !d_m3HasFloat |
547 | | if (i_type == c_m3Type_f32 || i_type == c_m3Type_f64) { |
548 | | return m3Err_unknownOpcode; |
549 | | } |
550 | | #endif |
551 | | |
552 | 13.1k | u16 stackIndex = o->stackIndex++; // printf ("push: %d\n", (i32) i); |
553 | | |
554 | 13.1k | if (stackIndex < d_m3MaxFunctionStackHeight) |
555 | 12.7k | { |
556 | 12.7k | o->wasmStack [stackIndex] = i_slot; |
557 | 12.7k | o->typeStack [stackIndex] = i_type; |
558 | | |
559 | 12.7k | if (IsRegisterSlotAlias (i_slot)) |
560 | 127 | { |
561 | 127 | u32 regSelect = IsFpRegisterSlotAlias (i_slot); |
562 | 127 | AllocateRegister (o, regSelect, stackIndex); |
563 | 127 | } |
564 | | |
565 | 12.7k | if (d_m3LogWasmStack) dump_type_stack (o); |
566 | 12.7k | } |
567 | 423 | else result = m3Err_functionStackOverflow; |
568 | | |
569 | 13.1k | return result; |
570 | 13.1k | } |
571 | | |
572 | | static inline |
573 | | M3Result PushRegister (IM3Compilation o, u8 i_type) |
574 | 127 | { |
575 | 127 | M3Result result = m3Err_none; d_m3Assert ((u16) d_m3Reg0SlotAlias > (u16) d_m3MaxFunctionSlots); |
576 | 127 | u16 slot = IsFpType (i_type) ? d_m3Fp0SlotAlias : d_m3Reg0SlotAlias; d_m3Assert (i_type or IsStackPolymorphic (o)); |
577 | | |
578 | 127 | _ (Push (o, i_type, slot)); |
579 | | |
580 | 127 | _catch: return result; |
581 | 127 | } |
582 | | |
583 | | static |
584 | | M3Result Pop (IM3Compilation o) |
585 | 7.04k | { |
586 | 7.04k | M3Result result = m3Err_none; |
587 | | |
588 | 7.04k | if (o->stackIndex > o->block.blockStackIndex) |
589 | 6.34k | { |
590 | 6.34k | o->stackIndex--; // printf ("pop: %d\n", (i32) o->stackIndex); |
591 | | |
592 | 6.34k | u16 slot = o->wasmStack [o->stackIndex]; |
593 | 6.34k | u8 type = o->typeStack [o->stackIndex]; |
594 | | |
595 | 6.34k | if (IsRegisterSlotAlias (slot)) |
596 | 109 | { |
597 | 109 | u32 regSelect = IsFpRegisterSlotAlias (slot); |
598 | 109 | DeallocateRegister (o, regSelect); |
599 | 109 | } |
600 | 6.23k | else if (slot >= o->slotFirstDynamicIndex) |
601 | 5.87k | { |
602 | 5.87k | DeallocateSlot (o, slot, type); |
603 | 5.87k | } |
604 | 6.34k | } |
605 | 702 | else if (not IsStackPolymorphic (o)) |
606 | 0 | result = m3Err_functionStackUnderrun; |
607 | | |
608 | 7.04k | return result; |
609 | 7.04k | } |
610 | | |
611 | | static |
612 | | M3Result PopType (IM3Compilation o, u8 i_type) |
613 | 2.68k | { |
614 | 2.68k | M3Result result = m3Err_none; |
615 | | |
616 | 2.68k | u8 topType = GetStackTopType (o); |
617 | | |
618 | 2.68k | if (i_type == topType or o->block.isPolymorphic) |
619 | 2.68k | { |
620 | 2.68k | _ (Pop (o)); |
621 | 2.68k | } |
622 | 2.68k | else _throw (m3Err_typeMismatch); |
623 | | |
624 | 2.68k | _catch: |
625 | 2.68k | return result; |
626 | 2.68k | } |
627 | | |
628 | | static |
629 | | M3Result _PushAllocatedSlotAndEmit (IM3Compilation o, u8 i_type, bool i_doEmit) |
630 | 9.68k | { |
631 | 9.68k | M3Result result = m3Err_none; |
632 | | |
633 | 9.68k | u16 slot = c_slotUnused; |
634 | | |
635 | 9.68k | _ (AllocateSlots (o, & slot, i_type)); |
636 | 9.68k | _ (Push (o, i_type, slot)); |
637 | | |
638 | 9.67k | if (i_doEmit) |
639 | 4.44k | EmitSlotOffset (o, slot); |
640 | | |
641 | | // printf ("push: %d\n", (u32) slot); |
642 | | |
643 | 9.68k | _catch: return result; |
644 | 9.67k | } |
645 | | |
646 | | static inline |
647 | | M3Result PushAllocatedSlotAndEmit (IM3Compilation o, u8 i_type) |
648 | 4.44k | { |
649 | 4.44k | return _PushAllocatedSlotAndEmit (o, i_type, true); |
650 | 4.44k | } |
651 | | |
652 | | static inline |
653 | | M3Result PushAllocatedSlot (IM3Compilation o, u8 i_type) |
654 | 5.24k | { |
655 | 5.24k | return _PushAllocatedSlotAndEmit (o, i_type, false); |
656 | 5.24k | } |
657 | | |
658 | | static |
659 | | M3Result PushConst (IM3Compilation o, u64 i_word, u8 i_type) |
660 | 28.5k | { |
661 | 28.5k | M3Result result = m3Err_none; |
662 | | |
663 | | // Early-exit if we're not emitting |
664 | 28.5k | if (!o->page) return result; |
665 | | |
666 | 2.80k | bool matchFound = false; |
667 | 2.80k | bool is64BitType = Is64BitType (i_type); |
668 | | |
669 | 2.80k | u16 numRequiredSlots = GetTypeNumSlots (i_type); |
670 | 2.80k | u16 numUsedConstSlots = o->slotMaxConstIndex - o->slotFirstConstIndex; |
671 | | |
672 | | // search for duplicate matching constant slot to reuse |
673 | 2.80k | if (numRequiredSlots == 2 and numUsedConstSlots >= 2) |
674 | 0 | { |
675 | 0 | u16 firstConstSlot = o->slotFirstConstIndex; |
676 | 0 | AlignSlotToType (& firstConstSlot, c_m3Type_i64); |
677 | |
|
678 | 0 | for (u16 slot = firstConstSlot; slot < o->slotMaxConstIndex - 1; slot += 2) |
679 | 0 | { |
680 | 0 | if (IsSlotAllocated (o, slot) and IsSlotAllocated (o, slot + 1)) |
681 | 0 | { |
682 | 0 | u64 constant; |
683 | 0 | memcpy (&constant, &o->constants [slot - o->slotFirstConstIndex], sizeof(constant)); |
684 | |
|
685 | 0 | if (constant == i_word) |
686 | 0 | { |
687 | 0 | matchFound = true; |
688 | 0 | _ (Push (o, i_type, slot)); |
689 | 0 | break; |
690 | 0 | } |
691 | 0 | } |
692 | 0 | } |
693 | 0 | } |
694 | 2.80k | else if (numRequiredSlots == 1) |
695 | 677 | { |
696 | 677 | for (u16 i = 0; i < numUsedConstSlots; ++i) |
697 | 0 | { |
698 | 0 | u16 slot = o->slotFirstConstIndex + i; |
699 | |
|
700 | 0 | if (IsSlotAllocated (o, slot)) |
701 | 0 | { |
702 | 0 | bool matches; |
703 | 0 | if (is64BitType) { |
704 | 0 | u64 constant; |
705 | 0 | memcpy (&constant, &o->constants [i], sizeof(constant)); |
706 | 0 | matches = (constant == i_word); |
707 | 0 | } else { |
708 | 0 | u32 constant; |
709 | 0 | memcpy (&constant, &o->constants [i], sizeof(constant)); |
710 | 0 | matches = (constant == i_word); |
711 | 0 | } |
712 | 0 | if (matches) |
713 | 0 | { |
714 | 0 | matchFound = true; |
715 | 0 | _ (Push (o, i_type, slot)); |
716 | 0 | break; |
717 | 0 | } |
718 | 0 | } |
719 | 0 | } |
720 | 677 | } |
721 | | |
722 | 2.80k | if (not matchFound) |
723 | 2.80k | { |
724 | 2.80k | u16 slot = c_slotUnused; |
725 | 2.80k | result = AllocateConstantSlots (o, & slot, i_type); |
726 | | |
727 | 2.80k | if (result || slot == c_slotUnused) // no more constant table space; use inline constants |
728 | 2.80k | { |
729 | 2.80k | result = m3Err_none; |
730 | | |
731 | 2.80k | if (is64BitType) { |
732 | 2.12k | _ (EmitOp (o, op_Const64)); |
733 | 2.12k | EmitWord64 (o->page, i_word); |
734 | 2.12k | } else { |
735 | 677 | _ (EmitOp (o, op_Const32)); |
736 | 677 | EmitWord32 (o->page, (u32) i_word); |
737 | 677 | } |
738 | | |
739 | 2.80k | _ (PushAllocatedSlotAndEmit (o, i_type)); |
740 | 2.80k | } |
741 | 1 | else |
742 | 1 | { |
743 | 1 | u16 constTableIndex = slot - o->slotFirstConstIndex; |
744 | | |
745 | 1 | d_m3Assert(constTableIndex < d_m3MaxConstantTableSize); |
746 | | |
747 | 1 | if (is64BitType) { |
748 | 1 | memcpy (& o->constants [constTableIndex], &i_word, sizeof(i_word)); |
749 | 1 | } else { |
750 | 0 | u32 word32 = i_word; |
751 | 0 | memcpy (& o->constants [constTableIndex], &word32, sizeof(word32)); |
752 | 0 | } |
753 | | |
754 | 1 | _ (Push (o, i_type, slot)); |
755 | | |
756 | 1 | o->slotMaxConstIndex = M3_MAX (slot + numRequiredSlots, o->slotMaxConstIndex); |
757 | 1 | } |
758 | 2.80k | } |
759 | | |
760 | 2.80k | _catch: return result; |
761 | 2.80k | } |
762 | | |
763 | | static inline |
764 | | M3Result EmitSlotNumOfStackTopAndPop (IM3Compilation o) |
765 | 140 | { |
766 | | // no emit if value is in register |
767 | 140 | if (IsStackTopInSlot (o)) |
768 | 81 | EmitSlotOffset (o, GetStackTopSlotNumber (o)); |
769 | | |
770 | 140 | return Pop (o); |
771 | 140 | } |
772 | | |
773 | | |
774 | | // Or, maybe: EmitTrappingOp |
775 | | M3Result AddTrapRecord (IM3Compilation o) |
776 | 31 | { |
777 | 31 | M3Result result = m3Err_none; |
778 | | |
779 | 31 | if (o->function) |
780 | 31 | { |
781 | 31 | } |
782 | | |
783 | 31 | return result; |
784 | 31 | } |
785 | | |
786 | | static |
787 | | M3Result UnwindBlockStack (IM3Compilation o) |
788 | 35 | { |
789 | 35 | M3Result result = m3Err_none; |
790 | | |
791 | 35 | u32 popCount = 0; |
792 | 392 | while (o->stackIndex > o->block.blockStackIndex) |
793 | 357 | { |
794 | 357 | _ (Pop (o)); |
795 | 357 | ++popCount; |
796 | 357 | } |
797 | | |
798 | 35 | if (popCount) |
799 | 18 | { |
800 | 18 | m3log (compile, "unwound stack top: %d", popCount); |
801 | 18 | } |
802 | | |
803 | 35 | _catch: return result; |
804 | 35 | } |
805 | | |
806 | | static inline |
807 | | M3Result SetStackPolymorphic (IM3Compilation o) |
808 | 31 | { |
809 | 31 | o->block.isPolymorphic = true; m3log (compile, "stack set polymorphic"); |
810 | 31 | return UnwindBlockStack (o); |
811 | 31 | } |
812 | | |
813 | | static |
814 | | void PatchBranches (IM3Compilation o) |
815 | 356 | { |
816 | 356 | pc_t pc = GetPC (o); |
817 | | |
818 | 356 | pc_t patches = o->block.patches; |
819 | 356 | o->block.patches = NULL; |
820 | | |
821 | 356 | while (patches) |
822 | 0 | { m3log (compile, "patching location: %p to pc: %p", patches, pc); |
823 | 0 | pc_t next = * (pc_t *) patches; |
824 | 0 | * (pc_t *) patches = pc; |
825 | 0 | patches = next; |
826 | 0 | } |
827 | 356 | } |
828 | | |
829 | | //------------------------------------------------------------------------------------------------------------------------- |
830 | | |
831 | | static |
832 | | M3Result CopyStackIndexToSlot (IM3Compilation o, u16 i_destSlot, u16 i_stackIndex) // NoPushPop |
833 | 10.4k | { |
834 | 10.4k | M3Result result = m3Err_none; |
835 | | |
836 | 10.4k | IM3Operation op; |
837 | | |
838 | 10.4k | u8 type = GetStackTypeFromBottom (o, i_stackIndex); |
839 | 10.4k | bool inRegister = IsStackIndexInRegister (o, i_stackIndex); |
840 | | |
841 | 10.4k | if (inRegister) |
842 | 70 | { |
843 | 70 | op = c_setSetOps [type]; |
844 | 70 | } |
845 | 10.3k | else op = Is64BitType (type) ? op_CopySlot_64 : op_CopySlot_32; |
846 | | |
847 | 10.4k | _ (EmitOp (o, op)); |
848 | 10.4k | EmitSlotOffset (o, i_destSlot); |
849 | | |
850 | 10.4k | if (not inRegister) |
851 | 10.3k | { |
852 | 10.3k | u16 srcSlot = GetSlotForStackIndex (o, i_stackIndex); |
853 | 10.3k | EmitSlotOffset (o, srcSlot); |
854 | 10.3k | } |
855 | | |
856 | 10.4k | _catch: return result; |
857 | 10.4k | } |
858 | | |
859 | | static |
860 | | M3Result CopyStackTopToSlot (IM3Compilation o, u16 i_destSlot) // NoPushPop |
861 | 64 | { |
862 | 64 | M3Result result; |
863 | | |
864 | 64 | i16 stackTop = GetStackTopIndex (o); |
865 | 64 | _ (CopyStackIndexToSlot (o, i_destSlot, (u16) stackTop)); |
866 | | |
867 | 64 | _catch: return result; |
868 | 64 | } |
869 | | |
870 | | |
871 | | // a copy-on-write strategy is used with locals. when a get local occurs, it's not copied anywhere. the stack |
872 | | // entry just has a index pointer to that local memory slot. |
873 | | // then, when a previously referenced local is set, the current value needs to be preserved for those references |
874 | | |
875 | | // TODO: consider getting rid of these specialized operations: PreserveSetSlot & PreserveCopySlot. |
876 | | // They likely just take up space (which seems to reduce performance) without improving performance. |
877 | | static |
878 | | M3Result PreservedCopyTopSlot (IM3Compilation o, u16 i_destSlot, u16 i_preserveSlot) |
879 | 0 | { |
880 | 0 | M3Result result = m3Err_none; d_m3Assert (i_destSlot != i_preserveSlot); |
881 | |
|
882 | 0 | IM3Operation op; |
883 | |
|
884 | 0 | u8 type = GetStackTopType (o); |
885 | |
|
886 | 0 | if (IsStackTopInRegister (o)) |
887 | 0 | { |
888 | 0 | op = c_preserveSetSlot [type]; |
889 | 0 | } |
890 | 0 | else op = Is64BitType (type) ? op_PreserveCopySlot_64 : op_PreserveCopySlot_32; |
891 | |
|
892 | 0 | _ (EmitOp (o, op)); |
893 | 0 | EmitSlotOffset (o, i_destSlot); |
894 | |
|
895 | 0 | if (IsStackTopInSlot (o)) |
896 | 0 | EmitSlotOffset (o, GetStackTopSlotNumber (o)); |
897 | |
|
898 | 0 | EmitSlotOffset (o, i_preserveSlot); |
899 | |
|
900 | 0 | _catch: return result; |
901 | 0 | } |
902 | | |
903 | | static |
904 | | M3Result CopyStackTopToRegister (IM3Compilation o, bool i_updateStack) |
905 | 9 | { |
906 | 9 | M3Result result = m3Err_none; |
907 | | |
908 | 9 | if (IsStackTopInSlot (o)) |
909 | 9 | { |
910 | 9 | u8 type = GetStackTopType (o); |
911 | | |
912 | 9 | _ (PreserveRegisterIfOccupied (o, type)); |
913 | | |
914 | 9 | IM3Operation op = c_setRegisterOps [type]; |
915 | | |
916 | 9 | _ (EmitOp (o, op)); |
917 | 9 | EmitSlotOffset (o, GetStackTopSlotNumber (o)); |
918 | | |
919 | 9 | if (i_updateStack) |
920 | 0 | { |
921 | 0 | _ (PopType (o, type)); |
922 | 0 | _ (PushRegister (o, type)); |
923 | 0 | } |
924 | 9 | } |
925 | | |
926 | 9 | _catch: return result; |
927 | 9 | } |
928 | | |
929 | | |
930 | | // if local is unreferenced, o_preservedSlotNumber will be equal to localIndex on return |
931 | | static |
932 | | M3Result FindReferencedLocalWithinCurrentBlock (IM3Compilation o, u16 * o_preservedSlotNumber, u32 i_localSlot) |
933 | 36.6k | { |
934 | 36.6k | M3Result result = m3Err_none; |
935 | | |
936 | 36.6k | IM3CompilationScope scope = & o->block; |
937 | 36.6k | u16 startIndex = scope->blockStackIndex; |
938 | | |
939 | 44.5k | while (scope->opcode == c_waOp_block) |
940 | 7.92k | { |
941 | 7.92k | scope = scope->outer; |
942 | 7.92k | if (not scope) |
943 | 0 | break; |
944 | | |
945 | 7.92k | startIndex = scope->blockStackIndex; |
946 | 7.92k | } |
947 | | |
948 | 36.6k | * o_preservedSlotNumber = (u16) i_localSlot; |
949 | | |
950 | 956k | for (u32 i = startIndex; i < o->stackIndex; ++i) |
951 | 919k | { |
952 | 919k | if (o->wasmStack [i] == i_localSlot) |
953 | 46 | { |
954 | 46 | if (* o_preservedSlotNumber == i_localSlot) |
955 | 13 | { |
956 | 13 | u8 type = GetStackTypeFromBottom (o, i); d_m3Assert (type != c_m3Type_none) |
957 | | |
958 | 13 | _ (AllocateSlots (o, o_preservedSlotNumber, type)); |
959 | 13 | } |
960 | 33 | else |
961 | 46 | _ (IncrementSlotUsageCount (o, * o_preservedSlotNumber)); |
962 | | |
963 | 46 | o->wasmStack [i] = * o_preservedSlotNumber; |
964 | 46 | } |
965 | 919k | } |
966 | | |
967 | 36.6k | _catch: return result; |
968 | 36.6k | } |
969 | | |
970 | | static |
971 | | M3Result GetBlockScope (IM3Compilation o, IM3CompilationScope * o_scope, u32 i_depth) |
972 | 91 | { |
973 | 91 | M3Result result = m3Err_none; |
974 | | |
975 | 91 | IM3CompilationScope scope = & o->block; |
976 | | |
977 | 92 | while (i_depth--) |
978 | 3 | { |
979 | 3 | scope = scope->outer; |
980 | 3 | _throwif ("invalid block depth", not scope); |
981 | 1 | } |
982 | | |
983 | 89 | * o_scope = scope; |
984 | | |
985 | 91 | _catch: |
986 | 91 | return result; |
987 | 89 | } |
988 | | |
989 | | static |
990 | | M3Result CopyStackSlotsR (IM3Compilation o, u16 i_targetSlotStackIndex, u16 i_stackIndex, u16 i_endStackIndex, u16 i_tempSlot) |
991 | 6.83k | { |
992 | 6.83k | M3Result result = m3Err_none; |
993 | | |
994 | 6.83k | if (i_stackIndex < i_endStackIndex) |
995 | 6.75k | { |
996 | 6.75k | u16 srcSlot = GetSlotForStackIndex (o, i_stackIndex); |
997 | | |
998 | 6.75k | u8 type = GetStackTypeFromBottom (o, i_stackIndex); |
999 | 6.75k | u16 numSlots = GetTypeNumSlots (type); |
1000 | 6.75k | u16 extraSlot = numSlots - 1; |
1001 | | |
1002 | 6.75k | u16 targetSlot = GetSlotForStackIndex (o, i_targetSlotStackIndex); |
1003 | | |
1004 | 6.75k | u16 preserveIndex = i_stackIndex; |
1005 | 6.75k | u16 collisionSlot = srcSlot; |
1006 | | |
1007 | 6.75k | if (targetSlot != srcSlot) |
1008 | 5.93k | { |
1009 | | // search for collisions |
1010 | 5.93k | u16 checkIndex = i_stackIndex + 1; |
1011 | 92.7k | while (checkIndex < i_endStackIndex) |
1012 | 90.5k | { |
1013 | 90.5k | u16 otherSlot1 = GetSlotForStackIndex (o, checkIndex); |
1014 | 90.5k | u16 otherSlot2 = GetExtraSlotForStackIndex (o, checkIndex); |
1015 | | |
1016 | 90.5k | if (targetSlot == otherSlot1 or |
1017 | 90.5k | targetSlot == otherSlot2 or |
1018 | 90.5k | targetSlot + extraSlot == otherSlot1) |
1019 | 3.75k | { |
1020 | 3.75k | _throwif (m3Err_functionStackOverflow, i_tempSlot >= d_m3MaxFunctionSlots); |
1021 | | |
1022 | 3.75k | _ (CopyStackIndexToSlot (o, i_tempSlot, checkIndex)); |
1023 | 3.75k | o->wasmStack [checkIndex] = i_tempSlot; |
1024 | 3.75k | i_tempSlot += GetTypeNumSlots (c_m3Type_i64); |
1025 | 3.75k | TouchSlot (o, i_tempSlot - 1); |
1026 | | |
1027 | | // restore this on the way back down |
1028 | 3.75k | preserveIndex = checkIndex; |
1029 | 3.75k | collisionSlot = otherSlot1; |
1030 | | |
1031 | 3.75k | break; |
1032 | 3.75k | } |
1033 | | |
1034 | 86.7k | ++checkIndex; |
1035 | 86.7k | } |
1036 | | |
1037 | 5.93k | _ (CopyStackIndexToSlot (o, targetSlot, i_stackIndex)); m3log (compile, " copying slot: %d to slot: %d", srcSlot, targetSlot); |
1038 | 5.93k | o->wasmStack [i_stackIndex] = targetSlot; |
1039 | | |
1040 | 5.93k | } |
1041 | | |
1042 | 6.75k | _ (CopyStackSlotsR (o, i_targetSlotStackIndex + 1, i_stackIndex + 1, i_endStackIndex, i_tempSlot)); |
1043 | | |
1044 | | // restore the stack state |
1045 | 6.73k | o->wasmStack [i_stackIndex] = srcSlot; |
1046 | 6.73k | o->wasmStack [preserveIndex] = collisionSlot; |
1047 | 6.73k | } |
1048 | | |
1049 | 6.83k | _catch: |
1050 | 6.83k | return result; |
1051 | 6.83k | } |
1052 | | |
1053 | | static |
1054 | | M3Result ResolveBlockResults (IM3Compilation o, IM3CompilationScope i_targetBlock, bool i_isBranch) |
1055 | 81 | { |
1056 | 81 | M3Result result = m3Err_none; if (d_m3LogWasmStack) dump_type_stack (o); |
1057 | | |
1058 | 81 | bool isLoop = (i_targetBlock->opcode == c_waOp_loop and i_isBranch); |
1059 | | |
1060 | 81 | u16 numParams = GetFuncTypeNumParams (i_targetBlock->type); |
1061 | 81 | u16 numResults = GetFuncTypeNumResults (i_targetBlock->type); |
1062 | | |
1063 | 81 | u16 slotRecords = i_targetBlock->exitStackIndex; |
1064 | | |
1065 | 81 | u16 numValues; |
1066 | | |
1067 | 81 | if (not isLoop) |
1068 | 81 | { |
1069 | 81 | numValues = numResults; |
1070 | 81 | slotRecords += numParams; |
1071 | 81 | } |
1072 | 0 | else numValues = numParams; |
1073 | | |
1074 | 81 | u16 blockHeight = GetNumBlockValuesOnStack (o); |
1075 | | |
1076 | 81 | _throwif (m3Err_typeCountMismatch, i_isBranch ? (blockHeight < numValues) : (blockHeight != numValues)); |
1077 | | |
1078 | 81 | if (numValues) |
1079 | 81 | { |
1080 | 81 | u16 endIndex = GetStackTopIndex (o) + 1; |
1081 | 81 | u16 numRemValues = numValues; |
1082 | | |
1083 | | // The last result is taken from _fp0. See PushBlockResults. |
1084 | 81 | if (not isLoop and IsFpType (GetStackTopType (o))) |
1085 | 9 | { |
1086 | 9 | _ (CopyStackTopToRegister (o, false)); |
1087 | 9 | --endIndex; |
1088 | 9 | --numRemValues; |
1089 | 9 | } |
1090 | | |
1091 | | // TODO: tempslot affects maxStackSlots, so can grow unnecess each time. |
1092 | 81 | u16 tempSlot = o->maxStackSlots;// GetMaxUsedSlotPlusOne (o); doesn't work cause can collide with slotRecords |
1093 | 81 | AlignSlotToType (& tempSlot, c_m3Type_i64); |
1094 | | |
1095 | 81 | _ (CopyStackSlotsR (o, slotRecords, endIndex - numRemValues, endIndex, tempSlot)); |
1096 | | |
1097 | 79 | if (d_m3LogWasmStack) dump_type_stack (o); |
1098 | 79 | } |
1099 | | |
1100 | 81 | _catch: return result; |
1101 | 81 | } |
1102 | | |
1103 | | |
1104 | | static |
1105 | | M3Result ReturnValues (IM3Compilation o, IM3CompilationScope i_functionBlock, bool i_isBranch) |
1106 | 8 | { |
1107 | 8 | M3Result result = m3Err_none; if (d_m3LogWasmStack) dump_type_stack (o); |
1108 | | |
1109 | 8 | u16 numReturns = GetFuncTypeNumResults (i_functionBlock->type); // could just o->function too... |
1110 | 8 | u16 blockHeight = GetNumBlockValuesOnStack (o); |
1111 | | |
1112 | 8 | if (not IsStackPolymorphic (o)) |
1113 | 8 | _throwif (m3Err_typeCountMismatch, i_isBranch ? (blockHeight < numReturns) : (blockHeight != numReturns)); |
1114 | | |
1115 | 8 | if (numReturns) |
1116 | 8 | { |
1117 | | // return slots like args are 64-bit aligned |
1118 | 8 | u16 returnSlot = numReturns * c_ioSlotCount; |
1119 | 8 | u16 stackTop = GetStackTopIndex (o); |
1120 | | |
1121 | 712 | for (u16 i = 0; i < numReturns; ++i) |
1122 | 704 | { |
1123 | 704 | u8 returnType = GetFuncTypeResultType (i_functionBlock->type, numReturns - 1 - i); |
1124 | | |
1125 | 704 | u8 stackType = GetStackTypeFromTop (o, i); // using FromTop so that only dynamic items are checked |
1126 | | |
1127 | 704 | if (IsStackPolymorphic (o) and stackType == c_m3Type_none) |
1128 | 0 | stackType = returnType; |
1129 | | |
1130 | 704 | _throwif (m3Err_typeMismatch, returnType != stackType); |
1131 | | |
1132 | 704 | if (not IsStackPolymorphic (o)) |
1133 | 704 | { |
1134 | 704 | returnSlot -= c_ioSlotCount; |
1135 | 704 | _ (CopyStackIndexToSlot (o, returnSlot, stackTop--)); |
1136 | 704 | } |
1137 | 704 | } |
1138 | | |
1139 | 8 | if (not i_isBranch) |
1140 | 0 | { |
1141 | 0 | while (numReturns--) |
1142 | 0 | _ (Pop (o)); |
1143 | 0 | } |
1144 | 8 | } |
1145 | | |
1146 | 8 | _catch: return result; |
1147 | 8 | } |
1148 | | |
1149 | | |
1150 | | //------------------------------------------------------------------------------------------------------------------------- |
1151 | | |
1152 | | static |
1153 | | M3Result Compile_Const_i32 (IM3Compilation o, m3opcode_t i_opcode) |
1154 | 14.6k | { |
1155 | 14.6k | M3Result result; |
1156 | | |
1157 | 14.6k | i32 value; |
1158 | 14.6k | _ (ReadLEB_i32 (& value, & o->wasm, o->wasmEnd)); |
1159 | 14.6k | _ (PushConst (o, value, c_m3Type_i32)); m3log (compile, d_indent " (const i32 = %" PRIi32 ")", get_indention_string (o), value); |
1160 | 14.6k | _catch: return result; |
1161 | 14.6k | } |
1162 | | |
1163 | | static |
1164 | | M3Result Compile_Const_i64 (IM3Compilation o, m3opcode_t i_opcode) |
1165 | 13.0k | { |
1166 | 13.0k | M3Result result; |
1167 | | |
1168 | 13.0k | i64 value; |
1169 | 13.0k | _ (ReadLEB_i64 (& value, & o->wasm, o->wasmEnd)); |
1170 | 13.0k | _ (PushConst (o, value, c_m3Type_i64)); m3log (compile, d_indent " (const i64 = %" PRIi64 ")", get_indention_string (o), value); |
1171 | 13.0k | _catch: return result; |
1172 | 13.0k | } |
1173 | | |
1174 | | |
1175 | | #if d_m3ImplementFloat |
1176 | | static |
1177 | | M3Result Compile_Const_f32 (IM3Compilation o, m3opcode_t i_opcode) |
1178 | 510 | { |
1179 | 510 | M3Result result; |
1180 | | |
1181 | 510 | union { u32 u; f32 f; } value = { 0 }; |
1182 | | |
1183 | 510 | _ (Read_f32 (& value.f, & o->wasm, o->wasmEnd)); m3log (compile, d_indent " (const f32 = %" PRIf32 ")", get_indention_string (o), value.f); |
1184 | 510 | _ (PushConst (o, value.u, c_m3Type_f32)); |
1185 | | |
1186 | 510 | _catch: return result; |
1187 | 510 | } |
1188 | | |
1189 | | static |
1190 | | M3Result Compile_Const_f64 (IM3Compilation o, m3opcode_t i_opcode) |
1191 | 306 | { |
1192 | 306 | M3Result result; |
1193 | | |
1194 | 306 | union { u64 u; f64 f; } value = { 0 }; |
1195 | | |
1196 | 306 | _ (Read_f64 (& value.f, & o->wasm, o->wasmEnd)); m3log (compile, d_indent " (const f64 = %" PRIf64 ")", get_indention_string (o), value.f); |
1197 | 305 | _ (PushConst (o, value.u, c_m3Type_f64)); |
1198 | | |
1199 | 306 | _catch: return result; |
1200 | 305 | } |
1201 | | #endif |
1202 | | |
1203 | | #if d_m3CascadedOpcodes |
1204 | | |
1205 | | static |
1206 | | M3Result Compile_ExtendedOpcode (IM3Compilation o, m3opcode_t i_opcode) |
1207 | 0 | { |
1208 | 0 | _try { |
1209 | 0 | u8 opcode; |
1210 | 0 | _ (Read_u8 (& opcode, & o->wasm, o->wasmEnd)); m3log (compile, d_indent " (FC: %" PRIi32 ")", get_indention_string (o), opcode); |
1211 | |
|
1212 | 0 | i_opcode = (i_opcode << 8) | opcode; |
1213 | | |
1214 | | //printf("Extended opcode: 0x%x\n", i_opcode); |
1215 | |
|
1216 | 0 | IM3OpInfo opInfo = GetOpInfo (i_opcode); |
1217 | 0 | _throwif (m3Err_unknownOpcode, not opInfo); |
1218 | |
|
1219 | 0 | M3Compiler compiler = opInfo->compiler; |
1220 | 0 | _throwif (m3Err_noCompiler, not compiler); |
1221 | |
|
1222 | 0 | _ ((* compiler) (o, i_opcode)); |
1223 | |
|
1224 | 0 | o->previousOpcode = i_opcode; |
1225 | |
|
1226 | 0 | } _catch: return result; |
1227 | 0 | } |
1228 | | #endif |
1229 | | |
1230 | | static |
1231 | | M3Result Compile_Return (IM3Compilation o, m3opcode_t i_opcode) |
1232 | 0 | { |
1233 | 0 | M3Result result = m3Err_none; |
1234 | |
|
1235 | 0 | if (not IsStackPolymorphic (o)) |
1236 | 0 | { |
1237 | 0 | IM3CompilationScope functionScope; |
1238 | 0 | _ (GetBlockScope (o, & functionScope, o->block.depth)); |
1239 | |
|
1240 | 0 | _ (ReturnValues (o, functionScope, true)); |
1241 | |
|
1242 | 0 | _ (EmitOp (o, op_Return)); |
1243 | |
|
1244 | 0 | _ (SetStackPolymorphic (o)); |
1245 | 0 | } |
1246 | | |
1247 | 0 | _catch: return result; |
1248 | 0 | } |
1249 | | |
1250 | | static |
1251 | | M3Result ValidateBlockEnd (IM3Compilation o) |
1252 | 1.26k | { |
1253 | 1.26k | M3Result result = m3Err_none; |
1254 | | /* |
1255 | | u16 numResults = GetFuncTypeNumResults (o->block.type); |
1256 | | u16 blockHeight = GetNumBlockValuesOnStack (o); |
1257 | | |
1258 | | if (IsStackPolymorphic (o)) |
1259 | | { |
1260 | | } |
1261 | | else |
1262 | | { |
1263 | | } |
1264 | | |
1265 | 1.26k | _catch: */ return result; |
1266 | 1.26k | } |
1267 | | |
1268 | | static |
1269 | | M3Result Compile_End (IM3Compilation o, m3opcode_t i_opcode) |
1270 | 916 | { |
1271 | 916 | M3Result result = m3Err_none; //dump_type_stack (o); |
1272 | | |
1273 | | // function end: |
1274 | 916 | if (o->block.depth == 0) |
1275 | 912 | { |
1276 | 912 | ValidateBlockEnd (o); |
1277 | | |
1278 | | // if (not IsStackPolymorphic (o)) |
1279 | 912 | { |
1280 | 912 | if (o->function) |
1281 | 0 | { |
1282 | 0 | _ (ReturnValues (o, & o->block, false)); |
1283 | 0 | } |
1284 | | |
1285 | 912 | _ (EmitOp (o, op_Return)); |
1286 | 912 | } |
1287 | 912 | } |
1288 | | |
1289 | 916 | _catch: return result; |
1290 | 916 | } |
1291 | | |
1292 | | |
1293 | | static |
1294 | | M3Result Compile_SetLocal (IM3Compilation o, m3opcode_t i_opcode) |
1295 | 0 | { |
1296 | 0 | M3Result result; |
1297 | |
|
1298 | 0 | u32 localIndex; |
1299 | 0 | _ (ReadLEB_u32 (& localIndex, & o->wasm, o->wasmEnd)); // printf ("--- set local: %d \n", localSlot); |
1300 | |
|
1301 | 0 | if (localIndex < GetFunctionNumArgsAndLocals (o->function)) |
1302 | 0 | { |
1303 | 0 | u16 localSlot = GetSlotForStackIndex (o, localIndex); |
1304 | |
|
1305 | 0 | u16 preserveSlot; |
1306 | 0 | _ (FindReferencedLocalWithinCurrentBlock (o, & preserveSlot, localSlot)); // preserve will be different than local, if referenced |
1307 | |
|
1308 | 0 | if (preserveSlot == localSlot) |
1309 | 0 | _ (CopyStackTopToSlot (o, localSlot)) |
1310 | 0 | else |
1311 | 0 | _ (PreservedCopyTopSlot (o, localSlot, preserveSlot)) |
1312 | | |
1313 | 0 | if (i_opcode != c_waOp_teeLocal) |
1314 | 0 | _ (Pop (o)); |
1315 | 0 | } |
1316 | 0 | else _throw ("local index out of bounds"); |
1317 | |
|
1318 | 0 | _catch: return result; |
1319 | 0 | } |
1320 | | |
1321 | | static |
1322 | | M3Result Compile_GetLocal (IM3Compilation o, m3opcode_t i_opcode) |
1323 | 0 | { |
1324 | 0 | _try { |
1325 | |
|
1326 | 0 | u32 localIndex; |
1327 | 0 | _ (ReadLEB_u32 (& localIndex, & o->wasm, o->wasmEnd)); |
1328 | |
|
1329 | 0 | if (localIndex >= GetFunctionNumArgsAndLocals (o->function)) |
1330 | 0 | _throw ("local index out of bounds"); |
1331 | |
|
1332 | 0 | u8 type = GetStackTypeFromBottom (o, localIndex); |
1333 | 0 | u16 slot = GetSlotForStackIndex (o, localIndex); |
1334 | |
|
1335 | 0 | _ (Push (o, type, slot)); |
1336 | |
|
1337 | 0 | } _catch: return result; |
1338 | 0 | } |
1339 | | |
1340 | | static |
1341 | | M3Result Compile_GetGlobal (IM3Compilation o, M3Global * i_global) |
1342 | 1.63k | { |
1343 | 1.63k | M3Result result; |
1344 | | |
1345 | 1.63k | IM3Operation op = Is64BitType (i_global->type) ? op_GetGlobal_s64 : op_GetGlobal_s32; |
1346 | 1.63k | _ (EmitOp (o, op)); |
1347 | 1.63k | EmitPointer (o, & i_global->i64Value); |
1348 | 1.63k | _ (PushAllocatedSlotAndEmit (o, i_global->type)); |
1349 | | |
1350 | 1.63k | _catch: return result; |
1351 | 1.63k | } |
1352 | | |
1353 | | static |
1354 | | M3Result Compile_SetGlobal (IM3Compilation o, M3Global * i_global) |
1355 | 0 | { |
1356 | 0 | M3Result result = m3Err_none; |
1357 | |
|
1358 | 0 | if (i_global->isMutable) |
1359 | 0 | { |
1360 | 0 | IM3Operation op; |
1361 | 0 | u8 type = GetStackTopType (o); |
1362 | |
|
1363 | 0 | if (IsStackTopInRegister (o)) |
1364 | 0 | { |
1365 | 0 | op = c_setGlobalOps [type]; |
1366 | 0 | } |
1367 | 0 | else op = Is64BitType (type) ? op_SetGlobal_s64 : op_SetGlobal_s32; |
1368 | |
|
1369 | 0 | _ (EmitOp (o, op)); |
1370 | 0 | EmitPointer (o, & i_global->i64Value); |
1371 | |
|
1372 | 0 | if (IsStackTopInSlot (o)) |
1373 | 0 | EmitSlotOffset (o, GetStackTopSlotNumber (o)); |
1374 | |
|
1375 | 0 | _ (Pop (o)); |
1376 | 0 | } |
1377 | 0 | else _throw (m3Err_settingImmutableGlobal); |
1378 | |
|
1379 | 0 | _catch: return result; |
1380 | 0 | } |
1381 | | |
1382 | | static |
1383 | | M3Result Compile_GetSetGlobal (IM3Compilation o, m3opcode_t i_opcode) |
1384 | 1.63k | { |
1385 | 1.63k | M3Result result = m3Err_none; |
1386 | | |
1387 | 1.63k | u32 globalIndex; |
1388 | 1.63k | _ (ReadLEB_u32 (& globalIndex, & o->wasm, o->wasmEnd)); |
1389 | | |
1390 | 1.63k | if (globalIndex < o->module->numGlobals) |
1391 | 1.63k | { |
1392 | 1.63k | if (o->module->globals) |
1393 | 1.63k | { |
1394 | 1.63k | M3Global * global = & o->module->globals [globalIndex]; |
1395 | | |
1396 | 1.63k | _ ((i_opcode == c_waOp_getGlobal) ? Compile_GetGlobal (o, global) : Compile_SetGlobal (o, global)); |
1397 | 1.63k | } |
1398 | 1.63k | else _throw (ErrorCompile (m3Err_globalMemoryNotAllocated, o, "module '%s' is missing global memory", o->module->name)); |
1399 | 1.63k | } |
1400 | 1.63k | else _throw (m3Err_globaIndexOutOfBounds); |
1401 | | |
1402 | 1.63k | _catch: return result; |
1403 | 1.63k | } |
1404 | | |
1405 | | static |
1406 | | void EmitPatchingBranchPointer (IM3Compilation o, IM3CompilationScope i_scope) |
1407 | 79 | { |
1408 | 79 | pc_t patch = EmitPointer (o, i_scope->patches); m3log (compile, "branch patch required at: %p", patch); |
1409 | 79 | i_scope->patches = patch; |
1410 | 79 | } |
1411 | | |
1412 | | static |
1413 | | M3Result EmitPatchingBranch (IM3Compilation o, IM3CompilationScope i_scope) |
1414 | 79 | { |
1415 | 79 | M3Result result = m3Err_none; |
1416 | | |
1417 | 79 | _ (EmitOp (o, op_Branch)); |
1418 | 79 | EmitPatchingBranchPointer (o, i_scope); |
1419 | | |
1420 | 79 | _catch: return result; |
1421 | 79 | } |
1422 | | |
1423 | | static |
1424 | | M3Result Compile_Branch (IM3Compilation o, m3opcode_t i_opcode) |
1425 | 0 | { |
1426 | 0 | M3Result result; |
1427 | |
|
1428 | 0 | u32 depth; |
1429 | 0 | _ (ReadLEB_u32 (& depth, & o->wasm, o->wasmEnd)); |
1430 | |
|
1431 | 0 | IM3CompilationScope scope; |
1432 | 0 | _ (GetBlockScope (o, & scope, depth)); |
1433 | | |
1434 | | // branch target is a loop (continue) |
1435 | 0 | if (scope->opcode == c_waOp_loop) |
1436 | 0 | { |
1437 | 0 | if (i_opcode == c_waOp_branchIf) |
1438 | 0 | { |
1439 | 0 | if (GetFuncTypeNumParams (scope->type)) |
1440 | 0 | { |
1441 | 0 | IM3Operation op = IsStackTopInRegister (o) ? op_BranchIfPrologue_r : op_BranchIfPrologue_s; |
1442 | |
|
1443 | 0 | _ (EmitOp (o, op)); |
1444 | 0 | _ (EmitSlotNumOfStackTopAndPop (o)); |
1445 | |
|
1446 | 0 | pc_t * jumpTo = (pc_t *) ReservePointer (o); |
1447 | |
|
1448 | 0 | _ (ResolveBlockResults (o, scope, /* isBranch: */ true)); |
1449 | |
|
1450 | 0 | _ (EmitOp (o, op_ContinueLoop)); |
1451 | 0 | EmitPointer (o, scope->pc); |
1452 | |
|
1453 | 0 | * jumpTo = GetPC (o); |
1454 | 0 | } |
1455 | 0 | else |
1456 | 0 | { |
1457 | | // move the condition to a register |
1458 | 0 | _ (CopyStackTopToRegister (o, false)); |
1459 | 0 | _ (PopType (o, c_m3Type_i32)); |
1460 | |
|
1461 | 0 | _ (EmitOp (o, op_ContinueLoopIf)); |
1462 | 0 | EmitPointer (o, scope->pc); |
1463 | 0 | } |
1464 | | |
1465 | | // dump_type_stack(o); |
1466 | 0 | } |
1467 | 0 | else // is c_waOp_branch |
1468 | 0 | { |
1469 | 0 | _ (EmitOp (o, op_ContinueLoop)); |
1470 | 0 | EmitPointer (o, scope->pc); |
1471 | 0 | o->block.isPolymorphic = true; |
1472 | 0 | } |
1473 | 0 | } |
1474 | 0 | else // forward branch |
1475 | 0 | { |
1476 | 0 | pc_t * jumpTo = NULL; |
1477 | |
|
1478 | 0 | bool isReturn = (scope->depth == 0); |
1479 | 0 | bool targetHasResults = GetFuncTypeNumResults (scope->type); |
1480 | |
|
1481 | 0 | if (i_opcode == c_waOp_branchIf) |
1482 | 0 | { |
1483 | 0 | if (targetHasResults or isReturn) |
1484 | 0 | { |
1485 | 0 | IM3Operation op = IsStackTopInRegister (o) ? op_BranchIfPrologue_r : op_BranchIfPrologue_s; |
1486 | |
|
1487 | 0 | _ (EmitOp (o, op)); |
1488 | 0 | _ (EmitSlotNumOfStackTopAndPop (o)); // condition |
1489 | | |
1490 | | // this is continuation point, if the branch isn't taken |
1491 | 0 | jumpTo = (pc_t *) ReservePointer (o); |
1492 | 0 | } |
1493 | 0 | else |
1494 | 0 | { |
1495 | 0 | IM3Operation op = IsStackTopInRegister (o) ? op_BranchIf_r : op_BranchIf_s; |
1496 | |
|
1497 | 0 | _ (EmitOp (o, op)); |
1498 | 0 | _ (EmitSlotNumOfStackTopAndPop (o)); // condition |
1499 | |
|
1500 | 0 | EmitPatchingBranchPointer (o, scope); |
1501 | 0 | goto _catch; |
1502 | 0 | } |
1503 | 0 | } |
1504 | | |
1505 | 0 | if (not IsStackPolymorphic (o)) |
1506 | 0 | { |
1507 | 0 | if (isReturn) |
1508 | 0 | { |
1509 | 0 | _ (ReturnValues (o, scope, true)); |
1510 | 0 | _ (EmitOp (o, op_Return)); |
1511 | 0 | } |
1512 | 0 | else |
1513 | 0 | { |
1514 | 0 | _ (ResolveBlockResults (o, scope, true)); |
1515 | 0 | _ (EmitPatchingBranch (o, scope)); |
1516 | 0 | } |
1517 | 0 | } |
1518 | | |
1519 | 0 | if (jumpTo) |
1520 | 0 | { |
1521 | 0 | * jumpTo = GetPC (o); |
1522 | 0 | } |
1523 | |
|
1524 | 0 | if (i_opcode == c_waOp_branch) |
1525 | 0 | _ (SetStackPolymorphic (o)); |
1526 | 0 | } |
1527 | | |
1528 | 0 | _catch: return result; |
1529 | 0 | } |
1530 | | |
1531 | | static |
1532 | | M3Result Compile_BranchTable (IM3Compilation o, m3opcode_t i_opcode) |
1533 | 4 | { |
1534 | 4 | _try { |
1535 | 4 | u32 targetCount; |
1536 | 4 | _ (ReadLEB_u32 (& targetCount, & o->wasm, o->wasmEnd)); |
1537 | | |
1538 | 4 | _ (PreserveRegisterIfOccupied (o, c_m3Type_i64)); // move branch operand to a slot |
1539 | 4 | u16 slot = GetStackTopSlotNumber (o); |
1540 | 4 | _ (Pop (o)); |
1541 | | |
1542 | | // OPTZ: according to spec: "forward branches that target a control instruction with a non-empty |
1543 | | // result type consume matching operands first and push them back on the operand stack after unwinding" |
1544 | | // So, this move-to-reg is only necessary if the target scopes have a type. |
1545 | | |
1546 | 4 | u32 numCodeLines = targetCount + 4; // 3 => IM3Operation + slot + target_count + default_target |
1547 | 4 | _ (EnsureCodePageNumLines (o, numCodeLines)); |
1548 | | |
1549 | 4 | _ (EmitOp (o, op_BranchTable)); |
1550 | 4 | EmitSlotOffset (o, slot); |
1551 | 4 | EmitConstant32 (o, targetCount); |
1552 | | |
1553 | 4 | IM3CodePage continueOpPage = NULL; |
1554 | | |
1555 | 4 | ++targetCount; // include default |
1556 | 91 | for (u32 i = 0; i < targetCount; ++i) |
1557 | 91 | { |
1558 | 91 | u32 target; |
1559 | 91 | _ (ReadLEB_u32 (& target, & o->wasm, o->wasmEnd)); |
1560 | | |
1561 | 91 | IM3CompilationScope scope; |
1562 | 91 | _ (GetBlockScope (o, & scope, target)); |
1563 | | |
1564 | | // TODO: don't need codepage rigmarole for |
1565 | | // no-param forward-branch targets |
1566 | | |
1567 | 89 | _ (AcquireCompilationCodePage (o, & continueOpPage)); |
1568 | | |
1569 | 89 | pc_t startPC = GetPagePC (continueOpPage); |
1570 | 89 | IM3CodePage savedPage = o->page; |
1571 | 89 | o->page = continueOpPage; |
1572 | | |
1573 | 89 | if (scope->opcode == c_waOp_loop) |
1574 | 0 | { |
1575 | 0 | _ (ResolveBlockResults (o, scope, true)); |
1576 | |
|
1577 | 0 | _ (EmitOp (o, op_ContinueLoop)); |
1578 | 0 | EmitPointer (o, scope->pc); |
1579 | 0 | } |
1580 | 89 | else |
1581 | 89 | { |
1582 | | // TODO: this could be fused with equivalent targets |
1583 | 89 | if (not IsStackPolymorphic (o)) |
1584 | 89 | { |
1585 | 89 | if (scope->depth == 0) |
1586 | 8 | { |
1587 | 8 | _ (ReturnValues (o, scope, true)); |
1588 | 8 | _ (EmitOp (o, op_Return)); |
1589 | 8 | } |
1590 | 81 | else |
1591 | 81 | { |
1592 | 81 | _ (ResolveBlockResults (o, scope, true)); |
1593 | | |
1594 | 79 | _ (EmitPatchingBranch (o, scope)); |
1595 | 79 | } |
1596 | 89 | } |
1597 | 89 | } |
1598 | | |
1599 | 87 | ReleaseCompilationCodePage (o); // FIX: continueOpPage can get lost if thrown |
1600 | 87 | o->page = savedPage; |
1601 | | |
1602 | 87 | EmitPointer (o, startPC); |
1603 | 87 | } |
1604 | | |
1605 | 0 | _ (SetStackPolymorphic (o)); |
1606 | |
|
1607 | 0 | } |
1608 | | |
1609 | 4 | _catch: return result; |
1610 | 0 | } |
1611 | | |
1612 | | static |
1613 | | M3Result CompileCallArgsAndReturn (IM3Compilation o, u16 * o_stackOffset, IM3FuncType i_type, bool i_isIndirect) |
1614 | 10 | { |
1615 | 10 | _try { |
1616 | | |
1617 | 10 | u16 topSlot = GetMaxUsedSlotPlusOne (o); |
1618 | | |
1619 | | // force use of at least one stack slot; this is to help ensure |
1620 | | // the m3 stack overflows (and traps) before the native stack can overflow. |
1621 | | // e.g. see Wasm spec test 'runaway' in call.wast |
1622 | 10 | topSlot = M3_MAX (1, topSlot); |
1623 | | |
1624 | | // stack frame is 64-bit aligned |
1625 | 10 | AlignSlotToType (& topSlot, c_m3Type_i64); |
1626 | | |
1627 | 10 | * o_stackOffset = topSlot; |
1628 | | |
1629 | | // wait to pop this here so that topSlot search is correct |
1630 | 10 | if (i_isIndirect) |
1631 | 10 | _ (Pop (o)); |
1632 | | |
1633 | 10 | u16 numArgs = GetFuncTypeNumParams (i_type); |
1634 | 10 | u16 numRets = GetFuncTypeNumResults (i_type); |
1635 | | |
1636 | 10 | u16 argTop = topSlot + (numArgs + numRets) * c_ioSlotCount; |
1637 | | |
1638 | 74 | while (numArgs--) |
1639 | 64 | { |
1640 | 64 | _ (CopyStackTopToSlot (o, argTop -= c_ioSlotCount)); |
1641 | 64 | _ (Pop (o)); |
1642 | 64 | } |
1643 | | |
1644 | 10 | u16 i = 0; |
1645 | 668 | while (numRets--) |
1646 | 658 | { |
1647 | 658 | u8 type = GetFuncTypeResultType (i_type, i++); |
1648 | | |
1649 | 658 | _ (Push (o, type, topSlot)); |
1650 | 658 | MarkSlotsAllocatedByType (o, topSlot, type); |
1651 | | |
1652 | 658 | topSlot += c_ioSlotCount; |
1653 | 658 | } |
1654 | | |
1655 | 10 | } _catch: return result; |
1656 | 10 | } |
1657 | | |
1658 | | static |
1659 | | M3Result Compile_Call (IM3Compilation o, m3opcode_t i_opcode) |
1660 | 8 | { |
1661 | 8 | _try { |
1662 | 8 | u32 functionIndex; |
1663 | 8 | _ (ReadLEB_u32 (& functionIndex, & o->wasm, o->wasmEnd)); |
1664 | | |
1665 | 8 | IM3Function function = Module_GetFunction (o->module, functionIndex); |
1666 | | |
1667 | 8 | if (function) |
1668 | 8 | { m3log (compile, d_indent " (func= [%d] '%s'; args= %d)", |
1669 | 8 | get_indention_string (o), functionIndex, m3_GetFunctionName (function), function->funcType->numArgs); |
1670 | 8 | if (function->module) |
1671 | 8 | { |
1672 | 8 | u16 slotTop; |
1673 | 8 | _ (CompileCallArgsAndReturn (o, & slotTop, function->funcType, false)); |
1674 | | |
1675 | 8 | IM3Operation op; |
1676 | 8 | const void * operand; |
1677 | | |
1678 | 8 | if (function->compiled) |
1679 | 0 | { |
1680 | 0 | op = op_Call; |
1681 | 0 | operand = function->compiled; |
1682 | 0 | } |
1683 | 8 | else |
1684 | 8 | { |
1685 | 8 | op = op_Compile; |
1686 | 8 | operand = function; |
1687 | 8 | } |
1688 | | |
1689 | 8 | _ (EmitOp (o, op)); |
1690 | 8 | EmitPointer (o, operand); |
1691 | 8 | EmitSlotOffset (o, slotTop); |
1692 | 8 | } |
1693 | 0 | else |
1694 | 0 | { |
1695 | 0 | _throw (ErrorCompile (m3Err_functionImportMissing, o, "'%s.%s'", GetFunctionImportModuleName (function), m3_GetFunctionName (function))); |
1696 | 0 | } |
1697 | 8 | } |
1698 | 8 | else _throw (m3Err_functionLookupFailed); |
1699 | | |
1700 | 8 | } _catch: return result; |
1701 | 8 | } |
1702 | | |
1703 | | static |
1704 | | M3Result Compile_CallIndirect (IM3Compilation o, m3opcode_t i_opcode) |
1705 | 2 | { |
1706 | 2 | _try { |
1707 | 2 | u32 typeIndex; |
1708 | 2 | _ (ReadLEB_u32 (& typeIndex, & o->wasm, o->wasmEnd)); |
1709 | | |
1710 | 2 | u32 tableIndex; |
1711 | 2 | _ (ReadLEB_u32 (& tableIndex, & o->wasm, o->wasmEnd)); |
1712 | | |
1713 | 2 | _throwif ("function call type index out of range", typeIndex >= o->module->numFuncTypes); |
1714 | | |
1715 | 2 | if (IsStackTopInRegister (o)) |
1716 | 2 | _ (PreserveRegisterIfOccupied (o, c_m3Type_i32)); |
1717 | | |
1718 | 2 | u16 tableIndexSlot = GetStackTopSlotNumber (o); |
1719 | | |
1720 | 2 | u16 execTop; |
1721 | 2 | IM3FuncType type = o->module->funcTypes [typeIndex]; |
1722 | 2 | _ (CompileCallArgsAndReturn (o, & execTop, type, true)); |
1723 | | |
1724 | 2 | _ (EmitOp (o, op_CallIndirect)); |
1725 | 2 | EmitSlotOffset (o, tableIndexSlot); |
1726 | 2 | EmitPointer (o, o->module); |
1727 | 2 | EmitPointer (o, type); // TODO: unify all types in M3Environment |
1728 | 2 | EmitSlotOffset (o, execTop); |
1729 | | |
1730 | 2 | } _catch: |
1731 | 2 | return result; |
1732 | 2 | } |
1733 | | |
1734 | | static |
1735 | | M3Result Compile_Memory_Size (IM3Compilation o, m3opcode_t i_opcode) |
1736 | 0 | { |
1737 | 0 | M3Result result; |
1738 | |
|
1739 | 0 | i8 reserved; |
1740 | 0 | _ (ReadLEB_i7 (& reserved, & o->wasm, o->wasmEnd)); |
1741 | |
|
1742 | 0 | _ (PreserveRegisterIfOccupied (o, c_m3Type_i32)); |
1743 | |
|
1744 | 0 | _ (EmitOp (o, op_MemSize)); |
1745 | |
|
1746 | 0 | _ (PushRegister (o, c_m3Type_i32)); |
1747 | |
|
1748 | 0 | _catch: return result; |
1749 | 0 | } |
1750 | | |
1751 | | static |
1752 | | M3Result Compile_Memory_Grow (IM3Compilation o, m3opcode_t i_opcode) |
1753 | 0 | { |
1754 | 0 | M3Result result; |
1755 | |
|
1756 | 0 | i8 reserved; |
1757 | 0 | _ (ReadLEB_i7 (& reserved, & o->wasm, o->wasmEnd)); |
1758 | |
|
1759 | 0 | _ (CopyStackTopToRegister (o, false)); |
1760 | 0 | _ (PopType (o, c_m3Type_i32)); |
1761 | |
|
1762 | 0 | _ (EmitOp (o, op_MemGrow)); |
1763 | |
|
1764 | 0 | _ (PushRegister (o, c_m3Type_i32)); |
1765 | |
|
1766 | 0 | _catch: return result; |
1767 | 0 | } |
1768 | | |
1769 | | static |
1770 | | M3Result Compile_Memory_CopyFill (IM3Compilation o, m3opcode_t i_opcode) |
1771 | 0 | { |
1772 | 0 | M3Result result = m3Err_none; |
1773 | |
|
1774 | 0 | u32 sourceMemoryIdx, targetMemoryIdx; |
1775 | 0 | IM3Operation op; |
1776 | 0 | if (i_opcode == c_waOp_memoryCopy) |
1777 | 0 | { |
1778 | 0 | _ (ReadLEB_u32 (& sourceMemoryIdx, & o->wasm, o->wasmEnd)); |
1779 | 0 | op = op_MemCopy; |
1780 | 0 | } |
1781 | 0 | else op = op_MemFill; |
1782 | | |
1783 | 0 | _ (ReadLEB_u32 (& targetMemoryIdx, & o->wasm, o->wasmEnd)); |
1784 | |
|
1785 | 0 | _ (CopyStackTopToRegister (o, false)); |
1786 | |
|
1787 | 0 | _ (EmitOp (o, op)); |
1788 | 0 | _ (PopType (o, c_m3Type_i32)); |
1789 | 0 | _ (EmitSlotNumOfStackTopAndPop (o)); |
1790 | 0 | _ (EmitSlotNumOfStackTopAndPop (o)); |
1791 | |
|
1792 | 0 | _catch: return result; |
1793 | 0 | } |
1794 | | |
1795 | | |
1796 | | static |
1797 | | M3Result ReadBlockType (IM3Compilation o, IM3FuncType * o_blockType) |
1798 | 93 | { |
1799 | 93 | M3Result result; |
1800 | | |
1801 | 93 | i64 type; |
1802 | 93 | _ (ReadLebSigned (& type, 33, & o->wasm, o->wasmEnd)); |
1803 | | |
1804 | 92 | if (type < 0) |
1805 | 0 | { |
1806 | 0 | u8 valueType; |
1807 | 0 | _ (NormalizeType (&valueType, type)); m3log (compile, d_indent " (type: %s)", get_indention_string (o), c_waTypes [valueType]); |
1808 | 0 | *o_blockType = o->module->environment->retFuncTypes[valueType]; |
1809 | 0 | } |
1810 | 92 | else |
1811 | 92 | { |
1812 | 92 | _throwif("func type out of bounds", type >= o->module->numFuncTypes); |
1813 | 91 | *o_blockType = o->module->funcTypes[type]; m3log (compile, d_indent " (type: %s)", get_indention_string (o), SPrintFuncTypeSignature (*o_blockType)); |
1814 | 91 | } |
1815 | 93 | _catch: return result; |
1816 | 92 | } |
1817 | | |
1818 | | static |
1819 | | M3Result PreserveArgsAndLocals (IM3Compilation o) |
1820 | 93 | { |
1821 | 93 | M3Result result = m3Err_none; |
1822 | | |
1823 | 93 | if (o->stackIndex > o->stackFirstDynamicIndex) |
1824 | 92 | { |
1825 | 92 | u32 numArgsAndLocals = GetFunctionNumArgsAndLocals (o->function); |
1826 | | |
1827 | 36.6k | for (u32 i = 0; i < numArgsAndLocals; ++i) |
1828 | 36.6k | { |
1829 | 36.6k | u16 slot = GetSlotForStackIndex (o, i); |
1830 | | |
1831 | 36.6k | u16 preservedSlotNumber; |
1832 | 36.6k | _ (FindReferencedLocalWithinCurrentBlock (o, & preservedSlotNumber, slot)); |
1833 | | |
1834 | 36.6k | if (preservedSlotNumber != slot) |
1835 | 13 | { |
1836 | 13 | u8 type = GetStackTypeFromBottom (o, i); d_m3Assert (type != c_m3Type_none) |
1837 | 13 | IM3Operation op = Is64BitType (type) ? op_CopySlot_64 : op_CopySlot_32; |
1838 | | |
1839 | 13 | EmitOp (o, op); |
1840 | 13 | EmitSlotOffset (o, preservedSlotNumber); |
1841 | 13 | EmitSlotOffset (o, slot); |
1842 | 13 | } |
1843 | 36.6k | } |
1844 | 92 | } |
1845 | | |
1846 | 93 | _catch: |
1847 | 93 | return result; |
1848 | 93 | } |
1849 | | |
1850 | | static |
1851 | | M3Result Compile_LoopOrBlock (IM3Compilation o, m3opcode_t i_opcode) |
1852 | 80 | { |
1853 | 80 | M3Result result; |
1854 | | |
1855 | | // TODO: these shouldn't be necessary for non-loop blocks? |
1856 | 80 | _ (PreserveRegisters (o)); |
1857 | 80 | _ (PreserveArgsAndLocals (o)); |
1858 | | |
1859 | 80 | IM3FuncType blockType; |
1860 | 80 | _ (ReadBlockType (o, & blockType)); |
1861 | | |
1862 | 79 | if (i_opcode == c_waOp_loop) |
1863 | 70 | { |
1864 | 70 | u16 numParams = GetFuncTypeNumParams (blockType); |
1865 | 70 | if (numParams) |
1866 | 66 | { |
1867 | | // instantiate constants |
1868 | 66 | u16 numValues = GetNumBlockValuesOnStack (o); // CompileBlock enforces this at comptime |
1869 | 66 | d_m3Assert (numValues >= numParams); |
1870 | 66 | if (numValues >= numParams) |
1871 | 52 | { |
1872 | 52 | u16 stackTop = GetStackTopIndex (o) + 1; |
1873 | | |
1874 | 1.71k | for (u16 i = stackTop - numParams; i < stackTop; ++i) |
1875 | 1.66k | { |
1876 | 1.66k | u16 slot = GetSlotForStackIndex (o, i); |
1877 | 1.66k | u8 type = GetStackTypeFromBottom (o, i); |
1878 | | |
1879 | 1.66k | if (IsConstantSlot (o, slot)) |
1880 | 0 | { |
1881 | 0 | u16 newSlot = c_slotUnused; |
1882 | 0 | _ (AllocateSlots (o, & newSlot, type)); |
1883 | 0 | _ (CopyStackIndexToSlot (o, newSlot, i)); |
1884 | 0 | o->wasmStack [i] = newSlot; |
1885 | 0 | } |
1886 | 1.66k | } |
1887 | 52 | } |
1888 | 66 | } |
1889 | | |
1890 | 70 | _ (EmitOp (o, op_Loop)); |
1891 | 70 | } |
1892 | 9 | else |
1893 | 9 | { |
1894 | 9 | } |
1895 | | |
1896 | 79 | _ (CompileBlock (o, blockType, i_opcode)); |
1897 | | |
1898 | 80 | _catch: return result; |
1899 | 4 | } |
1900 | | |
1901 | | static |
1902 | | M3Result CompileElseBlock (IM3Compilation o, pc_t * o_startPC, IM3FuncType i_blockType) |
1903 | 0 | { |
1904 | 0 | IM3CodePage savedPage = o->page; |
1905 | 0 | _try { |
1906 | |
|
1907 | 0 | IM3CodePage elsePage; |
1908 | 0 | _ (AcquireCompilationCodePage (o, & elsePage)); |
1909 | |
|
1910 | 0 | * o_startPC = GetPagePC (elsePage); |
1911 | |
|
1912 | 0 | o->page = elsePage; |
1913 | |
|
1914 | 0 | _ (CompileBlock (o, i_blockType, c_waOp_else)); |
1915 | |
|
1916 | 0 | _ (EmitOp (o, op_Branch)); |
1917 | 0 | EmitPointer (o, GetPagePC (savedPage)); |
1918 | 0 | } _catch: |
1919 | 0 | if(o->page != savedPage) { |
1920 | 0 | ReleaseCompilationCodePage (o); |
1921 | 0 | } |
1922 | 0 | o->page = savedPage; |
1923 | 0 | return result; |
1924 | 0 | } |
1925 | | |
1926 | | static |
1927 | | M3Result Compile_If (IM3Compilation o, m3opcode_t i_opcode) |
1928 | 13 | { |
1929 | | /* [ op_If ] |
1930 | | [ <else-pc> ] ----> [ ..else.. ] |
1931 | | [ ..if.. ] [ ..block.. ] |
1932 | | [ ..block.. ] [ op_Branch ] |
1933 | | [ end ] <----- [ <end-pc> ] */ |
1934 | | |
1935 | 13 | _try { |
1936 | | |
1937 | 13 | _ (PreserveNonTopRegisters (o)); |
1938 | 13 | _ (PreserveArgsAndLocals (o)); |
1939 | | |
1940 | 13 | IM3Operation op = IsStackTopInRegister (o) ? op_If_r : op_If_s; |
1941 | | |
1942 | 13 | _ (EmitOp (o, op)); |
1943 | 13 | _ (EmitSlotNumOfStackTopAndPop (o)); |
1944 | | |
1945 | 13 | pc_t * pc = (pc_t *) ReservePointer (o); |
1946 | | |
1947 | 13 | IM3FuncType blockType; |
1948 | 13 | _ (ReadBlockType (o, & blockType)); |
1949 | | |
1950 | | // dump_type_stack (o); |
1951 | | |
1952 | 12 | u16 stackIndex = o->stackIndex; |
1953 | | |
1954 | 12 | _ (CompileBlock (o, blockType, i_opcode)); |
1955 | |
|
1956 | 0 | if (o->previousOpcode == c_waOp_else) |
1957 | 0 | { |
1958 | 0 | o->stackIndex = stackIndex; |
1959 | 0 | _ (CompileElseBlock (o, pc, blockType)); |
1960 | 0 | } |
1961 | 0 | else |
1962 | 0 | { |
1963 | | // if block produces values and there isn't a defined else |
1964 | | // case, then we need to make one up so that the pass-through |
1965 | | // results end up in the right place |
1966 | 0 | if (GetFuncTypeNumResults (blockType)) |
1967 | 0 | { |
1968 | | // rewind to the if's end to create a fake else block |
1969 | 0 | o->wasm--; |
1970 | 0 | o->stackIndex = stackIndex; |
1971 | | |
1972 | | // dump_type_stack (o); |
1973 | |
|
1974 | 0 | _ (CompileElseBlock (o, pc, blockType)); |
1975 | 0 | } |
1976 | 0 | else * pc = GetPC (o); |
1977 | 0 | } |
1978 | |
|
1979 | 13 | } _catch: return result; |
1980 | 0 | } |
1981 | | |
1982 | | static |
1983 | | M3Result Compile_Select (IM3Compilation o, m3opcode_t i_opcode) |
1984 | 0 | { |
1985 | 0 | M3Result result = m3Err_none; |
1986 | |
|
1987 | 0 | u16 slots [3] = { c_slotUnused, c_slotUnused, c_slotUnused }; |
1988 | |
|
1989 | 0 | u8 type = GetStackTypeFromTop (o, 1); // get type of selection |
1990 | |
|
1991 | 0 | IM3Operation op = NULL; |
1992 | |
|
1993 | 0 | if (IsFpType (type)) |
1994 | 0 | { |
1995 | 0 | # if d_m3HasFloat |
1996 | | // not consuming a fp reg, so preserve |
1997 | 0 | if (not IsStackTopMinus1InRegister (o) and |
1998 | 0 | not IsStackTopMinus2InRegister (o)) |
1999 | 0 | { |
2000 | 0 | _ (PreserveRegisterIfOccupied (o, type)); |
2001 | 0 | } |
2002 | | |
2003 | 0 | bool selectorInReg = IsStackTopInRegister (o); |
2004 | 0 | slots [0] = GetStackTopSlotNumber (o); |
2005 | 0 | _ (Pop (o)); |
2006 | |
|
2007 | 0 | u32 opIndex = 0; |
2008 | |
|
2009 | 0 | for (u32 i = 1; i <= 2; ++i) |
2010 | 0 | { |
2011 | 0 | if (IsStackTopInRegister (o)) |
2012 | 0 | opIndex = i; |
2013 | 0 | else |
2014 | 0 | slots [i] = GetStackTopSlotNumber (o); |
2015 | |
|
2016 | 0 | _ (Pop (o)); |
2017 | 0 | } |
2018 | | |
2019 | 0 | op = c_fpSelectOps [type - c_m3Type_f32] [selectorInReg] [opIndex]; |
2020 | | # else |
2021 | | _throw (m3Err_unknownOpcode); |
2022 | | # endif |
2023 | 0 | } |
2024 | 0 | else if (IsIntType (type)) |
2025 | 0 | { |
2026 | | // 'sss' operation doesn't consume a register, so might have to protected its contents |
2027 | 0 | if (not IsStackTopInRegister (o) and |
2028 | 0 | not IsStackTopMinus1InRegister (o) and |
2029 | 0 | not IsStackTopMinus2InRegister (o)) |
2030 | 0 | { |
2031 | 0 | _ (PreserveRegisterIfOccupied (o, type)); |
2032 | 0 | } |
2033 | | |
2034 | 0 | u32 opIndex = 3; // op_Select_*_sss |
2035 | |
|
2036 | 0 | for (u32 i = 0; i < 3; ++i) |
2037 | 0 | { |
2038 | 0 | if (IsStackTopInRegister (o)) |
2039 | 0 | opIndex = i; |
2040 | 0 | else |
2041 | 0 | slots [i] = GetStackTopSlotNumber (o); |
2042 | |
|
2043 | 0 | _ (Pop (o)); |
2044 | 0 | } |
2045 | | |
2046 | 0 | op = c_intSelectOps [type - c_m3Type_i32] [opIndex]; |
2047 | 0 | } |
2048 | 0 | else if (not IsStackPolymorphic (o)) |
2049 | 0 | _throw (m3Err_functionStackUnderrun); |
2050 | |
|
2051 | 0 | EmitOp (o, op); |
2052 | 0 | for (u32 i = 0; i < 3; i++) |
2053 | 0 | { |
2054 | 0 | if (IsValidSlot (slots [i])) |
2055 | 0 | EmitSlotOffset (o, slots [i]); |
2056 | 0 | } |
2057 | 0 | _ (PushRegister (o, type)); |
2058 | |
|
2059 | 0 | _catch: return result; |
2060 | 0 | } |
2061 | | |
2062 | | static |
2063 | | M3Result Compile_Drop (IM3Compilation o, m3opcode_t i_opcode) |
2064 | 1 | { |
2065 | 1 | M3Result result = Pop (o); if (d_m3LogWasmStack) dump_type_stack (o); |
2066 | 1 | return result; |
2067 | 1 | } |
2068 | | |
2069 | | static |
2070 | | M3Result Compile_Nop (IM3Compilation o, m3opcode_t i_opcode) |
2071 | 11 | { |
2072 | 11 | return m3Err_none; |
2073 | 11 | } |
2074 | | |
2075 | | static |
2076 | | M3Result Compile_Unreachable (IM3Compilation o, m3opcode_t i_opcode) |
2077 | 31 | { |
2078 | 31 | M3Result result; |
2079 | | |
2080 | 31 | _ (AddTrapRecord (o)); |
2081 | | |
2082 | 31 | _ (EmitOp (o, op_Unreachable)); |
2083 | 31 | _ (SetStackPolymorphic (o)); |
2084 | | |
2085 | 31 | _catch: |
2086 | 31 | return result; |
2087 | 31 | } |
2088 | | |
2089 | | |
2090 | | // OPTZ: currently all stack slot indices take up a full word, but |
2091 | | // dual stack source operands could be packed together |
2092 | | static |
2093 | | M3Result Compile_Operator (IM3Compilation o, m3opcode_t i_opcode) |
2094 | 69 | { |
2095 | 69 | M3Result result; |
2096 | | |
2097 | 69 | IM3OpInfo opInfo = GetOpInfo (i_opcode); |
2098 | 69 | _throwif (m3Err_unknownOpcode, not opInfo); |
2099 | | |
2100 | 69 | IM3Operation op; |
2101 | | |
2102 | | // This preserve is for for FP compare operations. |
2103 | | // either need additional slot destination operations or the |
2104 | | // easy fix, move _r0 out of the way. |
2105 | | // moving out the way might be the optimal solution most often? |
2106 | | // otherwise, the _r0 reg can get buried down in the stack |
2107 | | // and be idle & wasted for a moment. |
2108 | 69 | if (IsFpType (GetStackTopType (o)) and IsIntType (opInfo->type)) |
2109 | 14 | { |
2110 | 14 | _ (PreserveRegisterIfOccupied (o, opInfo->type)); |
2111 | 14 | } |
2112 | | |
2113 | 69 | if (opInfo->stackOffset == 0) |
2114 | 18 | { |
2115 | 18 | if (IsStackTopInRegister (o)) |
2116 | 14 | { |
2117 | 14 | op = opInfo->operations [0]; // _s |
2118 | 14 | } |
2119 | 4 | else |
2120 | 4 | { |
2121 | 4 | _ (PreserveRegisterIfOccupied (o, opInfo->type)); |
2122 | 4 | op = opInfo->operations [1]; // _r |
2123 | 4 | } |
2124 | 18 | } |
2125 | 51 | else |
2126 | 51 | { |
2127 | 51 | if (IsStackTopInRegister (o)) |
2128 | 24 | { |
2129 | 24 | op = opInfo->operations [0]; // _rs |
2130 | | |
2131 | 24 | if (IsStackTopMinus1InRegister (o)) |
2132 | 0 | { d_m3Assert (i_opcode == c_waOp_store_f32 or i_opcode == c_waOp_store_f64); |
2133 | 0 | op = opInfo->operations [3]; // _rr for fp.store |
2134 | 0 | } |
2135 | 24 | } |
2136 | 27 | else if (IsStackTopMinus1InRegister (o)) |
2137 | 1 | { |
2138 | 1 | op = opInfo->operations [1]; // _sr |
2139 | | |
2140 | 1 | if (not op) // must be commutative, then |
2141 | 0 | op = opInfo->operations [0]; |
2142 | 1 | } |
2143 | 26 | else |
2144 | 26 | { |
2145 | 26 | _ (PreserveRegisterIfOccupied (o, opInfo->type)); // _ss |
2146 | 26 | op = opInfo->operations [2]; |
2147 | 26 | } |
2148 | 51 | } |
2149 | | |
2150 | 69 | if (op) |
2151 | 69 | { |
2152 | 69 | _ (EmitOp (o, op)); |
2153 | | |
2154 | 69 | _ (EmitSlotNumOfStackTopAndPop (o)); |
2155 | | |
2156 | 69 | if (opInfo->stackOffset < 0) |
2157 | 69 | _ (EmitSlotNumOfStackTopAndPop (o)); |
2158 | | |
2159 | 69 | if (opInfo->type != c_m3Type_none) |
2160 | 69 | _ (PushRegister (o, opInfo->type)); |
2161 | 69 | } |
2162 | 0 | else |
2163 | 0 | { |
2164 | | # ifdef DEBUG |
2165 | | result = ErrorCompile ("no operation found for opcode", o, "'%s'", opInfo->name); |
2166 | | # else |
2167 | 0 | result = ErrorCompile ("no operation found for opcode", o, "%x", i_opcode); |
2168 | 0 | # endif |
2169 | 0 | _throw (result); |
2170 | 0 | } |
2171 | | |
2172 | 69 | _catch: return result; |
2173 | 69 | } |
2174 | | |
2175 | | static |
2176 | | M3Result Compile_Convert (IM3Compilation o, m3opcode_t i_opcode) |
2177 | 7 | { |
2178 | 7 | _try { |
2179 | 7 | IM3OpInfo opInfo = GetOpInfo (i_opcode); |
2180 | 7 | _throwif (m3Err_unknownOpcode, not opInfo); |
2181 | | |
2182 | 7 | bool destInSlot = IsRegisterTypeAllocated (o, opInfo->type); |
2183 | 7 | bool sourceInSlot = IsStackTopInSlot (o); |
2184 | | |
2185 | 7 | IM3Operation op = opInfo->operations [destInSlot * 2 + sourceInSlot]; |
2186 | | |
2187 | 7 | _ (EmitOp (o, op)); |
2188 | 7 | _ (EmitSlotNumOfStackTopAndPop (o)); |
2189 | | |
2190 | 7 | if (destInSlot) |
2191 | 4 | _ (PushAllocatedSlotAndEmit (o, opInfo->type)) |
2192 | 3 | else |
2193 | 3 | _ (PushRegister (o, opInfo->type)) |
2194 | | |
2195 | 7 | } |
2196 | 7 | _catch: return result; |
2197 | 7 | } |
2198 | | |
2199 | | static |
2200 | | M3Result Compile_Load_Store (IM3Compilation o, m3opcode_t i_opcode) |
2201 | 18 | { |
2202 | 18 | _try { |
2203 | 18 | u32 alignHint, memoryOffset; |
2204 | | |
2205 | 18 | _ (ReadLEB_u32 (& alignHint, & o->wasm, o->wasmEnd)); |
2206 | 18 | _ (ReadLEB_u32 (& memoryOffset, & o->wasm, o->wasmEnd)); |
2207 | 18 | m3log (compile, d_indent " (offset = %d)", get_indention_string (o), memoryOffset); |
2208 | 18 | IM3OpInfo opInfo = GetOpInfo (i_opcode); |
2209 | 18 | _throwif (m3Err_unknownOpcode, not opInfo); |
2210 | | |
2211 | 18 | if (IsFpType (opInfo->type)) |
2212 | 18 | _ (PreserveRegisterIfOccupied (o, c_m3Type_f64)); |
2213 | | |
2214 | 18 | _ (Compile_Operator (o, i_opcode)); |
2215 | | |
2216 | 18 | EmitConstant32 (o, memoryOffset); |
2217 | 18 | } |
2218 | 18 | _catch: return result; |
2219 | 18 | } |
2220 | | |
2221 | | |
2222 | | M3Result CompileRawFunction (IM3Module io_module, IM3Function io_function, const void * i_function, const void * i_userdata) |
2223 | 0 | { |
2224 | 0 | d_m3Assert (io_module->runtime); |
2225 | |
|
2226 | 0 | IM3CodePage page = AcquireCodePageWithCapacity (io_module->runtime, 4); |
2227 | |
|
2228 | 0 | if (page) |
2229 | 0 | { |
2230 | 0 | io_function->compiled = GetPagePC (page); |
2231 | 0 | io_function->module = io_module; |
2232 | |
|
2233 | 0 | EmitWord (page, op_CallRawFunction); |
2234 | 0 | EmitWord (page, i_function); |
2235 | 0 | EmitWord (page, io_function); |
2236 | 0 | EmitWord (page, i_userdata); |
2237 | |
|
2238 | 0 | ReleaseCodePage (io_module->runtime, page); |
2239 | 0 | return m3Err_none; |
2240 | 0 | } |
2241 | 0 | else { |
2242 | 0 | return m3Err_mallocFailedCodePage; |
2243 | 0 | } |
2244 | 0 | } |
2245 | | |
2246 | | |
2247 | | |
2248 | | // d_logOp, d_logOp2 macros aren't actually used by the compiler, just codepage decoding (d_m3LogCodePages = 1) |
2249 | | #define d_logOp(OP) { op_##OP, NULL, NULL, NULL } |
2250 | | #define d_logOp2(OP1,OP2) { op_##OP1, op_##OP2, NULL, NULL } |
2251 | | |
2252 | | #define d_emptyOpList { NULL, NULL, NULL, NULL } |
2253 | | #define d_unaryOpList(TYPE, NAME) { op_##TYPE##_##NAME##_r, op_##TYPE##_##NAME##_s, NULL, NULL } |
2254 | | #define d_binOpList(TYPE, NAME) { op_##TYPE##_##NAME##_rs, op_##TYPE##_##NAME##_sr, op_##TYPE##_##NAME##_ss, NULL } |
2255 | | #define d_storeFpOpList(TYPE, NAME) { op_##TYPE##_##NAME##_rs, op_##TYPE##_##NAME##_sr, op_##TYPE##_##NAME##_ss, op_##TYPE##_##NAME##_rr } |
2256 | | #define d_commutativeBinOpList(TYPE, NAME) { op_##TYPE##_##NAME##_rs, NULL, op_##TYPE##_##NAME##_ss, NULL } |
2257 | | #define d_convertOpList(OP) { op_##OP##_r_r, op_##OP##_r_s, op_##OP##_s_r, op_##OP##_s_s } |
2258 | | |
2259 | | |
2260 | | const M3OpInfo c_operations [] = |
2261 | | { |
2262 | | M3OP( "unreachable", 0, none, d_logOp (Unreachable), Compile_Unreachable ), // 0x00 |
2263 | | M3OP( "nop", 0, none, d_emptyOpList, Compile_Nop ), // 0x01 . |
2264 | | M3OP( "block", 0, none, d_emptyOpList, Compile_LoopOrBlock ), // 0x02 |
2265 | | M3OP( "loop", 0, none, d_logOp (Loop), Compile_LoopOrBlock ), // 0x03 |
2266 | | M3OP( "if", -1, none, d_emptyOpList, Compile_If ), // 0x04 |
2267 | | M3OP( "else", 0, none, d_emptyOpList, Compile_Nop ), // 0x05 |
2268 | | |
2269 | | M3OP_RESERVED, M3OP_RESERVED, M3OP_RESERVED, M3OP_RESERVED, M3OP_RESERVED, // 0x06...0x0a |
2270 | | |
2271 | | M3OP( "end", 0, none, d_emptyOpList, Compile_End ), // 0x0b |
2272 | | M3OP( "br", 0, none, d_logOp (Branch), Compile_Branch ), // 0x0c |
2273 | | M3OP( "br_if", -1, none, d_logOp2 (BranchIf_r, BranchIf_s), Compile_Branch ), // 0x0d |
2274 | | M3OP( "br_table", -1, none, d_logOp (BranchTable), Compile_BranchTable ), // 0x0e |
2275 | | M3OP( "return", 0, any, d_logOp (Return), Compile_Return ), // 0x0f |
2276 | | M3OP( "call", 0, any, d_logOp (Call), Compile_Call ), // 0x10 |
2277 | | M3OP( "call_indirect", 0, any, d_logOp (CallIndirect), Compile_CallIndirect ), // 0x11 |
2278 | | M3OP( "return_call", 0, any, d_emptyOpList, Compile_Call ), // 0x12 TODO: Optimize |
2279 | | M3OP( "return_call_indirect",0, any, d_emptyOpList, Compile_CallIndirect ), // 0x13 |
2280 | | |
2281 | | M3OP_RESERVED, M3OP_RESERVED, // 0x14... |
2282 | | M3OP_RESERVED, M3OP_RESERVED, M3OP_RESERVED, M3OP_RESERVED, // ...0x19 |
2283 | | |
2284 | | M3OP( "drop", -1, none, d_emptyOpList, Compile_Drop ), // 0x1a |
2285 | | M3OP( "select", -2, any, d_emptyOpList, Compile_Select ), // 0x1b |
2286 | | |
2287 | | M3OP_RESERVED, M3OP_RESERVED, M3OP_RESERVED, M3OP_RESERVED, // 0x1c...0x1f |
2288 | | |
2289 | | M3OP( "local.get", 1, any, d_emptyOpList, Compile_GetLocal ), // 0x20 |
2290 | | M3OP( "local.set", 1, none, d_emptyOpList, Compile_SetLocal ), // 0x21 |
2291 | | M3OP( "local.tee", 0, any, d_emptyOpList, Compile_SetLocal ), // 0x22 |
2292 | | M3OP( "global.get", 1, none, d_emptyOpList, Compile_GetSetGlobal ), // 0x23 |
2293 | | M3OP( "global.set", 1, none, d_emptyOpList, Compile_GetSetGlobal ), // 0x24 |
2294 | | |
2295 | | M3OP_RESERVED, M3OP_RESERVED, M3OP_RESERVED, // 0x25...0x27 |
2296 | | |
2297 | | M3OP( "i32.load", 0, i_32, d_unaryOpList (i32, Load_i32), Compile_Load_Store ), // 0x28 |
2298 | | M3OP( "i64.load", 0, i_64, d_unaryOpList (i64, Load_i64), Compile_Load_Store ), // 0x29 |
2299 | | M3OP_F( "f32.load", 0, f_32, d_unaryOpList (f32, Load_f32), Compile_Load_Store ), // 0x2a |
2300 | | M3OP_F( "f64.load", 0, f_64, d_unaryOpList (f64, Load_f64), Compile_Load_Store ), // 0x2b |
2301 | | |
2302 | | M3OP( "i32.load8_s", 0, i_32, d_unaryOpList (i32, Load_i8), Compile_Load_Store ), // 0x2c |
2303 | | M3OP( "i32.load8_u", 0, i_32, d_unaryOpList (i32, Load_u8), Compile_Load_Store ), // 0x2d |
2304 | | M3OP( "i32.load16_s", 0, i_32, d_unaryOpList (i32, Load_i16), Compile_Load_Store ), // 0x2e |
2305 | | M3OP( "i32.load16_u", 0, i_32, d_unaryOpList (i32, Load_u16), Compile_Load_Store ), // 0x2f |
2306 | | |
2307 | | M3OP( "i64.load8_s", 0, i_64, d_unaryOpList (i64, Load_i8), Compile_Load_Store ), // 0x30 |
2308 | | M3OP( "i64.load8_u", 0, i_64, d_unaryOpList (i64, Load_u8), Compile_Load_Store ), // 0x31 |
2309 | | M3OP( "i64.load16_s", 0, i_64, d_unaryOpList (i64, Load_i16), Compile_Load_Store ), // 0x32 |
2310 | | M3OP( "i64.load16_u", 0, i_64, d_unaryOpList (i64, Load_u16), Compile_Load_Store ), // 0x33 |
2311 | | M3OP( "i64.load32_s", 0, i_64, d_unaryOpList (i64, Load_i32), Compile_Load_Store ), // 0x34 |
2312 | | M3OP( "i64.load32_u", 0, i_64, d_unaryOpList (i64, Load_u32), Compile_Load_Store ), // 0x35 |
2313 | | |
2314 | | M3OP( "i32.store", -2, none, d_binOpList (i32, Store_i32), Compile_Load_Store ), // 0x36 |
2315 | | M3OP( "i64.store", -2, none, d_binOpList (i64, Store_i64), Compile_Load_Store ), // 0x37 |
2316 | | M3OP_F( "f32.store", -2, none, d_storeFpOpList (f32, Store_f32), Compile_Load_Store ), // 0x38 |
2317 | | M3OP_F( "f64.store", -2, none, d_storeFpOpList (f64, Store_f64), Compile_Load_Store ), // 0x39 |
2318 | | |
2319 | | M3OP( "i32.store8", -2, none, d_binOpList (i32, Store_u8), Compile_Load_Store ), // 0x3a |
2320 | | M3OP( "i32.store16", -2, none, d_binOpList (i32, Store_i16), Compile_Load_Store ), // 0x3b |
2321 | | |
2322 | | M3OP( "i64.store8", -2, none, d_binOpList (i64, Store_u8), Compile_Load_Store ), // 0x3c |
2323 | | M3OP( "i64.store16", -2, none, d_binOpList (i64, Store_i16), Compile_Load_Store ), // 0x3d |
2324 | | M3OP( "i64.store32", -2, none, d_binOpList (i64, Store_i32), Compile_Load_Store ), // 0x3e |
2325 | | |
2326 | | M3OP( "memory.size", 1, i_32, d_logOp (MemSize), Compile_Memory_Size ), // 0x3f |
2327 | | M3OP( "memory.grow", 1, i_32, d_logOp (MemGrow), Compile_Memory_Grow ), // 0x40 |
2328 | | |
2329 | | M3OP( "i32.const", 1, i_32, d_logOp (Const32), Compile_Const_i32 ), // 0x41 |
2330 | | M3OP( "i64.const", 1, i_64, d_logOp (Const64), Compile_Const_i64 ), // 0x42 |
2331 | | M3OP_F( "f32.const", 1, f_32, d_emptyOpList, Compile_Const_f32 ), // 0x43 |
2332 | | M3OP_F( "f64.const", 1, f_64, d_emptyOpList, Compile_Const_f64 ), // 0x44 |
2333 | | |
2334 | | M3OP( "i32.eqz", 0, i_32, d_unaryOpList (i32, EqualToZero) , NULL ), // 0x45 |
2335 | | M3OP( "i32.eq", -1, i_32, d_commutativeBinOpList (i32, Equal) , NULL ), // 0x46 |
2336 | | M3OP( "i32.ne", -1, i_32, d_commutativeBinOpList (i32, NotEqual) , NULL ), // 0x47 |
2337 | | M3OP( "i32.lt_s", -1, i_32, d_binOpList (i32, LessThan) , NULL ), // 0x48 |
2338 | | M3OP( "i32.lt_u", -1, i_32, d_binOpList (u32, LessThan) , NULL ), // 0x49 |
2339 | | M3OP( "i32.gt_s", -1, i_32, d_binOpList (i32, GreaterThan) , NULL ), // 0x4a |
2340 | | M3OP( "i32.gt_u", -1, i_32, d_binOpList (u32, GreaterThan) , NULL ), // 0x4b |
2341 | | M3OP( "i32.le_s", -1, i_32, d_binOpList (i32, LessThanOrEqual) , NULL ), // 0x4c |
2342 | | M3OP( "i32.le_u", -1, i_32, d_binOpList (u32, LessThanOrEqual) , NULL ), // 0x4d |
2343 | | M3OP( "i32.ge_s", -1, i_32, d_binOpList (i32, GreaterThanOrEqual) , NULL ), // 0x4e |
2344 | | M3OP( "i32.ge_u", -1, i_32, d_binOpList (u32, GreaterThanOrEqual) , NULL ), // 0x4f |
2345 | | |
2346 | | M3OP( "i64.eqz", 0, i_32, d_unaryOpList (i64, EqualToZero) , NULL ), // 0x50 |
2347 | | M3OP( "i64.eq", -1, i_32, d_commutativeBinOpList (i64, Equal) , NULL ), // 0x51 |
2348 | | M3OP( "i64.ne", -1, i_32, d_commutativeBinOpList (i64, NotEqual) , NULL ), // 0x52 |
2349 | | M3OP( "i64.lt_s", -1, i_32, d_binOpList (i64, LessThan) , NULL ), // 0x53 |
2350 | | M3OP( "i64.lt_u", -1, i_32, d_binOpList (u64, LessThan) , NULL ), // 0x54 |
2351 | | M3OP( "i64.gt_s", -1, i_32, d_binOpList (i64, GreaterThan) , NULL ), // 0x55 |
2352 | | M3OP( "i64.gt_u", -1, i_32, d_binOpList (u64, GreaterThan) , NULL ), // 0x56 |
2353 | | M3OP( "i64.le_s", -1, i_32, d_binOpList (i64, LessThanOrEqual) , NULL ), // 0x57 |
2354 | | M3OP( "i64.le_u", -1, i_32, d_binOpList (u64, LessThanOrEqual) , NULL ), // 0x58 |
2355 | | M3OP( "i64.ge_s", -1, i_32, d_binOpList (i64, GreaterThanOrEqual) , NULL ), // 0x59 |
2356 | | M3OP( "i64.ge_u", -1, i_32, d_binOpList (u64, GreaterThanOrEqual) , NULL ), // 0x5a |
2357 | | |
2358 | | M3OP_F( "f32.eq", -1, i_32, d_commutativeBinOpList (f32, Equal) , NULL ), // 0x5b |
2359 | | M3OP_F( "f32.ne", -1, i_32, d_commutativeBinOpList (f32, NotEqual) , NULL ), // 0x5c |
2360 | | M3OP_F( "f32.lt", -1, i_32, d_binOpList (f32, LessThan) , NULL ), // 0x5d |
2361 | | M3OP_F( "f32.gt", -1, i_32, d_binOpList (f32, GreaterThan) , NULL ), // 0x5e |
2362 | | M3OP_F( "f32.le", -1, i_32, d_binOpList (f32, LessThanOrEqual) , NULL ), // 0x5f |
2363 | | M3OP_F( "f32.ge", -1, i_32, d_binOpList (f32, GreaterThanOrEqual) , NULL ), // 0x60 |
2364 | | |
2365 | | M3OP_F( "f64.eq", -1, i_32, d_commutativeBinOpList (f64, Equal) , NULL ), // 0x61 |
2366 | | M3OP_F( "f64.ne", -1, i_32, d_commutativeBinOpList (f64, NotEqual) , NULL ), // 0x62 |
2367 | | M3OP_F( "f64.lt", -1, i_32, d_binOpList (f64, LessThan) , NULL ), // 0x63 |
2368 | | M3OP_F( "f64.gt", -1, i_32, d_binOpList (f64, GreaterThan) , NULL ), // 0x64 |
2369 | | M3OP_F( "f64.le", -1, i_32, d_binOpList (f64, LessThanOrEqual) , NULL ), // 0x65 |
2370 | | M3OP_F( "f64.ge", -1, i_32, d_binOpList (f64, GreaterThanOrEqual) , NULL ), // 0x66 |
2371 | | |
2372 | | M3OP( "i32.clz", 0, i_32, d_unaryOpList (u32, Clz) , NULL ), // 0x67 |
2373 | | M3OP( "i32.ctz", 0, i_32, d_unaryOpList (u32, Ctz) , NULL ), // 0x68 |
2374 | | M3OP( "i32.popcnt", 0, i_32, d_unaryOpList (u32, Popcnt) , NULL ), // 0x69 |
2375 | | |
2376 | | M3OP( "i32.add", -1, i_32, d_commutativeBinOpList (i32, Add) , NULL ), // 0x6a |
2377 | | M3OP( "i32.sub", -1, i_32, d_binOpList (i32, Subtract) , NULL ), // 0x6b |
2378 | | M3OP( "i32.mul", -1, i_32, d_commutativeBinOpList (i32, Multiply) , NULL ), // 0x6c |
2379 | | M3OP( "i32.div_s", -1, i_32, d_binOpList (i32, Divide) , NULL ), // 0x6d |
2380 | | M3OP( "i32.div_u", -1, i_32, d_binOpList (u32, Divide) , NULL ), // 0x6e |
2381 | | M3OP( "i32.rem_s", -1, i_32, d_binOpList (i32, Remainder) , NULL ), // 0x6f |
2382 | | M3OP( "i32.rem_u", -1, i_32, d_binOpList (u32, Remainder) , NULL ), // 0x70 |
2383 | | M3OP( "i32.and", -1, i_32, d_commutativeBinOpList (u32, And) , NULL ), // 0x71 |
2384 | | M3OP( "i32.or", -1, i_32, d_commutativeBinOpList (u32, Or) , NULL ), // 0x72 |
2385 | | M3OP( "i32.xor", -1, i_32, d_commutativeBinOpList (u32, Xor) , NULL ), // 0x73 |
2386 | | M3OP( "i32.shl", -1, i_32, d_binOpList (u32, ShiftLeft) , NULL ), // 0x74 |
2387 | | M3OP( "i32.shr_s", -1, i_32, d_binOpList (i32, ShiftRight) , NULL ), // 0x75 |
2388 | | M3OP( "i32.shr_u", -1, i_32, d_binOpList (u32, ShiftRight) , NULL ), // 0x76 |
2389 | | M3OP( "i32.rotl", -1, i_32, d_binOpList (u32, Rotl) , NULL ), // 0x77 |
2390 | | M3OP( "i32.rotr", -1, i_32, d_binOpList (u32, Rotr) , NULL ), // 0x78 |
2391 | | |
2392 | | M3OP( "i64.clz", 0, i_64, d_unaryOpList (u64, Clz) , NULL ), // 0x79 |
2393 | | M3OP( "i64.ctz", 0, i_64, d_unaryOpList (u64, Ctz) , NULL ), // 0x7a |
2394 | | M3OP( "i64.popcnt", 0, i_64, d_unaryOpList (u64, Popcnt) , NULL ), // 0x7b |
2395 | | |
2396 | | M3OP( "i64.add", -1, i_64, d_commutativeBinOpList (i64, Add) , NULL ), // 0x7c |
2397 | | M3OP( "i64.sub", -1, i_64, d_binOpList (i64, Subtract) , NULL ), // 0x7d |
2398 | | M3OP( "i64.mul", -1, i_64, d_commutativeBinOpList (i64, Multiply) , NULL ), // 0x7e |
2399 | | M3OP( "i64.div_s", -1, i_64, d_binOpList (i64, Divide) , NULL ), // 0x7f |
2400 | | M3OP( "i64.div_u", -1, i_64, d_binOpList (u64, Divide) , NULL ), // 0x80 |
2401 | | M3OP( "i64.rem_s", -1, i_64, d_binOpList (i64, Remainder) , NULL ), // 0x81 |
2402 | | M3OP( "i64.rem_u", -1, i_64, d_binOpList (u64, Remainder) , NULL ), // 0x82 |
2403 | | M3OP( "i64.and", -1, i_64, d_commutativeBinOpList (u64, And) , NULL ), // 0x83 |
2404 | | M3OP( "i64.or", -1, i_64, d_commutativeBinOpList (u64, Or) , NULL ), // 0x84 |
2405 | | M3OP( "i64.xor", -1, i_64, d_commutativeBinOpList (u64, Xor) , NULL ), // 0x85 |
2406 | | M3OP( "i64.shl", -1, i_64, d_binOpList (u64, ShiftLeft) , NULL ), // 0x86 |
2407 | | M3OP( "i64.shr_s", -1, i_64, d_binOpList (i64, ShiftRight) , NULL ), // 0x87 |
2408 | | M3OP( "i64.shr_u", -1, i_64, d_binOpList (u64, ShiftRight) , NULL ), // 0x88 |
2409 | | M3OP( "i64.rotl", -1, i_64, d_binOpList (u64, Rotl) , NULL ), // 0x89 |
2410 | | M3OP( "i64.rotr", -1, i_64, d_binOpList (u64, Rotr) , NULL ), // 0x8a |
2411 | | |
2412 | | M3OP_F( "f32.abs", 0, f_32, d_unaryOpList(f32, Abs) , NULL ), // 0x8b |
2413 | | M3OP_F( "f32.neg", 0, f_32, d_unaryOpList(f32, Negate) , NULL ), // 0x8c |
2414 | | M3OP_F( "f32.ceil", 0, f_32, d_unaryOpList(f32, Ceil) , NULL ), // 0x8d |
2415 | | M3OP_F( "f32.floor", 0, f_32, d_unaryOpList(f32, Floor) , NULL ), // 0x8e |
2416 | | M3OP_F( "f32.trunc", 0, f_32, d_unaryOpList(f32, Trunc) , NULL ), // 0x8f |
2417 | | M3OP_F( "f32.nearest", 0, f_32, d_unaryOpList(f32, Nearest) , NULL ), // 0x90 |
2418 | | M3OP_F( "f32.sqrt", 0, f_32, d_unaryOpList(f32, Sqrt) , NULL ), // 0x91 |
2419 | | |
2420 | | M3OP_F( "f32.add", -1, f_32, d_commutativeBinOpList (f32, Add) , NULL ), // 0x92 |
2421 | | M3OP_F( "f32.sub", -1, f_32, d_binOpList (f32, Subtract) , NULL ), // 0x93 |
2422 | | M3OP_F( "f32.mul", -1, f_32, d_commutativeBinOpList (f32, Multiply) , NULL ), // 0x94 |
2423 | | M3OP_F( "f32.div", -1, f_32, d_binOpList (f32, Divide) , NULL ), // 0x95 |
2424 | | M3OP_F( "f32.min", -1, f_32, d_commutativeBinOpList (f32, Min) , NULL ), // 0x96 |
2425 | | M3OP_F( "f32.max", -1, f_32, d_commutativeBinOpList (f32, Max) , NULL ), // 0x97 |
2426 | | M3OP_F( "f32.copysign", -1, f_32, d_binOpList (f32, CopySign) , NULL ), // 0x98 |
2427 | | |
2428 | | M3OP_F( "f64.abs", 0, f_64, d_unaryOpList(f64, Abs) , NULL ), // 0x99 |
2429 | | M3OP_F( "f64.neg", 0, f_64, d_unaryOpList(f64, Negate) , NULL ), // 0x9a |
2430 | | M3OP_F( "f64.ceil", 0, f_64, d_unaryOpList(f64, Ceil) , NULL ), // 0x9b |
2431 | | M3OP_F( "f64.floor", 0, f_64, d_unaryOpList(f64, Floor) , NULL ), // 0x9c |
2432 | | M3OP_F( "f64.trunc", 0, f_64, d_unaryOpList(f64, Trunc) , NULL ), // 0x9d |
2433 | | M3OP_F( "f64.nearest", 0, f_64, d_unaryOpList(f64, Nearest) , NULL ), // 0x9e |
2434 | | M3OP_F( "f64.sqrt", 0, f_64, d_unaryOpList(f64, Sqrt) , NULL ), // 0x9f |
2435 | | |
2436 | | M3OP_F( "f64.add", -1, f_64, d_commutativeBinOpList (f64, Add) , NULL ), // 0xa0 |
2437 | | M3OP_F( "f64.sub", -1, f_64, d_binOpList (f64, Subtract) , NULL ), // 0xa1 |
2438 | | M3OP_F( "f64.mul", -1, f_64, d_commutativeBinOpList (f64, Multiply) , NULL ), // 0xa2 |
2439 | | M3OP_F( "f64.div", -1, f_64, d_binOpList (f64, Divide) , NULL ), // 0xa3 |
2440 | | M3OP_F( "f64.min", -1, f_64, d_commutativeBinOpList (f64, Min) , NULL ), // 0xa4 |
2441 | | M3OP_F( "f64.max", -1, f_64, d_commutativeBinOpList (f64, Max) , NULL ), // 0xa5 |
2442 | | M3OP_F( "f64.copysign", -1, f_64, d_binOpList (f64, CopySign) , NULL ), // 0xa6 |
2443 | | |
2444 | | M3OP( "i32.wrap/i64", 0, i_32, d_unaryOpList (i32, Wrap_i64), NULL ), // 0xa7 |
2445 | | M3OP_F( "i32.trunc_s/f32", 0, i_32, d_convertOpList (i32_Trunc_f32), Compile_Convert ), // 0xa8 |
2446 | | M3OP_F( "i32.trunc_u/f32", 0, i_32, d_convertOpList (u32_Trunc_f32), Compile_Convert ), // 0xa9 |
2447 | | M3OP_F( "i32.trunc_s/f64", 0, i_32, d_convertOpList (i32_Trunc_f64), Compile_Convert ), // 0xaa |
2448 | | M3OP_F( "i32.trunc_u/f64", 0, i_32, d_convertOpList (u32_Trunc_f64), Compile_Convert ), // 0xab |
2449 | | |
2450 | | M3OP( "i64.extend_s/i32", 0, i_64, d_unaryOpList (i64, Extend_i32), NULL ), // 0xac |
2451 | | M3OP( "i64.extend_u/i32", 0, i_64, d_unaryOpList (i64, Extend_u32), NULL ), // 0xad |
2452 | | |
2453 | | M3OP_F( "i64.trunc_s/f32", 0, i_64, d_convertOpList (i64_Trunc_f32), Compile_Convert ), // 0xae |
2454 | | M3OP_F( "i64.trunc_u/f32", 0, i_64, d_convertOpList (u64_Trunc_f32), Compile_Convert ), // 0xaf |
2455 | | M3OP_F( "i64.trunc_s/f64", 0, i_64, d_convertOpList (i64_Trunc_f64), Compile_Convert ), // 0xb0 |
2456 | | M3OP_F( "i64.trunc_u/f64", 0, i_64, d_convertOpList (u64_Trunc_f64), Compile_Convert ), // 0xb1 |
2457 | | |
2458 | | M3OP_F( "f32.convert_s/i32",0, f_32, d_convertOpList (f32_Convert_i32), Compile_Convert ), // 0xb2 |
2459 | | M3OP_F( "f32.convert_u/i32",0, f_32, d_convertOpList (f32_Convert_u32), Compile_Convert ), // 0xb3 |
2460 | | M3OP_F( "f32.convert_s/i64",0, f_32, d_convertOpList (f32_Convert_i64), Compile_Convert ), // 0xb4 |
2461 | | M3OP_F( "f32.convert_u/i64",0, f_32, d_convertOpList (f32_Convert_u64), Compile_Convert ), // 0xb5 |
2462 | | |
2463 | | M3OP_F( "f32.demote/f64", 0, f_32, d_unaryOpList (f32, Demote_f64), NULL ), // 0xb6 |
2464 | | |
2465 | | M3OP_F( "f64.convert_s/i32",0, f_64, d_convertOpList (f64_Convert_i32), Compile_Convert ), // 0xb7 |
2466 | | M3OP_F( "f64.convert_u/i32",0, f_64, d_convertOpList (f64_Convert_u32), Compile_Convert ), // 0xb8 |
2467 | | M3OP_F( "f64.convert_s/i64",0, f_64, d_convertOpList (f64_Convert_i64), Compile_Convert ), // 0xb9 |
2468 | | M3OP_F( "f64.convert_u/i64",0, f_64, d_convertOpList (f64_Convert_u64), Compile_Convert ), // 0xba |
2469 | | |
2470 | | M3OP_F( "f64.promote/f32", 0, f_64, d_unaryOpList (f64, Promote_f32), NULL ), // 0xbb |
2471 | | |
2472 | | M3OP_F( "i32.reinterpret/f32",0,i_32, d_convertOpList (i32_Reinterpret_f32), Compile_Convert ), // 0xbc |
2473 | | M3OP_F( "i64.reinterpret/f64",0,i_64, d_convertOpList (i64_Reinterpret_f64), Compile_Convert ), // 0xbd |
2474 | | M3OP_F( "f32.reinterpret/i32",0,f_32, d_convertOpList (f32_Reinterpret_i32), Compile_Convert ), // 0xbe |
2475 | | M3OP_F( "f64.reinterpret/i64",0,f_64, d_convertOpList (f64_Reinterpret_i64), Compile_Convert ), // 0xbf |
2476 | | |
2477 | | M3OP( "i32.extend8_s", 0, i_32, d_unaryOpList (i32, Extend8_s), NULL ), // 0xc0 |
2478 | | M3OP( "i32.extend16_s", 0, i_32, d_unaryOpList (i32, Extend16_s), NULL ), // 0xc1 |
2479 | | M3OP( "i64.extend8_s", 0, i_64, d_unaryOpList (i64, Extend8_s), NULL ), // 0xc2 |
2480 | | M3OP( "i64.extend16_s", 0, i_64, d_unaryOpList (i64, Extend16_s), NULL ), // 0xc3 |
2481 | | M3OP( "i64.extend32_s", 0, i_64, d_unaryOpList (i64, Extend32_s), NULL ), // 0xc4 |
2482 | | |
2483 | | # ifdef DEBUG // for codepage logging. the order doesn't matter: |
2484 | | # define d_m3DebugOp(OP) M3OP (#OP, 0, none, { op_##OP }) |
2485 | | |
2486 | | # if d_m3HasFloat |
2487 | | # define d_m3DebugTypedOp(OP) M3OP (#OP, 0, none, { op_##OP##_i32, op_##OP##_i64, op_##OP##_f32, op_##OP##_f64, }) |
2488 | | # else |
2489 | | # define d_m3DebugTypedOp(OP) M3OP (#OP, 0, none, { op_##OP##_i32, op_##OP##_i64 }) |
2490 | | # endif |
2491 | | |
2492 | | d_m3DebugOp (Compile), d_m3DebugOp (Entry), d_m3DebugOp (End), |
2493 | | d_m3DebugOp (Unsupported), d_m3DebugOp (CallRawFunction), |
2494 | | |
2495 | | d_m3DebugOp (GetGlobal_s32), d_m3DebugOp (GetGlobal_s64), d_m3DebugOp (ContinueLoop), d_m3DebugOp (ContinueLoopIf), |
2496 | | |
2497 | | d_m3DebugOp (CopySlot_32), d_m3DebugOp (PreserveCopySlot_32), d_m3DebugOp (If_s), d_m3DebugOp (BranchIfPrologue_s), |
2498 | | d_m3DebugOp (CopySlot_64), d_m3DebugOp (PreserveCopySlot_64), d_m3DebugOp (If_r), d_m3DebugOp (BranchIfPrologue_r), |
2499 | | |
2500 | | d_m3DebugOp (Select_i32_rss), d_m3DebugOp (Select_i32_srs), d_m3DebugOp (Select_i32_ssr), d_m3DebugOp (Select_i32_sss), |
2501 | | d_m3DebugOp (Select_i64_rss), d_m3DebugOp (Select_i64_srs), d_m3DebugOp (Select_i64_ssr), d_m3DebugOp (Select_i64_sss), |
2502 | | |
2503 | | # if d_m3HasFloat |
2504 | | d_m3DebugOp (Select_f32_sss), d_m3DebugOp (Select_f32_srs), d_m3DebugOp (Select_f32_ssr), |
2505 | | d_m3DebugOp (Select_f32_rss), d_m3DebugOp (Select_f32_rrs), d_m3DebugOp (Select_f32_rsr), |
2506 | | |
2507 | | d_m3DebugOp (Select_f64_sss), d_m3DebugOp (Select_f64_srs), d_m3DebugOp (Select_f64_ssr), |
2508 | | d_m3DebugOp (Select_f64_rss), d_m3DebugOp (Select_f64_rrs), d_m3DebugOp (Select_f64_rsr), |
2509 | | # endif |
2510 | | |
2511 | | d_m3DebugOp (MemFill), d_m3DebugOp (MemCopy), |
2512 | | |
2513 | | d_m3DebugTypedOp (SetGlobal), d_m3DebugOp (SetGlobal_s32), d_m3DebugOp (SetGlobal_s64), |
2514 | | |
2515 | | d_m3DebugTypedOp (SetRegister), d_m3DebugTypedOp (SetSlot), d_m3DebugTypedOp (PreserveSetSlot), |
2516 | | # endif |
2517 | | |
2518 | | # if d_m3CascadedOpcodes |
2519 | | [c_waOp_extended] = M3OP( "0xFC", 0, c_m3Type_unknown, d_emptyOpList, Compile_ExtendedOpcode ), |
2520 | | # endif |
2521 | | |
2522 | | # ifdef DEBUG |
2523 | | M3OP( "termination", 0, c_m3Type_unknown ) // for find_operation_info |
2524 | | # endif |
2525 | | }; |
2526 | | |
2527 | | const M3OpInfo c_operationsFC [] = |
2528 | | { |
2529 | | M3OP_F( "i32.trunc_s:sat/f32",0, i_32, d_convertOpList (i32_TruncSat_f32), Compile_Convert ), // 0x00 |
2530 | | M3OP_F( "i32.trunc_u:sat/f32",0, i_32, d_convertOpList (u32_TruncSat_f32), Compile_Convert ), // 0x01 |
2531 | | M3OP_F( "i32.trunc_s:sat/f64",0, i_32, d_convertOpList (i32_TruncSat_f64), Compile_Convert ), // 0x02 |
2532 | | M3OP_F( "i32.trunc_u:sat/f64",0, i_32, d_convertOpList (u32_TruncSat_f64), Compile_Convert ), // 0x03 |
2533 | | M3OP_F( "i64.trunc_s:sat/f32",0, i_64, d_convertOpList (i64_TruncSat_f32), Compile_Convert ), // 0x04 |
2534 | | M3OP_F( "i64.trunc_u:sat/f32",0, i_64, d_convertOpList (u64_TruncSat_f32), Compile_Convert ), // 0x05 |
2535 | | M3OP_F( "i64.trunc_s:sat/f64",0, i_64, d_convertOpList (i64_TruncSat_f64), Compile_Convert ), // 0x06 |
2536 | | M3OP_F( "i64.trunc_u:sat/f64",0, i_64, d_convertOpList (u64_TruncSat_f64), Compile_Convert ), // 0x07 |
2537 | | |
2538 | | M3OP_RESERVED, M3OP_RESERVED, |
2539 | | |
2540 | | M3OP( "memory.copy", 0, none, d_emptyOpList, Compile_Memory_CopyFill ), // 0x0a |
2541 | | M3OP( "memory.fill", 0, none, d_emptyOpList, Compile_Memory_CopyFill ), // 0x0b |
2542 | | |
2543 | | |
2544 | | # ifdef DEBUG |
2545 | | M3OP( "termination", 0, c_m3Type_unknown ) // for find_operation_info |
2546 | | # endif |
2547 | | }; |
2548 | | |
2549 | | |
2550 | | IM3OpInfo GetOpInfo (m3opcode_t opcode) |
2551 | 31.4k | { |
2552 | 31.4k | switch (opcode >> 8) { |
2553 | 31.4k | case 0x00: |
2554 | 31.4k | if (M3_LIKELY(opcode < M3_COUNT_OF(c_operations))) { |
2555 | 31.4k | return &c_operations[opcode]; |
2556 | 31.4k | } |
2557 | 0 | break; |
2558 | 0 | case c_waOp_extended: |
2559 | 0 | opcode &= 0xFF; |
2560 | 0 | if (M3_LIKELY(opcode < M3_COUNT_OF(c_operationsFC))) { |
2561 | 0 | return &c_operationsFC[opcode]; |
2562 | 0 | } |
2563 | 0 | break; |
2564 | 31.4k | } |
2565 | 0 | return NULL; |
2566 | 31.4k | } |
2567 | | |
2568 | | M3Result CompileBlockStatements (IM3Compilation o) |
2569 | 1.04k | { |
2570 | 1.04k | M3Result result = m3Err_none; |
2571 | 1.04k | bool validEnd = false; |
2572 | | |
2573 | 31.3k | while (o->wasm < o->wasmEnd) |
2574 | 31.3k | { |
2575 | | # if d_m3EnableOpTracing |
2576 | | if (o->numEmits) |
2577 | | { |
2578 | | EmitOp (o, op_DumpStack); |
2579 | | EmitConstant32 (o, o->numOpcodes); |
2580 | | EmitConstant32 (o, GetMaxUsedSlotPlusOne(o)); |
2581 | | EmitPointer (o, o->function); |
2582 | | |
2583 | | o->numEmits = 0; |
2584 | | } |
2585 | | # endif |
2586 | 31.3k | m3opcode_t opcode; |
2587 | 31.3k | o->lastOpcodeStart = o->wasm; |
2588 | 31.3k | _ (Read_opcode (& opcode, & o->wasm, o->wasmEnd)); log_opcode (o, opcode); |
2589 | | |
2590 | | // Restrict opcodes when evaluating expressions |
2591 | 31.3k | if (not o->function) { |
2592 | 31.1k | switch (opcode) { |
2593 | 27.7k | case c_waOp_i32_const: case c_waOp_i64_const: |
2594 | 28.5k | case c_waOp_f32_const: case c_waOp_f64_const: |
2595 | 31.1k | case c_waOp_getGlobal: case c_waOp_end: |
2596 | 31.1k | break; |
2597 | 24 | default: |
2598 | 24 | _throw(m3Err_restrictedOpcode); |
2599 | 31.1k | } |
2600 | 31.1k | } |
2601 | | |
2602 | 31.3k | IM3OpInfo opinfo = GetOpInfo (opcode); |
2603 | | |
2604 | 31.3k | if (opinfo == NULL) |
2605 | 31.3k | _throw (ErrorCompile (m3Err_unknownOpcode, o, "opcode '%x' not available", opcode)); |
2606 | | |
2607 | 31.3k | if (opinfo->compiler) { |
2608 | 31.2k | _ ((* opinfo->compiler) (o, opcode)) |
2609 | 31.2k | } else { |
2610 | 51 | _ (Compile_Operator (o, opcode)); |
2611 | 51 | } |
2612 | | |
2613 | 31.2k | o->previousOpcode = opcode; |
2614 | | |
2615 | 31.2k | if (opcode == c_waOp_else) |
2616 | 0 | { |
2617 | 0 | _throwif (m3Err_wasmMalformed, o->block.opcode != c_waOp_if); |
2618 | 0 | validEnd = true; |
2619 | 0 | break; |
2620 | 0 | } |
2621 | 31.2k | else if (opcode == c_waOp_end) |
2622 | 916 | { |
2623 | 916 | validEnd = true; |
2624 | 916 | break; |
2625 | 916 | } |
2626 | 31.2k | } |
2627 | 918 | _throwif(m3Err_wasmMalformed, !(validEnd)); |
2628 | | |
2629 | 1.04k | _catch: |
2630 | 1.04k | return result; |
2631 | 916 | } |
2632 | | |
2633 | | static |
2634 | | M3Result PushBlockResults (IM3Compilation o) |
2635 | 447 | { |
2636 | 447 | M3Result result = m3Err_none; |
2637 | | |
2638 | 447 | u16 numResults = GetFuncTypeNumResults (o->block.type); |
2639 | | |
2640 | 4.10k | for (u16 i = 0; i < numResults; ++i) |
2641 | 3.66k | { |
2642 | 3.66k | u8 type = GetFuncTypeResultType (o->block.type, i); |
2643 | | |
2644 | 3.66k | if (i == numResults - 1 and IsFpType (type)) |
2645 | 65 | { |
2646 | 65 | _ (PushRegister (o, type)); |
2647 | 65 | } |
2648 | 3.60k | else |
2649 | 3.65k | _ (PushAllocatedSlot (o, type)); |
2650 | 3.65k | } |
2651 | | |
2652 | 447 | _catch: return result; |
2653 | 447 | } |
2654 | | |
2655 | | |
2656 | | M3Result CompileBlock (IM3Compilation o, IM3FuncType i_blockType, m3opcode_t i_blockOpcode) |
2657 | 443 | { |
2658 | 443 | d_m3Assert (not IsRegisterAllocated (o, 0)); |
2659 | 443 | d_m3Assert (not IsRegisterAllocated (o, 1)); |
2660 | 443 | M3CompilationScope outerScope = o->block; |
2661 | 443 | M3CompilationScope * block = & o->block; |
2662 | | |
2663 | 443 | block->outer = & outerScope; |
2664 | 443 | block->pc = GetPagePC (o->page); |
2665 | 443 | block->patches = NULL; |
2666 | 443 | block->type = i_blockType; |
2667 | 443 | block->depth ++; |
2668 | 443 | block->opcode = i_blockOpcode; |
2669 | | |
2670 | | /* |
2671 | | The block stack frame is a little strange but for good reasons. Because blocks need to be restarted to |
2672 | | compile different pathways (if/else), the incoming params must be saved. The parameters are popped |
2673 | | and validated. But, then the stack top is readjusted so they aren't subsequently overwritten. |
2674 | | Next, the result are preallocated to find destination slots. But again these are immediately popped |
2675 | | (deallocated) and the stack top is readjusted to keep these records in pace. This allows branch instructions |
2676 | | to find their result landing pads. Finally, the params are copied from the "dead" records and pushed back |
2677 | | onto the stack as active stack items for the CompileBlockStatements () call. |
2678 | | |
2679 | | [ block ] |
2680 | | [ params ] |
2681 | | ------------------ |
2682 | | [ result ] <---- blockStackIndex |
2683 | | [ slots ] |
2684 | | ------------------ |
2685 | | [ saved param ] |
2686 | | [ records ] |
2687 | | <----- exitStackIndex |
2688 | | */ |
2689 | | |
2690 | 443 | _try { |
2691 | | // validate and dealloc params ---------------------------- |
2692 | | |
2693 | 443 | u16 stackIndex = o->stackIndex; |
2694 | | |
2695 | 443 | u16 numParams = GetFuncTypeNumParams (i_blockType); |
2696 | | |
2697 | 443 | if (i_blockOpcode != c_waOp_else) |
2698 | 443 | { |
2699 | 3.13k | for (u16 i = 0; i < numParams; ++i) |
2700 | 2.68k | { |
2701 | 2.68k | u8 type = GetFuncTypeParamType (i_blockType, numParams - 1 - i); |
2702 | 2.68k | _ (PopType (o, type)); |
2703 | 2.68k | } |
2704 | 443 | } |
2705 | 0 | else { |
2706 | 0 | if (IsStackPolymorphic (o) && o->block.blockStackIndex + numParams > o->stackIndex) { |
2707 | 0 | o->stackIndex = o->block.blockStackIndex; |
2708 | 0 | } else { |
2709 | 0 | o->stackIndex -= numParams; |
2710 | 0 | } |
2711 | 0 | } |
2712 | | |
2713 | 443 | u16 paramIndex = o->stackIndex; |
2714 | 443 | block->exitStackIndex = paramIndex; // consume the params at block exit |
2715 | | |
2716 | | // keep copies of param slots in the stack |
2717 | 443 | o->stackIndex = stackIndex; |
2718 | | |
2719 | | // find slots for the results ---------------------------- |
2720 | 443 | PushBlockResults (o); |
2721 | | |
2722 | 443 | stackIndex = o->stackIndex; |
2723 | | |
2724 | | // dealloc but keep record of the result slots in the stack |
2725 | 443 | u16 numResults = GetFuncTypeNumResults (i_blockType); |
2726 | 4.23k | while (numResults--) |
2727 | 3.78k | Pop (o); |
2728 | | |
2729 | 443 | block->blockStackIndex = o->stackIndex = stackIndex; |
2730 | | |
2731 | | // push the params back onto the stack ------------------- |
2732 | 3.13k | for (u16 i = 0; i < numParams; ++i) |
2733 | 2.68k | { |
2734 | 2.68k | u8 type = GetFuncTypeParamType (i_blockType, i); |
2735 | | |
2736 | 2.68k | u16 slot = GetSlotForStackIndex (o, paramIndex + i); |
2737 | 2.68k | Push (o, type, slot); |
2738 | | |
2739 | 2.68k | if (slot >= o->slotFirstDynamicIndex && slot != c_slotUnused) |
2740 | 1.87k | MarkSlotsAllocatedByType (o, slot, type); |
2741 | 2.68k | } |
2742 | | |
2743 | | //-------------------------------------------------------- |
2744 | | |
2745 | 443 | _ (CompileBlockStatements (o)); |
2746 | | |
2747 | 356 | _ (ValidateBlockEnd (o)); |
2748 | | |
2749 | 356 | if (o->function) // skip for expressions |
2750 | 4 | { |
2751 | 4 | if (not IsStackPolymorphic (o)) |
2752 | 4 | _ (ResolveBlockResults (o, & o->block, /* isBranch: */ false)); |
2753 | | |
2754 | 4 | _ (UnwindBlockStack (o)) |
2755 | | |
2756 | 4 | if (not ((i_blockOpcode == c_waOp_if and numResults) or o->previousOpcode == c_waOp_else)) |
2757 | 4 | { |
2758 | 4 | o->stackIndex = o->block.exitStackIndex; |
2759 | 4 | _ (PushBlockResults (o)); |
2760 | 4 | } |
2761 | 4 | } |
2762 | | |
2763 | 356 | PatchBranches (o); |
2764 | | |
2765 | 356 | o->block = outerScope; |
2766 | | |
2767 | 443 | } _catch: return result; |
2768 | 356 | } |
2769 | | |
2770 | | static |
2771 | | M3Result CompileLocals (IM3Compilation o) |
2772 | 7 | { |
2773 | 7 | M3Result result; |
2774 | | |
2775 | 7 | u32 numLocals = 0; |
2776 | 7 | u32 numLocalBlocks; |
2777 | 7 | _ (ReadLEB_u32 (& numLocalBlocks, & o->wasm, o->wasmEnd)); |
2778 | | |
2779 | 8 | for (u32 l = 0; l < numLocalBlocks; ++l) |
2780 | 1 | { |
2781 | 1 | u32 varCount; |
2782 | 1 | i8 waType; |
2783 | 1 | u8 localType; |
2784 | | |
2785 | 1 | _ (ReadLEB_u32 (& varCount, & o->wasm, o->wasmEnd)); |
2786 | 1 | _ (ReadLEB_i7 (& waType, & o->wasm, o->wasmEnd)); |
2787 | 1 | _ (NormalizeType (& localType, waType)); |
2788 | 1 | numLocals += varCount; m3log (compile, "pushing locals. count: %d; type: %s", varCount, c_waTypes [localType]); |
2789 | 1.54k | while (varCount--) |
2790 | 1.54k | _ (PushAllocatedSlot (o, localType)); |
2791 | 1 | } |
2792 | | |
2793 | 7 | if (o->function) |
2794 | 7 | o->function->numLocals = numLocals; |
2795 | | |
2796 | 7 | _catch: return result; |
2797 | 7 | } |
2798 | | |
2799 | | static |
2800 | | M3Result ReserveConstants (IM3Compilation o) |
2801 | 7 | { |
2802 | 7 | M3Result result = m3Err_none; |
2803 | | |
2804 | | // in the interest of speed, this blindly scans the Wasm code looking for any byte |
2805 | | // that looks like an const opcode. |
2806 | 7 | u16 numConstantSlots = 0; |
2807 | | |
2808 | 7 | bytes_t wa = o->wasm; |
2809 | 564 | while (wa < o->wasmEnd) |
2810 | 557 | { |
2811 | 557 | u8 code = * wa++; |
2812 | 557 | u16 addSlots = 0; |
2813 | | |
2814 | 557 | if (code == c_waOp_i32_const or code == c_waOp_f32_const) |
2815 | 1 | addSlots = 1; |
2816 | 556 | else if (code == c_waOp_i64_const or code == c_waOp_f64_const) |
2817 | 5 | addSlots = GetTypeNumSlots (c_m3Type_i64); |
2818 | | |
2819 | 557 | if (numConstantSlots + addSlots >= d_m3MaxConstantTableSize) |
2820 | 0 | break; |
2821 | | |
2822 | 557 | numConstantSlots += addSlots; |
2823 | 557 | } |
2824 | | |
2825 | | // if constants overflow their reserved stack space, the compiler simply emits op_Const |
2826 | | // operations as needed. Compiled expressions (global inits) don't pass through this |
2827 | | // ReserveConstants function and thus always produce inline constants. |
2828 | | |
2829 | 7 | AlignSlotToType (& numConstantSlots, c_m3Type_i64); m3log (compile, "reserved constant slots: %d", numConstantSlots); |
2830 | | |
2831 | 7 | o->slotFirstDynamicIndex = o->slotFirstConstIndex + numConstantSlots; |
2832 | | |
2833 | 7 | if (o->slotFirstDynamicIndex >= d_m3MaxFunctionSlots) |
2834 | 7 | _throw (m3Err_functionStackOverflow); |
2835 | | |
2836 | 7 | _catch: |
2837 | 7 | return result; |
2838 | 7 | } |
2839 | | |
2840 | | |
2841 | | M3Result CompileFunction (IM3Function io_function) |
2842 | 7 | { |
2843 | 7 | if (!io_function->wasm) return "function body is missing"; |
2844 | | |
2845 | 7 | IM3FuncType funcType = io_function->funcType; m3log (compile, "compiling: [%d] %s %s; wasm-size: %d", |
2846 | 7 | io_function->index, m3_GetFunctionName (io_function), SPrintFuncTypeSignature (funcType), (u32) (io_function->wasmEnd - io_function->wasm)); |
2847 | 7 | IM3Runtime runtime = io_function->module->runtime; |
2848 | | |
2849 | 7 | IM3Compilation o = & runtime->compilation; d_m3Assert (d_m3MaxFunctionSlots >= d_m3MaxFunctionStackHeight * (d_m3Use32BitSlots + 1)) // need twice as many slots in 32-bit mode |
2850 | 7 | memset (o, 0x0, sizeof (M3Compilation)); |
2851 | | |
2852 | 7 | o->runtime = runtime; |
2853 | 7 | o->module = io_function->module; |
2854 | 7 | o->function = io_function; |
2855 | 7 | o->wasm = io_function->wasm; |
2856 | 7 | o->wasmEnd = io_function->wasmEnd; |
2857 | 7 | o->block.type = funcType; |
2858 | | |
2859 | 7 | _try { |
2860 | | // skip over code size. the end was already calculated during parse phase |
2861 | 7 | u32 size; |
2862 | 7 | _ (ReadLEB_u32 (& size, & o->wasm, o->wasmEnd)); d_m3Assert (size == (o->wasmEnd - o->wasm)) |
2863 | | |
2864 | 7 | _ (AcquireCompilationCodePage (o, & o->page)); |
2865 | | |
2866 | 7 | pc_t pc = GetPagePC (o->page); |
2867 | | |
2868 | 7 | u16 numRetSlots = GetFunctionNumReturns (o->function) * c_ioSlotCount; |
2869 | | |
2870 | 855 | for (u16 i = 0; i < numRetSlots; ++i) |
2871 | 848 | MarkSlotAllocated (o, i); |
2872 | | |
2873 | 7 | o->function->numRetSlots = o->slotFirstDynamicIndex = numRetSlots; |
2874 | | |
2875 | 7 | u16 numArgs = GetFunctionNumArgs (o->function); |
2876 | | |
2877 | | // push the arg types to the type stack |
2878 | 103 | for (u16 i = 0; i < numArgs; ++i) |
2879 | 96 | { |
2880 | 96 | u8 type = GetFunctionArgType (o->function, i); |
2881 | 96 | _ (PushAllocatedSlot (o, type)); |
2882 | | |
2883 | | // prevent allocator fill-in |
2884 | 96 | o->slotFirstDynamicIndex += c_ioSlotCount; |
2885 | 96 | } |
2886 | | |
2887 | 7 | o->slotMaxAllocatedIndexPlusOne = o->function->numRetAndArgSlots = o->slotFirstLocalIndex = o->slotFirstDynamicIndex; |
2888 | | |
2889 | 7 | _ (CompileLocals (o)); |
2890 | | |
2891 | 7 | u16 maxSlot = GetMaxUsedSlotPlusOne (o); |
2892 | | |
2893 | 7 | o->function->numLocalBytes = (maxSlot - o->slotFirstLocalIndex) * sizeof (m3slot_t); |
2894 | | |
2895 | 7 | o->slotFirstConstIndex = o->slotMaxConstIndex = maxSlot; |
2896 | | |
2897 | | // ReserveConstants initializes o->firstDynamicSlotNumber |
2898 | 7 | _ (ReserveConstants (o)); |
2899 | | |
2900 | | // start tracking the max stack used (Push() also updates this value) so that op_Entry can precisely detect stack overflow |
2901 | 7 | o->maxStackSlots = o->slotMaxAllocatedIndexPlusOne = o->slotFirstDynamicIndex; |
2902 | | |
2903 | 7 | o->block.blockStackIndex = o->stackFirstDynamicIndex = o->stackIndex; m3log (compile, "start stack index: %d", |
2904 | 7 | (u32) o->stackFirstDynamicIndex); |
2905 | 7 | _ (EmitOp (o, op_Entry)); |
2906 | 7 | EmitPointer (o, io_function); |
2907 | | |
2908 | 7 | _ (CompileBlockStatements (o)); |
2909 | | |
2910 | | // TODO: validate opcode sequences |
2911 | 0 | _throwif(m3Err_wasmMalformed, o->previousOpcode != c_waOp_end); |
2912 | |
|
2913 | 0 | io_function->compiled = pc; |
2914 | 0 | io_function->maxStackSlots = o->maxStackSlots; |
2915 | |
|
2916 | 0 | u16 numConstantSlots = o->slotMaxConstIndex - o->slotFirstConstIndex; m3log (compile, "unique constant slots: %d; unused slots: %d", |
2917 | 0 | numConstantSlots, o->slotFirstDynamicIndex - o->slotMaxConstIndex); |
2918 | 0 | io_function->numConstantBytes = numConstantSlots * sizeof (m3slot_t); |
2919 | |
|
2920 | 0 | if (numConstantSlots) |
2921 | 0 | { |
2922 | 0 | io_function->constants = m3_CopyMem (o->constants, io_function->numConstantBytes); |
2923 | 0 | _throwifnull(io_function->constants); |
2924 | 0 | } |
2925 | |
|
2926 | 7 | } _catch: |
2927 | | |
2928 | 7 | ReleaseCompilationCodePage (o); |
2929 | | |
2930 | 7 | return result; |
2931 | 0 | } |