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