mirror of
https://github.com/ziglang/zig.git
synced 2026-02-12 20:37:54 +00:00
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.
This commit is contained in:
parent
b26b72f540
commit
d3135f7682
@ -555,6 +555,7 @@ set(ZIG_STAGE2_SOURCES
|
||||
"${CMAKE_SOURCE_DIR}/src/arch/arm/bits.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/arch/riscv64/bits.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/arch/x86_64/bits.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/arch/wasm/CodeGen.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/clang.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/clang_options.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/clang_options_data.zig"
|
||||
@ -562,7 +563,6 @@ set(ZIG_STAGE2_SOURCES
|
||||
"${CMAKE_SOURCE_DIR}/src/codegen/c.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/codegen/llvm.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/codegen/llvm/bindings.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/codegen/wasm.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/glibc.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/introspect.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/libc_installation.zig"
|
||||
|
||||
1710
src/arch/wasm/CodeGen.zig
Normal file
1710
src/arch/wasm/CodeGen.zig
Normal file
File diff suppressed because it is too large
Load Diff
251
src/arch/wasm/Emit.zig
Normal file
251
src/arch/wasm/Emit.zig
Normal file
@ -0,0 +1,251 @@
|
||||
//! 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);
|
||||
// Reserve space to write the size after generating the code.
|
||||
try emit.code.resize(5);
|
||||
// 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),
|
||||
.global_get => try emit.emitGlobal(tag, inst),
|
||||
.global_set => try emit.emitGlobal(tag, 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),
|
||||
.i32_store => try emit.emitMemArg(tag, inst),
|
||||
|
||||
.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),
|
||||
|
||||
// no-ops
|
||||
.end => try emit.emitTag(tag),
|
||||
.memory_size => 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),
|
||||
.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),
|
||||
}
|
||||
}
|
||||
|
||||
// Fill in the size of the generated code to the reserved space at the
|
||||
// beginning of the buffer.
|
||||
const size = emit.code.items.len - 5;
|
||||
leb128.writeUnsignedFixed(5, emit.code.items[0..5], @intCast(u32, size));
|
||||
}
|
||||
|
||||
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);
|
||||
try emit.code.appendSlice(&buf);
|
||||
|
||||
// TODO: Append label to the relocation list of this function
|
||||
}
|
||||
|
||||
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.writeULEB128(emit.code.writer(), 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));
|
||||
try leb128.writeULEB128(emit.code.writer(), mem_arg.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 offset = @intCast(u32, emit.code.items.len);
|
||||
var buf: [5]u8 = undefined;
|
||||
leb128.writeUnsignedFixed(5, &buf, label);
|
||||
try emit.code.appendSlice(&buf);
|
||||
|
||||
// The function index immediate argument will be filled in using this data
|
||||
// in link.Wasm.flush().
|
||||
// TODO: Replace this with proper relocations saved in the Atom.
|
||||
try emit.decl.fn_link.wasm.idx_refs.append(emit.bin_file.allocator, .{
|
||||
.offset = offset,
|
||||
.decl = label,
|
||||
});
|
||||
}
|
||||
367
src/arch/wasm/Mir.zig
Normal file
367
src/arch/wasm/Mir.zig
Normal file
@ -0,0 +1,367 @@
|
||||
//! Machine Intermediate Representation.
|
||||
//! This representation is produced by wasm Codegen.
|
||||
//! Each of these instructions have a 1:1 mapping to a wasm opcode,
|
||||
//! but may contain metadata for a specific opcode such as an immediate.
|
||||
//! MIR can be lowered to both textual code (wat) and binary format (wasm).
|
||||
//! The main benefits of MIR is optimization passes, pre-allocated locals,
|
||||
//! and known jump labels for blocks.
|
||||
|
||||
const Mir = @This();
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
/// A struct of array that represents each individual wasm
|
||||
instructions: std.MultiArrayList(Inst).Slice,
|
||||
/// A slice of indexes where the meaning of the data is determined by the
|
||||
/// `Inst.Tag` value.
|
||||
extra: []const u32,
|
||||
|
||||
pub const Inst = struct {
|
||||
/// The opcode that represents this instruction
|
||||
tag: Tag,
|
||||
/// Data is determined by the set `tag`.
|
||||
/// For example, `data` will be an i32 for when `tag` is 'i32_const'.
|
||||
data: Data,
|
||||
|
||||
/// The position of a given MIR isntruction with the instruction list.
|
||||
pub const Index = u32;
|
||||
|
||||
/// Contains all possible wasm opcodes the Zig compiler may emit
|
||||
/// Rather than re-using std.wasm.Opcode, we only declare the opcodes
|
||||
/// we need, and also use this possibility to document how to access
|
||||
/// their payload.
|
||||
///
|
||||
/// Note: Uses its actual opcode value representation to easily convert
|
||||
/// to and from its binary representation.
|
||||
pub const Tag = enum(u8) {
|
||||
/// Uses `nop`
|
||||
@"unreachable" = 0x00,
|
||||
/// Creates a new block that can be jump from.
|
||||
///
|
||||
/// Type of the block is given in data `block_type`
|
||||
block = 0x02,
|
||||
/// Creates a new loop.
|
||||
///
|
||||
/// Type of the loop is given in data `block_type`
|
||||
loop = 0x03,
|
||||
/// Represents the end of a function body or an initialization expression
|
||||
///
|
||||
/// Payload is `nop`
|
||||
end = 0x0B,
|
||||
/// Breaks from the current block to a label
|
||||
///
|
||||
/// Data is `label` where index represents the label to jump to
|
||||
br = 0x0C,
|
||||
/// Breaks from the current block if the stack value is non-zero
|
||||
///
|
||||
/// Data is `label` where index represents the label to jump to
|
||||
br_if = 0x0D,
|
||||
/// Jump table that takes the stack value as an index where each value
|
||||
/// represents the label to jump to.
|
||||
///
|
||||
/// Data is extra of which the Payload's type is `JumpTable`
|
||||
br_table = 0x0E,
|
||||
/// Returns from the function
|
||||
///
|
||||
/// Uses `nop`
|
||||
@"return" = 0x0F,
|
||||
/// Calls a function by its index
|
||||
///
|
||||
/// Uses `label`
|
||||
call = 0x10,
|
||||
/// Loads a local at given index onto the stack.
|
||||
///
|
||||
/// Uses `label`
|
||||
local_get = 0x20,
|
||||
/// Pops a value from the stack into the local at given index.
|
||||
/// Stack value must be of the same type as the local.
|
||||
///
|
||||
/// Uses `label`
|
||||
local_set = 0x21,
|
||||
/// Sets a local at given index using the value at the top of the stack without popping the value.
|
||||
/// Stack value must have the same type as the local.
|
||||
///
|
||||
/// Uses `label`
|
||||
local_tee = 0x22,
|
||||
/// Loads a (mutable) global at given index onto the stack
|
||||
///
|
||||
/// Uses `label`
|
||||
global_get = 0x23,
|
||||
/// Pops a value from the stack and sets the global at given index.
|
||||
/// Note: Both types must be equal and global must be marked mutable.
|
||||
///
|
||||
/// Uses `label`.
|
||||
global_set = 0x24,
|
||||
/// Loads a 32-bit integer from memory (data section) onto the stack
|
||||
/// Pops the value from the stack which represents the offset into memory.
|
||||
///
|
||||
/// Uses `payload` of type `MemArg`.
|
||||
i32_load = 0x28,
|
||||
/// Pops 2 values from the stack, where the first value represents the value to write into memory
|
||||
/// and the second value represents the offset into memory where the value must be written to.
|
||||
///
|
||||
/// Uses `payload` of type `MemArg`.
|
||||
i32_store = 0x36,
|
||||
/// Returns the memory size in amount of pages.
|
||||
///
|
||||
/// Uses `nop`
|
||||
memory_size = 0x3F,
|
||||
/// Increases the memory at by given number of pages.
|
||||
///
|
||||
/// Uses `label`
|
||||
memory_grow = 0x40,
|
||||
/// Loads a 32-bit signed immediate value onto the stack
|
||||
///
|
||||
/// Uses `imm32`
|
||||
i32_const = 0x41,
|
||||
/// Loads a i64-bit signed immediate value onto the stack
|
||||
///
|
||||
/// uses `payload` of type `Imm64`
|
||||
i64_const = 0x42,
|
||||
/// Loads a 32-bit float value onto the stack.
|
||||
///
|
||||
/// Uses `float32`
|
||||
f32_const = 0x43,
|
||||
/// Loads a 64-bit float value onto the stack.
|
||||
///
|
||||
/// Uses `payload` of type `Float64`
|
||||
f64_const = 0x44,
|
||||
/// Uses `tag`
|
||||
i32_eqz = 0x45,
|
||||
/// Uses `tag`
|
||||
i32_eq = 0x46,
|
||||
/// Uses `tag`
|
||||
i32_ne = 0x47,
|
||||
/// Uses `tag`
|
||||
i32_lt_s = 0x48,
|
||||
/// Uses `tag`
|
||||
i32_lt_u = 0x49,
|
||||
/// Uses `tag`
|
||||
i32_gt_s = 0x4A,
|
||||
/// Uses `tag`
|
||||
i32_gt_u = 0x4B,
|
||||
/// Uses `tag`
|
||||
i32_le_s = 0x4C,
|
||||
/// Uses `tag`
|
||||
i32_le_u = 0x4D,
|
||||
/// Uses `tag`
|
||||
i32_ge_s = 0x4E,
|
||||
/// Uses `tag`
|
||||
i32_ge_u = 0x4F,
|
||||
/// Uses `tag`
|
||||
i64_eqz = 0x50,
|
||||
/// Uses `tag`
|
||||
i64_eq = 0x51,
|
||||
/// Uses `tag`
|
||||
i64_ne = 0x52,
|
||||
/// Uses `tag`
|
||||
i64_lt_s = 0x53,
|
||||
/// Uses `tag`
|
||||
i64_lt_u = 0x54,
|
||||
/// Uses `tag`
|
||||
i64_gt_s = 0x55,
|
||||
/// Uses `tag`
|
||||
i64_gt_u = 0x56,
|
||||
/// Uses `tag`
|
||||
i64_le_s = 0x57,
|
||||
/// Uses `tag`
|
||||
i64_le_u = 0x58,
|
||||
/// Uses `tag`
|
||||
i64_ge_s = 0x59,
|
||||
/// Uses `tag`
|
||||
i64_ge_u = 0x5A,
|
||||
/// Uses `tag`
|
||||
f32_eq = 0x5B,
|
||||
/// Uses `tag`
|
||||
f32_ne = 0x5C,
|
||||
/// Uses `tag`
|
||||
f32_lt = 0x5D,
|
||||
/// Uses `tag`
|
||||
f32_gt = 0x5E,
|
||||
/// Uses `tag`
|
||||
f32_le = 0x5F,
|
||||
/// Uses `tag`
|
||||
f32_ge = 0x60,
|
||||
/// Uses `tag`
|
||||
f64_eq = 0x61,
|
||||
/// Uses `tag`
|
||||
f64_ne = 0x62,
|
||||
/// Uses `tag`
|
||||
f64_lt = 0x63,
|
||||
/// Uses `tag`
|
||||
f64_gt = 0x64,
|
||||
/// Uses `tag`
|
||||
f64_le = 0x65,
|
||||
/// Uses `tag`
|
||||
f64_ge = 0x66,
|
||||
/// Uses `tag`
|
||||
i32_add = 0x6A,
|
||||
/// Uses `tag`
|
||||
i32_sub = 0x6B,
|
||||
/// Uses `tag`
|
||||
i32_mul = 0x6C,
|
||||
/// Uses `tag`
|
||||
i32_div_s = 0x6D,
|
||||
/// Uses `tag`
|
||||
i32_div_u = 0x6E,
|
||||
/// Uses `tag`
|
||||
i32_and = 0x71,
|
||||
/// Uses `tag`
|
||||
i32_or = 0x72,
|
||||
/// Uses `tag`
|
||||
i32_xor = 0x73,
|
||||
/// Uses `tag`
|
||||
i32_shl = 0x74,
|
||||
/// Uses `tag`
|
||||
i32_shr_s = 0x75,
|
||||
/// Uses `tag`
|
||||
i32_shr_u = 0x76,
|
||||
/// Uses `tag`
|
||||
i64_add = 0x7C,
|
||||
/// Uses `tag`
|
||||
i64_sub = 0x7D,
|
||||
/// Uses `tag`
|
||||
i64_mul = 0x7E,
|
||||
/// Uses `tag`
|
||||
i64_div_s = 0x7F,
|
||||
/// Uses `tag`
|
||||
i64_div_u = 0x80,
|
||||
/// Uses `tag`
|
||||
i64_and = 0x83,
|
||||
/// Uses `tag`
|
||||
i32_wrap_i64 = 0xA7,
|
||||
/// Uses `tag`
|
||||
i64_extend_i32_s = 0xAC,
|
||||
/// Uses `tag`
|
||||
i64_extend_i32_u = 0xAD,
|
||||
/// Uses `tag`
|
||||
i32_extend8_s = 0xC0,
|
||||
/// Uses `tag`
|
||||
i32_extend16_s = 0xC1,
|
||||
/// Uses `tag`
|
||||
i64_extend8_s = 0xC2,
|
||||
/// Uses `tag`
|
||||
i64_extend16_s = 0xC3,
|
||||
/// Uses `tag`
|
||||
i64_extend32_s = 0xC4,
|
||||
|
||||
/// From a given wasm opcode, returns a MIR tag.
|
||||
pub fn fromOpcode(opcode: std.wasm.Opcode) Tag {
|
||||
return @intToEnum(Tag, @enumToInt(opcode));
|
||||
}
|
||||
|
||||
/// Returns a wasm opcode from a given MIR tag.
|
||||
pub fn toOpcode(self: Tag) std.wasm.Opcode {
|
||||
return @intToEnum(std.wasm.Opcode, @enumToInt(self));
|
||||
}
|
||||
};
|
||||
|
||||
/// All instructions contain a 4-byte payload, which is contained within
|
||||
/// this union. `Tag` determines which union tag is active, as well as
|
||||
/// how to interpret the data within.
|
||||
pub const Data = union {
|
||||
/// Uses no additional data
|
||||
tag: void,
|
||||
/// Contains the result type of a block
|
||||
///
|
||||
/// Used by `block` and `loop`
|
||||
block_type: u8,
|
||||
/// Contains an u32 index into a wasm section entry, such as a local.
|
||||
/// Note: This is not an index to another instruction.
|
||||
///
|
||||
/// Used by e.g. `local_get`, `local_set`, etc.
|
||||
label: u32,
|
||||
/// A 32-bit immediate value.
|
||||
///
|
||||
/// Used by `i32_const`
|
||||
imm32: i32,
|
||||
/// A 32-bit float value
|
||||
///
|
||||
/// Used by `f32_float`
|
||||
float32: f32,
|
||||
/// Index into `extra`. Meaning of what can be found there is context-dependent.
|
||||
///
|
||||
/// Used by e.g. `br_table`
|
||||
payload: u32,
|
||||
};
|
||||
};
|
||||
|
||||
pub fn deinit(self: *Mir, gpa: *std.mem.Allocator) void {
|
||||
self.instructions.deinit(gpa);
|
||||
gpa.free(self.extra);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn extraData(self: Mir, comptime T: type, index: usize) struct { data: T, end: usize } {
|
||||
const fields = std.meta.fields(T);
|
||||
var i: usize = index;
|
||||
var result: T = undefined;
|
||||
inline for (fields) |field| {
|
||||
@field(result, field.name) = switch (field.field_type) {
|
||||
u32 => self.extra[i],
|
||||
else => |field_type| @compileError("Unsupported field type " ++ @typeName(field_type)),
|
||||
};
|
||||
i += 1;
|
||||
}
|
||||
|
||||
return .{ .data = result, .end = i };
|
||||
}
|
||||
|
||||
pub const JumpTable = struct {
|
||||
/// Length of the jump table and the amount of entries it contains (includes default)
|
||||
length: u32,
|
||||
};
|
||||
|
||||
/// Stores an unsigned 64bit integer
|
||||
/// into a 32bit most significant bits field
|
||||
/// and a 32bit least significant bits field.
|
||||
///
|
||||
/// This uses an unsigned integer rather than a signed integer
|
||||
/// as we can easily store those into `extra`
|
||||
pub const Imm64 = struct {
|
||||
msb: u32,
|
||||
lsb: u32,
|
||||
|
||||
pub fn fromU64(imm: u64) Imm64 {
|
||||
return .{
|
||||
.msb = @truncate(u32, imm >> 32),
|
||||
.lsb = @truncate(u32, imm),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toU64(self: Imm64) u64 {
|
||||
var result: u64 = 0;
|
||||
result |= @as(u64, self.msb) << 32;
|
||||
result |= @as(u64, self.lsb);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Float64 = struct {
|
||||
msb: u32,
|
||||
lsb: u32,
|
||||
|
||||
pub fn fromFloat64(float: f64) Float64 {
|
||||
const tmp = @bitCast(u64, float);
|
||||
return .{
|
||||
.msb = @truncate(u32, tmp >> 32),
|
||||
.lsb = @truncate(u32, tmp),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toF64(self: Float64) f64 {
|
||||
@bitCast(f64, self.toU64());
|
||||
}
|
||||
|
||||
pub fn toU64(self: Float64) u64 {
|
||||
var result: u64 = 0;
|
||||
result |= @as(u64, self.msb) << 32;
|
||||
result |= @as(u64, self.lsb);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
pub const MemArg = struct {
|
||||
offset: u32,
|
||||
alignment: u32,
|
||||
};
|
||||
1717
src/codegen/wasm.zig
1717
src/codegen/wasm.zig
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,7 @@ const wasm = std.wasm;
|
||||
|
||||
const Module = @import("../Module.zig");
|
||||
const Compilation = @import("../Compilation.zig");
|
||||
const codegen = @import("../codegen/wasm.zig");
|
||||
const CodeGen = @import("../arch/wasm/CodeGen.zig");
|
||||
const link = @import("../link.zig");
|
||||
const trace = @import("../tracy.zig").trace;
|
||||
const build_options = @import("build_options");
|
||||
@ -54,6 +54,8 @@ offset_table_free_list: std.ArrayListUnmanaged(u32) = .{},
|
||||
/// This is ment for bookkeeping so we can safely cleanup all codegen memory
|
||||
/// when calling `deinit`
|
||||
symbols: std.ArrayListUnmanaged(*Module.Decl) = .{},
|
||||
/// List of symbol indexes which are free to be used.
|
||||
symbols_free_list: std.ArrayListUnmanaged(u32) = .{},
|
||||
|
||||
pub const FnData = struct {
|
||||
/// Generated code for the type of the function
|
||||
@ -62,7 +64,8 @@ pub const FnData = struct {
|
||||
code: std.ArrayListUnmanaged(u8),
|
||||
/// Locations in the generated code where function indexes must be filled in.
|
||||
/// This must be kept ordered by offset.
|
||||
idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: *Module.Decl }),
|
||||
/// `decl` is the symbol_index of the target.
|
||||
idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: u32 }),
|
||||
|
||||
pub const empty: FnData = .{
|
||||
.functype = .{},
|
||||
@ -156,7 +159,18 @@ pub fn deinit(self: *Wasm) void {
|
||||
if (build_options.have_llvm) {
|
||||
if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator);
|
||||
}
|
||||
for (self.symbols.items) |decl| {
|
||||
|
||||
for (self.symbols.items) |decl, symbol_index| {
|
||||
// Check if we already freed all memory for the symbol
|
||||
// TODO: Audit this when we refactor the linker.
|
||||
var already_freed = false;
|
||||
for (self.symbols_free_list.items) |index| {
|
||||
if (symbol_index == index) {
|
||||
already_freed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (already_freed) continue;
|
||||
decl.fn_link.wasm.functype.deinit(self.base.allocator);
|
||||
decl.fn_link.wasm.code.deinit(self.base.allocator);
|
||||
decl.fn_link.wasm.idx_refs.deinit(self.base.allocator);
|
||||
@ -167,6 +181,7 @@ pub fn deinit(self: *Wasm) void {
|
||||
self.offset_table.deinit(self.base.allocator);
|
||||
self.offset_table_free_list.deinit(self.base.allocator);
|
||||
self.symbols.deinit(self.base.allocator);
|
||||
self.symbols_free_list.deinit(self.base.allocator);
|
||||
}
|
||||
|
||||
pub fn allocateDeclIndexes(self: *Wasm, decl: *Module.Decl) !void {
|
||||
@ -178,9 +193,6 @@ pub fn allocateDeclIndexes(self: *Wasm, decl: *Module.Decl) !void {
|
||||
const block = &decl.link.wasm;
|
||||
block.init = true;
|
||||
|
||||
block.symbol_index = @intCast(u32, self.symbols.items.len);
|
||||
self.symbols.appendAssumeCapacity(decl);
|
||||
|
||||
if (self.offset_table_free_list.popOrNull()) |index| {
|
||||
block.offset_index = index;
|
||||
} else {
|
||||
@ -188,6 +200,14 @@ pub fn allocateDeclIndexes(self: *Wasm, decl: *Module.Decl) !void {
|
||||
_ = self.offset_table.addOneAssumeCapacity();
|
||||
}
|
||||
|
||||
if (self.symbols_free_list.popOrNull()) |index| {
|
||||
block.symbol_index = index;
|
||||
self.symbols.items[block.symbol_index] = decl;
|
||||
} else {
|
||||
block.symbol_index = @intCast(u32, self.symbols.items.len);
|
||||
self.symbols.appendAssumeCapacity(decl);
|
||||
}
|
||||
|
||||
self.offset_table.items[block.offset_index] = 0;
|
||||
|
||||
if (decl.ty.zigTypeTag() == .Fn) {
|
||||
@ -215,7 +235,7 @@ pub fn updateFunc(self: *Wasm, module: *Module, func: *Module.Fn, air: Air, live
|
||||
fn_data.code.items.len = 0;
|
||||
fn_data.idx_refs.items.len = 0;
|
||||
|
||||
var context = codegen.Context{
|
||||
var codegen: CodeGen = .{
|
||||
.gpa = self.base.allocator,
|
||||
.air = air,
|
||||
.liveness = liveness,
|
||||
@ -226,20 +246,21 @@ pub fn updateFunc(self: *Wasm, module: *Module, func: *Module.Fn, air: Air, live
|
||||
.err_msg = undefined,
|
||||
.locals = .{},
|
||||
.target = self.base.options.target,
|
||||
.bin_file = &self.base,
|
||||
.global_error_set = self.base.options.module.?.global_error_set,
|
||||
};
|
||||
defer context.deinit();
|
||||
defer codegen.deinit();
|
||||
|
||||
// generate the 'code' section for the function declaration
|
||||
const result = context.genFunc() catch |err| switch (err) {
|
||||
const result = codegen.genFunc() catch |err| switch (err) {
|
||||
error.CodegenFail => {
|
||||
decl.analysis = .codegen_failure;
|
||||
try module.failed_decls.put(module.gpa, decl, context.err_msg);
|
||||
try module.failed_decls.put(module.gpa, decl, codegen.err_msg);
|
||||
return;
|
||||
},
|
||||
else => |e| return e,
|
||||
};
|
||||
return self.finishUpdateDecl(decl, result, &context);
|
||||
return self.finishUpdateDecl(decl, result, &codegen);
|
||||
}
|
||||
|
||||
// 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 {
|
||||
fn_data.code.items.len = 0;
|
||||
fn_data.idx_refs.items.len = 0;
|
||||
|
||||
var context = codegen.Context{
|
||||
var codegen: CodeGen = .{
|
||||
.gpa = self.base.allocator,
|
||||
.air = undefined,
|
||||
.liveness = undefined,
|
||||
@ -270,28 +291,29 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
|
||||
.err_msg = undefined,
|
||||
.locals = .{},
|
||||
.target = self.base.options.target,
|
||||
.bin_file = &self.base,
|
||||
.global_error_set = self.base.options.module.?.global_error_set,
|
||||
};
|
||||
defer context.deinit();
|
||||
defer codegen.deinit();
|
||||
|
||||
// generate the 'code' section for the function declaration
|
||||
const result = context.gen(decl.ty, decl.val) catch |err| switch (err) {
|
||||
const result = codegen.gen(decl.ty, decl.val) catch |err| switch (err) {
|
||||
error.CodegenFail => {
|
||||
decl.analysis = .codegen_failure;
|
||||
try module.failed_decls.put(module.gpa, decl, context.err_msg);
|
||||
try module.failed_decls.put(module.gpa, decl, codegen.err_msg);
|
||||
return;
|
||||
},
|
||||
else => |e| return e,
|
||||
};
|
||||
|
||||
return self.finishUpdateDecl(decl, result, &context);
|
||||
return self.finishUpdateDecl(decl, result, &codegen);
|
||||
}
|
||||
|
||||
fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, result: codegen.Result, context: *codegen.Context) !void {
|
||||
fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, result: CodeGen.Result, codegen: *CodeGen) !void {
|
||||
const fn_data: *FnData = &decl.fn_link.wasm;
|
||||
|
||||
fn_data.code = context.code.toUnmanaged();
|
||||
fn_data.functype = context.func_type_data.toUnmanaged();
|
||||
fn_data.code = codegen.code.toUnmanaged();
|
||||
fn_data.functype = codegen.func_type_data.toUnmanaged();
|
||||
|
||||
const code: []const u8 = switch (result) {
|
||||
.appended => @as([]const u8, fn_data.code.items),
|
||||
@ -299,14 +321,7 @@ fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, result: codegen.Result, con
|
||||
};
|
||||
|
||||
const block = &decl.link.wasm;
|
||||
if (decl.ty.zigTypeTag() == .Fn) {
|
||||
// as locals are patched afterwards, the offsets of funcidx's are off,
|
||||
// here we update them to correct them
|
||||
for (fn_data.idx_refs.items) |*func| {
|
||||
// For each local, add 6 bytes (count + type)
|
||||
func.offset += @intCast(u32, context.locals.items.len * 6);
|
||||
}
|
||||
} else {
|
||||
if (decl.ty.zigTypeTag() != .Fn) {
|
||||
block.size = @intCast(u32, code.len);
|
||||
block.data = code.ptr;
|
||||
}
|
||||
@ -359,18 +374,13 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
|
||||
block.unplug();
|
||||
|
||||
self.offset_table_free_list.append(self.base.allocator, decl.link.wasm.offset_index) catch {};
|
||||
_ = self.symbols.swapRemove(block.symbol_index);
|
||||
|
||||
// update symbol_index as we swap removed the last symbol into the removed's position
|
||||
if (block.symbol_index < self.symbols.items.len)
|
||||
self.symbols.items[block.symbol_index].link.wasm.symbol_index = block.symbol_index;
|
||||
self.symbols_free_list.append(self.base.allocator, block.symbol_index) catch {};
|
||||
|
||||
block.init = false;
|
||||
|
||||
decl.fn_link.wasm.functype.deinit(self.base.allocator);
|
||||
decl.fn_link.wasm.code.deinit(self.base.allocator);
|
||||
decl.fn_link.wasm.idx_refs.deinit(self.base.allocator);
|
||||
decl.fn_link.wasm = undefined;
|
||||
}
|
||||
|
||||
pub fn flush(self: *Wasm, comp: *Compilation) !void {
|
||||
@ -553,18 +563,12 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
|
||||
|
||||
// Write the already generated code to the file, inserting
|
||||
// function indexes where required.
|
||||
var current: u32 = 0;
|
||||
for (fn_data.idx_refs.items) |idx_ref| {
|
||||
try writer.writeAll(fn_data.code.items[current..idx_ref.offset]);
|
||||
current = idx_ref.offset;
|
||||
// Use a fixed width here to make calculating the code size
|
||||
// in codegen.wasm.gen() simpler.
|
||||
var buf: [5]u8 = undefined;
|
||||
leb.writeUnsignedFixed(5, &buf, self.getFuncidx(idx_ref.decl).?);
|
||||
try writer.writeAll(&buf);
|
||||
const relocatable_decl = self.symbols.items[idx_ref.decl];
|
||||
const index = self.getFuncidx(relocatable_decl).?;
|
||||
leb.writeUnsignedFixed(5, fn_data.code.items[idx_ref.offset..][0..5], index);
|
||||
}
|
||||
|
||||
try writer.writeAll(fn_data.code.items[current..]);
|
||||
try writer.writeAll(fn_data.code.items);
|
||||
}
|
||||
try writeVecSectionHeader(
|
||||
file,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user