authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-11-15 09:02:24-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-11-15 18:02:24+01:00
logd3135f76823a053c4e580830f6a6c080caae5ab5
tree254a2617b03c3c002dfee9c7444ea3ae59385b2d
parentb26b72f54051b89e6fc349f453aafe9f3a3135a5
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Stage2: wasm - Implement the MIR pass (#10153)

* wasm: Move wasm's codegen to arch/wasm/CodeGen.zig * wasm: Define Wasm's Mir This declares the initial most-used instructions for wasm as well as the data that represents them. TODO: Add binary operand opcodes. By re-using the wasm opcode values, we can emit each opcode very easily by simply using `@enumToInt()`. However, this poses a possible problem: If we use all of wasm's opcodes, it leaves us no room to use synthetic opcodes such as debugging instructions. We could use reserved opcodes, but the wasm spec may use them at some point. TODO: Check if we should perhaps use a 16bit tag where the highest bits are used for synthetic opcodes. * wasm: Define basic Emit structure * wasm: Implement corresponding Emit functions for MIR * wasm: Initial lowering to MIR - This implements lowering to MIR from AIR for storing and loading of locals as well as emitting immediates. - Relocating function indexes has been simplified a lot as well as we no longer need to patch offsets and we write a relocatable value instead. - Locals are now emitted at the beginning of the function section entry meaning all offsets we generate are stable. * wasm: Lower all AIR instructions to MIR * wasm: Implement remaining MIR instructions * wasm: Fix function relocations * wasm: Get all tests working * wasm: Make `Data` 4 bytes instead of 8. - 64bit immediates are now stored in 2 seperate u32's. - 64bit floats are now stored in 2 seperate u32's. - `mem_arg` is now stored as a seperate payload in extra.

6 files changed, 2376 insertions(+), 1761 deletions(-)

CMakeLists.txt+1-1
......@@ -555,6 +555,7 @@ set(ZIG_STAGE2_SOURCES
555555 "${CMAKE_SOURCE_DIR}/src/arch/arm/bits.zig"
556556 "${CMAKE_SOURCE_DIR}/src/arch/riscv64/bits.zig"
557557 "${CMAKE_SOURCE_DIR}/src/arch/x86_64/bits.zig"
558 "${CMAKE_SOURCE_DIR}/src/arch/wasm/CodeGen.zig"
558559 "${CMAKE_SOURCE_DIR}/src/clang.zig"
559560 "${CMAKE_SOURCE_DIR}/src/clang_options.zig"
560561 "${CMAKE_SOURCE_DIR}/src/clang_options_data.zig"
......@@ -562,7 +563,6 @@ set(ZIG_STAGE2_SOURCES
562563 "${CMAKE_SOURCE_DIR}/src/codegen/c.zig"
563564 "${CMAKE_SOURCE_DIR}/src/codegen/llvm.zig"
564565 "${CMAKE_SOURCE_DIR}/src/codegen/llvm/bindings.zig"
565 "${CMAKE_SOURCE_DIR}/src/codegen/wasm.zig"
566566 "${CMAKE_SOURCE_DIR}/src/glibc.zig"
567567 "${CMAKE_SOURCE_DIR}/src/introspect.zig"
568568 "${CMAKE_SOURCE_DIR}/src/libc_installation.zig"
src/arch/wasm/CodeGen.zig created+1710
......@@ -0,0 +1,1710 @@
1const std = @import("std");
2const Allocator = std.mem.Allocator;
3const ArrayList = std.ArrayList;
4const assert = std.debug.assert;
5const testing = std.testing;
6const leb = std.leb;
7const mem = std.mem;
8const wasm = std.wasm;
9
10const Module = @import("../../Module.zig");
11const Decl = Module.Decl;
12const Type = @import("../../type.zig").Type;
13const Value = @import("../../value.zig").Value;
14const Compilation = @import("../../Compilation.zig");
15const LazySrcLoc = Module.LazySrcLoc;
16const link = @import("../../link.zig");
17const TypedValue = @import("../../TypedValue.zig");
18const Air = @import("../../Air.zig");
19const Liveness = @import("../../Liveness.zig");
20const Mir = @import("Mir.zig");
21const Emit = @import("Emit.zig");
22
23/// Wasm Value, created when generating an instruction
24const WValue = union(enum) {
25 /// May be referenced but is unused
26 none: void,
27 /// Index of the local variable
28 local: u32,
29 /// Holds a memoized typed value
30 constant: TypedValue,
31 /// Offset position in the list of MIR instructions
32 mir_offset: usize,
33 /// Used for variables that create multiple locals on the stack when allocated
34 /// such as structs and optionals.
35 multi_value: struct {
36 /// The index of the first local variable
37 index: u32,
38 /// The count of local variables this `WValue` consists of.
39 /// i.e. an ErrorUnion has a 'count' of 2.
40 count: u32,
41 },
42};
43
44/// Wasm ops, but without input/output/signedness information
45/// Used for `buildOpcode`
46const Op = enum {
47 @"unreachable",
48 nop,
49 block,
50 loop,
51 @"if",
52 @"else",
53 end,
54 br,
55 br_if,
56 br_table,
57 @"return",
58 call,
59 call_indirect,
60 drop,
61 select,
62 local_get,
63 local_set,
64 local_tee,
65 global_get,
66 global_set,
67 load,
68 store,
69 memory_size,
70 memory_grow,
71 @"const",
72 eqz,
73 eq,
74 ne,
75 lt,
76 gt,
77 le,
78 ge,
79 clz,
80 ctz,
81 popcnt,
82 add,
83 sub,
84 mul,
85 div,
86 rem,
87 @"and",
88 @"or",
89 xor,
90 shl,
91 shr,
92 rotl,
93 rotr,
94 abs,
95 neg,
96 ceil,
97 floor,
98 trunc,
99 nearest,
100 sqrt,
101 min,
102 max,
103 copysign,
104 wrap,
105 convert,
106 demote,
107 promote,
108 reinterpret,
109 extend,
110};
111
112/// Contains the settings needed to create an `Opcode` using `buildOpcode`.
113///
114/// The fields correspond to the opcode name. Here is an example
115/// i32_trunc_f32_s
116/// ^ ^ ^ ^
117/// | | | |
118/// valtype1 | | |
119/// = .i32 | | |
120/// | | |
121/// op | |
122/// = .trunc | |
123/// | |
124/// valtype2 |
125/// = .f32 |
126/// |
127/// width |
128/// = null |
129/// |
130/// signed
131/// = true
132///
133/// There can be missing fields, here are some more examples:
134/// i64_load8_u
135/// --> .{ .valtype1 = .i64, .op = .load, .width = 8, signed = false }
136/// i32_mul
137/// --> .{ .valtype1 = .i32, .op = .trunc }
138/// nop
139/// --> .{ .op = .nop }
140const OpcodeBuildArguments = struct {
141 /// First valtype in the opcode (usually represents the type of the output)
142 valtype1: ?wasm.Valtype = null,
143 /// The operation (e.g. call, unreachable, div, min, sqrt, etc.)
144 op: Op,
145 /// Width of the operation (e.g. 8 for i32_load8_s, 16 for i64_extend16_i32_s)
146 width: ?u8 = null,
147 /// Second valtype in the opcode name (usually represents the type of the input)
148 valtype2: ?wasm.Valtype = null,
149 /// Signedness of the op
150 signedness: ?std.builtin.Signedness = null,
151};
152
153/// Helper function that builds an Opcode given the arguments needed
154fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
155 switch (args.op) {
156 .@"unreachable" => return .@"unreachable",
157 .nop => return .nop,
158 .block => return .block,
159 .loop => return .loop,
160 .@"if" => return .@"if",
161 .@"else" => return .@"else",
162 .end => return .end,
163 .br => return .br,
164 .br_if => return .br_if,
165 .br_table => return .br_table,
166 .@"return" => return .@"return",
167 .call => return .call,
168 .call_indirect => return .call_indirect,
169 .drop => return .drop,
170 .select => return .select,
171 .local_get => return .local_get,
172 .local_set => return .local_set,
173 .local_tee => return .local_tee,
174 .global_get => return .global_get,
175 .global_set => return .global_set,
176
177 .load => if (args.width) |width| switch (width) {
178 8 => switch (args.valtype1.?) {
179 .i32 => if (args.signedness.? == .signed) return .i32_load8_s else return .i32_load8_u,
180 .i64 => if (args.signedness.? == .signed) return .i64_load8_s else return .i64_load8_u,
181 .f32, .f64 => unreachable,
182 },
183 16 => switch (args.valtype1.?) {
184 .i32 => if (args.signedness.? == .signed) return .i32_load16_s else return .i32_load16_u,
185 .i64 => if (args.signedness.? == .signed) return .i64_load16_s else return .i64_load16_u,
186 .f32, .f64 => unreachable,
187 },
188 32 => switch (args.valtype1.?) {
189 .i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u,
190 .i32, .f32, .f64 => unreachable,
191 },
192 else => unreachable,
193 } else switch (args.valtype1.?) {
194 .i32 => return .i32_load,
195 .i64 => return .i64_load,
196 .f32 => return .f32_load,
197 .f64 => return .f64_load,
198 },
199 .store => if (args.width) |width| {
200 switch (width) {
201 8 => switch (args.valtype1.?) {
202 .i32 => return .i32_store8,
203 .i64 => return .i64_store8,
204 .f32, .f64 => unreachable,
205 },
206 16 => switch (args.valtype1.?) {
207 .i32 => return .i32_store16,
208 .i64 => return .i64_store16,
209 .f32, .f64 => unreachable,
210 },
211 32 => switch (args.valtype1.?) {
212 .i64 => return .i64_store32,
213 .i32, .f32, .f64 => unreachable,
214 },
215 else => unreachable,
216 }
217 } else {
218 switch (args.valtype1.?) {
219 .i32 => return .i32_store,
220 .i64 => return .i64_store,
221 .f32 => return .f32_store,
222 .f64 => return .f64_store,
223 }
224 },
225
226 .memory_size => return .memory_size,
227 .memory_grow => return .memory_grow,
228
229 .@"const" => switch (args.valtype1.?) {
230 .i32 => return .i32_const,
231 .i64 => return .i64_const,
232 .f32 => return .f32_const,
233 .f64 => return .f64_const,
234 },
235
236 .eqz => switch (args.valtype1.?) {
237 .i32 => return .i32_eqz,
238 .i64 => return .i64_eqz,
239 .f32, .f64 => unreachable,
240 },
241 .eq => switch (args.valtype1.?) {
242 .i32 => return .i32_eq,
243 .i64 => return .i64_eq,
244 .f32 => return .f32_eq,
245 .f64 => return .f64_eq,
246 },
247 .ne => switch (args.valtype1.?) {
248 .i32 => return .i32_ne,
249 .i64 => return .i64_ne,
250 .f32 => return .f32_ne,
251 .f64 => return .f64_ne,
252 },
253
254 .lt => switch (args.valtype1.?) {
255 .i32 => if (args.signedness.? == .signed) return .i32_lt_s else return .i32_lt_u,
256 .i64 => if (args.signedness.? == .signed) return .i64_lt_s else return .i64_lt_u,
257 .f32 => return .f32_lt,
258 .f64 => return .f64_lt,
259 },
260 .gt => switch (args.valtype1.?) {
261 .i32 => if (args.signedness.? == .signed) return .i32_gt_s else return .i32_gt_u,
262 .i64 => if (args.signedness.? == .signed) return .i64_gt_s else return .i64_gt_u,
263 .f32 => return .f32_gt,
264 .f64 => return .f64_gt,
265 },
266 .le => switch (args.valtype1.?) {
267 .i32 => if (args.signedness.? == .signed) return .i32_le_s else return .i32_le_u,
268 .i64 => if (args.signedness.? == .signed) return .i64_le_s else return .i64_le_u,
269 .f32 => return .f32_le,
270 .f64 => return .f64_le,
271 },
272 .ge => switch (args.valtype1.?) {
273 .i32 => if (args.signedness.? == .signed) return .i32_ge_s else return .i32_ge_u,
274 .i64 => if (args.signedness.? == .signed) return .i64_ge_s else return .i64_ge_u,
275 .f32 => return .f32_ge,
276 .f64 => return .f64_ge,
277 },
278
279 .clz => switch (args.valtype1.?) {
280 .i32 => return .i32_clz,
281 .i64 => return .i64_clz,
282 .f32, .f64 => unreachable,
283 },
284 .ctz => switch (args.valtype1.?) {
285 .i32 => return .i32_ctz,
286 .i64 => return .i64_ctz,
287 .f32, .f64 => unreachable,
288 },
289 .popcnt => switch (args.valtype1.?) {
290 .i32 => return .i32_popcnt,
291 .i64 => return .i64_popcnt,
292 .f32, .f64 => unreachable,
293 },
294
295 .add => switch (args.valtype1.?) {
296 .i32 => return .i32_add,
297 .i64 => return .i64_add,
298 .f32 => return .f32_add,
299 .f64 => return .f64_add,
300 },
301 .sub => switch (args.valtype1.?) {
302 .i32 => return .i32_sub,
303 .i64 => return .i64_sub,
304 .f32 => return .f32_sub,
305 .f64 => return .f64_sub,
306 },
307 .mul => switch (args.valtype1.?) {
308 .i32 => return .i32_mul,
309 .i64 => return .i64_mul,
310 .f32 => return .f32_mul,
311 .f64 => return .f64_mul,
312 },
313
314 .div => switch (args.valtype1.?) {
315 .i32 => if (args.signedness.? == .signed) return .i32_div_s else return .i32_div_u,
316 .i64 => if (args.signedness.? == .signed) return .i64_div_s else return .i64_div_u,
317 .f32 => return .f32_div,
318 .f64 => return .f64_div,
319 },
320 .rem => switch (args.valtype1.?) {
321 .i32 => if (args.signedness.? == .signed) return .i32_rem_s else return .i32_rem_u,
322 .i64 => if (args.signedness.? == .signed) return .i64_rem_s else return .i64_rem_u,
323 .f32, .f64 => unreachable,
324 },
325
326 .@"and" => switch (args.valtype1.?) {
327 .i32 => return .i32_and,
328 .i64 => return .i64_and,
329 .f32, .f64 => unreachable,
330 },
331 .@"or" => switch (args.valtype1.?) {
332 .i32 => return .i32_or,
333 .i64 => return .i64_or,
334 .f32, .f64 => unreachable,
335 },
336 .xor => switch (args.valtype1.?) {
337 .i32 => return .i32_xor,
338 .i64 => return .i64_xor,
339 .f32, .f64 => unreachable,
340 },
341
342 .shl => switch (args.valtype1.?) {
343 .i32 => return .i32_shl,
344 .i64 => return .i64_shl,
345 .f32, .f64 => unreachable,
346 },
347 .shr => switch (args.valtype1.?) {
348 .i32 => if (args.signedness.? == .signed) return .i32_shr_s else return .i32_shr_u,
349 .i64 => if (args.signedness.? == .signed) return .i64_shr_s else return .i64_shr_u,
350 .f32, .f64 => unreachable,
351 },
352 .rotl => switch (args.valtype1.?) {
353 .i32 => return .i32_rotl,
354 .i64 => return .i64_rotl,
355 .f32, .f64 => unreachable,
356 },
357 .rotr => switch (args.valtype1.?) {
358 .i32 => return .i32_rotr,
359 .i64 => return .i64_rotr,
360 .f32, .f64 => unreachable,
361 },
362
363 .abs => switch (args.valtype1.?) {
364 .i32, .i64 => unreachable,
365 .f32 => return .f32_abs,
366 .f64 => return .f64_abs,
367 },
368 .neg => switch (args.valtype1.?) {
369 .i32, .i64 => unreachable,
370 .f32 => return .f32_neg,
371 .f64 => return .f64_neg,
372 },
373 .ceil => switch (args.valtype1.?) {
374 .i32, .i64 => unreachable,
375 .f32 => return .f32_ceil,
376 .f64 => return .f64_ceil,
377 },
378 .floor => switch (args.valtype1.?) {
379 .i32, .i64 => unreachable,
380 .f32 => return .f32_floor,
381 .f64 => return .f64_floor,
382 },
383 .trunc => switch (args.valtype1.?) {
384 .i32 => switch (args.valtype2.?) {
385 .i32 => unreachable,
386 .i64 => unreachable,
387 .f32 => if (args.signedness.? == .signed) return .i32_trunc_f32_s else return .i32_trunc_f32_u,
388 .f64 => if (args.signedness.? == .signed) return .i32_trunc_f64_s else return .i32_trunc_f64_u,
389 },
390 .i64 => unreachable,
391 .f32 => return .f32_trunc,
392 .f64 => return .f64_trunc,
393 },
394 .nearest => switch (args.valtype1.?) {
395 .i32, .i64 => unreachable,
396 .f32 => return .f32_nearest,
397 .f64 => return .f64_nearest,
398 },
399 .sqrt => switch (args.valtype1.?) {
400 .i32, .i64 => unreachable,
401 .f32 => return .f32_sqrt,
402 .f64 => return .f64_sqrt,
403 },
404 .min => switch (args.valtype1.?) {
405 .i32, .i64 => unreachable,
406 .f32 => return .f32_min,
407 .f64 => return .f64_min,
408 },
409 .max => switch (args.valtype1.?) {
410 .i32, .i64 => unreachable,
411 .f32 => return .f32_max,
412 .f64 => return .f64_max,
413 },
414 .copysign => switch (args.valtype1.?) {
415 .i32, .i64 => unreachable,
416 .f32 => return .f32_copysign,
417 .f64 => return .f64_copysign,
418 },
419
420 .wrap => switch (args.valtype1.?) {
421 .i32 => switch (args.valtype2.?) {
422 .i32 => unreachable,
423 .i64 => return .i32_wrap_i64,
424 .f32, .f64 => unreachable,
425 },
426 .i64, .f32, .f64 => unreachable,
427 },
428 .convert => switch (args.valtype1.?) {
429 .i32, .i64 => unreachable,
430 .f32 => switch (args.valtype2.?) {
431 .i32 => if (args.signedness.? == .signed) return .f32_convert_i32_s else return .f32_convert_i32_u,
432 .i64 => if (args.signedness.? == .signed) return .f32_convert_i64_s else return .f32_convert_i64_u,
433 .f32, .f64 => unreachable,
434 },
435 .f64 => switch (args.valtype2.?) {
436 .i32 => if (args.signedness.? == .signed) return .f64_convert_i32_s else return .f64_convert_i32_u,
437 .i64 => if (args.signedness.? == .signed) return .f64_convert_i64_s else return .f64_convert_i64_u,
438 .f32, .f64 => unreachable,
439 },
440 },
441 .demote => if (args.valtype1.? == .f32 and args.valtype2.? == .f64) return .f32_demote_f64 else unreachable,
442 .promote => if (args.valtype1.? == .f64 and args.valtype2.? == .f32) return .f64_promote_f32 else unreachable,
443 .reinterpret => switch (args.valtype1.?) {
444 .i32 => if (args.valtype2.? == .f32) return .i32_reinterpret_f32 else unreachable,
445 .i64 => if (args.valtype2.? == .f64) return .i64_reinterpret_f64 else unreachable,
446 .f32 => if (args.valtype2.? == .i32) return .f32_reinterpret_i32 else unreachable,
447 .f64 => if (args.valtype2.? == .i64) return .f64_reinterpret_i64 else unreachable,
448 },
449 .extend => switch (args.valtype1.?) {
450 .i32 => switch (args.width.?) {
451 8 => if (args.signedness.? == .signed) return .i32_extend8_s else unreachable,
452 16 => if (args.signedness.? == .signed) return .i32_extend16_s else unreachable,
453 else => unreachable,
454 },
455 .i64 => switch (args.width.?) {
456 8 => if (args.signedness.? == .signed) return .i64_extend8_s else unreachable,
457 16 => if (args.signedness.? == .signed) return .i64_extend16_s else unreachable,
458 32 => if (args.signedness.? == .signed) return .i64_extend32_s else unreachable,
459 else => unreachable,
460 },
461 .f32, .f64 => unreachable,
462 },
463 }
464}
465
466test "Wasm - buildOpcode" {
467 // Make sure buildOpcode is referenced, and test some examples
468 const i32_const = buildOpcode(.{ .op = .@"const", .valtype1 = .i32 });
469 const end = buildOpcode(.{ .op = .end });
470 const local_get = buildOpcode(.{ .op = .local_get });
471 const i64_extend32_s = buildOpcode(.{ .op = .extend, .valtype1 = .i64, .width = 32, .signedness = .signed });
472 const f64_reinterpret_i64 = buildOpcode(.{ .op = .reinterpret, .valtype1 = .f64, .valtype2 = .i64 });
473
474 try testing.expectEqual(@as(wasm.Opcode, .i32_const), i32_const);
475 try testing.expectEqual(@as(wasm.Opcode, .end), end);
476 try testing.expectEqual(@as(wasm.Opcode, .local_get), local_get);
477 try testing.expectEqual(@as(wasm.Opcode, .i64_extend32_s), i64_extend32_s);
478 try testing.expectEqual(@as(wasm.Opcode, .f64_reinterpret_i64), f64_reinterpret_i64);
479}
480
481pub const Result = union(enum) {
482 /// The codegen bytes have been appended to `Context.code`
483 appended: void,
484 /// The data is managed externally and are part of the `Result`
485 externally_managed: []const u8,
486};
487
488/// Hashmap to store generated `WValue` for each `Air.Inst.Ref`
489pub const ValueTable = std.AutoHashMapUnmanaged(Air.Inst.Index, WValue);
490
491const Self = @This();
492
493/// Reference to the function declaration the code
494/// section belongs to
495decl: *Decl,
496air: Air,
497liveness: Liveness,
498gpa: *mem.Allocator,
499/// Table to save `WValue`'s generated by an `Air.Inst`
500values: ValueTable,
501/// Mapping from Air.Inst.Index to block ids
502blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, u32) = .{},
503/// `bytes` contains the wasm bytecode belonging to the 'code' section.
504code: ArrayList(u8),
505/// Contains the generated function type bytecode for the current function
506/// found in `decl`
507func_type_data: ArrayList(u8),
508/// The index the next local generated will have
509/// NOTE: arguments share the index with locals therefore the first variable
510/// will have the index that comes after the last argument's index
511local_index: u32 = 0,
512/// If codegen fails, an error messages will be allocated and saved in `err_msg`
513err_msg: *Module.ErrorMsg,
514/// Current block depth. Used to calculate the relative difference between a break
515/// and block
516block_depth: u32 = 0,
517/// List of all locals' types generated throughout this declaration
518/// used to emit locals count at start of 'code' section.
519locals: std.ArrayListUnmanaged(u8),
520/// The Target we're emitting (used to call intInfo)
521target: std.Target,
522/// Represents the wasm binary file that is being linked.
523bin_file: *link.File,
524/// Table with the global error set. Consists of every error found in
525/// the compiled code. Each error name maps to a `Module.ErrorInt` which is emitted
526/// during codegen to determine the error value.
527global_error_set: std.StringHashMapUnmanaged(Module.ErrorInt),
528/// List of MIR Instructions
529mir_instructions: std.MultiArrayList(Mir.Inst) = .{},
530/// Contains extra data for MIR
531mir_extra: std.ArrayListUnmanaged(u32) = .{},
532
533const InnerError = error{
534 OutOfMemory,
535 /// An error occured when trying to lower AIR to MIR.
536 CodegenFail,
537 /// Can occur when dereferencing a pointer that points to a `Decl` of which the analysis has failed
538 AnalysisFail,
539 /// Failed to emit MIR instructions to binary/textual representation.
540 EmitFail,
541};
542
543pub fn deinit(self: *Self) void {
544 self.values.deinit(self.gpa);
545 self.blocks.deinit(self.gpa);
546 self.locals.deinit(self.gpa);
547 self.mir_instructions.deinit(self.gpa);
548 self.mir_extra.deinit(self.gpa);
549 self.* = undefined;
550}
551
552/// Sets `err_msg` on `Context` and returns `error.CodegemFail` which is caught in link/Wasm.zig
553fn fail(self: *Self, comptime fmt: []const u8, args: anytype) InnerError {
554 const src: LazySrcLoc = .{ .node_offset = 0 };
555 const src_loc = src.toSrcLoc(self.decl);
556 self.err_msg = try Module.ErrorMsg.create(self.gpa, src_loc, fmt, args);
557 return error.CodegenFail;
558}
559
560/// Resolves the `WValue` for the given instruction `inst`
561/// When the given instruction has a `Value`, it returns a constant instead
562fn resolveInst(self: Self, ref: Air.Inst.Ref) WValue {
563 const inst_index = Air.refToIndex(ref) orelse {
564 const tv = Air.Inst.Ref.typed_value_map[@enumToInt(ref)];
565 if (!tv.ty.hasCodeGenBits()) {
566 return WValue.none;
567 }
568 return WValue{ .constant = tv };
569 };
570
571 const inst_type = self.air.typeOfIndex(inst_index);
572 if (!inst_type.hasCodeGenBits()) return .none;
573
574 if (self.air.instructions.items(.tag)[inst_index] == .constant) {
575 const ty_pl = self.air.instructions.items(.data)[inst_index].ty_pl;
576 return WValue{ .constant = .{ .ty = inst_type, .val = self.air.values[ty_pl.payload] } };
577 }
578
579 return self.values.get(inst_index).?; // Instruction does not dominate all uses!
580}
581
582/// Appends a MIR instruction and returns its index within the list of instructions
583fn addInst(self: *Self, inst: Mir.Inst) error{OutOfMemory}!void {
584 try self.mir_instructions.append(self.gpa, inst);
585}
586
587/// Inserts a Mir instruction at the given `offset`.
588/// Asserts offset is within bound.
589fn addInstAt(self: *Self, offset: usize, inst: Mir.Inst) error{OutOfMemory}!void {
590 try self.mir_instructions.ensureUnusedCapacity(self.gpa, 1);
591 self.mir_instructions.insertAssumeCapacity(offset, inst);
592}
593
594fn addTag(self: *Self, tag: Mir.Inst.Tag) error{OutOfMemory}!void {
595 try self.addInst(.{ .tag = tag, .data = .{ .tag = {} } });
596}
597
598fn addLabel(self: *Self, tag: Mir.Inst.Tag, label: u32) error{OutOfMemory}!void {
599 try self.addInst(.{ .tag = tag, .data = .{ .label = label } });
600}
601
602fn addImm32(self: *Self, imm: i32) error{OutOfMemory}!void {
603 try self.addInst(.{ .tag = .i32_const, .data = .{ .imm32 = imm } });
604}
605
606/// Accepts an unsigned 64bit integer rather than a signed integer to
607/// prevent us from having to bitcast multiple times as most values
608/// within codegen are represented as unsigned rather than signed.
609fn addImm64(self: *Self, imm: u64) error{OutOfMemory}!void {
610 const extra_index = try self.addExtra(Mir.Imm64.fromU64(imm));
611 try self.addInst(.{ .tag = .i64_const, .data = .{ .payload = extra_index } });
612}
613
614fn addFloat64(self: *Self, float: f64) error{OutOfMemory}!void {
615 const extra_index = try self.addExtra(Mir.Float64.fromFloat64(float));
616 try self.addInst(.{ .tag = .f64_const, .data = .{ .payload = extra_index } });
617}
618
619/// Appends entries to `mir_extra` based on the type of `extra`.
620/// Returns the index into `mir_extra`
621fn addExtra(self: *Self, extra: anytype) error{OutOfMemory}!u32 {
622 const fields = std.meta.fields(@TypeOf(extra));
623 try self.mir_extra.ensureUnusedCapacity(self.gpa, fields.len);
624 return self.addExtraAssumeCapacity(extra);
625}
626
627/// Appends entries to `mir_extra` based on the type of `extra`.
628/// Returns the index into `mir_extra`
629fn addExtraAssumeCapacity(self: *Self, extra: anytype) error{OutOfMemory}!u32 {
630 const fields = std.meta.fields(@TypeOf(extra));
631 const result = @intCast(u32, self.mir_extra.items.len);
632 inline for (fields) |field| {
633 self.mir_extra.appendAssumeCapacity(switch (field.field_type) {
634 u32 => @field(extra, field.name),
635 else => |field_type| @compileError("Unsupported field type " ++ @typeName(field_type)),
636 });
637 }
638 return result;
639}
640
641/// Using a given `Type`, returns the corresponding wasm Valtype
642fn typeToValtype(self: *Self, ty: Type) InnerError!wasm.Valtype {
643 return switch (ty.zigTypeTag()) {
644 .Float => blk: {
645 const bits = ty.floatBits(self.target);
646 if (bits == 16 or bits == 32) break :blk wasm.Valtype.f32;
647 if (bits == 64) break :blk wasm.Valtype.f64;
648 return self.fail("Float bit size not supported by wasm: '{d}'", .{bits});
649 },
650 .Int => blk: {
651 const info = ty.intInfo(self.target);
652 if (info.bits <= 32) break :blk wasm.Valtype.i32;
653 if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64;
654 return self.fail("Integer bit size not supported by wasm: '{d}'", .{info.bits});
655 },
656 .Enum => switch (ty.tag()) {
657 .enum_simple => wasm.Valtype.i32,
658 else => self.typeToValtype(ty.cast(Type.Payload.EnumFull).?.data.tag_ty),
659 },
660 .Bool,
661 .Pointer,
662 .ErrorSet,
663 => wasm.Valtype.i32,
664 .Struct, .ErrorUnion, .Optional => unreachable, // Multi typed, must be handled individually.
665 else => |tag| self.fail("TODO - Wasm valtype for type '{s}'", .{tag}),
666 };
667}
668
669/// Using a given `Type`, returns the byte representation of its wasm value type
670fn genValtype(self: *Self, ty: Type) InnerError!u8 {
671 return wasm.valtype(try self.typeToValtype(ty));
672}
673
674/// Using a given `Type`, returns the corresponding wasm value type
675/// Differently from `genValtype` this also allows `void` to create a block
676/// with no return type
677fn genBlockType(self: *Self, ty: Type) InnerError!u8 {
678 return switch (ty.tag()) {
679 .void, .noreturn => wasm.block_empty,
680 else => self.genValtype(ty),
681 };
682}
683
684/// Writes the bytecode depending on the given `WValue` in `val`
685fn emitWValue(self: *Self, val: WValue) InnerError!void {
686 switch (val) {
687 .multi_value => unreachable, // multi_value can never be written directly, and must be accessed individually
688 .none, .mir_offset => {}, // no-op
689 .local => |idx| {
690 try self.addLabel(.local_get, idx);
691 },
692 .constant => |tv| try self.emitConstant(tv.val, tv.ty), // Creates a new constant on the stack
693 }
694}
695
696/// Creates one or multiple locals for a given `Type`.
697/// Returns a corresponding `Wvalue` that can either be of tag
698/// local or multi_value
699fn allocLocal(self: *Self, ty: Type) InnerError!WValue {
700 const initial_index = self.local_index;
701 switch (ty.zigTypeTag()) {
702 .Struct => {
703 // for each struct field, generate a local
704 const struct_data: *Module.Struct = ty.castTag(.@"struct").?.data;
705 const fields_len = @intCast(u32, struct_data.fields.count());
706 try self.locals.ensureUnusedCapacity(self.gpa, fields_len);
707 for (struct_data.fields.values()) |*value| {
708 const val_type = try self.genValtype(value.ty);
709 self.locals.appendAssumeCapacity(val_type);
710 self.local_index += 1;
711 }
712 return WValue{ .multi_value = .{
713 .index = initial_index,
714 .count = fields_len,
715 } };
716 },
717 .ErrorUnion => {
718 const payload_type = ty.errorUnionPayload();
719 const val_type = try self.genValtype(payload_type);
720
721 // we emit the error value as the first local, and the payload as the following.
722 // The first local is also used to find the index of the error and payload.
723 //
724 // TODO: Add support where the payload is a type that contains multiple locals such as a struct.
725 try self.locals.ensureUnusedCapacity(self.gpa, 2);
726 self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // error values are always i32
727 self.locals.appendAssumeCapacity(val_type);
728 self.local_index += 2;
729
730 return WValue{ .multi_value = .{
731 .index = initial_index,
732 .count = 2,
733 } };
734 },
735 .Optional => {
736 var opt_buf: Type.Payload.ElemType = undefined;
737 const child_type = ty.optionalChild(&opt_buf);
738 if (ty.isPtrLikeOptional()) {
739 return self.fail("TODO: wasm optional pointer", .{});
740 }
741
742 try self.locals.ensureUnusedCapacity(self.gpa, 2);
743 self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // optional 'tag' for null-checking is always i32
744 self.locals.appendAssumeCapacity(try self.genValtype(child_type));
745 self.local_index += 2;
746
747 return WValue{ .multi_value = .{
748 .index = initial_index,
749 .count = 2,
750 } };
751 },
752 else => {
753 const valtype = try self.genValtype(ty);
754 try self.locals.append(self.gpa, valtype);
755 self.local_index += 1;
756 return WValue{ .local = initial_index };
757 },
758 }
759}
760
761fn genFunctype(self: *Self) InnerError!void {
762 assert(self.decl.has_tv);
763 const ty = self.decl.ty;
764 const writer = self.func_type_data.writer();
765
766 try writer.writeByte(wasm.function_type);
767
768 // param types
769 try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen()));
770 if (ty.fnParamLen() != 0) {
771 const params = try self.gpa.alloc(Type, ty.fnParamLen());
772 defer self.gpa.free(params);
773 ty.fnParamTypes(params);
774 for (params) |param_type| {
775 // Can we maybe get the source index of each param?
776 const val_type = try self.genValtype(param_type);
777 try writer.writeByte(val_type);
778 }
779 }
780
781 // return type
782 const return_type = ty.fnReturnType();
783 switch (return_type.zigTypeTag()) {
784 .Void, .NoReturn => try leb.writeULEB128(writer, @as(u32, 0)),
785 .Struct => return self.fail("TODO: Implement struct as return type for wasm", .{}),
786 .Optional => return self.fail("TODO: Implement optionals as return type for wasm", .{}),
787 .ErrorUnion => {
788 const val_type = try self.genValtype(return_type.errorUnionPayload());
789
790 // write down the amount of return values
791 try leb.writeULEB128(writer, @as(u32, 2));
792 try writer.writeByte(wasm.valtype(.i32)); // error code is always an i32 integer.
793 try writer.writeByte(val_type);
794 },
795 else => {
796 try leb.writeULEB128(writer, @as(u32, 1));
797 // Can we maybe get the source index of the return type?
798 const val_type = try self.genValtype(return_type);
799 try writer.writeByte(val_type);
800 },
801 }
802}
803
804pub fn genFunc(self: *Self) InnerError!Result {
805 try self.genFunctype();
806 // TODO: check for and handle death of instructions
807
808 // Generate MIR for function body
809 try self.genBody(self.air.getMainBody());
810 // End of function body
811 try self.addTag(.end);
812
813 var mir: Mir = .{
814 .instructions = self.mir_instructions.toOwnedSlice(),
815 .extra = self.mir_extra.toOwnedSlice(self.gpa),
816 };
817 defer mir.deinit(self.gpa);
818
819 var emit: Emit = .{
820 .mir = mir,
821 .bin_file = self.bin_file,
822 .code = &self.code,
823 .locals = self.locals.items,
824 .decl = self.decl,
825 };
826
827 emit.emitMir() catch |err| switch (err) {
828 error.EmitFail => {
829 self.err_msg = emit.error_msg.?;
830 return error.EmitFail;
831 },
832 else => |e| return e,
833 };
834
835 // codegen data has been appended to `code`
836 return Result.appended;
837}
838
839/// Generates the wasm bytecode for the declaration belonging to `Context`
840pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {
841 switch (ty.zigTypeTag()) {
842 .Fn => {
843 try self.genFunctype();
844 if (val.tag() == .extern_fn) {
845 return Result.appended; // don't need code body for extern functions
846 }
847 return self.fail("TODO implement wasm codegen for function pointers", .{});
848 },
849 .Array => {
850 if (val.castTag(.bytes)) |payload| {
851 if (ty.sentinel()) |sentinel| {
852 try self.code.appendSlice(payload.data);
853
854 switch (try self.gen(ty.elemType(), sentinel)) {
855 .appended => return Result.appended,
856 .externally_managed => |data| {
857 try self.code.appendSlice(data);
858 return Result.appended;
859 },
860 }
861 }
862 return Result{ .externally_managed = payload.data };
863 } else return self.fail("TODO implement gen for more kinds of arrays", .{});
864 },
865 .Int => {
866 const info = ty.intInfo(self.target);
867 if (info.bits == 8 and info.signedness == .unsigned) {
868 const int_byte = val.toUnsignedInt();
869 try self.code.append(@intCast(u8, int_byte));
870 return Result.appended;
871 }
872 return self.fail("TODO: Implement codegen for int type: '{}'", .{ty});
873 },
874 .Enum => {
875 try self.emitConstant(val, ty);
876 return Result.appended;
877 },
878 .Struct => {
879 // TODO write the fields for real
880 try self.code.writer().writeByteNTimes(0xaa, ty.abiSize(self.target));
881 return Result{ .appended = {} };
882 },
883 else => |tag| return self.fail("TODO: Implement zig type codegen for type: '{s}'", .{tag}),
884 }
885}
886
887fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
888 const air_tags = self.air.instructions.items(.tag);
889 return switch (air_tags[inst]) {
890 .add => self.airBinOp(inst, .add),
891 .addwrap => self.airWrapBinOp(inst, .add),
892 .sub => self.airBinOp(inst, .sub),
893 .subwrap => self.airWrapBinOp(inst, .sub),
894 .mul => self.airBinOp(inst, .mul),
895 .mulwrap => self.airWrapBinOp(inst, .mul),
896 .div_trunc => self.airBinOp(inst, .div),
897 .bit_and => self.airBinOp(inst, .@"and"),
898 .bit_or => self.airBinOp(inst, .@"or"),
899 .bool_and => self.airBinOp(inst, .@"and"),
900 .bool_or => self.airBinOp(inst, .@"or"),
901 .xor => self.airBinOp(inst, .xor),
902
903 .cmp_eq => self.airCmp(inst, .eq),
904 .cmp_gte => self.airCmp(inst, .gte),
905 .cmp_gt => self.airCmp(inst, .gt),
906 .cmp_lte => self.airCmp(inst, .lte),
907 .cmp_lt => self.airCmp(inst, .lt),
908 .cmp_neq => self.airCmp(inst, .neq),
909
910 .alloc => self.airAlloc(inst),
911 .arg => self.airArg(inst),
912 .bitcast => self.airBitcast(inst),
913 .block => self.airBlock(inst),
914 .breakpoint => self.airBreakpoint(inst),
915 .br => self.airBr(inst),
916 .call => self.airCall(inst),
917 .cond_br => self.airCondBr(inst),
918 .constant => unreachable,
919 .dbg_stmt => WValue.none,
920 .intcast => self.airIntcast(inst),
921
922 .is_err => self.airIsErr(inst, .i32_ne),
923 .is_non_err => self.airIsErr(inst, .i32_eq),
924
925 .is_null => self.airIsNull(inst, .i32_ne),
926 .is_non_null => self.airIsNull(inst, .i32_eq),
927 .is_null_ptr => self.airIsNull(inst, .i32_ne),
928 .is_non_null_ptr => self.airIsNull(inst, .i32_eq),
929
930 .load => self.airLoad(inst),
931 .loop => self.airLoop(inst),
932 .not => self.airNot(inst),
933 .ret => self.airRet(inst),
934 .store => self.airStore(inst),
935 .struct_field_ptr => self.airStructFieldPtr(inst),
936 .struct_field_ptr_index_0 => self.airStructFieldPtrIndex(inst, 0),
937 .struct_field_ptr_index_1 => self.airStructFieldPtrIndex(inst, 1),
938 .struct_field_ptr_index_2 => self.airStructFieldPtrIndex(inst, 2),
939 .struct_field_ptr_index_3 => self.airStructFieldPtrIndex(inst, 3),
940 .struct_field_val => self.airStructFieldVal(inst),
941 .switch_br => self.airSwitchBr(inst),
942 .unreach => self.airUnreachable(inst),
943 .wrap_optional => self.airWrapOptional(inst),
944
945 .unwrap_errunion_payload => self.airUnwrapErrUnionPayload(inst),
946 .wrap_errunion_payload => self.airWrapErrUnionPayload(inst),
947
948 .optional_payload => self.airOptionalPayload(inst),
949 .optional_payload_ptr => self.airOptionalPayload(inst),
950 .optional_payload_ptr_set => self.airOptionalPayloadPtrSet(inst),
951 else => |tag| self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),
952 };
953}
954
955fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
956 for (body) |inst| {
957 const result = try self.genInst(inst);
958 try self.values.putNoClobber(self.gpa, inst, result);
959 }
960}
961
962fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
963 const un_op = self.air.instructions.items(.data)[inst].un_op;
964 const operand = self.resolveInst(un_op);
965 try self.emitWValue(operand);
966 try self.addTag(.@"return");
967 return .none;
968}
969
970fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
971 const pl_op = self.air.instructions.items(.data)[inst].pl_op;
972 const extra = self.air.extraData(Air.Call, pl_op.payload);
973 const args = self.air.extra[extra.end..][0..extra.data.args_len];
974
975 const target: *Decl = blk: {
976 const func_val = self.air.value(pl_op.operand).?;
977
978 if (func_val.castTag(.function)) |func| {
979 break :blk func.data.owner_decl;
980 } else if (func_val.castTag(.extern_fn)) |ext_fn| {
981 break :blk ext_fn.data;
982 }
983 return self.fail("Expected a function, but instead found type '{s}'", .{func_val.tag()});
984 };
985
986 for (args) |arg| {
987 const arg_val = self.resolveInst(@intToEnum(Air.Inst.Ref, arg));
988 try self.emitWValue(arg_val);
989 }
990
991 try self.addLabel(.call, target.link.wasm.symbol_index);
992
993 return .none;
994}
995
996fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
997 const elem_type = self.air.typeOfIndex(inst).elemType();
998 return self.allocLocal(elem_type);
999}
1000
1001fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1002 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
1003
1004 const lhs = self.resolveInst(bin_op.lhs);
1005 const rhs = self.resolveInst(bin_op.rhs);
1006
1007 switch (lhs) {
1008 .multi_value => |multi_value| switch (rhs) {
1009 // When assigning a value to a multi_value such as a struct,
1010 // we simply assign the local_index to the rhs one.
1011 // This allows us to update struct fields without having to individually
1012 // set each local as each field's index will be calculated off the struct's base index
1013 .multi_value => self.values.put(self.gpa, Air.refToIndex(bin_op.lhs).?, rhs) catch unreachable, // Instruction does not dominate all uses!
1014 .constant, .none => {
1015 // emit all values onto the stack if constant
1016 try self.emitWValue(rhs);
1017
1018 // for each local, pop the stack value into the local
1019 // As the last element is on top of the stack, we must populate the locals
1020 // in reverse.
1021 var i: u32 = multi_value.count;
1022 while (i > 0) : (i -= 1) {
1023 try self.addLabel(.local_set, multi_value.index + i - 1);
1024 }
1025 },
1026 .local => {
1027 // This can occur when we wrap a single value into a multi-value,
1028 // such as wrapping a non-optional value into an optional.
1029 // This means we must zero the null-tag, and set the payload.
1030 assert(multi_value.count == 2);
1031 // set payload
1032 try self.emitWValue(rhs);
1033 try self.addLabel(.local_set, multi_value.index + 1);
1034 },
1035 else => unreachable,
1036 },
1037 .local => |local| {
1038 try self.emitWValue(rhs);
1039 try self.addLabel(.local_set, local);
1040 },
1041 else => unreachable,
1042 }
1043 return .none;
1044}
1045
1046fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1047 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1048 return self.resolveInst(ty_op.operand);
1049}
1050
1051fn airArg(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1052 _ = inst;
1053 // arguments share the index with locals
1054 defer self.local_index += 1;
1055 return WValue{ .local = self.local_index };
1056}
1057
1058fn airBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
1059 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
1060 const lhs = self.resolveInst(bin_op.lhs);
1061 const rhs = self.resolveInst(bin_op.rhs);
1062
1063 // it's possible for both lhs and/or rhs to return an offset as well,
1064 // in which case we return the first offset occurrence we find.
1065 const offset = blk: {
1066 if (lhs == .mir_offset) break :blk lhs.mir_offset;
1067 if (rhs == .mir_offset) break :blk rhs.mir_offset;
1068 break :blk self.mir_instructions.len;
1069 };
1070
1071 try self.emitWValue(lhs);
1072 try self.emitWValue(rhs);
1073
1074 const bin_ty = self.air.typeOf(bin_op.lhs);
1075 const opcode: wasm.Opcode = buildOpcode(.{
1076 .op = op,
1077 .valtype1 = try self.typeToValtype(bin_ty),
1078 .signedness = if (bin_ty.isSignedInt()) .signed else .unsigned,
1079 });
1080 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
1081 return WValue{ .mir_offset = offset };
1082}
1083
1084fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
1085 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
1086 const lhs = self.resolveInst(bin_op.lhs);
1087 const rhs = self.resolveInst(bin_op.rhs);
1088
1089 // it's possible for both lhs and/or rhs to return an offset as well,
1090 // in which case we return the first offset occurrence we find.
1091 const offset = blk: {
1092 if (lhs == .mir_offset) break :blk lhs.mir_offset;
1093 if (rhs == .mir_offset) break :blk rhs.mir_offset;
1094 break :blk self.mir_instructions.len;
1095 };
1096
1097 try self.emitWValue(lhs);
1098 try self.emitWValue(rhs);
1099
1100 const bin_ty = self.air.typeOf(bin_op.lhs);
1101 const opcode: wasm.Opcode = buildOpcode(.{
1102 .op = op,
1103 .valtype1 = try self.typeToValtype(bin_ty),
1104 .signedness = if (bin_ty.isSignedInt()) .signed else .unsigned,
1105 });
1106 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
1107
1108 const int_info = bin_ty.intInfo(self.target);
1109 const bitsize = int_info.bits;
1110 const is_signed = int_info.signedness == .signed;
1111 // if target type bitsize is x < 32 and 32 > x < 64, we perform
1112 // result & ((1<<N)-1) where N = bitsize or bitsize -1 incase of signed.
1113 if (bitsize != 32 and bitsize < 64) {
1114 // first check if we can use a single instruction,
1115 // wasm provides those if the integers are signed and 8/16-bit.
1116 // For arbitrary integer sizes, we use the algorithm mentioned above.
1117 if (is_signed and bitsize == 8) {
1118 try self.addTag(.i32_extend8_s);
1119 } else if (is_signed and bitsize == 16) {
1120 try self.addTag(.i32_extend16_s);
1121 } else {
1122 const result = (@as(u64, 1) << @intCast(u6, bitsize - @boolToInt(is_signed))) - 1;
1123 if (bitsize < 32) {
1124 try self.addImm32(@bitCast(i32, @intCast(u32, result)));
1125 try self.addTag(.i32_and);
1126 } else {
1127 try self.addImm64(result);
1128 try self.addTag(.i64_and);
1129 }
1130 }
1131 } else if (int_info.bits > 64) {
1132 return self.fail("TODO wasm: Integer wrapping for bitsizes larger than 64", .{});
1133 }
1134
1135 return WValue{ .mir_offset = offset };
1136}
1137
1138fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {
1139 switch (ty.zigTypeTag()) {
1140 .Int => {
1141 const int_info = ty.intInfo(self.target);
1142 // write constant
1143 switch (int_info.signedness) {
1144 .signed => switch (int_info.bits) {
1145 0...32 => try self.addImm32(@intCast(i32, val.toSignedInt())),
1146 33...64 => try self.addImm64(@bitCast(u64, val.toSignedInt())),
1147 else => |bits| return self.fail("Wasm todo: emitConstant for integer with {d} bits", .{bits}),
1148 },
1149 .unsigned => switch (int_info.bits) {
1150 0...32 => try self.addImm32(@bitCast(i32, @intCast(u32, val.toUnsignedInt()))),
1151 33...64 => try self.addImm64(val.toUnsignedInt()),
1152 else => |bits| return self.fail("Wasm TODO: emitConstant for integer with {d} bits", .{bits}),
1153 },
1154 }
1155 },
1156 .Bool => try self.addImm32(@intCast(i32, val.toSignedInt())),
1157 .Float => {
1158 // write constant
1159 switch (ty.floatBits(self.target)) {
1160 0...32 => try self.addInst(.{ .tag = .f32_const, .data = .{ .float32 = val.toFloat(f32) } }),
1161 64 => try self.addFloat64(val.toFloat(f64)),
1162 else => |bits| return self.fail("Wasm TODO: emitConstant for float with {d} bits", .{bits}),
1163 }
1164 },
1165 .Pointer => {
1166 if (val.castTag(.decl_ref)) |payload| {
1167 const decl = payload.data;
1168 decl.alive = true;
1169
1170 // offset into the offset table within the 'data' section
1171 const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8;
1172 try self.addImm32(@bitCast(i32, decl.link.wasm.offset_index * ptr_width));
1173
1174 // memory instruction followed by their memarg immediate
1175 // memarg ::== x:u32, y:u32 => {align x, offset y}
1176 const extra_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 0 });
1177 try self.addInst(.{ .tag = .i32_load, .data = .{ .payload = extra_index } });
1178 } else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{val.tag()});
1179 },
1180 .Void => {},
1181 .Enum => {
1182 if (val.castTag(.enum_field_index)) |field_index| {
1183 switch (ty.tag()) {
1184 .enum_simple => try self.addImm32(@bitCast(i32, field_index.data)),
1185 .enum_full, .enum_nonexhaustive => {
1186 const enum_full = ty.cast(Type.Payload.EnumFull).?.data;
1187 if (enum_full.values.count() != 0) {
1188 const tag_val = enum_full.values.keys()[field_index.data];
1189 try self.emitConstant(tag_val, enum_full.tag_ty);
1190 } else {
1191 try self.addImm32(@bitCast(i32, field_index.data));
1192 }
1193 },
1194 else => unreachable,
1195 }
1196 } else {
1197 var int_tag_buffer: Type.Payload.Bits = undefined;
1198 const int_tag_ty = ty.intTagType(&int_tag_buffer);
1199 try self.emitConstant(val, int_tag_ty);
1200 }
1201 },
1202 .ErrorSet => {
1203 const error_index = self.global_error_set.get(val.getError().?).?;
1204 try self.addImm32(@bitCast(i32, error_index));
1205 },
1206 .ErrorUnion => {
1207 const error_type = ty.errorUnionSet();
1208 const payload_type = ty.errorUnionPayload();
1209 if (val.castTag(.eu_payload)) |pl| {
1210 const payload_val = pl.data;
1211 // no error, so write a '0' const
1212 try self.addImm32(0);
1213 // after the error code, we emit the payload
1214 try self.emitConstant(payload_val, payload_type);
1215 } else {
1216 // write the error val
1217 try self.emitConstant(val, error_type);
1218
1219 // no payload, so write a '0' const
1220 try self.addImm32(0);
1221 }
1222 },
1223 .Optional => {
1224 var buf: Type.Payload.ElemType = undefined;
1225 const payload_type = ty.optionalChild(&buf);
1226 if (ty.isPtrLikeOptional()) {
1227 return self.fail("Wasm TODO: emitConstant for optional pointer", .{});
1228 }
1229
1230 // When constant has value 'null', set is_null local to '1'
1231 // and payload to '0'
1232 if (val.castTag(.opt_payload)) |pl| {
1233 const payload_val = pl.data;
1234 try self.addImm32(0);
1235 try self.emitConstant(payload_val, payload_type);
1236 } else {
1237 // set null-tag
1238 try self.addImm32(1);
1239 // null-tag is set, so write a '0' const
1240 try self.addImm32(0);
1241 }
1242 },
1243 else => |zig_type| return self.fail("Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}),
1244 }
1245}
1246
1247/// Returns a `Value` as a signed 32 bit value.
1248/// It's illegal to provide a value with a type that cannot be represented
1249/// as an integer value.
1250fn valueAsI32(self: Self, val: Value, ty: Type) i32 {
1251 switch (ty.zigTypeTag()) {
1252 .Enum => {
1253 if (val.castTag(.enum_field_index)) |field_index| {
1254 switch (ty.tag()) {
1255 .enum_simple => return @bitCast(i32, field_index.data),
1256 .enum_full, .enum_nonexhaustive => {
1257 const enum_full = ty.cast(Type.Payload.EnumFull).?.data;
1258 if (enum_full.values.count() != 0) {
1259 const tag_val = enum_full.values.keys()[field_index.data];
1260 return self.valueAsI32(tag_val, enum_full.tag_ty);
1261 } else return @bitCast(i32, field_index.data);
1262 },
1263 else => unreachable,
1264 }
1265 } else {
1266 var int_tag_buffer: Type.Payload.Bits = undefined;
1267 const int_tag_ty = ty.intTagType(&int_tag_buffer);
1268 return self.valueAsI32(val, int_tag_ty);
1269 }
1270 },
1271 .Int => switch (ty.intInfo(self.target).signedness) {
1272 .signed => return @truncate(i32, val.toSignedInt()),
1273 .unsigned => return @bitCast(i32, @truncate(u32, val.toUnsignedInt())),
1274 },
1275 .ErrorSet => {
1276 const error_index = self.global_error_set.get(val.getError().?).?;
1277 return @bitCast(i32, error_index);
1278 },
1279 else => unreachable, // Programmer called this function for an illegal type
1280 }
1281}
1282
1283fn airBlock(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1284 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
1285 const block_ty = try self.genBlockType(self.air.getRefType(ty_pl.ty));
1286 const extra = self.air.extraData(Air.Block, ty_pl.payload);
1287 const body = self.air.extra[extra.end..][0..extra.data.body_len];
1288
1289 try self.startBlock(.block, block_ty, null);
1290 // Here we set the current block idx, so breaks know the depth to jump
1291 // to when breaking out.
1292 try self.blocks.putNoClobber(self.gpa, inst, self.block_depth);
1293 try self.genBody(body);
1294 try self.endBlock();
1295
1296 return .none;
1297}
1298
1299/// appends a new wasm block to the code section and increases the `block_depth` by 1
1300fn startBlock(self: *Self, block_tag: wasm.Opcode, valtype: u8, with_offset: ?usize) !void {
1301 self.block_depth += 1;
1302 const offset = with_offset orelse self.mir_instructions.len;
1303 try self.addInstAt(offset, .{
1304 .tag = Mir.Inst.Tag.fromOpcode(block_tag),
1305 .data = .{ .block_type = valtype },
1306 });
1307}
1308
1309/// Ends the current wasm block and decreases the `block_depth` by 1
1310fn endBlock(self: *Self) !void {
1311 try self.addTag(.end);
1312 self.block_depth -= 1;
1313}
1314
1315fn airLoop(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1316 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
1317 const loop = self.air.extraData(Air.Block, ty_pl.payload);
1318 const body = self.air.extra[loop.end..][0..loop.data.body_len];
1319
1320 // result type of loop is always 'noreturn', meaning we can always
1321 // emit the wasm type 'block_empty'.
1322 try self.startBlock(.loop, wasm.block_empty, null);
1323 try self.genBody(body);
1324
1325 // breaking to the index of a loop block will continue the loop instead
1326 try self.addLabel(.br, 0);
1327 try self.endBlock();
1328
1329 return .none;
1330}
1331
1332fn airCondBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1333 const pl_op = self.air.instructions.items(.data)[inst].pl_op;
1334 const condition = self.resolveInst(pl_op.operand);
1335 const extra = self.air.extraData(Air.CondBr, pl_op.payload);
1336 const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len];
1337 const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
1338 // TODO: Handle death instructions for then and else body
1339
1340 // insert blocks at the position of `offset` so
1341 // the condition can jump to it
1342 const offset = switch (condition) {
1343 .mir_offset => |offset| offset,
1344 else => blk: {
1345 const offset = self.mir_instructions.len;
1346 try self.emitWValue(condition);
1347 break :blk offset;
1348 },
1349 };
1350
1351 // result type is always noreturn, so use `block_empty` as type.
1352 try self.startBlock(.block, wasm.block_empty, offset);
1353
1354 // we inserted the block in front of the condition
1355 // so now check if condition matches. If not, break outside this block
1356 // and continue with the then codepath
1357 try self.addLabel(.br_if, 0);
1358
1359 try self.genBody(else_body);
1360 try self.endBlock();
1361
1362 // Outer block that matches the condition
1363 try self.genBody(then_body);
1364
1365 return .none;
1366}
1367
1368fn airCmp(self: *Self, inst: Air.Inst.Index, op: std.math.CompareOperator) InnerError!WValue {
1369 // save offset, so potential conditions can insert blocks in front of
1370 // the comparison that we can later jump back to
1371 const offset = self.mir_instructions.len;
1372
1373 const data: Air.Inst.Data = self.air.instructions.items(.data)[inst];
1374 const lhs = self.resolveInst(data.bin_op.lhs);
1375 const rhs = self.resolveInst(data.bin_op.rhs);
1376 const lhs_ty = self.air.typeOf(data.bin_op.lhs);
1377
1378 try self.emitWValue(lhs);
1379 try self.emitWValue(rhs);
1380
1381 const signedness: std.builtin.Signedness = blk: {
1382 // by default we tell the operand type is unsigned (i.e. bools and enum values)
1383 if (lhs_ty.zigTypeTag() != .Int) break :blk .unsigned;
1384
1385 // incase of an actual integer, we emit the correct signedness
1386 break :blk lhs_ty.intInfo(self.target).signedness;
1387 };
1388 const opcode: wasm.Opcode = buildOpcode(.{
1389 .valtype1 = try self.typeToValtype(lhs_ty),
1390 .op = switch (op) {
1391 .lt => .lt,
1392 .lte => .le,
1393 .eq => .eq,
1394 .neq => .ne,
1395 .gte => .ge,
1396 .gt => .gt,
1397 },
1398 .signedness = signedness,
1399 });
1400 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
1401 return WValue{ .mir_offset = offset };
1402}
1403
1404fn airBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1405 const br = self.air.instructions.items(.data)[inst].br;
1406
1407 // if operand has codegen bits we should break with a value
1408 if (self.air.typeOf(br.operand).hasCodeGenBits()) {
1409 try self.emitWValue(self.resolveInst(br.operand));
1410 }
1411
1412 // We map every block to its block index.
1413 // We then determine how far we have to jump to it by subtracting it from current block depth
1414 const idx: u32 = self.block_depth - self.blocks.get(br.block_inst).?;
1415 try self.addLabel(.br, idx);
1416
1417 return .none;
1418}
1419
1420fn airNot(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1421 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1422 const offset = self.mir_instructions.len;
1423
1424 const operand = self.resolveInst(ty_op.operand);
1425 try self.emitWValue(operand);
1426
1427 // wasm does not have booleans nor the `not` instruction, therefore compare with 0
1428 // to create the same logic
1429 try self.addImm32(0);
1430 try self.addTag(.i32_eq);
1431
1432 return WValue{ .mir_offset = offset };
1433}
1434
1435fn airBreakpoint(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1436 _ = self;
1437 _ = inst;
1438 // unsupported by wasm itself. Can be implemented once we support DWARF
1439 // for wasm
1440 return .none;
1441}
1442
1443fn airUnreachable(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1444 _ = inst;
1445 try self.addTag(.@"unreachable");
1446 return .none;
1447}
1448
1449fn airBitcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1450 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1451 return self.resolveInst(ty_op.operand);
1452}
1453
1454fn airStructFieldPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1455 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
1456 const extra = self.air.extraData(Air.StructField, ty_pl.payload);
1457 const struct_ptr = self.resolveInst(extra.data.struct_operand);
1458 return structFieldPtr(struct_ptr, extra.data.field_index);
1459}
1460fn airStructFieldPtrIndex(self: *Self, inst: Air.Inst.Index, index: u32) InnerError!WValue {
1461 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1462 const struct_ptr = self.resolveInst(ty_op.operand);
1463 return structFieldPtr(struct_ptr, index);
1464}
1465fn structFieldPtr(struct_ptr: WValue, index: u32) InnerError!WValue {
1466 return WValue{ .local = struct_ptr.multi_value.index + index };
1467}
1468
1469fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1470 if (self.liveness.isUnused(inst)) return WValue.none;
1471
1472 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
1473 const extra = self.air.extraData(Air.StructField, ty_pl.payload).data;
1474 const struct_multivalue = self.resolveInst(extra.struct_operand).multi_value;
1475 return WValue{ .local = struct_multivalue.index + extra.field_index };
1476}
1477
1478fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1479 // result type is always 'noreturn'
1480 const blocktype = wasm.block_empty;
1481 const pl_op = self.air.instructions.items(.data)[inst].pl_op;
1482 const target = self.resolveInst(pl_op.operand);
1483 const target_ty = self.air.typeOf(pl_op.operand);
1484 const switch_br = self.air.extraData(Air.SwitchBr, pl_op.payload);
1485 var extra_index: usize = switch_br.end;
1486 var case_i: u32 = 0;
1487
1488 // a list that maps each value with its value and body based on the order inside the list.
1489 const CaseValue = struct { integer: i32, value: Value };
1490 var case_list = try std.ArrayList(struct {
1491 values: []const CaseValue,
1492 body: []const Air.Inst.Index,
1493 }).initCapacity(self.gpa, switch_br.data.cases_len);
1494 defer for (case_list.items) |case| {
1495 self.gpa.free(case.values);
1496 } else case_list.deinit();
1497
1498 var lowest: i32 = 0;
1499 var highest: i32 = 0;
1500 while (case_i < switch_br.data.cases_len) : (case_i += 1) {
1501 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
1502 const items = @bitCast([]const Air.Inst.Ref, self.air.extra[case.end..][0..case.data.items_len]);
1503 const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len];
1504 extra_index = case.end + items.len + case_body.len;
1505 const values = try self.gpa.alloc(CaseValue, items.len);
1506 errdefer self.gpa.free(values);
1507
1508 for (items) |ref, i| {
1509 const item_val = self.air.value(ref).?;
1510 const int_val = self.valueAsI32(item_val, target_ty);
1511 if (int_val < lowest) {
1512 lowest = int_val;
1513 }
1514 if (int_val > highest) {
1515 highest = int_val;
1516 }
1517 values[i] = .{ .integer = int_val, .value = item_val };
1518 }
1519
1520 case_list.appendAssumeCapacity(.{ .values = values, .body = case_body });
1521 try self.startBlock(.block, blocktype, null);
1522 }
1523
1524 // When the highest and lowest values are seperated by '50',
1525 // we define it as sparse and use an if/else-chain, rather than a jump table.
1526 // When the target is an integer size larger than u32, we have no way to use the value
1527 // as an index, therefore we also use an if/else-chain for those cases.
1528 // TODO: Benchmark this to find a proper value, LLVM seems to draw the line at '40~45'.
1529 const is_sparse = highest - lowest > 50 or target_ty.bitSize(self.target) > 32;
1530
1531 const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len];
1532 const has_else_body = else_body.len != 0;
1533 if (has_else_body) {
1534 try self.startBlock(.block, blocktype, null);
1535 }
1536
1537 if (!is_sparse) {
1538 // Generate the jump table 'br_table' when the prongs are not sparse.
1539 // The value 'target' represents the index into the table.
1540 // Each index in the table represents a label to the branch
1541 // to jump to.
1542 try self.startBlock(.block, blocktype, null);
1543 try self.emitWValue(target);
1544 if (lowest < 0) {
1545 // since br_table works using indexes, starting from '0', we must ensure all values
1546 // we put inside, are atleast 0.
1547 try self.addImm32(lowest * -1);
1548 try self.addTag(.i32_add);
1549 }
1550
1551 // Account for default branch so always add '1'
1552 const depth = @intCast(u32, highest - lowest + @boolToInt(has_else_body)) + 1;
1553 const jump_table: Mir.JumpTable = .{ .length = depth };
1554 const table_extra_index = try self.addExtra(jump_table);
1555 try self.addInst(.{ .tag = .br_table, .data = .{ .payload = table_extra_index } });
1556 try self.mir_extra.ensureUnusedCapacity(self.gpa, depth);
1557 while (lowest <= highest) : (lowest += 1) {
1558 // idx represents the branch we jump to
1559 const idx = blk: {
1560 for (case_list.items) |case, idx| {
1561 for (case.values) |case_value| {
1562 if (case_value.integer == lowest) break :blk @intCast(u32, idx);
1563 }
1564 }
1565 break :blk if (has_else_body) case_i else unreachable;
1566 };
1567 self.mir_extra.appendAssumeCapacity(idx);
1568 } else if (has_else_body) {
1569 self.mir_extra.appendAssumeCapacity(case_i); // default branch
1570 }
1571 try self.endBlock();
1572 }
1573
1574 const signedness: std.builtin.Signedness = blk: {
1575 // by default we tell the operand type is unsigned (i.e. bools and enum values)
1576 if (target_ty.zigTypeTag() != .Int) break :blk .unsigned;
1577
1578 // incase of an actual integer, we emit the correct signedness
1579 break :blk target_ty.intInfo(self.target).signedness;
1580 };
1581
1582 for (case_list.items) |case| {
1583 // when sparse, we use if/else-chain, so emit conditional checks
1584 if (is_sparse) {
1585 // for single value prong we can emit a simple if
1586 if (case.values.len == 1) {
1587 try self.emitWValue(target);
1588 try self.emitConstant(case.values[0].value, target_ty);
1589 const opcode = buildOpcode(.{
1590 .valtype1 = try self.typeToValtype(target_ty),
1591 .op = .ne, // not equal, because we want to jump out of this block if it does not match the condition.
1592 .signedness = signedness,
1593 });
1594 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
1595 try self.addLabel(.br_if, 0);
1596 } else {
1597 // in multi-value prongs we must check if any prongs match the target value.
1598 try self.startBlock(.block, blocktype, null);
1599 for (case.values) |value| {
1600 try self.emitWValue(target);
1601 try self.emitConstant(value.value, target_ty);
1602 const opcode = buildOpcode(.{
1603 .valtype1 = try self.typeToValtype(target_ty),
1604 .op = .eq,
1605 .signedness = signedness,
1606 });
1607 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
1608 try self.addLabel(.br_if, 0);
1609 }
1610 // value did not match any of the prong values
1611 try self.addLabel(.br, 1);
1612 try self.endBlock();
1613 }
1614 }
1615 try self.genBody(case.body);
1616 try self.endBlock();
1617 }
1618
1619 if (has_else_body) {
1620 try self.genBody(else_body);
1621 try self.endBlock();
1622 }
1623 return .none;
1624}
1625
1626fn airIsErr(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {
1627 const un_op = self.air.instructions.items(.data)[inst].un_op;
1628 const operand = self.resolveInst(un_op);
1629 const offset = self.mir_instructions.len;
1630
1631 // load the error value which is positioned at multi_value's index
1632 try self.emitWValue(.{ .local = operand.multi_value.index });
1633 // Compare the error value with '0'
1634 try self.addImm32(0);
1635 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
1636
1637 return WValue{ .mir_offset = offset };
1638}
1639
1640fn airUnwrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1641 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1642 const operand = self.resolveInst(ty_op.operand);
1643 // The index of multi_value contains the error code. To get the initial index of the payload we get
1644 // the following index. Next, convert it to a `WValue.local`
1645 //
1646 // TODO: Check if payload is a type that requires a multi_value as well and emit that instead. i.e. a struct.
1647 return WValue{ .local = operand.multi_value.index + 1 };
1648}
1649
1650fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1651 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1652 return self.resolveInst(ty_op.operand);
1653}
1654
1655fn airIntcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1656 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1657 const ty = self.air.getRefType(ty_op.ty);
1658 const operand = self.resolveInst(ty_op.operand);
1659 const ref_ty = self.air.typeOf(ty_op.operand);
1660 const ref_info = ref_ty.intInfo(self.target);
1661 const op_bits = ref_info.bits;
1662 const wanted_bits = ty.intInfo(self.target).bits;
1663
1664 try self.emitWValue(operand);
1665 if (op_bits > 32 and wanted_bits <= 32) {
1666 try self.addTag(.i32_wrap_i64);
1667 } else if (op_bits <= 32 and wanted_bits > 32) {
1668 try self.addTag(switch (ref_info.signedness) {
1669 .signed => .i64_extend_i32_s,
1670 .unsigned => .i64_extend_i32_u,
1671 });
1672 }
1673
1674 // other cases are no-op
1675 return .none;
1676}
1677
1678fn airIsNull(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {
1679 const un_op = self.air.instructions.items(.data)[inst].un_op;
1680 const operand = self.resolveInst(un_op);
1681
1682 // load the null value which is positioned at multi_value's index
1683 try self.emitWValue(.{ .local = operand.multi_value.index });
1684 try self.addImm32(0);
1685 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
1686
1687 // we save the result in a new local
1688 const local = try self.allocLocal(Type.initTag(.i32));
1689 try self.addLabel(.local_set, local.local);
1690
1691 return local;
1692}
1693
1694fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1695 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1696 const operand = self.resolveInst(ty_op.operand);
1697 return WValue{ .local = operand.multi_value.index + 1 };
1698}
1699
1700fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1701 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1702 const operand = self.resolveInst(ty_op.operand);
1703 _ = operand;
1704 return self.fail("TODO - wasm codegen for optional_payload_ptr_set", .{});
1705}
1706
1707fn airWrapOptional(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1708 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1709 return self.resolveInst(ty_op.operand);
1710}
src/arch/wasm/Emit.zig created+251
......@@ -0,0 +1,251 @@
1//! Contains all logic to lower wasm MIR into its binary
2//! or textual representation.
3
4const Emit = @This();
5const std = @import("std");
6const Mir = @import("Mir.zig");
7const link = @import("../../link.zig");
8const Module = @import("../../Module.zig");
9const leb128 = std.leb;
10
11/// Contains our list of instructions
12mir: Mir,
13/// Reference to the file handler
14bin_file: *link.File,
15/// Possible error message. When set, the value is allocated and
16/// must be freed manually.
17error_msg: ?*Module.ErrorMsg = null,
18/// The binary representation that will be emit by this module.
19code: *std.ArrayList(u8),
20/// List of allocated locals.
21locals: []const u8,
22/// The declaration that code is being generated for.
23decl: *Module.Decl,
24
25const InnerError = error{
26 OutOfMemory,
27 EmitFail,
28};
29
30pub fn emitMir(emit: *Emit) InnerError!void {
31 const mir_tags = emit.mir.instructions.items(.tag);
32 // Reserve space to write the size after generating the code.
33 try emit.code.resize(5);
34 // write the locals in the prologue of the function body
35 // before we emit the function body when lowering MIR
36 try emit.emitLocals();
37
38 for (mir_tags) |tag, index| {
39 const inst = @intCast(u32, index);
40 switch (tag) {
41 // block instructions
42 .block => try emit.emitBlock(tag, inst),
43 .loop => try emit.emitBlock(tag, inst),
44
45 // branch instructions
46 .br_if => try emit.emitLabel(tag, inst),
47 .br_table => try emit.emitBrTable(inst),
48 .br => try emit.emitLabel(tag, inst),
49
50 // relocatables
51 .call => try emit.emitCall(inst),
52 .global_get => try emit.emitGlobal(tag, inst),
53 .global_set => try emit.emitGlobal(tag, inst),
54
55 // immediates
56 .f32_const => try emit.emitFloat32(inst),
57 .f64_const => try emit.emitFloat64(inst),
58 .i32_const => try emit.emitImm32(inst),
59 .i64_const => try emit.emitImm64(inst),
60
61 // memory instructions
62 .i32_load => try emit.emitMemArg(tag, inst),
63 .i32_store => try emit.emitMemArg(tag, inst),
64
65 .local_get => try emit.emitLabel(tag, inst),
66 .local_set => try emit.emitLabel(tag, inst),
67 .local_tee => try emit.emitLabel(tag, inst),
68 .memory_grow => try emit.emitLabel(tag, inst),
69
70 // no-ops
71 .end => try emit.emitTag(tag),
72 .memory_size => try emit.emitTag(tag),
73 .@"return" => try emit.emitTag(tag),
74 .@"unreachable" => try emit.emitTag(tag),
75
76 // arithmetic
77 .i32_eqz => try emit.emitTag(tag),
78 .i32_eq => try emit.emitTag(tag),
79 .i32_ne => try emit.emitTag(tag),
80 .i32_lt_s => try emit.emitTag(tag),
81 .i32_lt_u => try emit.emitTag(tag),
82 .i32_gt_s => try emit.emitTag(tag),
83 .i32_gt_u => try emit.emitTag(tag),
84 .i32_le_s => try emit.emitTag(tag),
85 .i32_le_u => try emit.emitTag(tag),
86 .i32_ge_s => try emit.emitTag(tag),
87 .i32_ge_u => try emit.emitTag(tag),
88 .i64_eqz => try emit.emitTag(tag),
89 .i64_eq => try emit.emitTag(tag),
90 .i64_ne => try emit.emitTag(tag),
91 .i64_lt_s => try emit.emitTag(tag),
92 .i64_lt_u => try emit.emitTag(tag),
93 .i64_gt_s => try emit.emitTag(tag),
94 .i64_gt_u => try emit.emitTag(tag),
95 .i64_le_s => try emit.emitTag(tag),
96 .i64_le_u => try emit.emitTag(tag),
97 .i64_ge_s => try emit.emitTag(tag),
98 .i64_ge_u => try emit.emitTag(tag),
99 .f32_eq => try emit.emitTag(tag),
100 .f32_ne => try emit.emitTag(tag),
101 .f32_lt => try emit.emitTag(tag),
102 .f32_gt => try emit.emitTag(tag),
103 .f32_le => try emit.emitTag(tag),
104 .f32_ge => try emit.emitTag(tag),
105 .f64_eq => try emit.emitTag(tag),
106 .f64_ne => try emit.emitTag(tag),
107 .f64_lt => try emit.emitTag(tag),
108 .f64_gt => try emit.emitTag(tag),
109 .f64_le => try emit.emitTag(tag),
110 .f64_ge => try emit.emitTag(tag),
111 .i32_add => try emit.emitTag(tag),
112 .i32_sub => try emit.emitTag(tag),
113 .i32_mul => try emit.emitTag(tag),
114 .i32_div_s => try emit.emitTag(tag),
115 .i32_div_u => try emit.emitTag(tag),
116 .i32_and => try emit.emitTag(tag),
117 .i32_or => try emit.emitTag(tag),
118 .i32_xor => try emit.emitTag(tag),
119 .i32_shl => try emit.emitTag(tag),
120 .i32_shr_s => try emit.emitTag(tag),
121 .i32_shr_u => try emit.emitTag(tag),
122 .i64_add => try emit.emitTag(tag),
123 .i64_sub => try emit.emitTag(tag),
124 .i64_mul => try emit.emitTag(tag),
125 .i64_div_s => try emit.emitTag(tag),
126 .i64_div_u => try emit.emitTag(tag),
127 .i64_and => try emit.emitTag(tag),
128 .i32_wrap_i64 => try emit.emitTag(tag),
129 .i64_extend_i32_s => try emit.emitTag(tag),
130 .i64_extend_i32_u => try emit.emitTag(tag),
131 .i32_extend8_s => try emit.emitTag(tag),
132 .i32_extend16_s => try emit.emitTag(tag),
133 .i64_extend8_s => try emit.emitTag(tag),
134 .i64_extend16_s => try emit.emitTag(tag),
135 .i64_extend32_s => try emit.emitTag(tag),
136 }
137 }
138
139 // Fill in the size of the generated code to the reserved space at the
140 // beginning of the buffer.
141 const size = emit.code.items.len - 5;
142 leb128.writeUnsignedFixed(5, emit.code.items[0..5], @intCast(u32, size));
143}
144
145fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError {
146 @setCold(true);
147 std.debug.assert(emit.error_msg == null);
148 // TODO: Determine the source location.
149 emit.error_msg = try Module.ErrorMsg.create(emit.bin_file.allocator, emit.decl.srcLoc(), format, args);
150 return error.EmitFail;
151}
152
153fn emitLocals(emit: *Emit) !void {
154 const writer = emit.code.writer();
155 try leb128.writeULEB128(writer, @intCast(u32, emit.locals.len));
156 // emit the actual locals amount
157 for (emit.locals) |local| {
158 try leb128.writeULEB128(writer, @as(u32, 1));
159 try writer.writeByte(local);
160 }
161}
162
163fn emitTag(emit: *Emit, tag: Mir.Inst.Tag) !void {
164 try emit.code.append(@enumToInt(tag));
165}
166
167fn emitBlock(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
168 const block_type = emit.mir.instructions.items(.data)[inst].block_type;
169 try emit.code.append(@enumToInt(tag));
170 try emit.code.append(block_type);
171}
172
173fn emitBrTable(emit: *Emit, inst: Mir.Inst.Index) !void {
174 const extra_index = emit.mir.instructions.items(.data)[inst].payload;
175 const extra = emit.mir.extraData(Mir.JumpTable, extra_index);
176 const labels = emit.mir.extra[extra.end..][0..extra.data.length];
177 const writer = emit.code.writer();
178
179 try emit.code.append(std.wasm.opcode(.br_table));
180 try leb128.writeULEB128(writer, extra.data.length - 1); // Default label is not part of length/depth
181 for (labels) |label| {
182 try leb128.writeULEB128(writer, label);
183 }
184}
185
186fn emitLabel(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
187 const label = emit.mir.instructions.items(.data)[inst].label;
188 try emit.code.append(@enumToInt(tag));
189 try leb128.writeULEB128(emit.code.writer(), label);
190}
191
192fn emitGlobal(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
193 const label = emit.mir.instructions.items(.data)[inst].label;
194 try emit.code.append(@enumToInt(tag));
195 var buf: [5]u8 = undefined;
196 leb128.writeUnsignedFixed(5, &buf, label);
197 try emit.code.appendSlice(&buf);
198
199 // TODO: Append label to the relocation list of this function
200}
201
202fn emitImm32(emit: *Emit, inst: Mir.Inst.Index) !void {
203 const value: i32 = emit.mir.instructions.items(.data)[inst].imm32;
204 try emit.code.append(std.wasm.opcode(.i32_const));
205 try leb128.writeILEB128(emit.code.writer(), value);
206}
207
208fn emitImm64(emit: *Emit, inst: Mir.Inst.Index) !void {
209 const extra_index = emit.mir.instructions.items(.data)[inst].payload;
210 const value = emit.mir.extraData(Mir.Imm64, extra_index);
211 try emit.code.append(std.wasm.opcode(.i64_const));
212 try leb128.writeULEB128(emit.code.writer(), value.data.toU64());
213}
214
215fn emitFloat32(emit: *Emit, inst: Mir.Inst.Index) !void {
216 const value: f32 = emit.mir.instructions.items(.data)[inst].float32;
217 try emit.code.append(std.wasm.opcode(.f32_const));
218 try emit.code.writer().writeIntLittle(u32, @bitCast(u32, value));
219}
220
221fn emitFloat64(emit: *Emit, inst: Mir.Inst.Index) !void {
222 const extra_index = emit.mir.instructions.items(.data)[inst].payload;
223 const value = emit.mir.extraData(Mir.Float64, extra_index);
224 try emit.code.append(std.wasm.opcode(.f64_const));
225 try emit.code.writer().writeIntLittle(u64, value.data.toU64());
226}
227
228fn emitMemArg(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
229 const extra_index = emit.mir.instructions.items(.data)[inst].payload;
230 const mem_arg = emit.mir.extraData(Mir.MemArg, extra_index).data;
231 try emit.code.append(@enumToInt(tag));
232 try leb128.writeULEB128(emit.code.writer(), mem_arg.alignment);
233 try leb128.writeULEB128(emit.code.writer(), mem_arg.offset);
234}
235
236fn emitCall(emit: *Emit, inst: Mir.Inst.Index) !void {
237 const label = emit.mir.instructions.items(.data)[inst].label;
238 try emit.code.append(std.wasm.opcode(.call));
239 const offset = @intCast(u32, emit.code.items.len);
240 var buf: [5]u8 = undefined;
241 leb128.writeUnsignedFixed(5, &buf, label);
242 try emit.code.appendSlice(&buf);
243
244 // The function index immediate argument will be filled in using this data
245 // in link.Wasm.flush().
246 // TODO: Replace this with proper relocations saved in the Atom.
247 try emit.decl.fn_link.wasm.idx_refs.append(emit.bin_file.allocator, .{
248 .offset = offset,
249 .decl = label,
250 });
251}
src/arch/wasm/Mir.zig created+367
......@@ -0,0 +1,367 @@
1//! Machine Intermediate Representation.
2//! This representation is produced by wasm Codegen.
3//! Each of these instructions have a 1:1 mapping to a wasm opcode,
4//! but may contain metadata for a specific opcode such as an immediate.
5//! MIR can be lowered to both textual code (wat) and binary format (wasm).
6//! The main benefits of MIR is optimization passes, pre-allocated locals,
7//! and known jump labels for blocks.
8
9const Mir = @This();
10
11const std = @import("std");
12
13/// A struct of array that represents each individual wasm
14instructions: std.MultiArrayList(Inst).Slice,
15/// A slice of indexes where the meaning of the data is determined by the
16/// `Inst.Tag` value.
17extra: []const u32,
18
19pub const Inst = struct {
20 /// The opcode that represents this instruction
21 tag: Tag,
22 /// Data is determined by the set `tag`.
23 /// For example, `data` will be an i32 for when `tag` is 'i32_const'.
24 data: Data,
25
26 /// The position of a given MIR isntruction with the instruction list.
27 pub const Index = u32;
28
29 /// Contains all possible wasm opcodes the Zig compiler may emit
30 /// Rather than re-using std.wasm.Opcode, we only declare the opcodes
31 /// we need, and also use this possibility to document how to access
32 /// their payload.
33 ///
34 /// Note: Uses its actual opcode value representation to easily convert
35 /// to and from its binary representation.
36 pub const Tag = enum(u8) {
37 /// Uses `nop`
38 @"unreachable" = 0x00,
39 /// Creates a new block that can be jump from.
40 ///
41 /// Type of the block is given in data `block_type`
42 block = 0x02,
43 /// Creates a new loop.
44 ///
45 /// Type of the loop is given in data `block_type`
46 loop = 0x03,
47 /// Represents the end of a function body or an initialization expression
48 ///
49 /// Payload is `nop`
50 end = 0x0B,
51 /// Breaks from the current block to a label
52 ///
53 /// Data is `label` where index represents the label to jump to
54 br = 0x0C,
55 /// Breaks from the current block if the stack value is non-zero
56 ///
57 /// Data is `label` where index represents the label to jump to
58 br_if = 0x0D,
59 /// Jump table that takes the stack value as an index where each value
60 /// represents the label to jump to.
61 ///
62 /// Data is extra of which the Payload's type is `JumpTable`
63 br_table = 0x0E,
64 /// Returns from the function
65 ///
66 /// Uses `nop`
67 @"return" = 0x0F,
68 /// Calls a function by its index
69 ///
70 /// Uses `label`
71 call = 0x10,
72 /// Loads a local at given index onto the stack.
73 ///
74 /// Uses `label`
75 local_get = 0x20,
76 /// Pops a value from the stack into the local at given index.
77 /// Stack value must be of the same type as the local.
78 ///
79 /// Uses `label`
80 local_set = 0x21,
81 /// Sets a local at given index using the value at the top of the stack without popping the value.
82 /// Stack value must have the same type as the local.
83 ///
84 /// Uses `label`
85 local_tee = 0x22,
86 /// Loads a (mutable) global at given index onto the stack
87 ///
88 /// Uses `label`
89 global_get = 0x23,
90 /// Pops a value from the stack and sets the global at given index.
91 /// Note: Both types must be equal and global must be marked mutable.
92 ///
93 /// Uses `label`.
94 global_set = 0x24,
95 /// Loads a 32-bit integer from memory (data section) onto the stack
96 /// Pops the value from the stack which represents the offset into memory.
97 ///
98 /// Uses `payload` of type `MemArg`.
99 i32_load = 0x28,
100 /// Pops 2 values from the stack, where the first value represents the value to write into memory
101 /// and the second value represents the offset into memory where the value must be written to.
102 ///
103 /// Uses `payload` of type `MemArg`.
104 i32_store = 0x36,
105 /// Returns the memory size in amount of pages.
106 ///
107 /// Uses `nop`
108 memory_size = 0x3F,
109 /// Increases the memory at by given number of pages.
110 ///
111 /// Uses `label`
112 memory_grow = 0x40,
113 /// Loads a 32-bit signed immediate value onto the stack
114 ///
115 /// Uses `imm32`
116 i32_const = 0x41,
117 /// Loads a i64-bit signed immediate value onto the stack
118 ///
119 /// uses `payload` of type `Imm64`
120 i64_const = 0x42,
121 /// Loads a 32-bit float value onto the stack.
122 ///
123 /// Uses `float32`
124 f32_const = 0x43,
125 /// Loads a 64-bit float value onto the stack.
126 ///
127 /// Uses `payload` of type `Float64`
128 f64_const = 0x44,
129 /// Uses `tag`
130 i32_eqz = 0x45,
131 /// Uses `tag`
132 i32_eq = 0x46,
133 /// Uses `tag`
134 i32_ne = 0x47,
135 /// Uses `tag`
136 i32_lt_s = 0x48,
137 /// Uses `tag`
138 i32_lt_u = 0x49,
139 /// Uses `tag`
140 i32_gt_s = 0x4A,
141 /// Uses `tag`
142 i32_gt_u = 0x4B,
143 /// Uses `tag`
144 i32_le_s = 0x4C,
145 /// Uses `tag`
146 i32_le_u = 0x4D,
147 /// Uses `tag`
148 i32_ge_s = 0x4E,
149 /// Uses `tag`
150 i32_ge_u = 0x4F,
151 /// Uses `tag`
152 i64_eqz = 0x50,
153 /// Uses `tag`
154 i64_eq = 0x51,
155 /// Uses `tag`
156 i64_ne = 0x52,
157 /// Uses `tag`
158 i64_lt_s = 0x53,
159 /// Uses `tag`
160 i64_lt_u = 0x54,
161 /// Uses `tag`
162 i64_gt_s = 0x55,
163 /// Uses `tag`
164 i64_gt_u = 0x56,
165 /// Uses `tag`
166 i64_le_s = 0x57,
167 /// Uses `tag`
168 i64_le_u = 0x58,
169 /// Uses `tag`
170 i64_ge_s = 0x59,
171 /// Uses `tag`
172 i64_ge_u = 0x5A,
173 /// Uses `tag`
174 f32_eq = 0x5B,
175 /// Uses `tag`
176 f32_ne = 0x5C,
177 /// Uses `tag`
178 f32_lt = 0x5D,
179 /// Uses `tag`
180 f32_gt = 0x5E,
181 /// Uses `tag`
182 f32_le = 0x5F,
183 /// Uses `tag`
184 f32_ge = 0x60,
185 /// Uses `tag`
186 f64_eq = 0x61,
187 /// Uses `tag`
188 f64_ne = 0x62,
189 /// Uses `tag`
190 f64_lt = 0x63,
191 /// Uses `tag`
192 f64_gt = 0x64,
193 /// Uses `tag`
194 f64_le = 0x65,
195 /// Uses `tag`
196 f64_ge = 0x66,
197 /// Uses `tag`
198 i32_add = 0x6A,
199 /// Uses `tag`
200 i32_sub = 0x6B,
201 /// Uses `tag`
202 i32_mul = 0x6C,
203 /// Uses `tag`
204 i32_div_s = 0x6D,
205 /// Uses `tag`
206 i32_div_u = 0x6E,
207 /// Uses `tag`
208 i32_and = 0x71,
209 /// Uses `tag`
210 i32_or = 0x72,
211 /// Uses `tag`
212 i32_xor = 0x73,
213 /// Uses `tag`
214 i32_shl = 0x74,
215 /// Uses `tag`
216 i32_shr_s = 0x75,
217 /// Uses `tag`
218 i32_shr_u = 0x76,
219 /// Uses `tag`
220 i64_add = 0x7C,
221 /// Uses `tag`
222 i64_sub = 0x7D,
223 /// Uses `tag`
224 i64_mul = 0x7E,
225 /// Uses `tag`
226 i64_div_s = 0x7F,
227 /// Uses `tag`
228 i64_div_u = 0x80,
229 /// Uses `tag`
230 i64_and = 0x83,
231 /// Uses `tag`
232 i32_wrap_i64 = 0xA7,
233 /// Uses `tag`
234 i64_extend_i32_s = 0xAC,
235 /// Uses `tag`
236 i64_extend_i32_u = 0xAD,
237 /// Uses `tag`
238 i32_extend8_s = 0xC0,
239 /// Uses `tag`
240 i32_extend16_s = 0xC1,
241 /// Uses `tag`
242 i64_extend8_s = 0xC2,
243 /// Uses `tag`
244 i64_extend16_s = 0xC3,
245 /// Uses `tag`
246 i64_extend32_s = 0xC4,
247
248 /// From a given wasm opcode, returns a MIR tag.
249 pub fn fromOpcode(opcode: std.wasm.Opcode) Tag {
250 return @intToEnum(Tag, @enumToInt(opcode));
251 }
252
253 /// Returns a wasm opcode from a given MIR tag.
254 pub fn toOpcode(self: Tag) std.wasm.Opcode {
255 return @intToEnum(std.wasm.Opcode, @enumToInt(self));
256 }
257 };
258
259 /// All instructions contain a 4-byte payload, which is contained within
260 /// this union. `Tag` determines which union tag is active, as well as
261 /// how to interpret the data within.
262 pub const Data = union {
263 /// Uses no additional data
264 tag: void,
265 /// Contains the result type of a block
266 ///
267 /// Used by `block` and `loop`
268 block_type: u8,
269 /// Contains an u32 index into a wasm section entry, such as a local.
270 /// Note: This is not an index to another instruction.
271 ///
272 /// Used by e.g. `local_get`, `local_set`, etc.
273 label: u32,
274 /// A 32-bit immediate value.
275 ///
276 /// Used by `i32_const`
277 imm32: i32,
278 /// A 32-bit float value
279 ///
280 /// Used by `f32_float`
281 float32: f32,
282 /// Index into `extra`. Meaning of what can be found there is context-dependent.
283 ///
284 /// Used by e.g. `br_table`
285 payload: u32,
286 };
287};
288
289pub fn deinit(self: *Mir, gpa: *std.mem.Allocator) void {
290 self.instructions.deinit(gpa);
291 gpa.free(self.extra);
292 self.* = undefined;
293}
294
295pub fn extraData(self: Mir, comptime T: type, index: usize) struct { data: T, end: usize } {
296 const fields = std.meta.fields(T);
297 var i: usize = index;
298 var result: T = undefined;
299 inline for (fields) |field| {
300 @field(result, field.name) = switch (field.field_type) {
301 u32 => self.extra[i],
302 else => |field_type| @compileError("Unsupported field type " ++ @typeName(field_type)),
303 };
304 i += 1;
305 }
306
307 return .{ .data = result, .end = i };
308}
309
310pub const JumpTable = struct {
311 /// Length of the jump table and the amount of entries it contains (includes default)
312 length: u32,
313};
314
315/// Stores an unsigned 64bit integer
316/// into a 32bit most significant bits field
317/// and a 32bit least significant bits field.
318///
319/// This uses an unsigned integer rather than a signed integer
320/// as we can easily store those into `extra`
321pub const Imm64 = struct {
322 msb: u32,
323 lsb: u32,
324
325 pub fn fromU64(imm: u64) Imm64 {
326 return .{
327 .msb = @truncate(u32, imm >> 32),
328 .lsb = @truncate(u32, imm),
329 };
330 }
331
332 pub fn toU64(self: Imm64) u64 {
333 var result: u64 = 0;
334 result |= @as(u64, self.msb) << 32;
335 result |= @as(u64, self.lsb);
336 return result;
337 }
338};
339
340pub const Float64 = struct {
341 msb: u32,
342 lsb: u32,
343
344 pub fn fromFloat64(float: f64) Float64 {
345 const tmp = @bitCast(u64, float);
346 return .{
347 .msb = @truncate(u32, tmp >> 32),
348 .lsb = @truncate(u32, tmp),
349 };
350 }
351
352 pub fn toF64(self: Float64) f64 {
353 @bitCast(f64, self.toU64());
354 }
355
356 pub fn toU64(self: Float64) u64 {
357 var result: u64 = 0;
358 result |= @as(u64, self.msb) << 32;
359 result |= @as(u64, self.lsb);
360 return result;
361 }
362};
363
364pub const MemArg = struct {
365 offset: u32,
366 alignment: u32,
367};
src/codegen/wasm.zig deleted-1717
......@@ -1,1717 +0,0 @@
1const std = @import("std");
2const Allocator = std.mem.Allocator;
3const ArrayList = std.ArrayList;
4const assert = std.debug.assert;
5const testing = std.testing;
6const leb = std.leb;
7const mem = std.mem;
8const wasm = std.wasm;
9
10const Module = @import("../Module.zig");
11const Decl = Module.Decl;
12const Type = @import("../type.zig").Type;
13const Value = @import("../value.zig").Value;
14const Compilation = @import("../Compilation.zig");
15const LazySrcLoc = Module.LazySrcLoc;
16const link = @import("../link.zig");
17const TypedValue = @import("../TypedValue.zig");
18const Air = @import("../Air.zig");
19const Liveness = @import("../Liveness.zig");
20
21/// Wasm Value, created when generating an instruction
22const WValue = union(enum) {
23 /// May be referenced but is unused
24 none: void,
25 /// Index of the local variable
26 local: u32,
27 /// Holds a memoized typed value
28 constant: TypedValue,
29 /// Offset position in the list of bytecode instructions
30 code_offset: usize,
31 /// Used for variables that create multiple locals on the stack when allocated
32 /// such as structs and optionals.
33 multi_value: struct {
34 /// The index of the first local variable
35 index: u32,
36 /// The count of local variables this `WValue` consists of.
37 /// i.e. an ErrorUnion has a 'count' of 2.
38 count: u32,
39 },
40};
41
42/// Wasm ops, but without input/output/signedness information
43/// Used for `buildOpcode`
44const Op = enum {
45 @"unreachable",
46 nop,
47 block,
48 loop,
49 @"if",
50 @"else",
51 end,
52 br,
53 br_if,
54 br_table,
55 @"return",
56 call,
57 call_indirect,
58 drop,
59 select,
60 local_get,
61 local_set,
62 local_tee,
63 global_get,
64 global_set,
65 load,
66 store,
67 memory_size,
68 memory_grow,
69 @"const",
70 eqz,
71 eq,
72 ne,
73 lt,
74 gt,
75 le,
76 ge,
77 clz,
78 ctz,
79 popcnt,
80 add,
81 sub,
82 mul,
83 div,
84 rem,
85 @"and",
86 @"or",
87 xor,
88 shl,
89 shr,
90 rotl,
91 rotr,
92 abs,
93 neg,
94 ceil,
95 floor,
96 trunc,
97 nearest,
98 sqrt,
99 min,
100 max,
101 copysign,
102 wrap,
103 convert,
104 demote,
105 promote,
106 reinterpret,
107 extend,
108};
109
110/// Contains the settings needed to create an `Opcode` using `buildOpcode`.
111///
112/// The fields correspond to the opcode name. Here is an example
113/// i32_trunc_f32_s
114/// ^ ^ ^ ^
115/// | | | |
116/// valtype1 | | |
117/// = .i32 | | |
118/// | | |
119/// op | |
120/// = .trunc | |
121/// | |
122/// valtype2 |
123/// = .f32 |
124/// |
125/// width |
126/// = null |
127/// |
128/// signed
129/// = true
130///
131/// There can be missing fields, here are some more examples:
132/// i64_load8_u
133/// --> .{ .valtype1 = .i64, .op = .load, .width = 8, signed = false }
134/// i32_mul
135/// --> .{ .valtype1 = .i32, .op = .trunc }
136/// nop
137/// --> .{ .op = .nop }
138const OpcodeBuildArguments = struct {
139 /// First valtype in the opcode (usually represents the type of the output)
140 valtype1: ?wasm.Valtype = null,
141 /// The operation (e.g. call, unreachable, div, min, sqrt, etc.)
142 op: Op,
143 /// Width of the operation (e.g. 8 for i32_load8_s, 16 for i64_extend16_i32_s)
144 width: ?u8 = null,
145 /// Second valtype in the opcode name (usually represents the type of the input)
146 valtype2: ?wasm.Valtype = null,
147 /// Signedness of the op
148 signedness: ?std.builtin.Signedness = null,
149};
150
151/// Helper function that builds an Opcode given the arguments needed
152fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
153 switch (args.op) {
154 .@"unreachable" => return .@"unreachable",
155 .nop => return .nop,
156 .block => return .block,
157 .loop => return .loop,
158 .@"if" => return .@"if",
159 .@"else" => return .@"else",
160 .end => return .end,
161 .br => return .br,
162 .br_if => return .br_if,
163 .br_table => return .br_table,
164 .@"return" => return .@"return",
165 .call => return .call,
166 .call_indirect => return .call_indirect,
167 .drop => return .drop,
168 .select => return .select,
169 .local_get => return .local_get,
170 .local_set => return .local_set,
171 .local_tee => return .local_tee,
172 .global_get => return .global_get,
173 .global_set => return .global_set,
174
175 .load => if (args.width) |width| switch (width) {
176 8 => switch (args.valtype1.?) {
177 .i32 => if (args.signedness.? == .signed) return .i32_load8_s else return .i32_load8_u,
178 .i64 => if (args.signedness.? == .signed) return .i64_load8_s else return .i64_load8_u,
179 .f32, .f64 => unreachable,
180 },
181 16 => switch (args.valtype1.?) {
182 .i32 => if (args.signedness.? == .signed) return .i32_load16_s else return .i32_load16_u,
183 .i64 => if (args.signedness.? == .signed) return .i64_load16_s else return .i64_load16_u,
184 .f32, .f64 => unreachable,
185 },
186 32 => switch (args.valtype1.?) {
187 .i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u,
188 .i32, .f32, .f64 => unreachable,
189 },
190 else => unreachable,
191 } else switch (args.valtype1.?) {
192 .i32 => return .i32_load,
193 .i64 => return .i64_load,
194 .f32 => return .f32_load,
195 .f64 => return .f64_load,
196 },
197 .store => if (args.width) |width| {
198 switch (width) {
199 8 => switch (args.valtype1.?) {
200 .i32 => return .i32_store8,
201 .i64 => return .i64_store8,
202 .f32, .f64 => unreachable,
203 },
204 16 => switch (args.valtype1.?) {
205 .i32 => return .i32_store16,
206 .i64 => return .i64_store16,
207 .f32, .f64 => unreachable,
208 },
209 32 => switch (args.valtype1.?) {
210 .i64 => return .i64_store32,
211 .i32, .f32, .f64 => unreachable,
212 },
213 else => unreachable,
214 }
215 } else {
216 switch (args.valtype1.?) {
217 .i32 => return .i32_store,
218 .i64 => return .i64_store,
219 .f32 => return .f32_store,
220 .f64 => return .f64_store,
221 }
222 },
223
224 .memory_size => return .memory_size,
225 .memory_grow => return .memory_grow,
226
227 .@"const" => switch (args.valtype1.?) {
228 .i32 => return .i32_const,
229 .i64 => return .i64_const,
230 .f32 => return .f32_const,
231 .f64 => return .f64_const,
232 },
233
234 .eqz => switch (args.valtype1.?) {
235 .i32 => return .i32_eqz,
236 .i64 => return .i64_eqz,
237 .f32, .f64 => unreachable,
238 },
239 .eq => switch (args.valtype1.?) {
240 .i32 => return .i32_eq,
241 .i64 => return .i64_eq,
242 .f32 => return .f32_eq,
243 .f64 => return .f64_eq,
244 },
245 .ne => switch (args.valtype1.?) {
246 .i32 => return .i32_ne,
247 .i64 => return .i64_ne,
248 .f32 => return .f32_ne,
249 .f64 => return .f64_ne,
250 },
251
252 .lt => switch (args.valtype1.?) {
253 .i32 => if (args.signedness.? == .signed) return .i32_lt_s else return .i32_lt_u,
254 .i64 => if (args.signedness.? == .signed) return .i64_lt_s else return .i64_lt_u,
255 .f32 => return .f32_lt,
256 .f64 => return .f64_lt,
257 },
258 .gt => switch (args.valtype1.?) {
259 .i32 => if (args.signedness.? == .signed) return .i32_gt_s else return .i32_gt_u,
260 .i64 => if (args.signedness.? == .signed) return .i64_gt_s else return .i64_gt_u,
261 .f32 => return .f32_gt,
262 .f64 => return .f64_gt,
263 },
264 .le => switch (args.valtype1.?) {
265 .i32 => if (args.signedness.? == .signed) return .i32_le_s else return .i32_le_u,
266 .i64 => if (args.signedness.? == .signed) return .i64_le_s else return .i64_le_u,
267 .f32 => return .f32_le,
268 .f64 => return .f64_le,
269 },
270 .ge => switch (args.valtype1.?) {
271 .i32 => if (args.signedness.? == .signed) return .i32_ge_s else return .i32_ge_u,
272 .i64 => if (args.signedness.? == .signed) return .i64_ge_s else return .i64_ge_u,
273 .f32 => return .f32_ge,
274 .f64 => return .f64_ge,
275 },
276
277 .clz => switch (args.valtype1.?) {
278 .i32 => return .i32_clz,
279 .i64 => return .i64_clz,
280 .f32, .f64 => unreachable,
281 },
282 .ctz => switch (args.valtype1.?) {
283 .i32 => return .i32_ctz,
284 .i64 => return .i64_ctz,
285 .f32, .f64 => unreachable,
286 },
287 .popcnt => switch (args.valtype1.?) {
288 .i32 => return .i32_popcnt,
289 .i64 => return .i64_popcnt,
290 .f32, .f64 => unreachable,
291 },
292
293 .add => switch (args.valtype1.?) {
294 .i32 => return .i32_add,
295 .i64 => return .i64_add,
296 .f32 => return .f32_add,
297 .f64 => return .f64_add,
298 },
299 .sub => switch (args.valtype1.?) {
300 .i32 => return .i32_sub,
301 .i64 => return .i64_sub,
302 .f32 => return .f32_sub,
303 .f64 => return .f64_sub,
304 },
305 .mul => switch (args.valtype1.?) {
306 .i32 => return .i32_mul,
307 .i64 => return .i64_mul,
308 .f32 => return .f32_mul,
309 .f64 => return .f64_mul,
310 },
311
312 .div => switch (args.valtype1.?) {
313 .i32 => if (args.signedness.? == .signed) return .i32_div_s else return .i32_div_u,
314 .i64 => if (args.signedness.? == .signed) return .i64_div_s else return .i64_div_u,
315 .f32 => return .f32_div,
316 .f64 => return .f64_div,
317 },
318 .rem => switch (args.valtype1.?) {
319 .i32 => if (args.signedness.? == .signed) return .i32_rem_s else return .i32_rem_u,
320 .i64 => if (args.signedness.? == .signed) return .i64_rem_s else return .i64_rem_u,
321 .f32, .f64 => unreachable,
322 },
323
324 .@"and" => switch (args.valtype1.?) {
325 .i32 => return .i32_and,
326 .i64 => return .i64_and,
327 .f32, .f64 => unreachable,
328 },
329 .@"or" => switch (args.valtype1.?) {
330 .i32 => return .i32_or,
331 .i64 => return .i64_or,
332 .f32, .f64 => unreachable,
333 },
334 .xor => switch (args.valtype1.?) {
335 .i32 => return .i32_xor,
336 .i64 => return .i64_xor,
337 .f32, .f64 => unreachable,
338 },
339
340 .shl => switch (args.valtype1.?) {
341 .i32 => return .i32_shl,
342 .i64 => return .i64_shl,
343 .f32, .f64 => unreachable,
344 },
345 .shr => switch (args.valtype1.?) {
346 .i32 => if (args.signedness.? == .signed) return .i32_shr_s else return .i32_shr_u,
347 .i64 => if (args.signedness.? == .signed) return .i64_shr_s else return .i64_shr_u,
348 .f32, .f64 => unreachable,
349 },
350 .rotl => switch (args.valtype1.?) {
351 .i32 => return .i32_rotl,
352 .i64 => return .i64_rotl,
353 .f32, .f64 => unreachable,
354 },
355 .rotr => switch (args.valtype1.?) {
356 .i32 => return .i32_rotr,
357 .i64 => return .i64_rotr,
358 .f32, .f64 => unreachable,
359 },
360
361 .abs => switch (args.valtype1.?) {
362 .i32, .i64 => unreachable,
363 .f32 => return .f32_abs,
364 .f64 => return .f64_abs,
365 },
366 .neg => switch (args.valtype1.?) {
367 .i32, .i64 => unreachable,
368 .f32 => return .f32_neg,
369 .f64 => return .f64_neg,
370 },
371 .ceil => switch (args.valtype1.?) {
372 .i32, .i64 => unreachable,
373 .f32 => return .f32_ceil,
374 .f64 => return .f64_ceil,
375 },
376 .floor => switch (args.valtype1.?) {
377 .i32, .i64 => unreachable,
378 .f32 => return .f32_floor,
379 .f64 => return .f64_floor,
380 },
381 .trunc => switch (args.valtype1.?) {
382 .i32 => switch (args.valtype2.?) {
383 .i32 => unreachable,
384 .i64 => unreachable,
385 .f32 => if (args.signedness.? == .signed) return .i32_trunc_f32_s else return .i32_trunc_f32_u,
386 .f64 => if (args.signedness.? == .signed) return .i32_trunc_f64_s else return .i32_trunc_f64_u,
387 },
388 .i64 => unreachable,
389 .f32 => return .f32_trunc,
390 .f64 => return .f64_trunc,
391 },
392 .nearest => switch (args.valtype1.?) {
393 .i32, .i64 => unreachable,
394 .f32 => return .f32_nearest,
395 .f64 => return .f64_nearest,
396 },
397 .sqrt => switch (args.valtype1.?) {
398 .i32, .i64 => unreachable,
399 .f32 => return .f32_sqrt,
400 .f64 => return .f64_sqrt,
401 },
402 .min => switch (args.valtype1.?) {
403 .i32, .i64 => unreachable,
404 .f32 => return .f32_min,
405 .f64 => return .f64_min,
406 },
407 .max => switch (args.valtype1.?) {
408 .i32, .i64 => unreachable,
409 .f32 => return .f32_max,
410 .f64 => return .f64_max,
411 },
412 .copysign => switch (args.valtype1.?) {
413 .i32, .i64 => unreachable,
414 .f32 => return .f32_copysign,
415 .f64 => return .f64_copysign,
416 },
417
418 .wrap => switch (args.valtype1.?) {
419 .i32 => switch (args.valtype2.?) {
420 .i32 => unreachable,
421 .i64 => return .i32_wrap_i64,
422 .f32, .f64 => unreachable,
423 },
424 .i64, .f32, .f64 => unreachable,
425 },
426 .convert => switch (args.valtype1.?) {
427 .i32, .i64 => unreachable,
428 .f32 => switch (args.valtype2.?) {
429 .i32 => if (args.signedness.? == .signed) return .f32_convert_i32_s else return .f32_convert_i32_u,
430 .i64 => if (args.signedness.? == .signed) return .f32_convert_i64_s else return .f32_convert_i64_u,
431 .f32, .f64 => unreachable,
432 },
433 .f64 => switch (args.valtype2.?) {
434 .i32 => if (args.signedness.? == .signed) return .f64_convert_i32_s else return .f64_convert_i32_u,
435 .i64 => if (args.signedness.? == .signed) return .f64_convert_i64_s else return .f64_convert_i64_u,
436 .f32, .f64 => unreachable,
437 },
438 },
439 .demote => if (args.valtype1.? == .f32 and args.valtype2.? == .f64) return .f32_demote_f64 else unreachable,
440 .promote => if (args.valtype1.? == .f64 and args.valtype2.? == .f32) return .f64_promote_f32 else unreachable,
441 .reinterpret => switch (args.valtype1.?) {
442 .i32 => if (args.valtype2.? == .f32) return .i32_reinterpret_f32 else unreachable,
443 .i64 => if (args.valtype2.? == .f64) return .i64_reinterpret_f64 else unreachable,
444 .f32 => if (args.valtype2.? == .i32) return .f32_reinterpret_i32 else unreachable,
445 .f64 => if (args.valtype2.? == .i64) return .f64_reinterpret_i64 else unreachable,
446 },
447 .extend => switch (args.valtype1.?) {
448 .i32 => switch (args.width.?) {
449 8 => if (args.signedness.? == .signed) return .i32_extend8_s else unreachable,
450 16 => if (args.signedness.? == .signed) return .i32_extend16_s else unreachable,
451 else => unreachable,
452 },
453 .i64 => switch (args.width.?) {
454 8 => if (args.signedness.? == .signed) return .i64_extend8_s else unreachable,
455 16 => if (args.signedness.? == .signed) return .i64_extend16_s else unreachable,
456 32 => if (args.signedness.? == .signed) return .i64_extend32_s else unreachable,
457 else => unreachable,
458 },
459 .f32, .f64 => unreachable,
460 },
461 }
462}
463
464test "Wasm - buildOpcode" {
465 // Make sure buildOpcode is referenced, and test some examples
466 const i32_const = buildOpcode(.{ .op = .@"const", .valtype1 = .i32 });
467 const end = buildOpcode(.{ .op = .end });
468 const local_get = buildOpcode(.{ .op = .local_get });
469 const i64_extend32_s = buildOpcode(.{ .op = .extend, .valtype1 = .i64, .width = 32, .signedness = .signed });
470 const f64_reinterpret_i64 = buildOpcode(.{ .op = .reinterpret, .valtype1 = .f64, .valtype2 = .i64 });
471
472 try testing.expectEqual(@as(wasm.Opcode, .i32_const), i32_const);
473 try testing.expectEqual(@as(wasm.Opcode, .end), end);
474 try testing.expectEqual(@as(wasm.Opcode, .local_get), local_get);
475 try testing.expectEqual(@as(wasm.Opcode, .i64_extend32_s), i64_extend32_s);
476 try testing.expectEqual(@as(wasm.Opcode, .f64_reinterpret_i64), f64_reinterpret_i64);
477}
478
479pub const Result = union(enum) {
480 /// The codegen bytes have been appended to `Context.code`
481 appended: void,
482 /// The data is managed externally and are part of the `Result`
483 externally_managed: []const u8,
484};
485
486/// Hashmap to store generated `WValue` for each `Air.Inst.Ref`
487pub const ValueTable = std.AutoHashMapUnmanaged(Air.Inst.Index, WValue);
488
489/// Code represents the `Code` section of wasm that
490/// belongs to a function
491pub const Context = struct {
492 /// Reference to the function declaration the code
493 /// section belongs to
494 decl: *Decl,
495 air: Air,
496 liveness: Liveness,
497 gpa: *mem.Allocator,
498 /// Table to save `WValue`'s generated by an `Air.Inst`
499 values: ValueTable,
500 /// Mapping from Air.Inst.Index to block ids
501 blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, u32) = .{},
502 /// `bytes` contains the wasm bytecode belonging to the 'code' section.
503 code: ArrayList(u8),
504 /// Contains the generated function type bytecode for the current function
505 /// found in `decl`
506 func_type_data: ArrayList(u8),
507 /// The index the next local generated will have
508 /// NOTE: arguments share the index with locals therefore the first variable
509 /// will have the index that comes after the last argument's index
510 local_index: u32 = 0,
511 /// If codegen fails, an error messages will be allocated and saved in `err_msg`
512 err_msg: *Module.ErrorMsg,
513 /// Current block depth. Used to calculate the relative difference between a break
514 /// and block
515 block_depth: u32 = 0,
516 /// List of all locals' types generated throughout this declaration
517 /// used to emit locals count at start of 'code' section.
518 locals: std.ArrayListUnmanaged(u8),
519 /// The Target we're emitting (used to call intInfo)
520 target: std.Target,
521 /// Table with the global error set. Consists of every error found in
522 /// the compiled code. Each error name maps to a `Module.ErrorInt` which is emitted
523 /// during codegen to determine the error value.
524 global_error_set: std.StringHashMapUnmanaged(Module.ErrorInt),
525
526 const InnerError = error{
527 OutOfMemory,
528 CodegenFail,
529 /// Can occur when dereferencing a pointer that points to a `Decl` of which the analysis has failed
530 AnalysisFail,
531 };
532
533 pub fn deinit(self: *Context) void {
534 self.values.deinit(self.gpa);
535 self.blocks.deinit(self.gpa);
536 self.locals.deinit(self.gpa);
537 self.* = undefined;
538 }
539
540 /// Sets `err_msg` on `Context` and returns `error.CodegemFail` which is caught in link/Wasm.zig
541 fn fail(self: *Context, comptime fmt: []const u8, args: anytype) InnerError {
542 const src: LazySrcLoc = .{ .node_offset = 0 };
543 const src_loc = src.toSrcLoc(self.decl);
544 self.err_msg = try Module.ErrorMsg.create(self.gpa, src_loc, fmt, args);
545 return error.CodegenFail;
546 }
547
548 /// Resolves the `WValue` for the given instruction `inst`
549 /// When the given instruction has a `Value`, it returns a constant instead
550 fn resolveInst(self: Context, ref: Air.Inst.Ref) WValue {
551 const inst_index = Air.refToIndex(ref) orelse {
552 const tv = Air.Inst.Ref.typed_value_map[@enumToInt(ref)];
553 if (!tv.ty.hasCodeGenBits()) {
554 return WValue.none;
555 }
556 return WValue{ .constant = tv };
557 };
558
559 const inst_type = self.air.typeOfIndex(inst_index);
560 if (!inst_type.hasCodeGenBits()) return .none;
561
562 if (self.air.instructions.items(.tag)[inst_index] == .constant) {
563 const ty_pl = self.air.instructions.items(.data)[inst_index].ty_pl;
564 return WValue{ .constant = .{ .ty = inst_type, .val = self.air.values[ty_pl.payload] } };
565 }
566
567 return self.values.get(inst_index).?; // Instruction does not dominate all uses!
568 }
569
570 /// Using a given `Type`, returns the corresponding wasm Valtype
571 fn typeToValtype(self: *Context, ty: Type) InnerError!wasm.Valtype {
572 return switch (ty.zigTypeTag()) {
573 .Float => blk: {
574 const bits = ty.floatBits(self.target);
575 if (bits == 16 or bits == 32) break :blk wasm.Valtype.f32;
576 if (bits == 64) break :blk wasm.Valtype.f64;
577 return self.fail("Float bit size not supported by wasm: '{d}'", .{bits});
578 },
579 .Int => blk: {
580 const info = ty.intInfo(self.target);
581 if (info.bits <= 32) break :blk wasm.Valtype.i32;
582 if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64;
583 return self.fail("Integer bit size not supported by wasm: '{d}'", .{info.bits});
584 },
585 .Enum => switch (ty.tag()) {
586 .enum_simple => wasm.Valtype.i32,
587 else => self.typeToValtype(ty.cast(Type.Payload.EnumFull).?.data.tag_ty),
588 },
589 .Bool,
590 .Pointer,
591 .ErrorSet,
592 => wasm.Valtype.i32,
593 .Struct, .ErrorUnion, .Optional => unreachable, // Multi typed, must be handled individually.
594 else => |tag| self.fail("TODO - Wasm valtype for type '{s}'", .{tag}),
595 };
596 }
597
598 /// Using a given `Type`, returns the byte representation of its wasm value type
599 fn genValtype(self: *Context, ty: Type) InnerError!u8 {
600 return wasm.valtype(try self.typeToValtype(ty));
601 }
602
603 /// Using a given `Type`, returns the corresponding wasm value type
604 /// Differently from `genValtype` this also allows `void` to create a block
605 /// with no return type
606 fn genBlockType(self: *Context, ty: Type) InnerError!u8 {
607 return switch (ty.tag()) {
608 .void, .noreturn => wasm.block_empty,
609 else => self.genValtype(ty),
610 };
611 }
612
613 /// Writes the bytecode depending on the given `WValue` in `val`
614 fn emitWValue(self: *Context, val: WValue) InnerError!void {
615 const writer = self.code.writer();
616 switch (val) {
617 .multi_value => unreachable, // multi_value can never be written directly, and must be accessed individually
618 .none, .code_offset => {}, // no-op
619 .local => |idx| {
620 try writer.writeByte(wasm.opcode(.local_get));
621 try leb.writeULEB128(writer, idx);
622 },
623 .constant => |tv| try self.emitConstant(tv.val, tv.ty), // Creates a new constant on the stack
624 }
625 }
626
627 /// Creates one or multiple locals for a given `Type`.
628 /// Returns a corresponding `Wvalue` that can either be of tag
629 /// local or multi_value
630 fn allocLocal(self: *Context, ty: Type) InnerError!WValue {
631 const initial_index = self.local_index;
632 switch (ty.zigTypeTag()) {
633 .Struct => {
634 // for each struct field, generate a local
635 const struct_data: *Module.Struct = ty.castTag(.@"struct").?.data;
636 const fields_len = @intCast(u32, struct_data.fields.count());
637 try self.locals.ensureUnusedCapacity(self.gpa, fields_len);
638 for (struct_data.fields.values()) |*value| {
639 const val_type = try self.genValtype(value.ty);
640 self.locals.appendAssumeCapacity(val_type);
641 self.local_index += 1;
642 }
643 return WValue{ .multi_value = .{
644 .index = initial_index,
645 .count = fields_len,
646 } };
647 },
648 .ErrorUnion => {
649 const payload_type = ty.errorUnionPayload();
650 const val_type = try self.genValtype(payload_type);
651
652 // we emit the error value as the first local, and the payload as the following.
653 // The first local is also used to find the index of the error and payload.
654 //
655 // TODO: Add support where the payload is a type that contains multiple locals such as a struct.
656 try self.locals.ensureUnusedCapacity(self.gpa, 2);
657 self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // error values are always i32
658 self.locals.appendAssumeCapacity(val_type);
659 self.local_index += 2;
660
661 return WValue{ .multi_value = .{
662 .index = initial_index,
663 .count = 2,
664 } };
665 },
666 .Optional => {
667 var opt_buf: Type.Payload.ElemType = undefined;
668 const child_type = ty.optionalChild(&opt_buf);
669 if (ty.isPtrLikeOptional()) {
670 return self.fail("TODO: wasm optional pointer", .{});
671 }
672
673 try self.locals.ensureUnusedCapacity(self.gpa, 2);
674 self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // optional 'tag' for null-checking is always i32
675 self.locals.appendAssumeCapacity(try self.genValtype(child_type));
676 self.local_index += 2;
677
678 return WValue{ .multi_value = .{
679 .index = initial_index,
680 .count = 2,
681 } };
682 },
683 else => {
684 const valtype = try self.genValtype(ty);
685 try self.locals.append(self.gpa, valtype);
686 self.local_index += 1;
687 return WValue{ .local = initial_index };
688 },
689 }
690 }
691
692 fn genFunctype(self: *Context) InnerError!void {
693 assert(self.decl.has_tv);
694 const ty = self.decl.ty;
695 const writer = self.func_type_data.writer();
696
697 try writer.writeByte(wasm.function_type);
698
699 // param types
700 try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen()));
701 if (ty.fnParamLen() != 0) {
702 const params = try self.gpa.alloc(Type, ty.fnParamLen());
703 defer self.gpa.free(params);
704 ty.fnParamTypes(params);
705 for (params) |param_type| {
706 // Can we maybe get the source index of each param?
707 const val_type = try self.genValtype(param_type);
708 try writer.writeByte(val_type);
709 }
710 }
711
712 // return type
713 const return_type = ty.fnReturnType();
714 switch (return_type.zigTypeTag()) {
715 .Void, .NoReturn => try leb.writeULEB128(writer, @as(u32, 0)),
716 .Struct => return self.fail("TODO: Implement struct as return type for wasm", .{}),
717 .Optional => return self.fail("TODO: Implement optionals as return type for wasm", .{}),
718 .ErrorUnion => {
719 const val_type = try self.genValtype(return_type.errorUnionPayload());
720
721 // write down the amount of return values
722 try leb.writeULEB128(writer, @as(u32, 2));
723 try writer.writeByte(wasm.valtype(.i32)); // error code is always an i32 integer.
724 try writer.writeByte(val_type);
725 },
726 else => {
727 try leb.writeULEB128(writer, @as(u32, 1));
728 // Can we maybe get the source index of the return type?
729 const val_type = try self.genValtype(return_type);
730 try writer.writeByte(val_type);
731 },
732 }
733 }
734
735 pub fn genFunc(self: *Context) InnerError!Result {
736 try self.genFunctype();
737 // TODO: check for and handle death of instructions
738
739 // Reserve space to write the size after generating the code as well as space for locals count
740 try self.code.resize(10);
741
742 try self.genBody(self.air.getMainBody());
743
744 // finally, write our local types at the 'offset' position
745 {
746 leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len));
747
748 // offset into 'code' section where we will put our locals types
749 var local_offset: usize = 10;
750
751 // emit the actual locals amount
752 for (self.locals.items) |local| {
753 var buf: [6]u8 = undefined;
754 leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1));
755 buf[5] = local;
756 try self.code.insertSlice(local_offset, &buf);
757 local_offset += 6;
758 }
759 }
760
761 const writer = self.code.writer();
762 try writer.writeByte(wasm.opcode(.end));
763
764 // Fill in the size of the generated code to the reserved space at the
765 // beginning of the buffer.
766 const size = self.code.items.len - 5 + self.decl.fn_link.wasm.idx_refs.items.len * 5;
767 leb.writeUnsignedFixed(5, self.code.items[0..5], @intCast(u32, size));
768
769 // codegen data has been appended to `code`
770 return Result.appended;
771 }
772
773 /// Generates the wasm bytecode for the declaration belonging to `Context`
774 pub fn gen(self: *Context, ty: Type, val: Value) InnerError!Result {
775 switch (ty.zigTypeTag()) {
776 .Fn => {
777 try self.genFunctype();
778 if (val.tag() == .extern_fn) {
779 return Result.appended; // don't need code body for extern functions
780 }
781 return self.fail("TODO implement wasm codegen for function pointers", .{});
782 },
783 .Array => {
784 if (val.castTag(.bytes)) |payload| {
785 if (ty.sentinel()) |sentinel| {
786 try self.code.appendSlice(payload.data);
787
788 switch (try self.gen(ty.elemType(), sentinel)) {
789 .appended => return Result.appended,
790 .externally_managed => |data| {
791 try self.code.appendSlice(data);
792 return Result.appended;
793 },
794 }
795 }
796 return Result{ .externally_managed = payload.data };
797 } else return self.fail("TODO implement gen for more kinds of arrays", .{});
798 },
799 .Int => {
800 const info = ty.intInfo(self.target);
801 if (info.bits == 8 and info.signedness == .unsigned) {
802 const int_byte = val.toUnsignedInt();
803 try self.code.append(@intCast(u8, int_byte));
804 return Result.appended;
805 }
806 return self.fail("TODO: Implement codegen for int type: '{}'", .{ty});
807 },
808 .Enum => {
809 try self.emitConstant(val, ty);
810 return Result.appended;
811 },
812 .Struct => {
813 // TODO write the fields for real
814 try self.code.writer().writeByteNTimes(0xaa, ty.abiSize(self.target));
815 return Result{ .appended = {} };
816 },
817 else => |tag| return self.fail("TODO: Implement zig type codegen for type: '{s}'", .{tag}),
818 }
819 }
820
821 fn genInst(self: *Context, inst: Air.Inst.Index) !WValue {
822 const air_tags = self.air.instructions.items(.tag);
823 return switch (air_tags[inst]) {
824 .add => self.airBinOp(inst, .add),
825 .addwrap => self.airWrapBinOp(inst, .add),
826 .sub => self.airBinOp(inst, .sub),
827 .subwrap => self.airWrapBinOp(inst, .sub),
828 .mul => self.airBinOp(inst, .mul),
829 .mulwrap => self.airWrapBinOp(inst, .mul),
830 .div_trunc => self.airBinOp(inst, .div),
831 .bit_and => self.airBinOp(inst, .@"and"),
832 .bit_or => self.airBinOp(inst, .@"or"),
833 .bool_and => self.airBinOp(inst, .@"and"),
834 .bool_or => self.airBinOp(inst, .@"or"),
835 .xor => self.airBinOp(inst, .xor),
836
837 .cmp_eq => self.airCmp(inst, .eq),
838 .cmp_gte => self.airCmp(inst, .gte),
839 .cmp_gt => self.airCmp(inst, .gt),
840 .cmp_lte => self.airCmp(inst, .lte),
841 .cmp_lt => self.airCmp(inst, .lt),
842 .cmp_neq => self.airCmp(inst, .neq),
843
844 .alloc => self.airAlloc(inst),
845 .arg => self.airArg(inst),
846 .bitcast => self.airBitcast(inst),
847 .block => self.airBlock(inst),
848 .breakpoint => self.airBreakpoint(inst),
849 .br => self.airBr(inst),
850 .call => self.airCall(inst),
851 .cond_br => self.airCondBr(inst),
852 .constant => unreachable,
853 .dbg_stmt => WValue.none,
854 .intcast => self.airIntcast(inst),
855
856 .is_err => self.airIsErr(inst, .i32_ne),
857 .is_non_err => self.airIsErr(inst, .i32_eq),
858
859 .is_null => self.airIsNull(inst, .i32_ne),
860 .is_non_null => self.airIsNull(inst, .i32_eq),
861 .is_null_ptr => self.airIsNull(inst, .i32_ne),
862 .is_non_null_ptr => self.airIsNull(inst, .i32_eq),
863
864 .load => self.airLoad(inst),
865 .loop => self.airLoop(inst),
866 .not => self.airNot(inst),
867 .ret => self.airRet(inst),
868 .store => self.airStore(inst),
869 .struct_field_ptr => self.airStructFieldPtr(inst),
870 .struct_field_ptr_index_0 => self.airStructFieldPtrIndex(inst, 0),
871 .struct_field_ptr_index_1 => self.airStructFieldPtrIndex(inst, 1),
872 .struct_field_ptr_index_2 => self.airStructFieldPtrIndex(inst, 2),
873 .struct_field_ptr_index_3 => self.airStructFieldPtrIndex(inst, 3),
874 .struct_field_val => self.airStructFieldVal(inst),
875 .switch_br => self.airSwitchBr(inst),
876 .unreach => self.airUnreachable(inst),
877 .wrap_optional => self.airWrapOptional(inst),
878
879 .unwrap_errunion_payload => self.airUnwrapErrUnionPayload(inst),
880 .wrap_errunion_payload => self.airWrapErrUnionPayload(inst),
881
882 .optional_payload => self.airOptionalPayload(inst),
883 .optional_payload_ptr => self.airOptionalPayload(inst),
884 .optional_payload_ptr_set => self.airOptionalPayloadPtrSet(inst),
885 else => |tag| self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),
886 };
887 }
888
889 fn genBody(self: *Context, body: []const Air.Inst.Index) InnerError!void {
890 for (body) |inst| {
891 const result = try self.genInst(inst);
892 try self.values.putNoClobber(self.gpa, inst, result);
893 }
894 }
895
896 fn airRet(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
897 const un_op = self.air.instructions.items(.data)[inst].un_op;
898 const operand = self.resolveInst(un_op);
899 try self.emitWValue(operand);
900 try self.code.append(wasm.opcode(.@"return"));
901 return .none;
902 }
903
904 fn airCall(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
905 const pl_op = self.air.instructions.items(.data)[inst].pl_op;
906 const extra = self.air.extraData(Air.Call, pl_op.payload);
907 const args = self.air.extra[extra.end..][0..extra.data.args_len];
908
909 const target: *Decl = blk: {
910 const func_val = self.air.value(pl_op.operand).?;
911
912 if (func_val.castTag(.function)) |func| {
913 break :blk func.data.owner_decl;
914 } else if (func_val.castTag(.extern_fn)) |ext_fn| {
915 break :blk ext_fn.data;
916 }
917 return self.fail("Expected a function, but instead found type '{s}'", .{func_val.tag()});
918 };
919
920 for (args) |arg| {
921 const arg_val = self.resolveInst(@intToEnum(Air.Inst.Ref, arg));
922 try self.emitWValue(arg_val);
923 }
924
925 try self.code.append(wasm.opcode(.call));
926
927 // The function index immediate argument will be filled in using this data
928 // in link.Wasm.flush().
929 try self.decl.fn_link.wasm.idx_refs.append(self.gpa, .{
930 .offset = @intCast(u32, self.code.items.len),
931 .decl = target,
932 });
933
934 return .none;
935 }
936
937 fn airAlloc(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
938 const elem_type = self.air.typeOfIndex(inst).elemType();
939 return self.allocLocal(elem_type);
940 }
941
942 fn airStore(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
943 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
944 const writer = self.code.writer();
945
946 const lhs = self.resolveInst(bin_op.lhs);
947 const rhs = self.resolveInst(bin_op.rhs);
948
949 switch (lhs) {
950 .multi_value => |multi_value| switch (rhs) {
951 // When assigning a value to a multi_value such as a struct,
952 // we simply assign the local_index to the rhs one.
953 // This allows us to update struct fields without having to individually
954 // set each local as each field's index will be calculated off the struct's base index
955 .multi_value => self.values.put(self.gpa, Air.refToIndex(bin_op.lhs).?, rhs) catch unreachable, // Instruction does not dominate all uses!
956 .constant, .none => {
957 // emit all values onto the stack if constant
958 try self.emitWValue(rhs);
959
960 // for each local, pop the stack value into the local
961 // As the last element is on top of the stack, we must populate the locals
962 // in reverse.
963 var i: u32 = multi_value.count;
964 while (i > 0) : (i -= 1) {
965 try writer.writeByte(wasm.opcode(.local_set));
966 try leb.writeULEB128(writer, multi_value.index + i - 1);
967 }
968 },
969 .local => {
970 // This can occur when we wrap a single value into a multi-value,
971 // such as wrapping a non-optional value into an optional.
972 // This means we must zero the null-tag, and set the payload.
973 assert(multi_value.count == 2);
974 // set null-tag
975 try writer.writeByte(wasm.opcode(.i32_const));
976 try leb.writeULEB128(writer, @as(u32, 0));
977 try writer.writeByte(wasm.opcode(.local_set));
978 try leb.writeULEB128(writer, multi_value.index);
979
980 // set payload
981 try self.emitWValue(rhs);
982 try writer.writeByte(wasm.opcode(.local_set));
983 try leb.writeULEB128(writer, multi_value.index + 1);
984 },
985 else => unreachable,
986 },
987 .local => |local| {
988 try self.emitWValue(rhs);
989 try writer.writeByte(wasm.opcode(.local_set));
990 try leb.writeULEB128(writer, local);
991 },
992 else => unreachable,
993 }
994 return .none;
995 }
996
997 fn airLoad(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
998 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
999 return self.resolveInst(ty_op.operand);
1000 }
1001
1002 fn airArg(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1003 _ = inst;
1004 // arguments share the index with locals
1005 defer self.local_index += 1;
1006 return WValue{ .local = self.local_index };
1007 }
1008
1009 fn airBinOp(self: *Context, inst: Air.Inst.Index, op: Op) InnerError!WValue {
1010 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
1011 const lhs = self.resolveInst(bin_op.lhs);
1012 const rhs = self.resolveInst(bin_op.rhs);
1013
1014 // it's possible for both lhs and/or rhs to return an offset as well,
1015 // in which case we return the first offset occurrence we find.
1016 const offset = blk: {
1017 if (lhs == .code_offset) break :blk lhs.code_offset;
1018 if (rhs == .code_offset) break :blk rhs.code_offset;
1019 break :blk self.code.items.len;
1020 };
1021
1022 try self.emitWValue(lhs);
1023 try self.emitWValue(rhs);
1024
1025 const bin_ty = self.air.typeOf(bin_op.lhs);
1026 const opcode: wasm.Opcode = buildOpcode(.{
1027 .op = op,
1028 .valtype1 = try self.typeToValtype(bin_ty),
1029 .signedness = if (bin_ty.isSignedInt()) .signed else .unsigned,
1030 });
1031 try self.code.append(wasm.opcode(opcode));
1032 return WValue{ .code_offset = offset };
1033 }
1034
1035 fn airWrapBinOp(self: *Context, inst: Air.Inst.Index, op: Op) InnerError!WValue {
1036 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
1037 const lhs = self.resolveInst(bin_op.lhs);
1038 const rhs = self.resolveInst(bin_op.rhs);
1039
1040 // it's possible for both lhs and/or rhs to return an offset as well,
1041 // in which case we return the first offset occurrence we find.
1042 const offset = blk: {
1043 if (lhs == .code_offset) break :blk lhs.code_offset;
1044 if (rhs == .code_offset) break :blk rhs.code_offset;
1045 break :blk self.code.items.len;
1046 };
1047
1048 try self.emitWValue(lhs);
1049 try self.emitWValue(rhs);
1050
1051 const bin_ty = self.air.typeOf(bin_op.lhs);
1052 const opcode: wasm.Opcode = buildOpcode(.{
1053 .op = op,
1054 .valtype1 = try self.typeToValtype(bin_ty),
1055 .signedness = if (bin_ty.isSignedInt()) .signed else .unsigned,
1056 });
1057 try self.code.append(wasm.opcode(opcode));
1058
1059 const int_info = bin_ty.intInfo(self.target);
1060 const bitsize = int_info.bits;
1061 const is_signed = int_info.signedness == .signed;
1062 // if target type bitsize is x < 32 and 32 > x < 64, we perform
1063 // result & ((1<<N)-1) where N = bitsize or bitsize -1 incase of signed.
1064 if (bitsize != 32 and bitsize < 64) {
1065 // first check if we can use a single instruction,
1066 // wasm provides those if the integers are signed and 8/16-bit.
1067 // For arbitrary integer sizes, we use the algorithm mentioned above.
1068 if (is_signed and bitsize == 8) {
1069 try self.code.append(wasm.opcode(.i32_extend8_s));
1070 } else if (is_signed and bitsize == 16) {
1071 try self.code.append(wasm.opcode(.i32_extend16_s));
1072 } else {
1073 const result = (@as(u64, 1) << @intCast(u6, bitsize - @boolToInt(is_signed))) - 1;
1074 if (bitsize < 32) {
1075 try self.code.append(wasm.opcode(.i32_const));
1076 try leb.writeILEB128(self.code.writer(), @bitCast(i32, @intCast(u32, result)));
1077 try self.code.append(wasm.opcode(.i32_and));
1078 } else {
1079 try self.code.append(wasm.opcode(.i64_const));
1080 try leb.writeILEB128(self.code.writer(), @bitCast(i64, result));
1081 try self.code.append(wasm.opcode(.i64_and));
1082 }
1083 }
1084 } else if (int_info.bits > 64) {
1085 return self.fail("TODO wasm: Integer wrapping for bitsizes larger than 64", .{});
1086 }
1087
1088 return WValue{ .code_offset = offset };
1089 }
1090
1091 fn emitConstant(self: *Context, val: Value, ty: Type) InnerError!void {
1092 const writer = self.code.writer();
1093 switch (ty.zigTypeTag()) {
1094 .Int => {
1095 // write opcode
1096 const opcode: wasm.Opcode = buildOpcode(.{
1097 .op = .@"const",
1098 .valtype1 = try self.typeToValtype(ty),
1099 });
1100 try writer.writeByte(wasm.opcode(opcode));
1101 const int_info = ty.intInfo(self.target);
1102 // write constant
1103 switch (int_info.signedness) {
1104 .signed => try leb.writeILEB128(writer, val.toSignedInt()),
1105 .unsigned => switch (int_info.bits) {
1106 0...32 => try leb.writeILEB128(writer, @bitCast(i32, @intCast(u32, val.toUnsignedInt()))),
1107 33...64 => try leb.writeILEB128(writer, @bitCast(i64, val.toUnsignedInt())),
1108 else => |bits| return self.fail("Wasm TODO: emitConstant for integer with {d} bits", .{bits}),
1109 },
1110 }
1111 },
1112 .Bool => {
1113 // write opcode
1114 try writer.writeByte(wasm.opcode(.i32_const));
1115 // write constant
1116 try leb.writeILEB128(writer, val.toSignedInt());
1117 },
1118 .Float => {
1119 // write opcode
1120 const opcode: wasm.Opcode = buildOpcode(.{
1121 .op = .@"const",
1122 .valtype1 = try self.typeToValtype(ty),
1123 });
1124 try writer.writeByte(wasm.opcode(opcode));
1125 // write constant
1126 switch (ty.floatBits(self.target)) {
1127 0...32 => try writer.writeIntLittle(u32, @bitCast(u32, val.toFloat(f32))),
1128 64 => try writer.writeIntLittle(u64, @bitCast(u64, val.toFloat(f64))),
1129 else => |bits| return self.fail("Wasm TODO: emitConstant for float with {d} bits", .{bits}),
1130 }
1131 },
1132 .Pointer => {
1133 if (val.castTag(.decl_ref)) |payload| {
1134 const decl = payload.data;
1135 decl.alive = true;
1136
1137 // offset into the offset table within the 'data' section
1138 const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8;
1139 try writer.writeByte(wasm.opcode(.i32_const));
1140 try leb.writeULEB128(writer, decl.link.wasm.offset_index * ptr_width);
1141
1142 // memory instruction followed by their memarg immediate
1143 // memarg ::== x:u32, y:u32 => {align x, offset y}
1144 try writer.writeByte(wasm.opcode(.i32_load));
1145 try leb.writeULEB128(writer, @as(u32, 0));
1146 try leb.writeULEB128(writer, @as(u32, 0));
1147 } else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{val.tag()});
1148 },
1149 .Void => {},
1150 .Enum => {
1151 if (val.castTag(.enum_field_index)) |field_index| {
1152 switch (ty.tag()) {
1153 .enum_simple => {
1154 try writer.writeByte(wasm.opcode(.i32_const));
1155 try leb.writeULEB128(writer, field_index.data);
1156 },
1157 .enum_full, .enum_nonexhaustive => {
1158 const enum_full = ty.cast(Type.Payload.EnumFull).?.data;
1159 if (enum_full.values.count() != 0) {
1160 const tag_val = enum_full.values.keys()[field_index.data];
1161 try self.emitConstant(tag_val, enum_full.tag_ty);
1162 } else {
1163 try writer.writeByte(wasm.opcode(.i32_const));
1164 try leb.writeULEB128(writer, field_index.data);
1165 }
1166 },
1167 else => unreachable,
1168 }
1169 } else {
1170 var int_tag_buffer: Type.Payload.Bits = undefined;
1171 const int_tag_ty = ty.intTagType(&int_tag_buffer);
1172 try self.emitConstant(val, int_tag_ty);
1173 }
1174 },
1175 .ErrorSet => {
1176 const error_index = self.global_error_set.get(val.getError().?).?;
1177 try writer.writeByte(wasm.opcode(.i32_const));
1178 try leb.writeULEB128(writer, error_index);
1179 },
1180 .ErrorUnion => {
1181 const error_type = ty.errorUnionSet();
1182 const payload_type = ty.errorUnionPayload();
1183 if (val.castTag(.eu_payload)) |pl| {
1184 const payload_val = pl.data;
1185 // no error, so write a '0' const
1186 try writer.writeByte(wasm.opcode(.i32_const));
1187 try leb.writeULEB128(writer, @as(u32, 0));
1188 // after the error code, we emit the payload
1189 try self.emitConstant(payload_val, payload_type);
1190 } else {
1191 // write the error val
1192 try self.emitConstant(val, error_type);
1193
1194 // no payload, so write a '0' const
1195 const opcode: wasm.Opcode = buildOpcode(.{
1196 .op = .@"const",
1197 .valtype1 = try self.typeToValtype(payload_type),
1198 });
1199 try writer.writeByte(wasm.opcode(opcode));
1200 try leb.writeULEB128(writer, @as(u32, 0));
1201 }
1202 },
1203 .Optional => {
1204 var buf: Type.Payload.ElemType = undefined;
1205 const payload_type = ty.optionalChild(&buf);
1206 if (ty.isPtrLikeOptional()) {
1207 return self.fail("Wasm TODO: emitConstant for optional pointer", .{});
1208 }
1209
1210 // When constant has value 'null', set is_null local to '1'
1211 // and payload to '0'
1212 if (val.castTag(.opt_payload)) |pl| {
1213 const payload_val = pl.data;
1214 try writer.writeByte(wasm.opcode(.i32_const));
1215 try leb.writeILEB128(writer, @as(i32, 0));
1216 try self.emitConstant(payload_val, payload_type);
1217 } else {
1218 try writer.writeByte(wasm.opcode(.i32_const));
1219 try leb.writeILEB128(writer, @as(i32, 1));
1220
1221 const opcode: wasm.Opcode = buildOpcode(.{
1222 .op = .@"const",
1223 .valtype1 = try self.typeToValtype(payload_type),
1224 });
1225 try writer.writeByte(wasm.opcode(opcode));
1226 try leb.writeULEB128(writer, @as(u32, 0));
1227 }
1228 },
1229 else => |zig_type| return self.fail("Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}),
1230 }
1231 }
1232
1233 /// Returns a `Value` as a signed 32 bit value.
1234 /// It's illegal to provide a value with a type that cannot be represented
1235 /// as an integer value.
1236 fn valueAsI32(self: Context, val: Value, ty: Type) i32 {
1237 switch (ty.zigTypeTag()) {
1238 .Enum => {
1239 if (val.castTag(.enum_field_index)) |field_index| {
1240 switch (ty.tag()) {
1241 .enum_simple => return @bitCast(i32, field_index.data),
1242 .enum_full, .enum_nonexhaustive => {
1243 const enum_full = ty.cast(Type.Payload.EnumFull).?.data;
1244 if (enum_full.values.count() != 0) {
1245 const tag_val = enum_full.values.keys()[field_index.data];
1246 return self.valueAsI32(tag_val, enum_full.tag_ty);
1247 } else return @bitCast(i32, field_index.data);
1248 },
1249 else => unreachable,
1250 }
1251 } else {
1252 var int_tag_buffer: Type.Payload.Bits = undefined;
1253 const int_tag_ty = ty.intTagType(&int_tag_buffer);
1254 return self.valueAsI32(val, int_tag_ty);
1255 }
1256 },
1257 .Int => switch (ty.intInfo(self.target).signedness) {
1258 .signed => return @truncate(i32, val.toSignedInt()),
1259 .unsigned => return @bitCast(i32, @truncate(u32, val.toUnsignedInt())),
1260 },
1261 .ErrorSet => {
1262 const error_index = self.global_error_set.get(val.getError().?).?;
1263 return @bitCast(i32, error_index);
1264 },
1265 else => unreachable, // Programmer called this function for an illegal type
1266 }
1267 }
1268
1269 fn airBlock(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1270 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
1271 const block_ty = try self.genBlockType(self.air.getRefType(ty_pl.ty));
1272 const extra = self.air.extraData(Air.Block, ty_pl.payload);
1273 const body = self.air.extra[extra.end..][0..extra.data.body_len];
1274
1275 try self.startBlock(.block, block_ty, null);
1276 // Here we set the current block idx, so breaks know the depth to jump
1277 // to when breaking out.
1278 try self.blocks.putNoClobber(self.gpa, inst, self.block_depth);
1279 try self.genBody(body);
1280 try self.endBlock();
1281
1282 return .none;
1283 }
1284
1285 /// appends a new wasm block to the code section and increases the `block_depth` by 1
1286 fn startBlock(self: *Context, block_type: wasm.Opcode, valtype: u8, with_offset: ?usize) !void {
1287 self.block_depth += 1;
1288 if (with_offset) |offset| {
1289 try self.code.insert(offset, wasm.opcode(block_type));
1290 try self.code.insert(offset + 1, valtype);
1291 } else {
1292 try self.code.append(wasm.opcode(block_type));
1293 try self.code.append(valtype);
1294 }
1295 }
1296
1297 /// Ends the current wasm block and decreases the `block_depth` by 1
1298 fn endBlock(self: *Context) !void {
1299 try self.code.append(wasm.opcode(.end));
1300 self.block_depth -= 1;
1301 }
1302
1303 fn airLoop(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1304 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
1305 const loop = self.air.extraData(Air.Block, ty_pl.payload);
1306 const body = self.air.extra[loop.end..][0..loop.data.body_len];
1307
1308 // result type of loop is always 'noreturn', meaning we can always
1309 // emit the wasm type 'block_empty'.
1310 try self.startBlock(.loop, wasm.block_empty, null);
1311 try self.genBody(body);
1312
1313 // breaking to the index of a loop block will continue the loop instead
1314 try self.code.append(wasm.opcode(.br));
1315 try leb.writeULEB128(self.code.writer(), @as(u32, 0));
1316
1317 try self.endBlock();
1318
1319 return .none;
1320 }
1321
1322 fn airCondBr(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1323 const pl_op = self.air.instructions.items(.data)[inst].pl_op;
1324 const condition = self.resolveInst(pl_op.operand);
1325 const extra = self.air.extraData(Air.CondBr, pl_op.payload);
1326 const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len];
1327 const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
1328 const writer = self.code.writer();
1329 // TODO: Handle death instructions for then and else body
1330
1331 // insert blocks at the position of `offset` so
1332 // the condition can jump to it
1333 const offset = switch (condition) {
1334 .code_offset => |offset| offset,
1335 else => blk: {
1336 const offset = self.code.items.len;
1337 try self.emitWValue(condition);
1338 break :blk offset;
1339 },
1340 };
1341
1342 // result type is always noreturn, so use `block_empty` as type.
1343 try self.startBlock(.block, wasm.block_empty, offset);
1344
1345 // we inserted the block in front of the condition
1346 // so now check if condition matches. If not, break outside this block
1347 // and continue with the then codepath
1348 try writer.writeByte(wasm.opcode(.br_if));
1349 try leb.writeULEB128(writer, @as(u32, 0));
1350
1351 try self.genBody(else_body);
1352 try self.endBlock();
1353
1354 // Outer block that matches the condition
1355 try self.genBody(then_body);
1356
1357 return .none;
1358 }
1359
1360 fn airCmp(self: *Context, inst: Air.Inst.Index, op: std.math.CompareOperator) InnerError!WValue {
1361 // save offset, so potential conditions can insert blocks in front of
1362 // the comparison that we can later jump back to
1363 const offset = self.code.items.len;
1364
1365 const data: Air.Inst.Data = self.air.instructions.items(.data)[inst];
1366 const lhs = self.resolveInst(data.bin_op.lhs);
1367 const rhs = self.resolveInst(data.bin_op.rhs);
1368 const lhs_ty = self.air.typeOf(data.bin_op.lhs);
1369
1370 try self.emitWValue(lhs);
1371 try self.emitWValue(rhs);
1372
1373 const signedness: std.builtin.Signedness = blk: {
1374 // by default we tell the operand type is unsigned (i.e. bools and enum values)
1375 if (lhs_ty.zigTypeTag() != .Int) break :blk .unsigned;
1376
1377 // incase of an actual integer, we emit the correct signedness
1378 break :blk lhs_ty.intInfo(self.target).signedness;
1379 };
1380 const opcode: wasm.Opcode = buildOpcode(.{
1381 .valtype1 = try self.typeToValtype(lhs_ty),
1382 .op = switch (op) {
1383 .lt => .lt,
1384 .lte => .le,
1385 .eq => .eq,
1386 .neq => .ne,
1387 .gte => .ge,
1388 .gt => .gt,
1389 },
1390 .signedness = signedness,
1391 });
1392 try self.code.append(wasm.opcode(opcode));
1393 return WValue{ .code_offset = offset };
1394 }
1395
1396 fn airBr(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1397 const br = self.air.instructions.items(.data)[inst].br;
1398
1399 // if operand has codegen bits we should break with a value
1400 if (self.air.typeOf(br.operand).hasCodeGenBits()) {
1401 try self.emitWValue(self.resolveInst(br.operand));
1402 }
1403
1404 // We map every block to its block index.
1405 // We then determine how far we have to jump to it by subtracting it from current block depth
1406 const idx: u32 = self.block_depth - self.blocks.get(br.block_inst).?;
1407 const writer = self.code.writer();
1408 try writer.writeByte(wasm.opcode(.br));
1409 try leb.writeULEB128(writer, idx);
1410
1411 return .none;
1412 }
1413
1414 fn airNot(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1415 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1416 const offset = self.code.items.len;
1417
1418 const operand = self.resolveInst(ty_op.operand);
1419 try self.emitWValue(operand);
1420
1421 // wasm does not have booleans nor the `not` instruction, therefore compare with 0
1422 // to create the same logic
1423 const writer = self.code.writer();
1424 try writer.writeByte(wasm.opcode(.i32_const));
1425 try leb.writeILEB128(writer, @as(i32, 0));
1426
1427 try writer.writeByte(wasm.opcode(.i32_eq));
1428
1429 return WValue{ .code_offset = offset };
1430 }
1431
1432 fn airBreakpoint(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1433 _ = self;
1434 _ = inst;
1435 // unsupported by wasm itself. Can be implemented once we support DWARF
1436 // for wasm
1437 return .none;
1438 }
1439
1440 fn airUnreachable(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1441 _ = inst;
1442 try self.code.append(wasm.opcode(.@"unreachable"));
1443 return .none;
1444 }
1445
1446 fn airBitcast(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1447 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1448 return self.resolveInst(ty_op.operand);
1449 }
1450
1451 fn airStructFieldPtr(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1452 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
1453 const extra = self.air.extraData(Air.StructField, ty_pl.payload);
1454 const struct_ptr = self.resolveInst(extra.data.struct_operand);
1455 return structFieldPtr(struct_ptr, extra.data.field_index);
1456 }
1457 fn airStructFieldPtrIndex(self: *Context, inst: Air.Inst.Index, index: u32) InnerError!WValue {
1458 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1459 const struct_ptr = self.resolveInst(ty_op.operand);
1460 return structFieldPtr(struct_ptr, index);
1461 }
1462 fn structFieldPtr(struct_ptr: WValue, index: u32) InnerError!WValue {
1463 return WValue{ .local = struct_ptr.multi_value.index + index };
1464 }
1465
1466 fn airStructFieldVal(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1467 if (self.liveness.isUnused(inst)) return WValue.none;
1468
1469 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
1470 const extra = self.air.extraData(Air.StructField, ty_pl.payload).data;
1471 const struct_multivalue = self.resolveInst(extra.struct_operand).multi_value;
1472 return WValue{ .local = struct_multivalue.index + extra.field_index };
1473 }
1474
1475 fn airSwitchBr(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1476 // result type is always 'noreturn'
1477 const blocktype = wasm.block_empty;
1478 const pl_op = self.air.instructions.items(.data)[inst].pl_op;
1479 const target = self.resolveInst(pl_op.operand);
1480 const target_ty = self.air.typeOf(pl_op.operand);
1481 const switch_br = self.air.extraData(Air.SwitchBr, pl_op.payload);
1482 var extra_index: usize = switch_br.end;
1483 var case_i: u32 = 0;
1484
1485 // a list that maps each value with its value and body based on the order inside the list.
1486 const CaseValue = struct { integer: i32, value: Value };
1487 var case_list = try std.ArrayList(struct {
1488 values: []const CaseValue,
1489 body: []const Air.Inst.Index,
1490 }).initCapacity(self.gpa, switch_br.data.cases_len);
1491 defer for (case_list.items) |case| {
1492 self.gpa.free(case.values);
1493 } else case_list.deinit();
1494
1495 var lowest: i32 = 0;
1496 var highest: i32 = 0;
1497 while (case_i < switch_br.data.cases_len) : (case_i += 1) {
1498 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
1499 const items = @bitCast([]const Air.Inst.Ref, self.air.extra[case.end..][0..case.data.items_len]);
1500 const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len];
1501 extra_index = case.end + items.len + case_body.len;
1502 const values = try self.gpa.alloc(CaseValue, items.len);
1503 errdefer self.gpa.free(values);
1504
1505 for (items) |ref, i| {
1506 const item_val = self.air.value(ref).?;
1507 const int_val = self.valueAsI32(item_val, target_ty);
1508 if (int_val < lowest) {
1509 lowest = int_val;
1510 }
1511 if (int_val > highest) {
1512 highest = int_val;
1513 }
1514 values[i] = .{ .integer = int_val, .value = item_val };
1515 }
1516
1517 case_list.appendAssumeCapacity(.{ .values = values, .body = case_body });
1518 try self.startBlock(.block, blocktype, null);
1519 }
1520
1521 // When the highest and lowest values are seperated by '50',
1522 // we define it as sparse and use an if/else-chain, rather than a jump table.
1523 // When the target is an integer size larger than u32, we have no way to use the value
1524 // as an index, therefore we also use an if/else-chain for those cases.
1525 // TODO: Benchmark this to find a proper value, LLVM seems to draw the line at '40~45'.
1526 const is_sparse = highest - lowest > 50 or target_ty.bitSize(self.target) > 32;
1527
1528 const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len];
1529 const has_else_body = else_body.len != 0;
1530 if (has_else_body) {
1531 try self.startBlock(.block, blocktype, null);
1532 }
1533
1534 if (!is_sparse) {
1535 // Generate the jump table 'br_table' when the prongs are not sparse.
1536 // The value 'target' represents the index into the table.
1537 // Each index in the table represents a label to the branch
1538 // to jump to.
1539 try self.startBlock(.block, blocktype, null);
1540 try self.emitWValue(target);
1541 if (lowest < 0) {
1542 // since br_table works using indexes, starting from '0', we must ensure all values
1543 // we put inside, are atleast 0.
1544 try self.code.append(wasm.opcode(.i32_const));
1545 try leb.writeILEB128(self.code.writer(), lowest * -1);
1546 try self.code.append(wasm.opcode(.i32_add));
1547 }
1548 try self.code.append(wasm.opcode(.br_table));
1549 const depth = highest - lowest + @boolToInt(has_else_body);
1550 try leb.writeILEB128(self.code.writer(), depth);
1551 while (lowest <= highest) : (lowest += 1) {
1552 // idx represents the branch we jump to
1553 const idx = blk: {
1554 for (case_list.items) |case, idx| {
1555 for (case.values) |case_value| {
1556 if (case_value.integer == lowest) break :blk @intCast(u32, idx);
1557 }
1558 }
1559 break :blk if (has_else_body) case_i else unreachable;
1560 };
1561 try leb.writeULEB128(self.code.writer(), idx);
1562 } else if (has_else_body) {
1563 try leb.writeULEB128(self.code.writer(), @as(u32, case_i)); // default branch
1564 }
1565 try self.endBlock();
1566 }
1567
1568 const signedness: std.builtin.Signedness = blk: {
1569 // by default we tell the operand type is unsigned (i.e. bools and enum values)
1570 if (target_ty.zigTypeTag() != .Int) break :blk .unsigned;
1571
1572 // incase of an actual integer, we emit the correct signedness
1573 break :blk target_ty.intInfo(self.target).signedness;
1574 };
1575
1576 for (case_list.items) |case| {
1577 // when sparse, we use if/else-chain, so emit conditional checks
1578 if (is_sparse) {
1579 // for single value prong we can emit a simple if
1580 if (case.values.len == 1) {
1581 try self.emitWValue(target);
1582 try self.emitConstant(case.values[0].value, target_ty);
1583 const opcode = buildOpcode(.{
1584 .valtype1 = try self.typeToValtype(target_ty),
1585 .op = .ne, // not equal, because we want to jump out of this block if it does not match the condition.
1586 .signedness = signedness,
1587 });
1588 try self.code.append(wasm.opcode(opcode));
1589 try self.code.append(wasm.opcode(.br_if));
1590 try leb.writeULEB128(self.code.writer(), @as(u32, 0));
1591 } else {
1592 // in multi-value prongs we must check if any prongs match the target value.
1593 try self.startBlock(.block, blocktype, null);
1594 for (case.values) |value| {
1595 try self.emitWValue(target);
1596 try self.emitConstant(value.value, target_ty);
1597 const opcode = buildOpcode(.{
1598 .valtype1 = try self.typeToValtype(target_ty),
1599 .op = .eq,
1600 .signedness = signedness,
1601 });
1602 try self.code.append(wasm.opcode(opcode));
1603 try self.code.append(wasm.opcode(.br_if));
1604 try leb.writeULEB128(self.code.writer(), @as(u32, 0));
1605 }
1606 // value did not match any of the prong values
1607 try self.code.append(wasm.opcode(.br));
1608 try leb.writeULEB128(self.code.writer(), @as(u32, 1));
1609 try self.endBlock();
1610 }
1611 }
1612 try self.genBody(case.body);
1613 try self.endBlock();
1614 }
1615
1616 if (has_else_body) {
1617 try self.genBody(else_body);
1618 try self.endBlock();
1619 }
1620 return .none;
1621 }
1622
1623 fn airIsErr(self: *Context, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {
1624 const un_op = self.air.instructions.items(.data)[inst].un_op;
1625 const operand = self.resolveInst(un_op);
1626 const offset = self.code.items.len;
1627 const writer = self.code.writer();
1628
1629 // load the error value which is positioned at multi_value's index
1630 try self.emitWValue(.{ .local = operand.multi_value.index });
1631 // Compare the error value with '0'
1632 try writer.writeByte(wasm.opcode(.i32_const));
1633 try leb.writeILEB128(writer, @as(i32, 0));
1634
1635 try writer.writeByte(@enumToInt(opcode));
1636
1637 return WValue{ .code_offset = offset };
1638 }
1639
1640 fn airUnwrapErrUnionPayload(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1641 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1642 const operand = self.resolveInst(ty_op.operand);
1643 // The index of multi_value contains the error code. To get the initial index of the payload we get
1644 // the following index. Next, convert it to a `WValue.local`
1645 //
1646 // TODO: Check if payload is a type that requires a multi_value as well and emit that instead. i.e. a struct.
1647 return WValue{ .local = operand.multi_value.index + 1 };
1648 }
1649
1650 fn airWrapErrUnionPayload(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1651 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1652 return self.resolveInst(ty_op.operand);
1653 }
1654
1655 fn airIntcast(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1656 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1657 const ty = self.air.getRefType(ty_op.ty);
1658 const operand = self.resolveInst(ty_op.operand);
1659 const ref_ty = self.air.typeOf(ty_op.operand);
1660 const ref_info = ref_ty.intInfo(self.target);
1661 const op_bits = ref_info.bits;
1662 const wanted_bits = ty.intInfo(self.target).bits;
1663
1664 try self.emitWValue(operand);
1665 if (op_bits > 32 and wanted_bits <= 32) {
1666 try self.code.append(wasm.opcode(.i32_wrap_i64));
1667 } else if (op_bits <= 32 and wanted_bits > 32) {
1668 try self.code.append(wasm.opcode(switch (ref_info.signedness) {
1669 .signed => .i64_extend_i32_s,
1670 .unsigned => .i64_extend_i32_u,
1671 }));
1672 }
1673
1674 // other cases are no-op
1675 return .none;
1676 }
1677
1678 fn airIsNull(self: *Context, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {
1679 const un_op = self.air.instructions.items(.data)[inst].un_op;
1680 const operand = self.resolveInst(un_op);
1681 // const offset = self.code.items.len;
1682 const writer = self.code.writer();
1683
1684 // load the null value which is positioned at multi_value's index
1685 try self.emitWValue(.{ .local = operand.multi_value.index });
1686 // Compare the null value with '0'
1687 try writer.writeByte(wasm.opcode(.i32_const));
1688 try leb.writeILEB128(writer, @as(i32, 0));
1689
1690 try writer.writeByte(@enumToInt(opcode));
1691
1692 // we save the result in a new local
1693 const local = try self.allocLocal(Type.initTag(.i32));
1694 try writer.writeByte(wasm.opcode(.local_set));
1695 try leb.writeULEB128(writer, local.local);
1696
1697 return local;
1698 }
1699
1700 fn airOptionalPayload(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1701 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1702 const operand = self.resolveInst(ty_op.operand);
1703 return WValue{ .local = operand.multi_value.index + 1 };
1704 }
1705
1706 fn airOptionalPayloadPtrSet(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1707 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1708 const operand = self.resolveInst(ty_op.operand);
1709 _ = operand;
1710 return self.fail("TODO - wasm codegen for optional_payload_ptr_set", .{});
1711 }
1712
1713 fn airWrapOptional(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1714 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1715 return self.resolveInst(ty_op.operand);
1716 }
1717};
src/link/Wasm.zig+47-43
......@@ -12,7 +12,7 @@ const wasm = std.wasm;
1212
1313const Module = @import("../Module.zig");
1414const Compilation = @import("../Compilation.zig");
15const codegen = @import("../codegen/wasm.zig");
15const CodeGen = @import("../arch/wasm/CodeGen.zig");
1616const link = @import("../link.zig");
1717const trace = @import("../tracy.zig").trace;
1818const build_options = @import("build_options");
......@@ -54,6 +54,8 @@ offset_table_free_list: std.ArrayListUnmanaged(u32) = .{},
5454/// This is ment for bookkeeping so we can safely cleanup all codegen memory
5555/// when calling `deinit`
5656symbols: std.ArrayListUnmanaged(*Module.Decl) = .{},
57/// List of symbol indexes which are free to be used.
58symbols_free_list: std.ArrayListUnmanaged(u32) = .{},
5759
5860pub const FnData = struct {
5961 /// Generated code for the type of the function
......@@ -62,7 +64,8 @@ pub const FnData = struct {
6264 code: std.ArrayListUnmanaged(u8),
6365 /// Locations in the generated code where function indexes must be filled in.
6466 /// This must be kept ordered by offset.
65 idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: *Module.Decl }),
67 /// `decl` is the symbol_index of the target.
68 idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: u32 }),
6669
6770 pub const empty: FnData = .{
6871 .functype = .{},
......@@ -156,7 +159,18 @@ pub fn deinit(self: *Wasm) void {
156159 if (build_options.have_llvm) {
157160 if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator);
158161 }
159 for (self.symbols.items) |decl| {
162
163 for (self.symbols.items) |decl, symbol_index| {
164 // Check if we already freed all memory for the symbol
165 // TODO: Audit this when we refactor the linker.
166 var already_freed = false;
167 for (self.symbols_free_list.items) |index| {
168 if (symbol_index == index) {
169 already_freed = true;
170 break;
171 }
172 }
173 if (already_freed) continue;
160174 decl.fn_link.wasm.functype.deinit(self.base.allocator);
161175 decl.fn_link.wasm.code.deinit(self.base.allocator);
162176 decl.fn_link.wasm.idx_refs.deinit(self.base.allocator);
......@@ -167,6 +181,7 @@ pub fn deinit(self: *Wasm) void {
167181 self.offset_table.deinit(self.base.allocator);
168182 self.offset_table_free_list.deinit(self.base.allocator);
169183 self.symbols.deinit(self.base.allocator);
184 self.symbols_free_list.deinit(self.base.allocator);
170185}
171186
172187pub fn allocateDeclIndexes(self: *Wasm, decl: *Module.Decl) !void {
......@@ -178,9 +193,6 @@ pub fn allocateDeclIndexes(self: *Wasm, decl: *Module.Decl) !void {
178193 const block = &decl.link.wasm;
179194 block.init = true;
180195
181 block.symbol_index = @intCast(u32, self.symbols.items.len);
182 self.symbols.appendAssumeCapacity(decl);
183
184196 if (self.offset_table_free_list.popOrNull()) |index| {
185197 block.offset_index = index;
186198 } else {
......@@ -188,6 +200,14 @@ pub fn allocateDeclIndexes(self: *Wasm, decl: *Module.Decl) !void {
188200 _ = self.offset_table.addOneAssumeCapacity();
189201 }
190202
203 if (self.symbols_free_list.popOrNull()) |index| {
204 block.symbol_index = index;
205 self.symbols.items[block.symbol_index] = decl;
206 } else {
207 block.symbol_index = @intCast(u32, self.symbols.items.len);
208 self.symbols.appendAssumeCapacity(decl);
209 }
210
191211 self.offset_table.items[block.offset_index] = 0;
192212
193213 if (decl.ty.zigTypeTag() == .Fn) {
......@@ -215,7 +235,7 @@ pub fn updateFunc(self: *Wasm, module: *Module, func: *Module.Fn, air: Air, live
215235 fn_data.code.items.len = 0;
216236 fn_data.idx_refs.items.len = 0;
217237
218 var context = codegen.Context{
238 var codegen: CodeGen = .{
219239 .gpa = self.base.allocator,
220240 .air = air,
221241 .liveness = liveness,
......@@ -226,20 +246,21 @@ pub fn updateFunc(self: *Wasm, module: *Module, func: *Module.Fn, air: Air, live
226246 .err_msg = undefined,
227247 .locals = .{},
228248 .target = self.base.options.target,
249 .bin_file = &self.base,
229250 .global_error_set = self.base.options.module.?.global_error_set,
230251 };
231 defer context.deinit();
252 defer codegen.deinit();
232253
233254 // generate the 'code' section for the function declaration
234 const result = context.genFunc() catch |err| switch (err) {
255 const result = codegen.genFunc() catch |err| switch (err) {
235256 error.CodegenFail => {
236257 decl.analysis = .codegen_failure;
237 try module.failed_decls.put(module.gpa, decl, context.err_msg);
258 try module.failed_decls.put(module.gpa, decl, codegen.err_msg);
238259 return;
239260 },
240261 else => |e| return e,
241262 };
242 return self.finishUpdateDecl(decl, result, &context);
263 return self.finishUpdateDecl(decl, result, &codegen);
243264}
244265
245266// Generate code for the Decl, storing it in memory to be later written to
......@@ -259,7 +280,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
259280 fn_data.code.items.len = 0;
260281 fn_data.idx_refs.items.len = 0;
261282
262 var context = codegen.Context{
283 var codegen: CodeGen = .{
263284 .gpa = self.base.allocator,
264285 .air = undefined,
265286 .liveness = undefined,
......@@ -270,28 +291,29 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
270291 .err_msg = undefined,
271292 .locals = .{},
272293 .target = self.base.options.target,
294 .bin_file = &self.base,
273295 .global_error_set = self.base.options.module.?.global_error_set,
274296 };
275 defer context.deinit();
297 defer codegen.deinit();
276298
277299 // generate the 'code' section for the function declaration
278 const result = context.gen(decl.ty, decl.val) catch |err| switch (err) {
300 const result = codegen.gen(decl.ty, decl.val) catch |err| switch (err) {
279301 error.CodegenFail => {
280302 decl.analysis = .codegen_failure;
281 try module.failed_decls.put(module.gpa, decl, context.err_msg);
303 try module.failed_decls.put(module.gpa, decl, codegen.err_msg);
282304 return;
283305 },
284306 else => |e| return e,
285307 };
286308
287 return self.finishUpdateDecl(decl, result, &context);
309 return self.finishUpdateDecl(decl, result, &codegen);
288310}
289311
290fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, result: codegen.Result, context: *codegen.Context) !void {
312fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, result: CodeGen.Result, codegen: *CodeGen) !void {
291313 const fn_data: *FnData = &decl.fn_link.wasm;
292314
293 fn_data.code = context.code.toUnmanaged();
294 fn_data.functype = context.func_type_data.toUnmanaged();
315 fn_data.code = codegen.code.toUnmanaged();
316 fn_data.functype = codegen.func_type_data.toUnmanaged();
295317
296318 const code: []const u8 = switch (result) {
297319 .appended => @as([]const u8, fn_data.code.items),
......@@ -299,14 +321,7 @@ fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, result: codegen.Result, con
299321 };
300322
301323 const block = &decl.link.wasm;
302 if (decl.ty.zigTypeTag() == .Fn) {
303 // as locals are patched afterwards, the offsets of funcidx's are off,
304 // here we update them to correct them
305 for (fn_data.idx_refs.items) |*func| {
306 // For each local, add 6 bytes (count + type)
307 func.offset += @intCast(u32, context.locals.items.len * 6);
308 }
309 } else {
324 if (decl.ty.zigTypeTag() != .Fn) {
310325 block.size = @intCast(u32, code.len);
311326 block.data = code.ptr;
312327 }
......@@ -359,18 +374,13 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
359374 block.unplug();
360375
361376 self.offset_table_free_list.append(self.base.allocator, decl.link.wasm.offset_index) catch {};
362 _ = self.symbols.swapRemove(block.symbol_index);
363
364 // update symbol_index as we swap removed the last symbol into the removed's position
365 if (block.symbol_index < self.symbols.items.len)
366 self.symbols.items[block.symbol_index].link.wasm.symbol_index = block.symbol_index;
377 self.symbols_free_list.append(self.base.allocator, block.symbol_index) catch {};
367378
368379 block.init = false;
369380
370381 decl.fn_link.wasm.functype.deinit(self.base.allocator);
371382 decl.fn_link.wasm.code.deinit(self.base.allocator);
372383 decl.fn_link.wasm.idx_refs.deinit(self.base.allocator);
373 decl.fn_link.wasm = undefined;
374384}
375385
376386pub fn flush(self: *Wasm, comp: *Compilation) !void {
......@@ -553,18 +563,12 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
553563
554564 // Write the already generated code to the file, inserting
555565 // function indexes where required.
556 var current: u32 = 0;
557566 for (fn_data.idx_refs.items) |idx_ref| {
558 try writer.writeAll(fn_data.code.items[current..idx_ref.offset]);
559 current = idx_ref.offset;
560 // Use a fixed width here to make calculating the code size
561 // in codegen.wasm.gen() simpler.
562 var buf: [5]u8 = undefined;
563 leb.writeUnsignedFixed(5, &buf, self.getFuncidx(idx_ref.decl).?);
564 try writer.writeAll(&buf);
567 const relocatable_decl = self.symbols.items[idx_ref.decl];
568 const index = self.getFuncidx(relocatable_decl).?;
569 leb.writeUnsignedFixed(5, fn_data.code.items[idx_ref.offset..][0..5], index);
565570 }
566
567 try writer.writeAll(fn_data.code.items[current..]);
571 try writer.writeAll(fn_data.code.items);
568572 }
569573 try writeVecSectionHeader(
570574 file,