mirror of
https://github.com/ziglang/zig.git
synced 2026-01-10 09:25:11 +00:00
406 lines
17 KiB
Zig
406 lines
17 KiB
Zig
//! Contains all logic to lower wasm MIR into its binary
|
|
//! or textual representation.
|
|
|
|
const Emit = @This();
|
|
const std = @import("std");
|
|
const Mir = @import("Mir.zig");
|
|
const link = @import("../../link.zig");
|
|
const Module = @import("../../Module.zig");
|
|
const leb128 = std.leb;
|
|
|
|
/// Contains our list of instructions
|
|
mir: Mir,
|
|
/// Reference to the file handler
|
|
bin_file: *link.File,
|
|
/// Possible error message. When set, the value is allocated and
|
|
/// must be freed manually.
|
|
error_msg: ?*Module.ErrorMsg = null,
|
|
/// The binary representation that will be emit by this module.
|
|
code: *std.ArrayList(u8),
|
|
/// List of allocated locals.
|
|
locals: []const u8,
|
|
/// The declaration that code is being generated for.
|
|
decl: *Module.Decl,
|
|
|
|
const InnerError = error{
|
|
OutOfMemory,
|
|
EmitFail,
|
|
};
|
|
|
|
pub fn emitMir(emit: *Emit) InnerError!void {
|
|
const mir_tags = emit.mir.instructions.items(.tag);
|
|
// write the locals in the prologue of the function body
|
|
// before we emit the function body when lowering MIR
|
|
try emit.emitLocals();
|
|
|
|
for (mir_tags) |tag, index| {
|
|
const inst = @intCast(u32, index);
|
|
switch (tag) {
|
|
// block instructions
|
|
.block => try emit.emitBlock(tag, inst),
|
|
.loop => try emit.emitBlock(tag, inst),
|
|
|
|
// branch instructions
|
|
.br_if => try emit.emitLabel(tag, inst),
|
|
.br_table => try emit.emitBrTable(inst),
|
|
.br => try emit.emitLabel(tag, inst),
|
|
|
|
// relocatables
|
|
.call => try emit.emitCall(inst),
|
|
.call_indirect => try emit.emitCallIndirect(inst),
|
|
.global_get => try emit.emitGlobal(tag, inst),
|
|
.global_set => try emit.emitGlobal(tag, inst),
|
|
.function_index => try emit.emitFunctionIndex(inst),
|
|
.memory_address => try emit.emitMemAddress(inst),
|
|
|
|
// immediates
|
|
.f32_const => try emit.emitFloat32(inst),
|
|
.f64_const => try emit.emitFloat64(inst),
|
|
.i32_const => try emit.emitImm32(inst),
|
|
.i64_const => try emit.emitImm64(inst),
|
|
|
|
// memory instructions
|
|
.i32_load => try emit.emitMemArg(tag, inst),
|
|
.i64_load => try emit.emitMemArg(tag, inst),
|
|
.f32_load => try emit.emitMemArg(tag, inst),
|
|
.f64_load => try emit.emitMemArg(tag, inst),
|
|
.i32_load8_s => try emit.emitMemArg(tag, inst),
|
|
.i32_load8_u => try emit.emitMemArg(tag, inst),
|
|
.i32_load16_s => try emit.emitMemArg(tag, inst),
|
|
.i32_load16_u => try emit.emitMemArg(tag, inst),
|
|
.i64_load8_s => try emit.emitMemArg(tag, inst),
|
|
.i64_load8_u => try emit.emitMemArg(tag, inst),
|
|
.i64_load16_s => try emit.emitMemArg(tag, inst),
|
|
.i64_load16_u => try emit.emitMemArg(tag, inst),
|
|
.i64_load32_s => try emit.emitMemArg(tag, inst),
|
|
.i64_load32_u => try emit.emitMemArg(tag, inst),
|
|
.i32_store => try emit.emitMemArg(tag, inst),
|
|
.i64_store => try emit.emitMemArg(tag, inst),
|
|
.f32_store => try emit.emitMemArg(tag, inst),
|
|
.f64_store => try emit.emitMemArg(tag, inst),
|
|
.i32_store8 => try emit.emitMemArg(tag, inst),
|
|
.i32_store16 => try emit.emitMemArg(tag, inst),
|
|
.i64_store8 => try emit.emitMemArg(tag, inst),
|
|
.i64_store16 => try emit.emitMemArg(tag, inst),
|
|
.i64_store32 => try emit.emitMemArg(tag, inst),
|
|
|
|
// Instructions with an index that do not require relocations
|
|
.local_get => try emit.emitLabel(tag, inst),
|
|
.local_set => try emit.emitLabel(tag, inst),
|
|
.local_tee => try emit.emitLabel(tag, inst),
|
|
.memory_grow => try emit.emitLabel(tag, inst),
|
|
.memory_size => try emit.emitLabel(tag, inst),
|
|
|
|
// no-ops
|
|
.end => try emit.emitTag(tag),
|
|
.@"return" => try emit.emitTag(tag),
|
|
.@"unreachable" => try emit.emitTag(tag),
|
|
|
|
// arithmetic
|
|
.i32_eqz => try emit.emitTag(tag),
|
|
.i32_eq => try emit.emitTag(tag),
|
|
.i32_ne => try emit.emitTag(tag),
|
|
.i32_lt_s => try emit.emitTag(tag),
|
|
.i32_lt_u => try emit.emitTag(tag),
|
|
.i32_gt_s => try emit.emitTag(tag),
|
|
.i32_gt_u => try emit.emitTag(tag),
|
|
.i32_le_s => try emit.emitTag(tag),
|
|
.i32_le_u => try emit.emitTag(tag),
|
|
.i32_ge_s => try emit.emitTag(tag),
|
|
.i32_ge_u => try emit.emitTag(tag),
|
|
.i64_eqz => try emit.emitTag(tag),
|
|
.i64_eq => try emit.emitTag(tag),
|
|
.i64_ne => try emit.emitTag(tag),
|
|
.i64_lt_s => try emit.emitTag(tag),
|
|
.i64_lt_u => try emit.emitTag(tag),
|
|
.i64_gt_s => try emit.emitTag(tag),
|
|
.i64_gt_u => try emit.emitTag(tag),
|
|
.i64_le_s => try emit.emitTag(tag),
|
|
.i64_le_u => try emit.emitTag(tag),
|
|
.i64_ge_s => try emit.emitTag(tag),
|
|
.i64_ge_u => try emit.emitTag(tag),
|
|
.f32_eq => try emit.emitTag(tag),
|
|
.f32_ne => try emit.emitTag(tag),
|
|
.f32_lt => try emit.emitTag(tag),
|
|
.f32_gt => try emit.emitTag(tag),
|
|
.f32_le => try emit.emitTag(tag),
|
|
.f32_ge => try emit.emitTag(tag),
|
|
.f64_eq => try emit.emitTag(tag),
|
|
.f64_ne => try emit.emitTag(tag),
|
|
.f64_lt => try emit.emitTag(tag),
|
|
.f64_gt => try emit.emitTag(tag),
|
|
.f64_le => try emit.emitTag(tag),
|
|
.f64_ge => try emit.emitTag(tag),
|
|
.i32_add => try emit.emitTag(tag),
|
|
.i32_sub => try emit.emitTag(tag),
|
|
.i32_mul => try emit.emitTag(tag),
|
|
.i32_div_s => try emit.emitTag(tag),
|
|
.i32_div_u => try emit.emitTag(tag),
|
|
.i32_and => try emit.emitTag(tag),
|
|
.i32_or => try emit.emitTag(tag),
|
|
.i32_xor => try emit.emitTag(tag),
|
|
.i32_shl => try emit.emitTag(tag),
|
|
.i32_shr_s => try emit.emitTag(tag),
|
|
.i32_shr_u => try emit.emitTag(tag),
|
|
.i64_add => try emit.emitTag(tag),
|
|
.i64_sub => try emit.emitTag(tag),
|
|
.i64_mul => try emit.emitTag(tag),
|
|
.i64_div_s => try emit.emitTag(tag),
|
|
.i64_div_u => try emit.emitTag(tag),
|
|
.i64_and => try emit.emitTag(tag),
|
|
.i64_or => try emit.emitTag(tag),
|
|
.i64_xor => try emit.emitTag(tag),
|
|
.i64_shl => try emit.emitTag(tag),
|
|
.i64_shr_s => try emit.emitTag(tag),
|
|
.i64_shr_u => try emit.emitTag(tag),
|
|
.f32_abs => try emit.emitTag(tag),
|
|
.f32_neg => try emit.emitTag(tag),
|
|
.f32_ceil => try emit.emitTag(tag),
|
|
.f32_floor => try emit.emitTag(tag),
|
|
.f32_trunc => try emit.emitTag(tag),
|
|
.f32_nearest => try emit.emitTag(tag),
|
|
.f32_sqrt => try emit.emitTag(tag),
|
|
.f32_add => try emit.emitTag(tag),
|
|
.f32_sub => try emit.emitTag(tag),
|
|
.f32_mul => try emit.emitTag(tag),
|
|
.f32_div => try emit.emitTag(tag),
|
|
.f32_min => try emit.emitTag(tag),
|
|
.f32_max => try emit.emitTag(tag),
|
|
.f32_copysign => try emit.emitTag(tag),
|
|
.f64_abs => try emit.emitTag(tag),
|
|
.f64_neg => try emit.emitTag(tag),
|
|
.f64_ceil => try emit.emitTag(tag),
|
|
.f64_floor => try emit.emitTag(tag),
|
|
.f64_trunc => try emit.emitTag(tag),
|
|
.f64_nearest => try emit.emitTag(tag),
|
|
.f64_sqrt => try emit.emitTag(tag),
|
|
.f64_add => try emit.emitTag(tag),
|
|
.f64_sub => try emit.emitTag(tag),
|
|
.f64_mul => try emit.emitTag(tag),
|
|
.f64_div => try emit.emitTag(tag),
|
|
.f64_min => try emit.emitTag(tag),
|
|
.f64_max => try emit.emitTag(tag),
|
|
.f64_copysign => try emit.emitTag(tag),
|
|
.i32_wrap_i64 => try emit.emitTag(tag),
|
|
.i64_extend_i32_s => try emit.emitTag(tag),
|
|
.i64_extend_i32_u => try emit.emitTag(tag),
|
|
.i32_extend8_s => try emit.emitTag(tag),
|
|
.i32_extend16_s => try emit.emitTag(tag),
|
|
.i64_extend8_s => try emit.emitTag(tag),
|
|
.i64_extend16_s => try emit.emitTag(tag),
|
|
.i64_extend32_s => try emit.emitTag(tag),
|
|
.f32_demote_f64 => try emit.emitTag(tag),
|
|
.f64_promote_f32 => try emit.emitTag(tag),
|
|
.i32_reinterpret_f32 => try emit.emitTag(tag),
|
|
.i64_reinterpret_f64 => try emit.emitTag(tag),
|
|
.f32_reinterpret_i32 => try emit.emitTag(tag),
|
|
.f64_reinterpret_i64 => try emit.emitTag(tag),
|
|
.i32_trunc_f32_s => try emit.emitTag(tag),
|
|
.i32_trunc_f32_u => try emit.emitTag(tag),
|
|
.i32_trunc_f64_s => try emit.emitTag(tag),
|
|
.i32_trunc_f64_u => try emit.emitTag(tag),
|
|
.i64_trunc_f32_s => try emit.emitTag(tag),
|
|
.i64_trunc_f32_u => try emit.emitTag(tag),
|
|
.i64_trunc_f64_s => try emit.emitTag(tag),
|
|
.i64_trunc_f64_u => try emit.emitTag(tag),
|
|
.i32_rem_s => try emit.emitTag(tag),
|
|
.i32_rem_u => try emit.emitTag(tag),
|
|
.i64_rem_s => try emit.emitTag(tag),
|
|
.i64_rem_u => try emit.emitTag(tag),
|
|
|
|
.extended => try emit.emitExtended(inst),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn offset(self: Emit) u32 {
|
|
return @intCast(u32, self.code.items.len);
|
|
}
|
|
|
|
fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError {
|
|
@setCold(true);
|
|
std.debug.assert(emit.error_msg == null);
|
|
// TODO: Determine the source location.
|
|
emit.error_msg = try Module.ErrorMsg.create(emit.bin_file.allocator, emit.decl.srcLoc(), format, args);
|
|
return error.EmitFail;
|
|
}
|
|
|
|
fn emitLocals(emit: *Emit) !void {
|
|
const writer = emit.code.writer();
|
|
try leb128.writeULEB128(writer, @intCast(u32, emit.locals.len));
|
|
// emit the actual locals amount
|
|
for (emit.locals) |local| {
|
|
try leb128.writeULEB128(writer, @as(u32, 1));
|
|
try writer.writeByte(local);
|
|
}
|
|
}
|
|
|
|
fn emitTag(emit: *Emit, tag: Mir.Inst.Tag) !void {
|
|
try emit.code.append(@enumToInt(tag));
|
|
}
|
|
|
|
fn emitBlock(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
|
|
const block_type = emit.mir.instructions.items(.data)[inst].block_type;
|
|
try emit.code.append(@enumToInt(tag));
|
|
try emit.code.append(block_type);
|
|
}
|
|
|
|
fn emitBrTable(emit: *Emit, inst: Mir.Inst.Index) !void {
|
|
const extra_index = emit.mir.instructions.items(.data)[inst].payload;
|
|
const extra = emit.mir.extraData(Mir.JumpTable, extra_index);
|
|
const labels = emit.mir.extra[extra.end..][0..extra.data.length];
|
|
const writer = emit.code.writer();
|
|
|
|
try emit.code.append(std.wasm.opcode(.br_table));
|
|
try leb128.writeULEB128(writer, extra.data.length - 1); // Default label is not part of length/depth
|
|
for (labels) |label| {
|
|
try leb128.writeULEB128(writer, label);
|
|
}
|
|
}
|
|
|
|
fn emitLabel(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
|
|
const label = emit.mir.instructions.items(.data)[inst].label;
|
|
try emit.code.append(@enumToInt(tag));
|
|
try leb128.writeULEB128(emit.code.writer(), label);
|
|
}
|
|
|
|
fn emitGlobal(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
|
|
const label = emit.mir.instructions.items(.data)[inst].label;
|
|
try emit.code.append(@enumToInt(tag));
|
|
var buf: [5]u8 = undefined;
|
|
leb128.writeUnsignedFixed(5, &buf, label);
|
|
const global_offset = emit.offset();
|
|
try emit.code.appendSlice(&buf);
|
|
|
|
// globals can have index 0 as it represents the stack pointer
|
|
try emit.decl.link.wasm.relocs.append(emit.bin_file.allocator, .{
|
|
.index = label,
|
|
.offset = global_offset,
|
|
.relocation_type = .R_WASM_GLOBAL_INDEX_LEB,
|
|
});
|
|
}
|
|
|
|
fn emitImm32(emit: *Emit, inst: Mir.Inst.Index) !void {
|
|
const value: i32 = emit.mir.instructions.items(.data)[inst].imm32;
|
|
try emit.code.append(std.wasm.opcode(.i32_const));
|
|
try leb128.writeILEB128(emit.code.writer(), value);
|
|
}
|
|
|
|
fn emitImm64(emit: *Emit, inst: Mir.Inst.Index) !void {
|
|
const extra_index = emit.mir.instructions.items(.data)[inst].payload;
|
|
const value = emit.mir.extraData(Mir.Imm64, extra_index);
|
|
try emit.code.append(std.wasm.opcode(.i64_const));
|
|
try leb128.writeILEB128(emit.code.writer(), @bitCast(i64, value.data.toU64()));
|
|
}
|
|
|
|
fn emitFloat32(emit: *Emit, inst: Mir.Inst.Index) !void {
|
|
const value: f32 = emit.mir.instructions.items(.data)[inst].float32;
|
|
try emit.code.append(std.wasm.opcode(.f32_const));
|
|
try emit.code.writer().writeIntLittle(u32, @bitCast(u32, value));
|
|
}
|
|
|
|
fn emitFloat64(emit: *Emit, inst: Mir.Inst.Index) !void {
|
|
const extra_index = emit.mir.instructions.items(.data)[inst].payload;
|
|
const value = emit.mir.extraData(Mir.Float64, extra_index);
|
|
try emit.code.append(std.wasm.opcode(.f64_const));
|
|
try emit.code.writer().writeIntLittle(u64, value.data.toU64());
|
|
}
|
|
|
|
fn emitMemArg(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
|
|
const extra_index = emit.mir.instructions.items(.data)[inst].payload;
|
|
const mem_arg = emit.mir.extraData(Mir.MemArg, extra_index).data;
|
|
try emit.code.append(@enumToInt(tag));
|
|
|
|
// wasm encodes alignment as power of 2, rather than natural alignment
|
|
const encoded_alignment = @ctz(u32, mem_arg.alignment);
|
|
try leb128.writeULEB128(emit.code.writer(), encoded_alignment);
|
|
try leb128.writeULEB128(emit.code.writer(), mem_arg.offset);
|
|
}
|
|
|
|
fn emitCall(emit: *Emit, inst: Mir.Inst.Index) !void {
|
|
const label = emit.mir.instructions.items(.data)[inst].label;
|
|
try emit.code.append(std.wasm.opcode(.call));
|
|
const call_offset = emit.offset();
|
|
var buf: [5]u8 = undefined;
|
|
leb128.writeUnsignedFixed(5, &buf, label);
|
|
try emit.code.appendSlice(&buf);
|
|
|
|
if (label != 0) {
|
|
try emit.decl.link.wasm.relocs.append(emit.bin_file.allocator, .{
|
|
.offset = call_offset,
|
|
.index = label,
|
|
.relocation_type = .R_WASM_FUNCTION_INDEX_LEB,
|
|
});
|
|
}
|
|
}
|
|
|
|
fn emitCallIndirect(emit: *Emit, inst: Mir.Inst.Index) !void {
|
|
const type_index = emit.mir.instructions.items(.data)[inst].label;
|
|
try emit.code.append(std.wasm.opcode(.call_indirect));
|
|
// NOTE: If we remove unused function types in the future for incremental
|
|
// linking, we must also emit a relocation for this `type_index`
|
|
try leb128.writeULEB128(emit.code.writer(), type_index);
|
|
try leb128.writeULEB128(emit.code.writer(), @as(u32, 0)); // TODO: Emit relocation for table index
|
|
}
|
|
|
|
fn emitFunctionIndex(emit: *Emit, inst: Mir.Inst.Index) !void {
|
|
const symbol_index = emit.mir.instructions.items(.data)[inst].label;
|
|
try emit.code.append(std.wasm.opcode(.i32_const));
|
|
const index_offset = emit.offset();
|
|
var buf: [5]u8 = undefined;
|
|
leb128.writeUnsignedFixed(5, &buf, symbol_index);
|
|
try emit.code.appendSlice(&buf);
|
|
|
|
if (symbol_index != 0) {
|
|
try emit.decl.link.wasm.relocs.append(emit.bin_file.allocator, .{
|
|
.offset = index_offset,
|
|
.index = symbol_index,
|
|
.relocation_type = .R_WASM_TABLE_INDEX_SLEB,
|
|
});
|
|
}
|
|
}
|
|
|
|
fn emitMemAddress(emit: *Emit, inst: Mir.Inst.Index) !void {
|
|
const extra_index = emit.mir.instructions.items(.data)[inst].payload;
|
|
const mem = emit.mir.extraData(Mir.Memory, extra_index).data;
|
|
const mem_offset = emit.offset() + 1;
|
|
const is_wasm32 = emit.bin_file.options.target.cpu.arch == .wasm32;
|
|
if (is_wasm32) {
|
|
try emit.code.append(std.wasm.opcode(.i32_const));
|
|
var buf: [5]u8 = undefined;
|
|
leb128.writeUnsignedFixed(5, &buf, mem.pointer);
|
|
try emit.code.appendSlice(&buf);
|
|
} else {
|
|
try emit.code.append(std.wasm.opcode(.i64_const));
|
|
var buf: [10]u8 = undefined;
|
|
leb128.writeUnsignedFixed(10, &buf, mem.pointer);
|
|
try emit.code.appendSlice(&buf);
|
|
}
|
|
|
|
if (mem.pointer != 0) {
|
|
try emit.decl.link.wasm.relocs.append(emit.bin_file.allocator, .{
|
|
.offset = mem_offset,
|
|
.index = mem.pointer,
|
|
.relocation_type = if (is_wasm32) .R_WASM_MEMORY_ADDR_LEB else .R_WASM_MEMORY_ADDR_LEB64,
|
|
.addend = mem.offset,
|
|
});
|
|
}
|
|
}
|
|
|
|
fn emitExtended(emit: *Emit, inst: Mir.Inst.Index) !void {
|
|
const opcode = emit.mir.instructions.items(.secondary)[inst];
|
|
switch (@intToEnum(std.wasm.PrefixedOpcode, opcode)) {
|
|
.memory_fill => try emit.emitMemFill(),
|
|
else => |tag| return emit.fail("TODO: Implement extension instruction: {s}\n", .{@tagName(tag)}),
|
|
}
|
|
}
|
|
|
|
fn emitMemFill(emit: *Emit) !void {
|
|
try emit.code.append(0xFC);
|
|
try emit.code.append(0x0B);
|
|
// When multi-memory proposal reaches phase 4, we
|
|
// can emit a different memory index here.
|
|
// For now we will always emit index 0.
|
|
try leb128.writeULEB128(emit.code.writer(), @as(u32, 0));
|
|
}
|