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    // write the locals in the prologue of the function body
33    // before we emit the function body when lowering MIR
34    try emit.emitLocals();
35
36    for (mir_tags) |tag, index| {
37        const inst = @intCast(u32, index);
38        switch (tag) {
39            // block instructions
40            .block => try emit.emitBlock(tag, inst),
41            .loop => try emit.emitBlock(tag, inst),
42
43            // branch instructions
44            .br_if => try emit.emitLabel(tag, inst),
45            .br_table => try emit.emitBrTable(inst),
46            .br => try emit.emitLabel(tag, inst),
47
48            // relocatables
49            .call => try emit.emitCall(inst),
50            .call_indirect => try emit.emitCallIndirect(inst),
51            .global_get => try emit.emitGlobal(tag, inst),
52            .global_set => try emit.emitGlobal(tag, inst),
53            .function_index => try emit.emitFunctionIndex(inst),
54            .memory_address => try emit.emitMemAddress(inst),
55
56            // immediates
57            .f32_const => try emit.emitFloat32(inst),
58            .f64_const => try emit.emitFloat64(inst),
59            .i32_const => try emit.emitImm32(inst),
60            .i64_const => try emit.emitImm64(inst),
61
62            // memory instructions
63            .i32_load => try emit.emitMemArg(tag, inst),
64            .i64_load => try emit.emitMemArg(tag, inst),
65            .f32_load => try emit.emitMemArg(tag, inst),
66            .f64_load => try emit.emitMemArg(tag, inst),
67            .i32_load8_s => try emit.emitMemArg(tag, inst),
68            .i32_load8_u => try emit.emitMemArg(tag, inst),
69            .i32_load16_s => try emit.emitMemArg(tag, inst),
70            .i32_load16_u => try emit.emitMemArg(tag, inst),
71            .i64_load8_s => try emit.emitMemArg(tag, inst),
72            .i64_load8_u => try emit.emitMemArg(tag, inst),
73            .i64_load16_s => try emit.emitMemArg(tag, inst),
74            .i64_load16_u => try emit.emitMemArg(tag, inst),
75            .i64_load32_s => try emit.emitMemArg(tag, inst),
76            .i64_load32_u => try emit.emitMemArg(tag, inst),
77            .i32_store => try emit.emitMemArg(tag, inst),
78            .i64_store => try emit.emitMemArg(tag, inst),
79            .f32_store => try emit.emitMemArg(tag, inst),
80            .f64_store => try emit.emitMemArg(tag, inst),
81            .i32_store8 => try emit.emitMemArg(tag, inst),
82            .i32_store16 => try emit.emitMemArg(tag, inst),
83            .i64_store8 => try emit.emitMemArg(tag, inst),
84            .i64_store16 => try emit.emitMemArg(tag, inst),
85            .i64_store32 => try emit.emitMemArg(tag, inst),
86
87            // Instructions with an index that do not require relocations
88            .local_get => try emit.emitLabel(tag, inst),
89            .local_set => try emit.emitLabel(tag, inst),
90            .local_tee => try emit.emitLabel(tag, inst),
91            .memory_grow => try emit.emitLabel(tag, inst),
92
93            // no-ops
94            .end => try emit.emitTag(tag),
95            .memory_size => try emit.emitTag(tag),
96            .@"return" => try emit.emitTag(tag),
97            .@"unreachable" => try emit.emitTag(tag),
98
99            // arithmetic
100            .i32_eqz => try emit.emitTag(tag),
101            .i32_eq => try emit.emitTag(tag),
102            .i32_ne => try emit.emitTag(tag),
103            .i32_lt_s => try emit.emitTag(tag),
104            .i32_lt_u => try emit.emitTag(tag),
105            .i32_gt_s => try emit.emitTag(tag),
106            .i32_gt_u => try emit.emitTag(tag),
107            .i32_le_s => try emit.emitTag(tag),
108            .i32_le_u => try emit.emitTag(tag),
109            .i32_ge_s => try emit.emitTag(tag),
110            .i32_ge_u => try emit.emitTag(tag),
111            .i64_eqz => try emit.emitTag(tag),
112            .i64_eq => try emit.emitTag(tag),
113            .i64_ne => try emit.emitTag(tag),
114            .i64_lt_s => try emit.emitTag(tag),
115            .i64_lt_u => try emit.emitTag(tag),
116            .i64_gt_s => try emit.emitTag(tag),
117            .i64_gt_u => try emit.emitTag(tag),
118            .i64_le_s => try emit.emitTag(tag),
119            .i64_le_u => try emit.emitTag(tag),
120            .i64_ge_s => try emit.emitTag(tag),
121            .i64_ge_u => try emit.emitTag(tag),
122            .f32_eq => try emit.emitTag(tag),
123            .f32_ne => try emit.emitTag(tag),
124            .f32_lt => try emit.emitTag(tag),
125            .f32_gt => try emit.emitTag(tag),
126            .f32_le => try emit.emitTag(tag),
127            .f32_ge => try emit.emitTag(tag),
128            .f64_eq => try emit.emitTag(tag),
129            .f64_ne => try emit.emitTag(tag),
130            .f64_lt => try emit.emitTag(tag),
131            .f64_gt => try emit.emitTag(tag),
132            .f64_le => try emit.emitTag(tag),
133            .f64_ge => try emit.emitTag(tag),
134            .i32_add => try emit.emitTag(tag),
135            .i32_sub => try emit.emitTag(tag),
136            .i32_mul => try emit.emitTag(tag),
137            .i32_div_s => try emit.emitTag(tag),
138            .i32_div_u => try emit.emitTag(tag),
139            .i32_and => try emit.emitTag(tag),
140            .i32_or => try emit.emitTag(tag),
141            .i32_xor => try emit.emitTag(tag),
142            .i32_shl => try emit.emitTag(tag),
143            .i32_shr_s => try emit.emitTag(tag),
144            .i32_shr_u => try emit.emitTag(tag),
145            .i64_add => try emit.emitTag(tag),
146            .i64_sub => try emit.emitTag(tag),
147            .i64_mul => try emit.emitTag(tag),
148            .i64_div_s => try emit.emitTag(tag),
149            .i64_div_u => try emit.emitTag(tag),
150            .i64_and => try emit.emitTag(tag),
151            .i64_or => try emit.emitTag(tag),
152            .i64_xor => try emit.emitTag(tag),
153            .i64_shl => try emit.emitTag(tag),
154            .i64_shr_s => try emit.emitTag(tag),
155            .i64_shr_u => try emit.emitTag(tag),
156            .i32_wrap_i64 => try emit.emitTag(tag),
157            .i64_extend_i32_s => try emit.emitTag(tag),
158            .i64_extend_i32_u => try emit.emitTag(tag),
159            .i32_extend8_s => try emit.emitTag(tag),
160            .i32_extend16_s => try emit.emitTag(tag),
161            .i64_extend8_s => try emit.emitTag(tag),
162            .i64_extend16_s => try emit.emitTag(tag),
163            .i64_extend32_s => try emit.emitTag(tag),
164        }
165    }
166}
167
168fn offset(self: Emit) u32 {
169    return @intCast(u32, self.code.items.len);
170}
171
172fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError {
173    @setCold(true);
174    std.debug.assert(emit.error_msg == null);
175    // TODO: Determine the source location.
176    emit.error_msg = try Module.ErrorMsg.create(emit.bin_file.allocator, emit.decl.srcLoc(), format, args);
177    return error.EmitFail;
178}
179
180fn emitLocals(emit: *Emit) !void {
181    const writer = emit.code.writer();
182    try leb128.writeULEB128(writer, @intCast(u32, emit.locals.len));
183    // emit the actual locals amount
184    for (emit.locals) |local| {
185        try leb128.writeULEB128(writer, @as(u32, 1));
186        try writer.writeByte(local);
187    }
188}
189
190fn emitTag(emit: *Emit, tag: Mir.Inst.Tag) !void {
191    try emit.code.append(@enumToInt(tag));
192}
193
194fn emitBlock(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
195    const block_type = emit.mir.instructions.items(.data)[inst].block_type;
196    try emit.code.append(@enumToInt(tag));
197    try emit.code.append(block_type);
198}
199
200fn emitBrTable(emit: *Emit, inst: Mir.Inst.Index) !void {
201    const extra_index = emit.mir.instructions.items(.data)[inst].payload;
202    const extra = emit.mir.extraData(Mir.JumpTable, extra_index);
203    const labels = emit.mir.extra[extra.end..][0..extra.data.length];
204    const writer = emit.code.writer();
205
206    try emit.code.append(std.wasm.opcode(.br_table));
207    try leb128.writeULEB128(writer, extra.data.length - 1); // Default label is not part of length/depth
208    for (labels) |label| {
209        try leb128.writeULEB128(writer, label);
210    }
211}
212
213fn emitLabel(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
214    const label = emit.mir.instructions.items(.data)[inst].label;
215    try emit.code.append(@enumToInt(tag));
216    try leb128.writeULEB128(emit.code.writer(), label);
217}
218
219fn emitGlobal(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
220    const label = emit.mir.instructions.items(.data)[inst].label;
221    try emit.code.append(@enumToInt(tag));
222    var buf: [5]u8 = undefined;
223    leb128.writeUnsignedFixed(5, &buf, label);
224    const global_offset = emit.offset();
225    try emit.code.appendSlice(&buf);
226
227    try emit.decl.link.wasm.relocs.append(emit.bin_file.allocator, .{
228        .index = label,
229        .offset = global_offset,
230        .relocation_type = .R_WASM_GLOBAL_INDEX_LEB,
231    });
232}
233
234fn emitImm32(emit: *Emit, inst: Mir.Inst.Index) !void {
235    const value: i32 = emit.mir.instructions.items(.data)[inst].imm32;
236    try emit.code.append(std.wasm.opcode(.i32_const));
237    try leb128.writeILEB128(emit.code.writer(), value);
238}
239
240fn emitImm64(emit: *Emit, inst: Mir.Inst.Index) !void {
241    const extra_index = emit.mir.instructions.items(.data)[inst].payload;
242    const value = emit.mir.extraData(Mir.Imm64, extra_index);
243    try emit.code.append(std.wasm.opcode(.i64_const));
244    try leb128.writeILEB128(emit.code.writer(), @bitCast(i64, value.data.toU64()));
245}
246
247fn emitFloat32(emit: *Emit, inst: Mir.Inst.Index) !void {
248    const value: f32 = emit.mir.instructions.items(.data)[inst].float32;
249    try emit.code.append(std.wasm.opcode(.f32_const));
250    try emit.code.writer().writeIntLittle(u32, @bitCast(u32, value));
251}
252
253fn emitFloat64(emit: *Emit, inst: Mir.Inst.Index) !void {
254    const extra_index = emit.mir.instructions.items(.data)[inst].payload;
255    const value = emit.mir.extraData(Mir.Float64, extra_index);
256    try emit.code.append(std.wasm.opcode(.f64_const));
257    try emit.code.writer().writeIntLittle(u64, value.data.toU64());
258}
259
260fn emitMemArg(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
261    const extra_index = emit.mir.instructions.items(.data)[inst].payload;
262    const mem_arg = emit.mir.extraData(Mir.MemArg, extra_index).data;
263    try emit.code.append(@enumToInt(tag));
264
265    // wasm encodes alignment as power of 2, rather than natural alignment
266    const encoded_alignment = @ctz(u32, mem_arg.alignment);
267    try leb128.writeULEB128(emit.code.writer(), encoded_alignment);
268    try leb128.writeULEB128(emit.code.writer(), mem_arg.offset);
269}
270
271fn emitCall(emit: *Emit, inst: Mir.Inst.Index) !void {
272    const label = emit.mir.instructions.items(.data)[inst].label;
273    try emit.code.append(std.wasm.opcode(.call));
274    const call_offset = emit.offset();
275    var buf: [5]u8 = undefined;
276    leb128.writeUnsignedFixed(5, &buf, label);
277    try emit.code.appendSlice(&buf);
278
279    try emit.decl.link.wasm.relocs.append(emit.bin_file.allocator, .{
280        .offset = call_offset,
281        .index = label,
282        .relocation_type = .R_WASM_FUNCTION_INDEX_LEB,
283    });
284}
285
286fn emitCallIndirect(emit: *Emit, inst: Mir.Inst.Index) !void {
287    const label = emit.mir.instructions.items(.data)[inst].label;
288    try emit.code.append(std.wasm.opcode(.call_indirect));
289    try leb128.writeULEB128(emit.code.writer(), @as(u32, 0)); // TODO: Emit relocation for table index
290    try leb128.writeULEB128(emit.code.writer(), label);
291}
292
293fn emitFunctionIndex(emit: *Emit, inst: Mir.Inst.Index) !void {
294    const symbol_index = emit.mir.instructions.items(.data)[inst].label;
295    try emit.code.append(std.wasm.opcode(.i32_const));
296    const index_offset = emit.offset();
297    var buf: [5]u8 = undefined;
298    leb128.writeUnsignedFixed(5, &buf, symbol_index);
299    try emit.code.appendSlice(&buf);
300
301    try emit.decl.link.wasm.relocs.append(emit.bin_file.allocator, .{
302        .offset = index_offset,
303        .index = symbol_index,
304        .relocation_type = .R_WASM_TABLE_INDEX_SLEB,
305    });
306}
307
308fn emitMemAddress(emit: *Emit, inst: Mir.Inst.Index) !void {
309    const symbol_index = emit.mir.instructions.items(.data)[inst].label;
310    try emit.code.append(std.wasm.opcode(.i32_const));
311    const mem_offset = emit.offset();
312    var buf: [5]u8 = undefined;
313    leb128.writeUnsignedFixed(5, &buf, symbol_index);
314    try emit.code.appendSlice(&buf);
315
316    try emit.decl.link.wasm.relocs.append(emit.bin_file.allocator, .{
317        .offset = mem_offset,
318        .index = symbol_index,
319        .relocation_type = .R_WASM_MEMORY_ADDR_LEB,
320    });
321}
322