Commit d3135f7682
Changed files (6)
src
src/arch/wasm/CodeGen.zig
@@ -0,0 +1,1710 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const ArrayList = std.ArrayList;
+const assert = std.debug.assert;
+const testing = std.testing;
+const leb = std.leb;
+const mem = std.mem;
+const wasm = std.wasm;
+
+const Module = @import("../../Module.zig");
+const Decl = Module.Decl;
+const Type = @import("../../type.zig").Type;
+const Value = @import("../../value.zig").Value;
+const Compilation = @import("../../Compilation.zig");
+const LazySrcLoc = Module.LazySrcLoc;
+const link = @import("../../link.zig");
+const TypedValue = @import("../../TypedValue.zig");
+const Air = @import("../../Air.zig");
+const Liveness = @import("../../Liveness.zig");
+const Mir = @import("Mir.zig");
+const Emit = @import("Emit.zig");
+
+/// Wasm Value, created when generating an instruction
+const WValue = union(enum) {
+ /// May be referenced but is unused
+ none: void,
+ /// Index of the local variable
+ local: u32,
+ /// Holds a memoized typed value
+ constant: TypedValue,
+ /// Offset position in the list of MIR instructions
+ mir_offset: usize,
+ /// Used for variables that create multiple locals on the stack when allocated
+ /// such as structs and optionals.
+ multi_value: struct {
+ /// The index of the first local variable
+ index: u32,
+ /// The count of local variables this `WValue` consists of.
+ /// i.e. an ErrorUnion has a 'count' of 2.
+ count: u32,
+ },
+};
+
+/// Wasm ops, but without input/output/signedness information
+/// Used for `buildOpcode`
+const Op = enum {
+ @"unreachable",
+ nop,
+ block,
+ loop,
+ @"if",
+ @"else",
+ end,
+ br,
+ br_if,
+ br_table,
+ @"return",
+ call,
+ call_indirect,
+ drop,
+ select,
+ local_get,
+ local_set,
+ local_tee,
+ global_get,
+ global_set,
+ load,
+ store,
+ memory_size,
+ memory_grow,
+ @"const",
+ eqz,
+ eq,
+ ne,
+ lt,
+ gt,
+ le,
+ ge,
+ clz,
+ ctz,
+ popcnt,
+ add,
+ sub,
+ mul,
+ div,
+ rem,
+ @"and",
+ @"or",
+ xor,
+ shl,
+ shr,
+ rotl,
+ rotr,
+ abs,
+ neg,
+ ceil,
+ floor,
+ trunc,
+ nearest,
+ sqrt,
+ min,
+ max,
+ copysign,
+ wrap,
+ convert,
+ demote,
+ promote,
+ reinterpret,
+ extend,
+};
+
+/// Contains the settings needed to create an `Opcode` using `buildOpcode`.
+///
+/// The fields correspond to the opcode name. Here is an example
+/// i32_trunc_f32_s
+/// ^ ^ ^ ^
+/// | | | |
+/// valtype1 | | |
+/// = .i32 | | |
+/// | | |
+/// op | |
+/// = .trunc | |
+/// | |
+/// valtype2 |
+/// = .f32 |
+/// |
+/// width |
+/// = null |
+/// |
+/// signed
+/// = true
+///
+/// There can be missing fields, here are some more examples:
+/// i64_load8_u
+/// --> .{ .valtype1 = .i64, .op = .load, .width = 8, signed = false }
+/// i32_mul
+/// --> .{ .valtype1 = .i32, .op = .trunc }
+/// nop
+/// --> .{ .op = .nop }
+const OpcodeBuildArguments = struct {
+ /// First valtype in the opcode (usually represents the type of the output)
+ valtype1: ?wasm.Valtype = null,
+ /// The operation (e.g. call, unreachable, div, min, sqrt, etc.)
+ op: Op,
+ /// Width of the operation (e.g. 8 for i32_load8_s, 16 for i64_extend16_i32_s)
+ width: ?u8 = null,
+ /// Second valtype in the opcode name (usually represents the type of the input)
+ valtype2: ?wasm.Valtype = null,
+ /// Signedness of the op
+ signedness: ?std.builtin.Signedness = null,
+};
+
+/// Helper function that builds an Opcode given the arguments needed
+fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
+ switch (args.op) {
+ .@"unreachable" => return .@"unreachable",
+ .nop => return .nop,
+ .block => return .block,
+ .loop => return .loop,
+ .@"if" => return .@"if",
+ .@"else" => return .@"else",
+ .end => return .end,
+ .br => return .br,
+ .br_if => return .br_if,
+ .br_table => return .br_table,
+ .@"return" => return .@"return",
+ .call => return .call,
+ .call_indirect => return .call_indirect,
+ .drop => return .drop,
+ .select => return .select,
+ .local_get => return .local_get,
+ .local_set => return .local_set,
+ .local_tee => return .local_tee,
+ .global_get => return .global_get,
+ .global_set => return .global_set,
+
+ .load => if (args.width) |width| switch (width) {
+ 8 => switch (args.valtype1.?) {
+ .i32 => if (args.signedness.? == .signed) return .i32_load8_s else return .i32_load8_u,
+ .i64 => if (args.signedness.? == .signed) return .i64_load8_s else return .i64_load8_u,
+ .f32, .f64 => unreachable,
+ },
+ 16 => switch (args.valtype1.?) {
+ .i32 => if (args.signedness.? == .signed) return .i32_load16_s else return .i32_load16_u,
+ .i64 => if (args.signedness.? == .signed) return .i64_load16_s else return .i64_load16_u,
+ .f32, .f64 => unreachable,
+ },
+ 32 => switch (args.valtype1.?) {
+ .i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u,
+ .i32, .f32, .f64 => unreachable,
+ },
+ else => unreachable,
+ } else switch (args.valtype1.?) {
+ .i32 => return .i32_load,
+ .i64 => return .i64_load,
+ .f32 => return .f32_load,
+ .f64 => return .f64_load,
+ },
+ .store => if (args.width) |width| {
+ switch (width) {
+ 8 => switch (args.valtype1.?) {
+ .i32 => return .i32_store8,
+ .i64 => return .i64_store8,
+ .f32, .f64 => unreachable,
+ },
+ 16 => switch (args.valtype1.?) {
+ .i32 => return .i32_store16,
+ .i64 => return .i64_store16,
+ .f32, .f64 => unreachable,
+ },
+ 32 => switch (args.valtype1.?) {
+ .i64 => return .i64_store32,
+ .i32, .f32, .f64 => unreachable,
+ },
+ else => unreachable,
+ }
+ } else {
+ switch (args.valtype1.?) {
+ .i32 => return .i32_store,
+ .i64 => return .i64_store,
+ .f32 => return .f32_store,
+ .f64 => return .f64_store,
+ }
+ },
+
+ .memory_size => return .memory_size,
+ .memory_grow => return .memory_grow,
+
+ .@"const" => switch (args.valtype1.?) {
+ .i32 => return .i32_const,
+ .i64 => return .i64_const,
+ .f32 => return .f32_const,
+ .f64 => return .f64_const,
+ },
+
+ .eqz => switch (args.valtype1.?) {
+ .i32 => return .i32_eqz,
+ .i64 => return .i64_eqz,
+ .f32, .f64 => unreachable,
+ },
+ .eq => switch (args.valtype1.?) {
+ .i32 => return .i32_eq,
+ .i64 => return .i64_eq,
+ .f32 => return .f32_eq,
+ .f64 => return .f64_eq,
+ },
+ .ne => switch (args.valtype1.?) {
+ .i32 => return .i32_ne,
+ .i64 => return .i64_ne,
+ .f32 => return .f32_ne,
+ .f64 => return .f64_ne,
+ },
+
+ .lt => switch (args.valtype1.?) {
+ .i32 => if (args.signedness.? == .signed) return .i32_lt_s else return .i32_lt_u,
+ .i64 => if (args.signedness.? == .signed) return .i64_lt_s else return .i64_lt_u,
+ .f32 => return .f32_lt,
+ .f64 => return .f64_lt,
+ },
+ .gt => switch (args.valtype1.?) {
+ .i32 => if (args.signedness.? == .signed) return .i32_gt_s else return .i32_gt_u,
+ .i64 => if (args.signedness.? == .signed) return .i64_gt_s else return .i64_gt_u,
+ .f32 => return .f32_gt,
+ .f64 => return .f64_gt,
+ },
+ .le => switch (args.valtype1.?) {
+ .i32 => if (args.signedness.? == .signed) return .i32_le_s else return .i32_le_u,
+ .i64 => if (args.signedness.? == .signed) return .i64_le_s else return .i64_le_u,
+ .f32 => return .f32_le,
+ .f64 => return .f64_le,
+ },
+ .ge => switch (args.valtype1.?) {
+ .i32 => if (args.signedness.? == .signed) return .i32_ge_s else return .i32_ge_u,
+ .i64 => if (args.signedness.? == .signed) return .i64_ge_s else return .i64_ge_u,
+ .f32 => return .f32_ge,
+ .f64 => return .f64_ge,
+ },
+
+ .clz => switch (args.valtype1.?) {
+ .i32 => return .i32_clz,
+ .i64 => return .i64_clz,
+ .f32, .f64 => unreachable,
+ },
+ .ctz => switch (args.valtype1.?) {
+ .i32 => return .i32_ctz,
+ .i64 => return .i64_ctz,
+ .f32, .f64 => unreachable,
+ },
+ .popcnt => switch (args.valtype1.?) {
+ .i32 => return .i32_popcnt,
+ .i64 => return .i64_popcnt,
+ .f32, .f64 => unreachable,
+ },
+
+ .add => switch (args.valtype1.?) {
+ .i32 => return .i32_add,
+ .i64 => return .i64_add,
+ .f32 => return .f32_add,
+ .f64 => return .f64_add,
+ },
+ .sub => switch (args.valtype1.?) {
+ .i32 => return .i32_sub,
+ .i64 => return .i64_sub,
+ .f32 => return .f32_sub,
+ .f64 => return .f64_sub,
+ },
+ .mul => switch (args.valtype1.?) {
+ .i32 => return .i32_mul,
+ .i64 => return .i64_mul,
+ .f32 => return .f32_mul,
+ .f64 => return .f64_mul,
+ },
+
+ .div => switch (args.valtype1.?) {
+ .i32 => if (args.signedness.? == .signed) return .i32_div_s else return .i32_div_u,
+ .i64 => if (args.signedness.? == .signed) return .i64_div_s else return .i64_div_u,
+ .f32 => return .f32_div,
+ .f64 => return .f64_div,
+ },
+ .rem => switch (args.valtype1.?) {
+ .i32 => if (args.signedness.? == .signed) return .i32_rem_s else return .i32_rem_u,
+ .i64 => if (args.signedness.? == .signed) return .i64_rem_s else return .i64_rem_u,
+ .f32, .f64 => unreachable,
+ },
+
+ .@"and" => switch (args.valtype1.?) {
+ .i32 => return .i32_and,
+ .i64 => return .i64_and,
+ .f32, .f64 => unreachable,
+ },
+ .@"or" => switch (args.valtype1.?) {
+ .i32 => return .i32_or,
+ .i64 => return .i64_or,
+ .f32, .f64 => unreachable,
+ },
+ .xor => switch (args.valtype1.?) {
+ .i32 => return .i32_xor,
+ .i64 => return .i64_xor,
+ .f32, .f64 => unreachable,
+ },
+
+ .shl => switch (args.valtype1.?) {
+ .i32 => return .i32_shl,
+ .i64 => return .i64_shl,
+ .f32, .f64 => unreachable,
+ },
+ .shr => switch (args.valtype1.?) {
+ .i32 => if (args.signedness.? == .signed) return .i32_shr_s else return .i32_shr_u,
+ .i64 => if (args.signedness.? == .signed) return .i64_shr_s else return .i64_shr_u,
+ .f32, .f64 => unreachable,
+ },
+ .rotl => switch (args.valtype1.?) {
+ .i32 => return .i32_rotl,
+ .i64 => return .i64_rotl,
+ .f32, .f64 => unreachable,
+ },
+ .rotr => switch (args.valtype1.?) {
+ .i32 => return .i32_rotr,
+ .i64 => return .i64_rotr,
+ .f32, .f64 => unreachable,
+ },
+
+ .abs => switch (args.valtype1.?) {
+ .i32, .i64 => unreachable,
+ .f32 => return .f32_abs,
+ .f64 => return .f64_abs,
+ },
+ .neg => switch (args.valtype1.?) {
+ .i32, .i64 => unreachable,
+ .f32 => return .f32_neg,
+ .f64 => return .f64_neg,
+ },
+ .ceil => switch (args.valtype1.?) {
+ .i32, .i64 => unreachable,
+ .f32 => return .f32_ceil,
+ .f64 => return .f64_ceil,
+ },
+ .floor => switch (args.valtype1.?) {
+ .i32, .i64 => unreachable,
+ .f32 => return .f32_floor,
+ .f64 => return .f64_floor,
+ },
+ .trunc => switch (args.valtype1.?) {
+ .i32 => switch (args.valtype2.?) {
+ .i32 => unreachable,
+ .i64 => unreachable,
+ .f32 => if (args.signedness.? == .signed) return .i32_trunc_f32_s else return .i32_trunc_f32_u,
+ .f64 => if (args.signedness.? == .signed) return .i32_trunc_f64_s else return .i32_trunc_f64_u,
+ },
+ .i64 => unreachable,
+ .f32 => return .f32_trunc,
+ .f64 => return .f64_trunc,
+ },
+ .nearest => switch (args.valtype1.?) {
+ .i32, .i64 => unreachable,
+ .f32 => return .f32_nearest,
+ .f64 => return .f64_nearest,
+ },
+ .sqrt => switch (args.valtype1.?) {
+ .i32, .i64 => unreachable,
+ .f32 => return .f32_sqrt,
+ .f64 => return .f64_sqrt,
+ },
+ .min => switch (args.valtype1.?) {
+ .i32, .i64 => unreachable,
+ .f32 => return .f32_min,
+ .f64 => return .f64_min,
+ },
+ .max => switch (args.valtype1.?) {
+ .i32, .i64 => unreachable,
+ .f32 => return .f32_max,
+ .f64 => return .f64_max,
+ },
+ .copysign => switch (args.valtype1.?) {
+ .i32, .i64 => unreachable,
+ .f32 => return .f32_copysign,
+ .f64 => return .f64_copysign,
+ },
+
+ .wrap => switch (args.valtype1.?) {
+ .i32 => switch (args.valtype2.?) {
+ .i32 => unreachable,
+ .i64 => return .i32_wrap_i64,
+ .f32, .f64 => unreachable,
+ },
+ .i64, .f32, .f64 => unreachable,
+ },
+ .convert => switch (args.valtype1.?) {
+ .i32, .i64 => unreachable,
+ .f32 => switch (args.valtype2.?) {
+ .i32 => if (args.signedness.? == .signed) return .f32_convert_i32_s else return .f32_convert_i32_u,
+ .i64 => if (args.signedness.? == .signed) return .f32_convert_i64_s else return .f32_convert_i64_u,
+ .f32, .f64 => unreachable,
+ },
+ .f64 => switch (args.valtype2.?) {
+ .i32 => if (args.signedness.? == .signed) return .f64_convert_i32_s else return .f64_convert_i32_u,
+ .i64 => if (args.signedness.? == .signed) return .f64_convert_i64_s else return .f64_convert_i64_u,
+ .f32, .f64 => unreachable,
+ },
+ },
+ .demote => if (args.valtype1.? == .f32 and args.valtype2.? == .f64) return .f32_demote_f64 else unreachable,
+ .promote => if (args.valtype1.? == .f64 and args.valtype2.? == .f32) return .f64_promote_f32 else unreachable,
+ .reinterpret => switch (args.valtype1.?) {
+ .i32 => if (args.valtype2.? == .f32) return .i32_reinterpret_f32 else unreachable,
+ .i64 => if (args.valtype2.? == .f64) return .i64_reinterpret_f64 else unreachable,
+ .f32 => if (args.valtype2.? == .i32) return .f32_reinterpret_i32 else unreachable,
+ .f64 => if (args.valtype2.? == .i64) return .f64_reinterpret_i64 else unreachable,
+ },
+ .extend => switch (args.valtype1.?) {
+ .i32 => switch (args.width.?) {
+ 8 => if (args.signedness.? == .signed) return .i32_extend8_s else unreachable,
+ 16 => if (args.signedness.? == .signed) return .i32_extend16_s else unreachable,
+ else => unreachable,
+ },
+ .i64 => switch (args.width.?) {
+ 8 => if (args.signedness.? == .signed) return .i64_extend8_s else unreachable,
+ 16 => if (args.signedness.? == .signed) return .i64_extend16_s else unreachable,
+ 32 => if (args.signedness.? == .signed) return .i64_extend32_s else unreachable,
+ else => unreachable,
+ },
+ .f32, .f64 => unreachable,
+ },
+ }
+}
+
+test "Wasm - buildOpcode" {
+ // Make sure buildOpcode is referenced, and test some examples
+ const i32_const = buildOpcode(.{ .op = .@"const", .valtype1 = .i32 });
+ const end = buildOpcode(.{ .op = .end });
+ const local_get = buildOpcode(.{ .op = .local_get });
+ const i64_extend32_s = buildOpcode(.{ .op = .extend, .valtype1 = .i64, .width = 32, .signedness = .signed });
+ const f64_reinterpret_i64 = buildOpcode(.{ .op = .reinterpret, .valtype1 = .f64, .valtype2 = .i64 });
+
+ try testing.expectEqual(@as(wasm.Opcode, .i32_const), i32_const);
+ try testing.expectEqual(@as(wasm.Opcode, .end), end);
+ try testing.expectEqual(@as(wasm.Opcode, .local_get), local_get);
+ try testing.expectEqual(@as(wasm.Opcode, .i64_extend32_s), i64_extend32_s);
+ try testing.expectEqual(@as(wasm.Opcode, .f64_reinterpret_i64), f64_reinterpret_i64);
+}
+
+pub const Result = union(enum) {
+ /// The codegen bytes have been appended to `Context.code`
+ appended: void,
+ /// The data is managed externally and are part of the `Result`
+ externally_managed: []const u8,
+};
+
+/// Hashmap to store generated `WValue` for each `Air.Inst.Ref`
+pub const ValueTable = std.AutoHashMapUnmanaged(Air.Inst.Index, WValue);
+
+const Self = @This();
+
+/// Reference to the function declaration the code
+/// section belongs to
+decl: *Decl,
+air: Air,
+liveness: Liveness,
+gpa: *mem.Allocator,
+/// Table to save `WValue`'s generated by an `Air.Inst`
+values: ValueTable,
+/// Mapping from Air.Inst.Index to block ids
+blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, u32) = .{},
+/// `bytes` contains the wasm bytecode belonging to the 'code' section.
+code: ArrayList(u8),
+/// Contains the generated function type bytecode for the current function
+/// found in `decl`
+func_type_data: ArrayList(u8),
+/// The index the next local generated will have
+/// NOTE: arguments share the index with locals therefore the first variable
+/// will have the index that comes after the last argument's index
+local_index: u32 = 0,
+/// If codegen fails, an error messages will be allocated and saved in `err_msg`
+err_msg: *Module.ErrorMsg,
+/// Current block depth. Used to calculate the relative difference between a break
+/// and block
+block_depth: u32 = 0,
+/// List of all locals' types generated throughout this declaration
+/// used to emit locals count at start of 'code' section.
+locals: std.ArrayListUnmanaged(u8),
+/// The Target we're emitting (used to call intInfo)
+target: std.Target,
+/// Represents the wasm binary file that is being linked.
+bin_file: *link.File,
+/// Table with the global error set. Consists of every error found in
+/// the compiled code. Each error name maps to a `Module.ErrorInt` which is emitted
+/// during codegen to determine the error value.
+global_error_set: std.StringHashMapUnmanaged(Module.ErrorInt),
+/// List of MIR Instructions
+mir_instructions: std.MultiArrayList(Mir.Inst) = .{},
+/// Contains extra data for MIR
+mir_extra: std.ArrayListUnmanaged(u32) = .{},
+
+const InnerError = error{
+ OutOfMemory,
+ /// An error occured when trying to lower AIR to MIR.
+ CodegenFail,
+ /// Can occur when dereferencing a pointer that points to a `Decl` of which the analysis has failed
+ AnalysisFail,
+ /// Failed to emit MIR instructions to binary/textual representation.
+ EmitFail,
+};
+
+pub fn deinit(self: *Self) void {
+ self.values.deinit(self.gpa);
+ self.blocks.deinit(self.gpa);
+ self.locals.deinit(self.gpa);
+ self.mir_instructions.deinit(self.gpa);
+ self.mir_extra.deinit(self.gpa);
+ self.* = undefined;
+}
+
+/// Sets `err_msg` on `Context` and returns `error.CodegemFail` which is caught in link/Wasm.zig
+fn fail(self: *Self, comptime fmt: []const u8, args: anytype) InnerError {
+ const src: LazySrcLoc = .{ .node_offset = 0 };
+ const src_loc = src.toSrcLoc(self.decl);
+ self.err_msg = try Module.ErrorMsg.create(self.gpa, src_loc, fmt, args);
+ return error.CodegenFail;
+}
+
+/// Resolves the `WValue` for the given instruction `inst`
+/// When the given instruction has a `Value`, it returns a constant instead
+fn resolveInst(self: Self, ref: Air.Inst.Ref) WValue {
+ const inst_index = Air.refToIndex(ref) orelse {
+ const tv = Air.Inst.Ref.typed_value_map[@enumToInt(ref)];
+ if (!tv.ty.hasCodeGenBits()) {
+ return WValue.none;
+ }
+ return WValue{ .constant = tv };
+ };
+
+ const inst_type = self.air.typeOfIndex(inst_index);
+ if (!inst_type.hasCodeGenBits()) return .none;
+
+ if (self.air.instructions.items(.tag)[inst_index] == .constant) {
+ const ty_pl = self.air.instructions.items(.data)[inst_index].ty_pl;
+ return WValue{ .constant = .{ .ty = inst_type, .val = self.air.values[ty_pl.payload] } };
+ }
+
+ return self.values.get(inst_index).?; // Instruction does not dominate all uses!
+}
+
+/// Appends a MIR instruction and returns its index within the list of instructions
+fn addInst(self: *Self, inst: Mir.Inst) error{OutOfMemory}!void {
+ try self.mir_instructions.append(self.gpa, inst);
+}
+
+/// Inserts a Mir instruction at the given `offset`.
+/// Asserts offset is within bound.
+fn addInstAt(self: *Self, offset: usize, inst: Mir.Inst) error{OutOfMemory}!void {
+ try self.mir_instructions.ensureUnusedCapacity(self.gpa, 1);
+ self.mir_instructions.insertAssumeCapacity(offset, inst);
+}
+
+fn addTag(self: *Self, tag: Mir.Inst.Tag) error{OutOfMemory}!void {
+ try self.addInst(.{ .tag = tag, .data = .{ .tag = {} } });
+}
+
+fn addLabel(self: *Self, tag: Mir.Inst.Tag, label: u32) error{OutOfMemory}!void {
+ try self.addInst(.{ .tag = tag, .data = .{ .label = label } });
+}
+
+fn addImm32(self: *Self, imm: i32) error{OutOfMemory}!void {
+ try self.addInst(.{ .tag = .i32_const, .data = .{ .imm32 = imm } });
+}
+
+/// Accepts an unsigned 64bit integer rather than a signed integer to
+/// prevent us from having to bitcast multiple times as most values
+/// within codegen are represented as unsigned rather than signed.
+fn addImm64(self: *Self, imm: u64) error{OutOfMemory}!void {
+ const extra_index = try self.addExtra(Mir.Imm64.fromU64(imm));
+ try self.addInst(.{ .tag = .i64_const, .data = .{ .payload = extra_index } });
+}
+
+fn addFloat64(self: *Self, float: f64) error{OutOfMemory}!void {
+ const extra_index = try self.addExtra(Mir.Float64.fromFloat64(float));
+ try self.addInst(.{ .tag = .f64_const, .data = .{ .payload = extra_index } });
+}
+
+/// Appends entries to `mir_extra` based on the type of `extra`.
+/// Returns the index into `mir_extra`
+fn addExtra(self: *Self, extra: anytype) error{OutOfMemory}!u32 {
+ const fields = std.meta.fields(@TypeOf(extra));
+ try self.mir_extra.ensureUnusedCapacity(self.gpa, fields.len);
+ return self.addExtraAssumeCapacity(extra);
+}
+
+/// Appends entries to `mir_extra` based on the type of `extra`.
+/// Returns the index into `mir_extra`
+fn addExtraAssumeCapacity(self: *Self, extra: anytype) error{OutOfMemory}!u32 {
+ const fields = std.meta.fields(@TypeOf(extra));
+ const result = @intCast(u32, self.mir_extra.items.len);
+ inline for (fields) |field| {
+ self.mir_extra.appendAssumeCapacity(switch (field.field_type) {
+ u32 => @field(extra, field.name),
+ else => |field_type| @compileError("Unsupported field type " ++ @typeName(field_type)),
+ });
+ }
+ return result;
+}
+
+/// Using a given `Type`, returns the corresponding wasm Valtype
+fn typeToValtype(self: *Self, ty: Type) InnerError!wasm.Valtype {
+ return switch (ty.zigTypeTag()) {
+ .Float => blk: {
+ const bits = ty.floatBits(self.target);
+ if (bits == 16 or bits == 32) break :blk wasm.Valtype.f32;
+ if (bits == 64) break :blk wasm.Valtype.f64;
+ return self.fail("Float bit size not supported by wasm: '{d}'", .{bits});
+ },
+ .Int => blk: {
+ const info = ty.intInfo(self.target);
+ if (info.bits <= 32) break :blk wasm.Valtype.i32;
+ if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64;
+ return self.fail("Integer bit size not supported by wasm: '{d}'", .{info.bits});
+ },
+ .Enum => switch (ty.tag()) {
+ .enum_simple => wasm.Valtype.i32,
+ else => self.typeToValtype(ty.cast(Type.Payload.EnumFull).?.data.tag_ty),
+ },
+ .Bool,
+ .Pointer,
+ .ErrorSet,
+ => wasm.Valtype.i32,
+ .Struct, .ErrorUnion, .Optional => unreachable, // Multi typed, must be handled individually.
+ else => |tag| self.fail("TODO - Wasm valtype for type '{s}'", .{tag}),
+ };
+}
+
+/// Using a given `Type`, returns the byte representation of its wasm value type
+fn genValtype(self: *Self, ty: Type) InnerError!u8 {
+ return wasm.valtype(try self.typeToValtype(ty));
+}
+
+/// Using a given `Type`, returns the corresponding wasm value type
+/// Differently from `genValtype` this also allows `void` to create a block
+/// with no return type
+fn genBlockType(self: *Self, ty: Type) InnerError!u8 {
+ return switch (ty.tag()) {
+ .void, .noreturn => wasm.block_empty,
+ else => self.genValtype(ty),
+ };
+}
+
+/// Writes the bytecode depending on the given `WValue` in `val`
+fn emitWValue(self: *Self, val: WValue) InnerError!void {
+ switch (val) {
+ .multi_value => unreachable, // multi_value can never be written directly, and must be accessed individually
+ .none, .mir_offset => {}, // no-op
+ .local => |idx| {
+ try self.addLabel(.local_get, idx);
+ },
+ .constant => |tv| try self.emitConstant(tv.val, tv.ty), // Creates a new constant on the stack
+ }
+}
+
+/// Creates one or multiple locals for a given `Type`.
+/// Returns a corresponding `Wvalue` that can either be of tag
+/// local or multi_value
+fn allocLocal(self: *Self, ty: Type) InnerError!WValue {
+ const initial_index = self.local_index;
+ switch (ty.zigTypeTag()) {
+ .Struct => {
+ // for each struct field, generate a local
+ const struct_data: *Module.Struct = ty.castTag(.@"struct").?.data;
+ const fields_len = @intCast(u32, struct_data.fields.count());
+ try self.locals.ensureUnusedCapacity(self.gpa, fields_len);
+ for (struct_data.fields.values()) |*value| {
+ const val_type = try self.genValtype(value.ty);
+ self.locals.appendAssumeCapacity(val_type);
+ self.local_index += 1;
+ }
+ return WValue{ .multi_value = .{
+ .index = initial_index,
+ .count = fields_len,
+ } };
+ },
+ .ErrorUnion => {
+ const payload_type = ty.errorUnionPayload();
+ const val_type = try self.genValtype(payload_type);
+
+ // we emit the error value as the first local, and the payload as the following.
+ // The first local is also used to find the index of the error and payload.
+ //
+ // TODO: Add support where the payload is a type that contains multiple locals such as a struct.
+ try self.locals.ensureUnusedCapacity(self.gpa, 2);
+ self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // error values are always i32
+ self.locals.appendAssumeCapacity(val_type);
+ self.local_index += 2;
+
+ return WValue{ .multi_value = .{
+ .index = initial_index,
+ .count = 2,
+ } };
+ },
+ .Optional => {
+ var opt_buf: Type.Payload.ElemType = undefined;
+ const child_type = ty.optionalChild(&opt_buf);
+ if (ty.isPtrLikeOptional()) {
+ return self.fail("TODO: wasm optional pointer", .{});
+ }
+
+ try self.locals.ensureUnusedCapacity(self.gpa, 2);
+ self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // optional 'tag' for null-checking is always i32
+ self.locals.appendAssumeCapacity(try self.genValtype(child_type));
+ self.local_index += 2;
+
+ return WValue{ .multi_value = .{
+ .index = initial_index,
+ .count = 2,
+ } };
+ },
+ else => {
+ const valtype = try self.genValtype(ty);
+ try self.locals.append(self.gpa, valtype);
+ self.local_index += 1;
+ return WValue{ .local = initial_index };
+ },
+ }
+}
+
+fn genFunctype(self: *Self) InnerError!void {
+ assert(self.decl.has_tv);
+ const ty = self.decl.ty;
+ const writer = self.func_type_data.writer();
+
+ try writer.writeByte(wasm.function_type);
+
+ // param types
+ try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen()));
+ if (ty.fnParamLen() != 0) {
+ const params = try self.gpa.alloc(Type, ty.fnParamLen());
+ defer self.gpa.free(params);
+ ty.fnParamTypes(params);
+ for (params) |param_type| {
+ // Can we maybe get the source index of each param?
+ const val_type = try self.genValtype(param_type);
+ try writer.writeByte(val_type);
+ }
+ }
+
+ // return type
+ const return_type = ty.fnReturnType();
+ switch (return_type.zigTypeTag()) {
+ .Void, .NoReturn => try leb.writeULEB128(writer, @as(u32, 0)),
+ .Struct => return self.fail("TODO: Implement struct as return type for wasm", .{}),
+ .Optional => return self.fail("TODO: Implement optionals as return type for wasm", .{}),
+ .ErrorUnion => {
+ const val_type = try self.genValtype(return_type.errorUnionPayload());
+
+ // write down the amount of return values
+ try leb.writeULEB128(writer, @as(u32, 2));
+ try writer.writeByte(wasm.valtype(.i32)); // error code is always an i32 integer.
+ try writer.writeByte(val_type);
+ },
+ else => {
+ try leb.writeULEB128(writer, @as(u32, 1));
+ // Can we maybe get the source index of the return type?
+ const val_type = try self.genValtype(return_type);
+ try writer.writeByte(val_type);
+ },
+ }
+}
+
+pub fn genFunc(self: *Self) InnerError!Result {
+ try self.genFunctype();
+ // TODO: check for and handle death of instructions
+
+ // Generate MIR for function body
+ try self.genBody(self.air.getMainBody());
+ // End of function body
+ try self.addTag(.end);
+
+ var mir: Mir = .{
+ .instructions = self.mir_instructions.toOwnedSlice(),
+ .extra = self.mir_extra.toOwnedSlice(self.gpa),
+ };
+ defer mir.deinit(self.gpa);
+
+ var emit: Emit = .{
+ .mir = mir,
+ .bin_file = self.bin_file,
+ .code = &self.code,
+ .locals = self.locals.items,
+ .decl = self.decl,
+ };
+
+ emit.emitMir() catch |err| switch (err) {
+ error.EmitFail => {
+ self.err_msg = emit.error_msg.?;
+ return error.EmitFail;
+ },
+ else => |e| return e,
+ };
+
+ // codegen data has been appended to `code`
+ return Result.appended;
+}
+
+/// Generates the wasm bytecode for the declaration belonging to `Context`
+pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {
+ switch (ty.zigTypeTag()) {
+ .Fn => {
+ try self.genFunctype();
+ if (val.tag() == .extern_fn) {
+ return Result.appended; // don't need code body for extern functions
+ }
+ return self.fail("TODO implement wasm codegen for function pointers", .{});
+ },
+ .Array => {
+ if (val.castTag(.bytes)) |payload| {
+ if (ty.sentinel()) |sentinel| {
+ try self.code.appendSlice(payload.data);
+
+ switch (try self.gen(ty.elemType(), sentinel)) {
+ .appended => return Result.appended,
+ .externally_managed => |data| {
+ try self.code.appendSlice(data);
+ return Result.appended;
+ },
+ }
+ }
+ return Result{ .externally_managed = payload.data };
+ } else return self.fail("TODO implement gen for more kinds of arrays", .{});
+ },
+ .Int => {
+ const info = ty.intInfo(self.target);
+ if (info.bits == 8 and info.signedness == .unsigned) {
+ const int_byte = val.toUnsignedInt();
+ try self.code.append(@intCast(u8, int_byte));
+ return Result.appended;
+ }
+ return self.fail("TODO: Implement codegen for int type: '{}'", .{ty});
+ },
+ .Enum => {
+ try self.emitConstant(val, ty);
+ return Result.appended;
+ },
+ .Struct => {
+ // TODO write the fields for real
+ try self.code.writer().writeByteNTimes(0xaa, ty.abiSize(self.target));
+ return Result{ .appended = {} };
+ },
+ else => |tag| return self.fail("TODO: Implement zig type codegen for type: '{s}'", .{tag}),
+ }
+}
+
+fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
+ const air_tags = self.air.instructions.items(.tag);
+ return switch (air_tags[inst]) {
+ .add => self.airBinOp(inst, .add),
+ .addwrap => self.airWrapBinOp(inst, .add),
+ .sub => self.airBinOp(inst, .sub),
+ .subwrap => self.airWrapBinOp(inst, .sub),
+ .mul => self.airBinOp(inst, .mul),
+ .mulwrap => self.airWrapBinOp(inst, .mul),
+ .div_trunc => self.airBinOp(inst, .div),
+ .bit_and => self.airBinOp(inst, .@"and"),
+ .bit_or => self.airBinOp(inst, .@"or"),
+ .bool_and => self.airBinOp(inst, .@"and"),
+ .bool_or => self.airBinOp(inst, .@"or"),
+ .xor => self.airBinOp(inst, .xor),
+
+ .cmp_eq => self.airCmp(inst, .eq),
+ .cmp_gte => self.airCmp(inst, .gte),
+ .cmp_gt => self.airCmp(inst, .gt),
+ .cmp_lte => self.airCmp(inst, .lte),
+ .cmp_lt => self.airCmp(inst, .lt),
+ .cmp_neq => self.airCmp(inst, .neq),
+
+ .alloc => self.airAlloc(inst),
+ .arg => self.airArg(inst),
+ .bitcast => self.airBitcast(inst),
+ .block => self.airBlock(inst),
+ .breakpoint => self.airBreakpoint(inst),
+ .br => self.airBr(inst),
+ .call => self.airCall(inst),
+ .cond_br => self.airCondBr(inst),
+ .constant => unreachable,
+ .dbg_stmt => WValue.none,
+ .intcast => self.airIntcast(inst),
+
+ .is_err => self.airIsErr(inst, .i32_ne),
+ .is_non_err => self.airIsErr(inst, .i32_eq),
+
+ .is_null => self.airIsNull(inst, .i32_ne),
+ .is_non_null => self.airIsNull(inst, .i32_eq),
+ .is_null_ptr => self.airIsNull(inst, .i32_ne),
+ .is_non_null_ptr => self.airIsNull(inst, .i32_eq),
+
+ .load => self.airLoad(inst),
+ .loop => self.airLoop(inst),
+ .not => self.airNot(inst),
+ .ret => self.airRet(inst),
+ .store => self.airStore(inst),
+ .struct_field_ptr => self.airStructFieldPtr(inst),
+ .struct_field_ptr_index_0 => self.airStructFieldPtrIndex(inst, 0),
+ .struct_field_ptr_index_1 => self.airStructFieldPtrIndex(inst, 1),
+ .struct_field_ptr_index_2 => self.airStructFieldPtrIndex(inst, 2),
+ .struct_field_ptr_index_3 => self.airStructFieldPtrIndex(inst, 3),
+ .struct_field_val => self.airStructFieldVal(inst),
+ .switch_br => self.airSwitchBr(inst),
+ .unreach => self.airUnreachable(inst),
+ .wrap_optional => self.airWrapOptional(inst),
+
+ .unwrap_errunion_payload => self.airUnwrapErrUnionPayload(inst),
+ .wrap_errunion_payload => self.airWrapErrUnionPayload(inst),
+
+ .optional_payload => self.airOptionalPayload(inst),
+ .optional_payload_ptr => self.airOptionalPayload(inst),
+ .optional_payload_ptr_set => self.airOptionalPayloadPtrSet(inst),
+ else => |tag| self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),
+ };
+}
+
+fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
+ for (body) |inst| {
+ const result = try self.genInst(inst);
+ try self.values.putNoClobber(self.gpa, inst, result);
+ }
+}
+
+fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ const un_op = self.air.instructions.items(.data)[inst].un_op;
+ const operand = self.resolveInst(un_op);
+ try self.emitWValue(operand);
+ try self.addTag(.@"return");
+ return .none;
+}
+
+fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ const pl_op = self.air.instructions.items(.data)[inst].pl_op;
+ const extra = self.air.extraData(Air.Call, pl_op.payload);
+ const args = self.air.extra[extra.end..][0..extra.data.args_len];
+
+ const target: *Decl = blk: {
+ const func_val = self.air.value(pl_op.operand).?;
+
+ if (func_val.castTag(.function)) |func| {
+ break :blk func.data.owner_decl;
+ } else if (func_val.castTag(.extern_fn)) |ext_fn| {
+ break :blk ext_fn.data;
+ }
+ return self.fail("Expected a function, but instead found type '{s}'", .{func_val.tag()});
+ };
+
+ for (args) |arg| {
+ const arg_val = self.resolveInst(@intToEnum(Air.Inst.Ref, arg));
+ try self.emitWValue(arg_val);
+ }
+
+ try self.addLabel(.call, target.link.wasm.symbol_index);
+
+ return .none;
+}
+
+fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ const elem_type = self.air.typeOfIndex(inst).elemType();
+ return self.allocLocal(elem_type);
+}
+
+fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+
+ const lhs = self.resolveInst(bin_op.lhs);
+ const rhs = self.resolveInst(bin_op.rhs);
+
+ switch (lhs) {
+ .multi_value => |multi_value| switch (rhs) {
+ // When assigning a value to a multi_value such as a struct,
+ // we simply assign the local_index to the rhs one.
+ // This allows us to update struct fields without having to individually
+ // set each local as each field's index will be calculated off the struct's base index
+ .multi_value => self.values.put(self.gpa, Air.refToIndex(bin_op.lhs).?, rhs) catch unreachable, // Instruction does not dominate all uses!
+ .constant, .none => {
+ // emit all values onto the stack if constant
+ try self.emitWValue(rhs);
+
+ // for each local, pop the stack value into the local
+ // As the last element is on top of the stack, we must populate the locals
+ // in reverse.
+ var i: u32 = multi_value.count;
+ while (i > 0) : (i -= 1) {
+ try self.addLabel(.local_set, multi_value.index + i - 1);
+ }
+ },
+ .local => {
+ // This can occur when we wrap a single value into a multi-value,
+ // such as wrapping a non-optional value into an optional.
+ // This means we must zero the null-tag, and set the payload.
+ assert(multi_value.count == 2);
+ // set payload
+ try self.emitWValue(rhs);
+ try self.addLabel(.local_set, multi_value.index + 1);
+ },
+ else => unreachable,
+ },
+ .local => |local| {
+ try self.emitWValue(rhs);
+ try self.addLabel(.local_set, local);
+ },
+ else => unreachable,
+ }
+ return .none;
+}
+
+fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ return self.resolveInst(ty_op.operand);
+}
+
+fn airArg(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ _ = inst;
+ // arguments share the index with locals
+ defer self.local_index += 1;
+ return WValue{ .local = self.local_index };
+}
+
+fn airBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const lhs = self.resolveInst(bin_op.lhs);
+ const rhs = self.resolveInst(bin_op.rhs);
+
+ // it's possible for both lhs and/or rhs to return an offset as well,
+ // in which case we return the first offset occurrence we find.
+ const offset = blk: {
+ if (lhs == .mir_offset) break :blk lhs.mir_offset;
+ if (rhs == .mir_offset) break :blk rhs.mir_offset;
+ break :blk self.mir_instructions.len;
+ };
+
+ try self.emitWValue(lhs);
+ try self.emitWValue(rhs);
+
+ const bin_ty = self.air.typeOf(bin_op.lhs);
+ const opcode: wasm.Opcode = buildOpcode(.{
+ .op = op,
+ .valtype1 = try self.typeToValtype(bin_ty),
+ .signedness = if (bin_ty.isSignedInt()) .signed else .unsigned,
+ });
+ try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
+ return WValue{ .mir_offset = offset };
+}
+
+fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const lhs = self.resolveInst(bin_op.lhs);
+ const rhs = self.resolveInst(bin_op.rhs);
+
+ // it's possible for both lhs and/or rhs to return an offset as well,
+ // in which case we return the first offset occurrence we find.
+ const offset = blk: {
+ if (lhs == .mir_offset) break :blk lhs.mir_offset;
+ if (rhs == .mir_offset) break :blk rhs.mir_offset;
+ break :blk self.mir_instructions.len;
+ };
+
+ try self.emitWValue(lhs);
+ try self.emitWValue(rhs);
+
+ const bin_ty = self.air.typeOf(bin_op.lhs);
+ const opcode: wasm.Opcode = buildOpcode(.{
+ .op = op,
+ .valtype1 = try self.typeToValtype(bin_ty),
+ .signedness = if (bin_ty.isSignedInt()) .signed else .unsigned,
+ });
+ try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
+
+ const int_info = bin_ty.intInfo(self.target);
+ const bitsize = int_info.bits;
+ const is_signed = int_info.signedness == .signed;
+ // if target type bitsize is x < 32 and 32 > x < 64, we perform
+ // result & ((1<<N)-1) where N = bitsize or bitsize -1 incase of signed.
+ if (bitsize != 32 and bitsize < 64) {
+ // first check if we can use a single instruction,
+ // wasm provides those if the integers are signed and 8/16-bit.
+ // For arbitrary integer sizes, we use the algorithm mentioned above.
+ if (is_signed and bitsize == 8) {
+ try self.addTag(.i32_extend8_s);
+ } else if (is_signed and bitsize == 16) {
+ try self.addTag(.i32_extend16_s);
+ } else {
+ const result = (@as(u64, 1) << @intCast(u6, bitsize - @boolToInt(is_signed))) - 1;
+ if (bitsize < 32) {
+ try self.addImm32(@bitCast(i32, @intCast(u32, result)));
+ try self.addTag(.i32_and);
+ } else {
+ try self.addImm64(result);
+ try self.addTag(.i64_and);
+ }
+ }
+ } else if (int_info.bits > 64) {
+ return self.fail("TODO wasm: Integer wrapping for bitsizes larger than 64", .{});
+ }
+
+ return WValue{ .mir_offset = offset };
+}
+
+fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {
+ switch (ty.zigTypeTag()) {
+ .Int => {
+ const int_info = ty.intInfo(self.target);
+ // write constant
+ switch (int_info.signedness) {
+ .signed => switch (int_info.bits) {
+ 0...32 => try self.addImm32(@intCast(i32, val.toSignedInt())),
+ 33...64 => try self.addImm64(@bitCast(u64, val.toSignedInt())),
+ else => |bits| return self.fail("Wasm todo: emitConstant for integer with {d} bits", .{bits}),
+ },
+ .unsigned => switch (int_info.bits) {
+ 0...32 => try self.addImm32(@bitCast(i32, @intCast(u32, val.toUnsignedInt()))),
+ 33...64 => try self.addImm64(val.toUnsignedInt()),
+ else => |bits| return self.fail("Wasm TODO: emitConstant for integer with {d} bits", .{bits}),
+ },
+ }
+ },
+ .Bool => try self.addImm32(@intCast(i32, val.toSignedInt())),
+ .Float => {
+ // write constant
+ switch (ty.floatBits(self.target)) {
+ 0...32 => try self.addInst(.{ .tag = .f32_const, .data = .{ .float32 = val.toFloat(f32) } }),
+ 64 => try self.addFloat64(val.toFloat(f64)),
+ else => |bits| return self.fail("Wasm TODO: emitConstant for float with {d} bits", .{bits}),
+ }
+ },
+ .Pointer => {
+ if (val.castTag(.decl_ref)) |payload| {
+ const decl = payload.data;
+ decl.alive = true;
+
+ // offset into the offset table within the 'data' section
+ const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8;
+ try self.addImm32(@bitCast(i32, decl.link.wasm.offset_index * ptr_width));
+
+ // memory instruction followed by their memarg immediate
+ // memarg ::== x:u32, y:u32 => {align x, offset y}
+ const extra_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 0 });
+ try self.addInst(.{ .tag = .i32_load, .data = .{ .payload = extra_index } });
+ } else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{val.tag()});
+ },
+ .Void => {},
+ .Enum => {
+ if (val.castTag(.enum_field_index)) |field_index| {
+ switch (ty.tag()) {
+ .enum_simple => try self.addImm32(@bitCast(i32, field_index.data)),
+ .enum_full, .enum_nonexhaustive => {
+ const enum_full = ty.cast(Type.Payload.EnumFull).?.data;
+ if (enum_full.values.count() != 0) {
+ const tag_val = enum_full.values.keys()[field_index.data];
+ try self.emitConstant(tag_val, enum_full.tag_ty);
+ } else {
+ try self.addImm32(@bitCast(i32, field_index.data));
+ }
+ },
+ else => unreachable,
+ }
+ } else {
+ var int_tag_buffer: Type.Payload.Bits = undefined;
+ const int_tag_ty = ty.intTagType(&int_tag_buffer);
+ try self.emitConstant(val, int_tag_ty);
+ }
+ },
+ .ErrorSet => {
+ const error_index = self.global_error_set.get(val.getError().?).?;
+ try self.addImm32(@bitCast(i32, error_index));
+ },
+ .ErrorUnion => {
+ const error_type = ty.errorUnionSet();
+ const payload_type = ty.errorUnionPayload();
+ if (val.castTag(.eu_payload)) |pl| {
+ const payload_val = pl.data;
+ // no error, so write a '0' const
+ try self.addImm32(0);
+ // after the error code, we emit the payload
+ try self.emitConstant(payload_val, payload_type);
+ } else {
+ // write the error val
+ try self.emitConstant(val, error_type);
+
+ // no payload, so write a '0' const
+ try self.addImm32(0);
+ }
+ },
+ .Optional => {
+ var buf: Type.Payload.ElemType = undefined;
+ const payload_type = ty.optionalChild(&buf);
+ if (ty.isPtrLikeOptional()) {
+ return self.fail("Wasm TODO: emitConstant for optional pointer", .{});
+ }
+
+ // When constant has value 'null', set is_null local to '1'
+ // and payload to '0'
+ if (val.castTag(.opt_payload)) |pl| {
+ const payload_val = pl.data;
+ try self.addImm32(0);
+ try self.emitConstant(payload_val, payload_type);
+ } else {
+ // set null-tag
+ try self.addImm32(1);
+ // null-tag is set, so write a '0' const
+ try self.addImm32(0);
+ }
+ },
+ else => |zig_type| return self.fail("Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}),
+ }
+}
+
+/// Returns a `Value` as a signed 32 bit value.
+/// It's illegal to provide a value with a type that cannot be represented
+/// as an integer value.
+fn valueAsI32(self: Self, val: Value, ty: Type) i32 {
+ switch (ty.zigTypeTag()) {
+ .Enum => {
+ if (val.castTag(.enum_field_index)) |field_index| {
+ switch (ty.tag()) {
+ .enum_simple => return @bitCast(i32, field_index.data),
+ .enum_full, .enum_nonexhaustive => {
+ const enum_full = ty.cast(Type.Payload.EnumFull).?.data;
+ if (enum_full.values.count() != 0) {
+ const tag_val = enum_full.values.keys()[field_index.data];
+ return self.valueAsI32(tag_val, enum_full.tag_ty);
+ } else return @bitCast(i32, field_index.data);
+ },
+ else => unreachable,
+ }
+ } else {
+ var int_tag_buffer: Type.Payload.Bits = undefined;
+ const int_tag_ty = ty.intTagType(&int_tag_buffer);
+ return self.valueAsI32(val, int_tag_ty);
+ }
+ },
+ .Int => switch (ty.intInfo(self.target).signedness) {
+ .signed => return @truncate(i32, val.toSignedInt()),
+ .unsigned => return @bitCast(i32, @truncate(u32, val.toUnsignedInt())),
+ },
+ .ErrorSet => {
+ const error_index = self.global_error_set.get(val.getError().?).?;
+ return @bitCast(i32, error_index);
+ },
+ else => unreachable, // Programmer called this function for an illegal type
+ }
+}
+
+fn airBlock(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+ const block_ty = try self.genBlockType(self.air.getRefType(ty_pl.ty));
+ const extra = self.air.extraData(Air.Block, ty_pl.payload);
+ const body = self.air.extra[extra.end..][0..extra.data.body_len];
+
+ try self.startBlock(.block, block_ty, null);
+ // Here we set the current block idx, so breaks know the depth to jump
+ // to when breaking out.
+ try self.blocks.putNoClobber(self.gpa, inst, self.block_depth);
+ try self.genBody(body);
+ try self.endBlock();
+
+ return .none;
+}
+
+/// appends a new wasm block to the code section and increases the `block_depth` by 1
+fn startBlock(self: *Self, block_tag: wasm.Opcode, valtype: u8, with_offset: ?usize) !void {
+ self.block_depth += 1;
+ const offset = with_offset orelse self.mir_instructions.len;
+ try self.addInstAt(offset, .{
+ .tag = Mir.Inst.Tag.fromOpcode(block_tag),
+ .data = .{ .block_type = valtype },
+ });
+}
+
+/// Ends the current wasm block and decreases the `block_depth` by 1
+fn endBlock(self: *Self) !void {
+ try self.addTag(.end);
+ self.block_depth -= 1;
+}
+
+fn airLoop(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+ const loop = self.air.extraData(Air.Block, ty_pl.payload);
+ const body = self.air.extra[loop.end..][0..loop.data.body_len];
+
+ // result type of loop is always 'noreturn', meaning we can always
+ // emit the wasm type 'block_empty'.
+ try self.startBlock(.loop, wasm.block_empty, null);
+ try self.genBody(body);
+
+ // breaking to the index of a loop block will continue the loop instead
+ try self.addLabel(.br, 0);
+ try self.endBlock();
+
+ return .none;
+}
+
+fn airCondBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ const pl_op = self.air.instructions.items(.data)[inst].pl_op;
+ const condition = self.resolveInst(pl_op.operand);
+ const extra = self.air.extraData(Air.CondBr, pl_op.payload);
+ const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len];
+ const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
+ // TODO: Handle death instructions for then and else body
+
+ // insert blocks at the position of `offset` so
+ // the condition can jump to it
+ const offset = switch (condition) {
+ .mir_offset => |offset| offset,
+ else => blk: {
+ const offset = self.mir_instructions.len;
+ try self.emitWValue(condition);
+ break :blk offset;
+ },
+ };
+
+ // result type is always noreturn, so use `block_empty` as type.
+ try self.startBlock(.block, wasm.block_empty, offset);
+
+ // we inserted the block in front of the condition
+ // so now check if condition matches. If not, break outside this block
+ // and continue with the then codepath
+ try self.addLabel(.br_if, 0);
+
+ try self.genBody(else_body);
+ try self.endBlock();
+
+ // Outer block that matches the condition
+ try self.genBody(then_body);
+
+ return .none;
+}
+
+fn airCmp(self: *Self, inst: Air.Inst.Index, op: std.math.CompareOperator) InnerError!WValue {
+ // save offset, so potential conditions can insert blocks in front of
+ // the comparison that we can later jump back to
+ const offset = self.mir_instructions.len;
+
+ const data: Air.Inst.Data = self.air.instructions.items(.data)[inst];
+ const lhs = self.resolveInst(data.bin_op.lhs);
+ const rhs = self.resolveInst(data.bin_op.rhs);
+ const lhs_ty = self.air.typeOf(data.bin_op.lhs);
+
+ try self.emitWValue(lhs);
+ try self.emitWValue(rhs);
+
+ const signedness: std.builtin.Signedness = blk: {
+ // by default we tell the operand type is unsigned (i.e. bools and enum values)
+ if (lhs_ty.zigTypeTag() != .Int) break :blk .unsigned;
+
+ // incase of an actual integer, we emit the correct signedness
+ break :blk lhs_ty.intInfo(self.target).signedness;
+ };
+ const opcode: wasm.Opcode = buildOpcode(.{
+ .valtype1 = try self.typeToValtype(lhs_ty),
+ .op = switch (op) {
+ .lt => .lt,
+ .lte => .le,
+ .eq => .eq,
+ .neq => .ne,
+ .gte => .ge,
+ .gt => .gt,
+ },
+ .signedness = signedness,
+ });
+ try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
+ return WValue{ .mir_offset = offset };
+}
+
+fn airBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ const br = self.air.instructions.items(.data)[inst].br;
+
+ // if operand has codegen bits we should break with a value
+ if (self.air.typeOf(br.operand).hasCodeGenBits()) {
+ try self.emitWValue(self.resolveInst(br.operand));
+ }
+
+ // We map every block to its block index.
+ // We then determine how far we have to jump to it by subtracting it from current block depth
+ const idx: u32 = self.block_depth - self.blocks.get(br.block_inst).?;
+ try self.addLabel(.br, idx);
+
+ return .none;
+}
+
+fn airNot(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ const offset = self.mir_instructions.len;
+
+ const operand = self.resolveInst(ty_op.operand);
+ try self.emitWValue(operand);
+
+ // wasm does not have booleans nor the `not` instruction, therefore compare with 0
+ // to create the same logic
+ try self.addImm32(0);
+ try self.addTag(.i32_eq);
+
+ return WValue{ .mir_offset = offset };
+}
+
+fn airBreakpoint(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ _ = self;
+ _ = inst;
+ // unsupported by wasm itself. Can be implemented once we support DWARF
+ // for wasm
+ return .none;
+}
+
+fn airUnreachable(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ _ = inst;
+ try self.addTag(.@"unreachable");
+ return .none;
+}
+
+fn airBitcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ return self.resolveInst(ty_op.operand);
+}
+
+fn airStructFieldPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+ const extra = self.air.extraData(Air.StructField, ty_pl.payload);
+ const struct_ptr = self.resolveInst(extra.data.struct_operand);
+ return structFieldPtr(struct_ptr, extra.data.field_index);
+}
+fn airStructFieldPtrIndex(self: *Self, inst: Air.Inst.Index, index: u32) InnerError!WValue {
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ const struct_ptr = self.resolveInst(ty_op.operand);
+ return structFieldPtr(struct_ptr, index);
+}
+fn structFieldPtr(struct_ptr: WValue, index: u32) InnerError!WValue {
+ return WValue{ .local = struct_ptr.multi_value.index + index };
+}
+
+fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ if (self.liveness.isUnused(inst)) return WValue.none;
+
+ const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+ const extra = self.air.extraData(Air.StructField, ty_pl.payload).data;
+ const struct_multivalue = self.resolveInst(extra.struct_operand).multi_value;
+ return WValue{ .local = struct_multivalue.index + extra.field_index };
+}
+
+fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ // result type is always 'noreturn'
+ const blocktype = wasm.block_empty;
+ const pl_op = self.air.instructions.items(.data)[inst].pl_op;
+ const target = self.resolveInst(pl_op.operand);
+ const target_ty = self.air.typeOf(pl_op.operand);
+ const switch_br = self.air.extraData(Air.SwitchBr, pl_op.payload);
+ var extra_index: usize = switch_br.end;
+ var case_i: u32 = 0;
+
+ // a list that maps each value with its value and body based on the order inside the list.
+ const CaseValue = struct { integer: i32, value: Value };
+ var case_list = try std.ArrayList(struct {
+ values: []const CaseValue,
+ body: []const Air.Inst.Index,
+ }).initCapacity(self.gpa, switch_br.data.cases_len);
+ defer for (case_list.items) |case| {
+ self.gpa.free(case.values);
+ } else case_list.deinit();
+
+ var lowest: i32 = 0;
+ var highest: i32 = 0;
+ while (case_i < switch_br.data.cases_len) : (case_i += 1) {
+ const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
+ const items = @bitCast([]const Air.Inst.Ref, self.air.extra[case.end..][0..case.data.items_len]);
+ const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len];
+ extra_index = case.end + items.len + case_body.len;
+ const values = try self.gpa.alloc(CaseValue, items.len);
+ errdefer self.gpa.free(values);
+
+ for (items) |ref, i| {
+ const item_val = self.air.value(ref).?;
+ const int_val = self.valueAsI32(item_val, target_ty);
+ if (int_val < lowest) {
+ lowest = int_val;
+ }
+ if (int_val > highest) {
+ highest = int_val;
+ }
+ values[i] = .{ .integer = int_val, .value = item_val };
+ }
+
+ case_list.appendAssumeCapacity(.{ .values = values, .body = case_body });
+ try self.startBlock(.block, blocktype, null);
+ }
+
+ // When the highest and lowest values are seperated by '50',
+ // we define it as sparse and use an if/else-chain, rather than a jump table.
+ // When the target is an integer size larger than u32, we have no way to use the value
+ // as an index, therefore we also use an if/else-chain for those cases.
+ // TODO: Benchmark this to find a proper value, LLVM seems to draw the line at '40~45'.
+ const is_sparse = highest - lowest > 50 or target_ty.bitSize(self.target) > 32;
+
+ const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len];
+ const has_else_body = else_body.len != 0;
+ if (has_else_body) {
+ try self.startBlock(.block, blocktype, null);
+ }
+
+ if (!is_sparse) {
+ // Generate the jump table 'br_table' when the prongs are not sparse.
+ // The value 'target' represents the index into the table.
+ // Each index in the table represents a label to the branch
+ // to jump to.
+ try self.startBlock(.block, blocktype, null);
+ try self.emitWValue(target);
+ if (lowest < 0) {
+ // since br_table works using indexes, starting from '0', we must ensure all values
+ // we put inside, are atleast 0.
+ try self.addImm32(lowest * -1);
+ try self.addTag(.i32_add);
+ }
+
+ // Account for default branch so always add '1'
+ const depth = @intCast(u32, highest - lowest + @boolToInt(has_else_body)) + 1;
+ const jump_table: Mir.JumpTable = .{ .length = depth };
+ const table_extra_index = try self.addExtra(jump_table);
+ try self.addInst(.{ .tag = .br_table, .data = .{ .payload = table_extra_index } });
+ try self.mir_extra.ensureUnusedCapacity(self.gpa, depth);
+ while (lowest <= highest) : (lowest += 1) {
+ // idx represents the branch we jump to
+ const idx = blk: {
+ for (case_list.items) |case, idx| {
+ for (case.values) |case_value| {
+ if (case_value.integer == lowest) break :blk @intCast(u32, idx);
+ }
+ }
+ break :blk if (has_else_body) case_i else unreachable;
+ };
+ self.mir_extra.appendAssumeCapacity(idx);
+ } else if (has_else_body) {
+ self.mir_extra.appendAssumeCapacity(case_i); // default branch
+ }
+ try self.endBlock();
+ }
+
+ const signedness: std.builtin.Signedness = blk: {
+ // by default we tell the operand type is unsigned (i.e. bools and enum values)
+ if (target_ty.zigTypeTag() != .Int) break :blk .unsigned;
+
+ // incase of an actual integer, we emit the correct signedness
+ break :blk target_ty.intInfo(self.target).signedness;
+ };
+
+ for (case_list.items) |case| {
+ // when sparse, we use if/else-chain, so emit conditional checks
+ if (is_sparse) {
+ // for single value prong we can emit a simple if
+ if (case.values.len == 1) {
+ try self.emitWValue(target);
+ try self.emitConstant(case.values[0].value, target_ty);
+ const opcode = buildOpcode(.{
+ .valtype1 = try self.typeToValtype(target_ty),
+ .op = .ne, // not equal, because we want to jump out of this block if it does not match the condition.
+ .signedness = signedness,
+ });
+ try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
+ try self.addLabel(.br_if, 0);
+ } else {
+ // in multi-value prongs we must check if any prongs match the target value.
+ try self.startBlock(.block, blocktype, null);
+ for (case.values) |value| {
+ try self.emitWValue(target);
+ try self.emitConstant(value.value, target_ty);
+ const opcode = buildOpcode(.{
+ .valtype1 = try self.typeToValtype(target_ty),
+ .op = .eq,
+ .signedness = signedness,
+ });
+ try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
+ try self.addLabel(.br_if, 0);
+ }
+ // value did not match any of the prong values
+ try self.addLabel(.br, 1);
+ try self.endBlock();
+ }
+ }
+ try self.genBody(case.body);
+ try self.endBlock();
+ }
+
+ if (has_else_body) {
+ try self.genBody(else_body);
+ try self.endBlock();
+ }
+ return .none;
+}
+
+fn airIsErr(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {
+ const un_op = self.air.instructions.items(.data)[inst].un_op;
+ const operand = self.resolveInst(un_op);
+ const offset = self.mir_instructions.len;
+
+ // load the error value which is positioned at multi_value's index
+ try self.emitWValue(.{ .local = operand.multi_value.index });
+ // Compare the error value with '0'
+ try self.addImm32(0);
+ try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
+
+ return WValue{ .mir_offset = offset };
+}
+
+fn airUnwrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ const operand = self.resolveInst(ty_op.operand);
+ // The index of multi_value contains the error code. To get the initial index of the payload we get
+ // the following index. Next, convert it to a `WValue.local`
+ //
+ // TODO: Check if payload is a type that requires a multi_value as well and emit that instead. i.e. a struct.
+ return WValue{ .local = operand.multi_value.index + 1 };
+}
+
+fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ return self.resolveInst(ty_op.operand);
+}
+
+fn airIntcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ const ty = self.air.getRefType(ty_op.ty);
+ const operand = self.resolveInst(ty_op.operand);
+ const ref_ty = self.air.typeOf(ty_op.operand);
+ const ref_info = ref_ty.intInfo(self.target);
+ const op_bits = ref_info.bits;
+ const wanted_bits = ty.intInfo(self.target).bits;
+
+ try self.emitWValue(operand);
+ if (op_bits > 32 and wanted_bits <= 32) {
+ try self.addTag(.i32_wrap_i64);
+ } else if (op_bits <= 32 and wanted_bits > 32) {
+ try self.addTag(switch (ref_info.signedness) {
+ .signed => .i64_extend_i32_s,
+ .unsigned => .i64_extend_i32_u,
+ });
+ }
+
+ // other cases are no-op
+ return .none;
+}
+
+fn airIsNull(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {
+ const un_op = self.air.instructions.items(.data)[inst].un_op;
+ const operand = self.resolveInst(un_op);
+
+ // load the null value which is positioned at multi_value's index
+ try self.emitWValue(.{ .local = operand.multi_value.index });
+ try self.addImm32(0);
+ try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
+
+ // we save the result in a new local
+ const local = try self.allocLocal(Type.initTag(.i32));
+ try self.addLabel(.local_set, local.local);
+
+ return local;
+}
+
+fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ const operand = self.resolveInst(ty_op.operand);
+ return WValue{ .local = operand.multi_value.index + 1 };
+}
+
+fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ const operand = self.resolveInst(ty_op.operand);
+ _ = operand;
+ return self.fail("TODO - wasm codegen for optional_payload_ptr_set", .{});
+}
+
+fn airWrapOptional(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ return self.resolveInst(ty_op.operand);
+}
src/arch/wasm/Emit.zig
@@ -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,
+ });
+}
src/arch/wasm/Mir.zig
@@ -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,
+};
src/codegen/wasm.zig
@@ -1,1717 +0,0 @@
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-const ArrayList = std.ArrayList;
-const assert = std.debug.assert;
-const testing = std.testing;
-const leb = std.leb;
-const mem = std.mem;
-const wasm = std.wasm;
-
-const Module = @import("../Module.zig");
-const Decl = Module.Decl;
-const Type = @import("../type.zig").Type;
-const Value = @import("../value.zig").Value;
-const Compilation = @import("../Compilation.zig");
-const LazySrcLoc = Module.LazySrcLoc;
-const link = @import("../link.zig");
-const TypedValue = @import("../TypedValue.zig");
-const Air = @import("../Air.zig");
-const Liveness = @import("../Liveness.zig");
-
-/// Wasm Value, created when generating an instruction
-const WValue = union(enum) {
- /// May be referenced but is unused
- none: void,
- /// Index of the local variable
- local: u32,
- /// Holds a memoized typed value
- constant: TypedValue,
- /// Offset position in the list of bytecode instructions
- code_offset: usize,
- /// Used for variables that create multiple locals on the stack when allocated
- /// such as structs and optionals.
- multi_value: struct {
- /// The index of the first local variable
- index: u32,
- /// The count of local variables this `WValue` consists of.
- /// i.e. an ErrorUnion has a 'count' of 2.
- count: u32,
- },
-};
-
-/// Wasm ops, but without input/output/signedness information
-/// Used for `buildOpcode`
-const Op = enum {
- @"unreachable",
- nop,
- block,
- loop,
- @"if",
- @"else",
- end,
- br,
- br_if,
- br_table,
- @"return",
- call,
- call_indirect,
- drop,
- select,
- local_get,
- local_set,
- local_tee,
- global_get,
- global_set,
- load,
- store,
- memory_size,
- memory_grow,
- @"const",
- eqz,
- eq,
- ne,
- lt,
- gt,
- le,
- ge,
- clz,
- ctz,
- popcnt,
- add,
- sub,
- mul,
- div,
- rem,
- @"and",
- @"or",
- xor,
- shl,
- shr,
- rotl,
- rotr,
- abs,
- neg,
- ceil,
- floor,
- trunc,
- nearest,
- sqrt,
- min,
- max,
- copysign,
- wrap,
- convert,
- demote,
- promote,
- reinterpret,
- extend,
-};
-
-/// Contains the settings needed to create an `Opcode` using `buildOpcode`.
-///
-/// The fields correspond to the opcode name. Here is an example
-/// i32_trunc_f32_s
-/// ^ ^ ^ ^
-/// | | | |
-/// valtype1 | | |
-/// = .i32 | | |
-/// | | |
-/// op | |
-/// = .trunc | |
-/// | |
-/// valtype2 |
-/// = .f32 |
-/// |
-/// width |
-/// = null |
-/// |
-/// signed
-/// = true
-///
-/// There can be missing fields, here are some more examples:
-/// i64_load8_u
-/// --> .{ .valtype1 = .i64, .op = .load, .width = 8, signed = false }
-/// i32_mul
-/// --> .{ .valtype1 = .i32, .op = .trunc }
-/// nop
-/// --> .{ .op = .nop }
-const OpcodeBuildArguments = struct {
- /// First valtype in the opcode (usually represents the type of the output)
- valtype1: ?wasm.Valtype = null,
- /// The operation (e.g. call, unreachable, div, min, sqrt, etc.)
- op: Op,
- /// Width of the operation (e.g. 8 for i32_load8_s, 16 for i64_extend16_i32_s)
- width: ?u8 = null,
- /// Second valtype in the opcode name (usually represents the type of the input)
- valtype2: ?wasm.Valtype = null,
- /// Signedness of the op
- signedness: ?std.builtin.Signedness = null,
-};
-
-/// Helper function that builds an Opcode given the arguments needed
-fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
- switch (args.op) {
- .@"unreachable" => return .@"unreachable",
- .nop => return .nop,
- .block => return .block,
- .loop => return .loop,
- .@"if" => return .@"if",
- .@"else" => return .@"else",
- .end => return .end,
- .br => return .br,
- .br_if => return .br_if,
- .br_table => return .br_table,
- .@"return" => return .@"return",
- .call => return .call,
- .call_indirect => return .call_indirect,
- .drop => return .drop,
- .select => return .select,
- .local_get => return .local_get,
- .local_set => return .local_set,
- .local_tee => return .local_tee,
- .global_get => return .global_get,
- .global_set => return .global_set,
-
- .load => if (args.width) |width| switch (width) {
- 8 => switch (args.valtype1.?) {
- .i32 => if (args.signedness.? == .signed) return .i32_load8_s else return .i32_load8_u,
- .i64 => if (args.signedness.? == .signed) return .i64_load8_s else return .i64_load8_u,
- .f32, .f64 => unreachable,
- },
- 16 => switch (args.valtype1.?) {
- .i32 => if (args.signedness.? == .signed) return .i32_load16_s else return .i32_load16_u,
- .i64 => if (args.signedness.? == .signed) return .i64_load16_s else return .i64_load16_u,
- .f32, .f64 => unreachable,
- },
- 32 => switch (args.valtype1.?) {
- .i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u,
- .i32, .f32, .f64 => unreachable,
- },
- else => unreachable,
- } else switch (args.valtype1.?) {
- .i32 => return .i32_load,
- .i64 => return .i64_load,
- .f32 => return .f32_load,
- .f64 => return .f64_load,
- },
- .store => if (args.width) |width| {
- switch (width) {
- 8 => switch (args.valtype1.?) {
- .i32 => return .i32_store8,
- .i64 => return .i64_store8,
- .f32, .f64 => unreachable,
- },
- 16 => switch (args.valtype1.?) {
- .i32 => return .i32_store16,
- .i64 => return .i64_store16,
- .f32, .f64 => unreachable,
- },
- 32 => switch (args.valtype1.?) {
- .i64 => return .i64_store32,
- .i32, .f32, .f64 => unreachable,
- },
- else => unreachable,
- }
- } else {
- switch (args.valtype1.?) {
- .i32 => return .i32_store,
- .i64 => return .i64_store,
- .f32 => return .f32_store,
- .f64 => return .f64_store,
- }
- },
-
- .memory_size => return .memory_size,
- .memory_grow => return .memory_grow,
-
- .@"const" => switch (args.valtype1.?) {
- .i32 => return .i32_const,
- .i64 => return .i64_const,
- .f32 => return .f32_const,
- .f64 => return .f64_const,
- },
-
- .eqz => switch (args.valtype1.?) {
- .i32 => return .i32_eqz,
- .i64 => return .i64_eqz,
- .f32, .f64 => unreachable,
- },
- .eq => switch (args.valtype1.?) {
- .i32 => return .i32_eq,
- .i64 => return .i64_eq,
- .f32 => return .f32_eq,
- .f64 => return .f64_eq,
- },
- .ne => switch (args.valtype1.?) {
- .i32 => return .i32_ne,
- .i64 => return .i64_ne,
- .f32 => return .f32_ne,
- .f64 => return .f64_ne,
- },
-
- .lt => switch (args.valtype1.?) {
- .i32 => if (args.signedness.? == .signed) return .i32_lt_s else return .i32_lt_u,
- .i64 => if (args.signedness.? == .signed) return .i64_lt_s else return .i64_lt_u,
- .f32 => return .f32_lt,
- .f64 => return .f64_lt,
- },
- .gt => switch (args.valtype1.?) {
- .i32 => if (args.signedness.? == .signed) return .i32_gt_s else return .i32_gt_u,
- .i64 => if (args.signedness.? == .signed) return .i64_gt_s else return .i64_gt_u,
- .f32 => return .f32_gt,
- .f64 => return .f64_gt,
- },
- .le => switch (args.valtype1.?) {
- .i32 => if (args.signedness.? == .signed) return .i32_le_s else return .i32_le_u,
- .i64 => if (args.signedness.? == .signed) return .i64_le_s else return .i64_le_u,
- .f32 => return .f32_le,
- .f64 => return .f64_le,
- },
- .ge => switch (args.valtype1.?) {
- .i32 => if (args.signedness.? == .signed) return .i32_ge_s else return .i32_ge_u,
- .i64 => if (args.signedness.? == .signed) return .i64_ge_s else return .i64_ge_u,
- .f32 => return .f32_ge,
- .f64 => return .f64_ge,
- },
-
- .clz => switch (args.valtype1.?) {
- .i32 => return .i32_clz,
- .i64 => return .i64_clz,
- .f32, .f64 => unreachable,
- },
- .ctz => switch (args.valtype1.?) {
- .i32 => return .i32_ctz,
- .i64 => return .i64_ctz,
- .f32, .f64 => unreachable,
- },
- .popcnt => switch (args.valtype1.?) {
- .i32 => return .i32_popcnt,
- .i64 => return .i64_popcnt,
- .f32, .f64 => unreachable,
- },
-
- .add => switch (args.valtype1.?) {
- .i32 => return .i32_add,
- .i64 => return .i64_add,
- .f32 => return .f32_add,
- .f64 => return .f64_add,
- },
- .sub => switch (args.valtype1.?) {
- .i32 => return .i32_sub,
- .i64 => return .i64_sub,
- .f32 => return .f32_sub,
- .f64 => return .f64_sub,
- },
- .mul => switch (args.valtype1.?) {
- .i32 => return .i32_mul,
- .i64 => return .i64_mul,
- .f32 => return .f32_mul,
- .f64 => return .f64_mul,
- },
-
- .div => switch (args.valtype1.?) {
- .i32 => if (args.signedness.? == .signed) return .i32_div_s else return .i32_div_u,
- .i64 => if (args.signedness.? == .signed) return .i64_div_s else return .i64_div_u,
- .f32 => return .f32_div,
- .f64 => return .f64_div,
- },
- .rem => switch (args.valtype1.?) {
- .i32 => if (args.signedness.? == .signed) return .i32_rem_s else return .i32_rem_u,
- .i64 => if (args.signedness.? == .signed) return .i64_rem_s else return .i64_rem_u,
- .f32, .f64 => unreachable,
- },
-
- .@"and" => switch (args.valtype1.?) {
- .i32 => return .i32_and,
- .i64 => return .i64_and,
- .f32, .f64 => unreachable,
- },
- .@"or" => switch (args.valtype1.?) {
- .i32 => return .i32_or,
- .i64 => return .i64_or,
- .f32, .f64 => unreachable,
- },
- .xor => switch (args.valtype1.?) {
- .i32 => return .i32_xor,
- .i64 => return .i64_xor,
- .f32, .f64 => unreachable,
- },
-
- .shl => switch (args.valtype1.?) {
- .i32 => return .i32_shl,
- .i64 => return .i64_shl,
- .f32, .f64 => unreachable,
- },
- .shr => switch (args.valtype1.?) {
- .i32 => if (args.signedness.? == .signed) return .i32_shr_s else return .i32_shr_u,
- .i64 => if (args.signedness.? == .signed) return .i64_shr_s else return .i64_shr_u,
- .f32, .f64 => unreachable,
- },
- .rotl => switch (args.valtype1.?) {
- .i32 => return .i32_rotl,
- .i64 => return .i64_rotl,
- .f32, .f64 => unreachable,
- },
- .rotr => switch (args.valtype1.?) {
- .i32 => return .i32_rotr,
- .i64 => return .i64_rotr,
- .f32, .f64 => unreachable,
- },
-
- .abs => switch (args.valtype1.?) {
- .i32, .i64 => unreachable,
- .f32 => return .f32_abs,
- .f64 => return .f64_abs,
- },
- .neg => switch (args.valtype1.?) {
- .i32, .i64 => unreachable,
- .f32 => return .f32_neg,
- .f64 => return .f64_neg,
- },
- .ceil => switch (args.valtype1.?) {
- .i32, .i64 => unreachable,
- .f32 => return .f32_ceil,
- .f64 => return .f64_ceil,
- },
- .floor => switch (args.valtype1.?) {
- .i32, .i64 => unreachable,
- .f32 => return .f32_floor,
- .f64 => return .f64_floor,
- },
- .trunc => switch (args.valtype1.?) {
- .i32 => switch (args.valtype2.?) {
- .i32 => unreachable,
- .i64 => unreachable,
- .f32 => if (args.signedness.? == .signed) return .i32_trunc_f32_s else return .i32_trunc_f32_u,
- .f64 => if (args.signedness.? == .signed) return .i32_trunc_f64_s else return .i32_trunc_f64_u,
- },
- .i64 => unreachable,
- .f32 => return .f32_trunc,
- .f64 => return .f64_trunc,
- },
- .nearest => switch (args.valtype1.?) {
- .i32, .i64 => unreachable,
- .f32 => return .f32_nearest,
- .f64 => return .f64_nearest,
- },
- .sqrt => switch (args.valtype1.?) {
- .i32, .i64 => unreachable,
- .f32 => return .f32_sqrt,
- .f64 => return .f64_sqrt,
- },
- .min => switch (args.valtype1.?) {
- .i32, .i64 => unreachable,
- .f32 => return .f32_min,
- .f64 => return .f64_min,
- },
- .max => switch (args.valtype1.?) {
- .i32, .i64 => unreachable,
- .f32 => return .f32_max,
- .f64 => return .f64_max,
- },
- .copysign => switch (args.valtype1.?) {
- .i32, .i64 => unreachable,
- .f32 => return .f32_copysign,
- .f64 => return .f64_copysign,
- },
-
- .wrap => switch (args.valtype1.?) {
- .i32 => switch (args.valtype2.?) {
- .i32 => unreachable,
- .i64 => return .i32_wrap_i64,
- .f32, .f64 => unreachable,
- },
- .i64, .f32, .f64 => unreachable,
- },
- .convert => switch (args.valtype1.?) {
- .i32, .i64 => unreachable,
- .f32 => switch (args.valtype2.?) {
- .i32 => if (args.signedness.? == .signed) return .f32_convert_i32_s else return .f32_convert_i32_u,
- .i64 => if (args.signedness.? == .signed) return .f32_convert_i64_s else return .f32_convert_i64_u,
- .f32, .f64 => unreachable,
- },
- .f64 => switch (args.valtype2.?) {
- .i32 => if (args.signedness.? == .signed) return .f64_convert_i32_s else return .f64_convert_i32_u,
- .i64 => if (args.signedness.? == .signed) return .f64_convert_i64_s else return .f64_convert_i64_u,
- .f32, .f64 => unreachable,
- },
- },
- .demote => if (args.valtype1.? == .f32 and args.valtype2.? == .f64) return .f32_demote_f64 else unreachable,
- .promote => if (args.valtype1.? == .f64 and args.valtype2.? == .f32) return .f64_promote_f32 else unreachable,
- .reinterpret => switch (args.valtype1.?) {
- .i32 => if (args.valtype2.? == .f32) return .i32_reinterpret_f32 else unreachable,
- .i64 => if (args.valtype2.? == .f64) return .i64_reinterpret_f64 else unreachable,
- .f32 => if (args.valtype2.? == .i32) return .f32_reinterpret_i32 else unreachable,
- .f64 => if (args.valtype2.? == .i64) return .f64_reinterpret_i64 else unreachable,
- },
- .extend => switch (args.valtype1.?) {
- .i32 => switch (args.width.?) {
- 8 => if (args.signedness.? == .signed) return .i32_extend8_s else unreachable,
- 16 => if (args.signedness.? == .signed) return .i32_extend16_s else unreachable,
- else => unreachable,
- },
- .i64 => switch (args.width.?) {
- 8 => if (args.signedness.? == .signed) return .i64_extend8_s else unreachable,
- 16 => if (args.signedness.? == .signed) return .i64_extend16_s else unreachable,
- 32 => if (args.signedness.? == .signed) return .i64_extend32_s else unreachable,
- else => unreachable,
- },
- .f32, .f64 => unreachable,
- },
- }
-}
-
-test "Wasm - buildOpcode" {
- // Make sure buildOpcode is referenced, and test some examples
- const i32_const = buildOpcode(.{ .op = .@"const", .valtype1 = .i32 });
- const end = buildOpcode(.{ .op = .end });
- const local_get = buildOpcode(.{ .op = .local_get });
- const i64_extend32_s = buildOpcode(.{ .op = .extend, .valtype1 = .i64, .width = 32, .signedness = .signed });
- const f64_reinterpret_i64 = buildOpcode(.{ .op = .reinterpret, .valtype1 = .f64, .valtype2 = .i64 });
-
- try testing.expectEqual(@as(wasm.Opcode, .i32_const), i32_const);
- try testing.expectEqual(@as(wasm.Opcode, .end), end);
- try testing.expectEqual(@as(wasm.Opcode, .local_get), local_get);
- try testing.expectEqual(@as(wasm.Opcode, .i64_extend32_s), i64_extend32_s);
- try testing.expectEqual(@as(wasm.Opcode, .f64_reinterpret_i64), f64_reinterpret_i64);
-}
-
-pub const Result = union(enum) {
- /// The codegen bytes have been appended to `Context.code`
- appended: void,
- /// The data is managed externally and are part of the `Result`
- externally_managed: []const u8,
-};
-
-/// Hashmap to store generated `WValue` for each `Air.Inst.Ref`
-pub const ValueTable = std.AutoHashMapUnmanaged(Air.Inst.Index, WValue);
-
-/// Code represents the `Code` section of wasm that
-/// belongs to a function
-pub const Context = struct {
- /// Reference to the function declaration the code
- /// section belongs to
- decl: *Decl,
- air: Air,
- liveness: Liveness,
- gpa: *mem.Allocator,
- /// Table to save `WValue`'s generated by an `Air.Inst`
- values: ValueTable,
- /// Mapping from Air.Inst.Index to block ids
- blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, u32) = .{},
- /// `bytes` contains the wasm bytecode belonging to the 'code' section.
- code: ArrayList(u8),
- /// Contains the generated function type bytecode for the current function
- /// found in `decl`
- func_type_data: ArrayList(u8),
- /// The index the next local generated will have
- /// NOTE: arguments share the index with locals therefore the first variable
- /// will have the index that comes after the last argument's index
- local_index: u32 = 0,
- /// If codegen fails, an error messages will be allocated and saved in `err_msg`
- err_msg: *Module.ErrorMsg,
- /// Current block depth. Used to calculate the relative difference between a break
- /// and block
- block_depth: u32 = 0,
- /// List of all locals' types generated throughout this declaration
- /// used to emit locals count at start of 'code' section.
- locals: std.ArrayListUnmanaged(u8),
- /// The Target we're emitting (used to call intInfo)
- target: std.Target,
- /// Table with the global error set. Consists of every error found in
- /// the compiled code. Each error name maps to a `Module.ErrorInt` which is emitted
- /// during codegen to determine the error value.
- global_error_set: std.StringHashMapUnmanaged(Module.ErrorInt),
-
- const InnerError = error{
- OutOfMemory,
- CodegenFail,
- /// Can occur when dereferencing a pointer that points to a `Decl` of which the analysis has failed
- AnalysisFail,
- };
-
- pub fn deinit(self: *Context) void {
- self.values.deinit(self.gpa);
- self.blocks.deinit(self.gpa);
- self.locals.deinit(self.gpa);
- self.* = undefined;
- }
-
- /// Sets `err_msg` on `Context` and returns `error.CodegemFail` which is caught in link/Wasm.zig
- fn fail(self: *Context, comptime fmt: []const u8, args: anytype) InnerError {
- const src: LazySrcLoc = .{ .node_offset = 0 };
- const src_loc = src.toSrcLoc(self.decl);
- self.err_msg = try Module.ErrorMsg.create(self.gpa, src_loc, fmt, args);
- return error.CodegenFail;
- }
-
- /// Resolves the `WValue` for the given instruction `inst`
- /// When the given instruction has a `Value`, it returns a constant instead
- fn resolveInst(self: Context, ref: Air.Inst.Ref) WValue {
- const inst_index = Air.refToIndex(ref) orelse {
- const tv = Air.Inst.Ref.typed_value_map[@enumToInt(ref)];
- if (!tv.ty.hasCodeGenBits()) {
- return WValue.none;
- }
- return WValue{ .constant = tv };
- };
-
- const inst_type = self.air.typeOfIndex(inst_index);
- if (!inst_type.hasCodeGenBits()) return .none;
-
- if (self.air.instructions.items(.tag)[inst_index] == .constant) {
- const ty_pl = self.air.instructions.items(.data)[inst_index].ty_pl;
- return WValue{ .constant = .{ .ty = inst_type, .val = self.air.values[ty_pl.payload] } };
- }
-
- return self.values.get(inst_index).?; // Instruction does not dominate all uses!
- }
-
- /// Using a given `Type`, returns the corresponding wasm Valtype
- fn typeToValtype(self: *Context, ty: Type) InnerError!wasm.Valtype {
- return switch (ty.zigTypeTag()) {
- .Float => blk: {
- const bits = ty.floatBits(self.target);
- if (bits == 16 or bits == 32) break :blk wasm.Valtype.f32;
- if (bits == 64) break :blk wasm.Valtype.f64;
- return self.fail("Float bit size not supported by wasm: '{d}'", .{bits});
- },
- .Int => blk: {
- const info = ty.intInfo(self.target);
- if (info.bits <= 32) break :blk wasm.Valtype.i32;
- if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64;
- return self.fail("Integer bit size not supported by wasm: '{d}'", .{info.bits});
- },
- .Enum => switch (ty.tag()) {
- .enum_simple => wasm.Valtype.i32,
- else => self.typeToValtype(ty.cast(Type.Payload.EnumFull).?.data.tag_ty),
- },
- .Bool,
- .Pointer,
- .ErrorSet,
- => wasm.Valtype.i32,
- .Struct, .ErrorUnion, .Optional => unreachable, // Multi typed, must be handled individually.
- else => |tag| self.fail("TODO - Wasm valtype for type '{s}'", .{tag}),
- };
- }
-
- /// Using a given `Type`, returns the byte representation of its wasm value type
- fn genValtype(self: *Context, ty: Type) InnerError!u8 {
- return wasm.valtype(try self.typeToValtype(ty));
- }
-
- /// Using a given `Type`, returns the corresponding wasm value type
- /// Differently from `genValtype` this also allows `void` to create a block
- /// with no return type
- fn genBlockType(self: *Context, ty: Type) InnerError!u8 {
- return switch (ty.tag()) {
- .void, .noreturn => wasm.block_empty,
- else => self.genValtype(ty),
- };
- }
-
- /// Writes the bytecode depending on the given `WValue` in `val`
- fn emitWValue(self: *Context, val: WValue) InnerError!void {
- const writer = self.code.writer();
- switch (val) {
- .multi_value => unreachable, // multi_value can never be written directly, and must be accessed individually
- .none, .code_offset => {}, // no-op
- .local => |idx| {
- try writer.writeByte(wasm.opcode(.local_get));
- try leb.writeULEB128(writer, idx);
- },
- .constant => |tv| try self.emitConstant(tv.val, tv.ty), // Creates a new constant on the stack
- }
- }
-
- /// Creates one or multiple locals for a given `Type`.
- /// Returns a corresponding `Wvalue` that can either be of tag
- /// local or multi_value
- fn allocLocal(self: *Context, ty: Type) InnerError!WValue {
- const initial_index = self.local_index;
- switch (ty.zigTypeTag()) {
- .Struct => {
- // for each struct field, generate a local
- const struct_data: *Module.Struct = ty.castTag(.@"struct").?.data;
- const fields_len = @intCast(u32, struct_data.fields.count());
- try self.locals.ensureUnusedCapacity(self.gpa, fields_len);
- for (struct_data.fields.values()) |*value| {
- const val_type = try self.genValtype(value.ty);
- self.locals.appendAssumeCapacity(val_type);
- self.local_index += 1;
- }
- return WValue{ .multi_value = .{
- .index = initial_index,
- .count = fields_len,
- } };
- },
- .ErrorUnion => {
- const payload_type = ty.errorUnionPayload();
- const val_type = try self.genValtype(payload_type);
-
- // we emit the error value as the first local, and the payload as the following.
- // The first local is also used to find the index of the error and payload.
- //
- // TODO: Add support where the payload is a type that contains multiple locals such as a struct.
- try self.locals.ensureUnusedCapacity(self.gpa, 2);
- self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // error values are always i32
- self.locals.appendAssumeCapacity(val_type);
- self.local_index += 2;
-
- return WValue{ .multi_value = .{
- .index = initial_index,
- .count = 2,
- } };
- },
- .Optional => {
- var opt_buf: Type.Payload.ElemType = undefined;
- const child_type = ty.optionalChild(&opt_buf);
- if (ty.isPtrLikeOptional()) {
- return self.fail("TODO: wasm optional pointer", .{});
- }
-
- try self.locals.ensureUnusedCapacity(self.gpa, 2);
- self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // optional 'tag' for null-checking is always i32
- self.locals.appendAssumeCapacity(try self.genValtype(child_type));
- self.local_index += 2;
-
- return WValue{ .multi_value = .{
- .index = initial_index,
- .count = 2,
- } };
- },
- else => {
- const valtype = try self.genValtype(ty);
- try self.locals.append(self.gpa, valtype);
- self.local_index += 1;
- return WValue{ .local = initial_index };
- },
- }
- }
-
- fn genFunctype(self: *Context) InnerError!void {
- assert(self.decl.has_tv);
- const ty = self.decl.ty;
- const writer = self.func_type_data.writer();
-
- try writer.writeByte(wasm.function_type);
-
- // param types
- try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen()));
- if (ty.fnParamLen() != 0) {
- const params = try self.gpa.alloc(Type, ty.fnParamLen());
- defer self.gpa.free(params);
- ty.fnParamTypes(params);
- for (params) |param_type| {
- // Can we maybe get the source index of each param?
- const val_type = try self.genValtype(param_type);
- try writer.writeByte(val_type);
- }
- }
-
- // return type
- const return_type = ty.fnReturnType();
- switch (return_type.zigTypeTag()) {
- .Void, .NoReturn => try leb.writeULEB128(writer, @as(u32, 0)),
- .Struct => return self.fail("TODO: Implement struct as return type for wasm", .{}),
- .Optional => return self.fail("TODO: Implement optionals as return type for wasm", .{}),
- .ErrorUnion => {
- const val_type = try self.genValtype(return_type.errorUnionPayload());
-
- // write down the amount of return values
- try leb.writeULEB128(writer, @as(u32, 2));
- try writer.writeByte(wasm.valtype(.i32)); // error code is always an i32 integer.
- try writer.writeByte(val_type);
- },
- else => {
- try leb.writeULEB128(writer, @as(u32, 1));
- // Can we maybe get the source index of the return type?
- const val_type = try self.genValtype(return_type);
- try writer.writeByte(val_type);
- },
- }
- }
-
- pub fn genFunc(self: *Context) InnerError!Result {
- try self.genFunctype();
- // TODO: check for and handle death of instructions
-
- // Reserve space to write the size after generating the code as well as space for locals count
- try self.code.resize(10);
-
- try self.genBody(self.air.getMainBody());
-
- // finally, write our local types at the 'offset' position
- {
- leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len));
-
- // offset into 'code' section where we will put our locals types
- var local_offset: usize = 10;
-
- // emit the actual locals amount
- for (self.locals.items) |local| {
- var buf: [6]u8 = undefined;
- leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1));
- buf[5] = local;
- try self.code.insertSlice(local_offset, &buf);
- local_offset += 6;
- }
- }
-
- const writer = self.code.writer();
- try writer.writeByte(wasm.opcode(.end));
-
- // Fill in the size of the generated code to the reserved space at the
- // beginning of the buffer.
- const size = self.code.items.len - 5 + self.decl.fn_link.wasm.idx_refs.items.len * 5;
- leb.writeUnsignedFixed(5, self.code.items[0..5], @intCast(u32, size));
-
- // codegen data has been appended to `code`
- return Result.appended;
- }
-
- /// Generates the wasm bytecode for the declaration belonging to `Context`
- pub fn gen(self: *Context, ty: Type, val: Value) InnerError!Result {
- switch (ty.zigTypeTag()) {
- .Fn => {
- try self.genFunctype();
- if (val.tag() == .extern_fn) {
- return Result.appended; // don't need code body for extern functions
- }
- return self.fail("TODO implement wasm codegen for function pointers", .{});
- },
- .Array => {
- if (val.castTag(.bytes)) |payload| {
- if (ty.sentinel()) |sentinel| {
- try self.code.appendSlice(payload.data);
-
- switch (try self.gen(ty.elemType(), sentinel)) {
- .appended => return Result.appended,
- .externally_managed => |data| {
- try self.code.appendSlice(data);
- return Result.appended;
- },
- }
- }
- return Result{ .externally_managed = payload.data };
- } else return self.fail("TODO implement gen for more kinds of arrays", .{});
- },
- .Int => {
- const info = ty.intInfo(self.target);
- if (info.bits == 8 and info.signedness == .unsigned) {
- const int_byte = val.toUnsignedInt();
- try self.code.append(@intCast(u8, int_byte));
- return Result.appended;
- }
- return self.fail("TODO: Implement codegen for int type: '{}'", .{ty});
- },
- .Enum => {
- try self.emitConstant(val, ty);
- return Result.appended;
- },
- .Struct => {
- // TODO write the fields for real
- try self.code.writer().writeByteNTimes(0xaa, ty.abiSize(self.target));
- return Result{ .appended = {} };
- },
- else => |tag| return self.fail("TODO: Implement zig type codegen for type: '{s}'", .{tag}),
- }
- }
-
- fn genInst(self: *Context, inst: Air.Inst.Index) !WValue {
- const air_tags = self.air.instructions.items(.tag);
- return switch (air_tags[inst]) {
- .add => self.airBinOp(inst, .add),
- .addwrap => self.airWrapBinOp(inst, .add),
- .sub => self.airBinOp(inst, .sub),
- .subwrap => self.airWrapBinOp(inst, .sub),
- .mul => self.airBinOp(inst, .mul),
- .mulwrap => self.airWrapBinOp(inst, .mul),
- .div_trunc => self.airBinOp(inst, .div),
- .bit_and => self.airBinOp(inst, .@"and"),
- .bit_or => self.airBinOp(inst, .@"or"),
- .bool_and => self.airBinOp(inst, .@"and"),
- .bool_or => self.airBinOp(inst, .@"or"),
- .xor => self.airBinOp(inst, .xor),
-
- .cmp_eq => self.airCmp(inst, .eq),
- .cmp_gte => self.airCmp(inst, .gte),
- .cmp_gt => self.airCmp(inst, .gt),
- .cmp_lte => self.airCmp(inst, .lte),
- .cmp_lt => self.airCmp(inst, .lt),
- .cmp_neq => self.airCmp(inst, .neq),
-
- .alloc => self.airAlloc(inst),
- .arg => self.airArg(inst),
- .bitcast => self.airBitcast(inst),
- .block => self.airBlock(inst),
- .breakpoint => self.airBreakpoint(inst),
- .br => self.airBr(inst),
- .call => self.airCall(inst),
- .cond_br => self.airCondBr(inst),
- .constant => unreachable,
- .dbg_stmt => WValue.none,
- .intcast => self.airIntcast(inst),
-
- .is_err => self.airIsErr(inst, .i32_ne),
- .is_non_err => self.airIsErr(inst, .i32_eq),
-
- .is_null => self.airIsNull(inst, .i32_ne),
- .is_non_null => self.airIsNull(inst, .i32_eq),
- .is_null_ptr => self.airIsNull(inst, .i32_ne),
- .is_non_null_ptr => self.airIsNull(inst, .i32_eq),
-
- .load => self.airLoad(inst),
- .loop => self.airLoop(inst),
- .not => self.airNot(inst),
- .ret => self.airRet(inst),
- .store => self.airStore(inst),
- .struct_field_ptr => self.airStructFieldPtr(inst),
- .struct_field_ptr_index_0 => self.airStructFieldPtrIndex(inst, 0),
- .struct_field_ptr_index_1 => self.airStructFieldPtrIndex(inst, 1),
- .struct_field_ptr_index_2 => self.airStructFieldPtrIndex(inst, 2),
- .struct_field_ptr_index_3 => self.airStructFieldPtrIndex(inst, 3),
- .struct_field_val => self.airStructFieldVal(inst),
- .switch_br => self.airSwitchBr(inst),
- .unreach => self.airUnreachable(inst),
- .wrap_optional => self.airWrapOptional(inst),
-
- .unwrap_errunion_payload => self.airUnwrapErrUnionPayload(inst),
- .wrap_errunion_payload => self.airWrapErrUnionPayload(inst),
-
- .optional_payload => self.airOptionalPayload(inst),
- .optional_payload_ptr => self.airOptionalPayload(inst),
- .optional_payload_ptr_set => self.airOptionalPayloadPtrSet(inst),
- else => |tag| self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),
- };
- }
-
- fn genBody(self: *Context, body: []const Air.Inst.Index) InnerError!void {
- for (body) |inst| {
- const result = try self.genInst(inst);
- try self.values.putNoClobber(self.gpa, inst, result);
- }
- }
-
- fn airRet(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- const un_op = self.air.instructions.items(.data)[inst].un_op;
- const operand = self.resolveInst(un_op);
- try self.emitWValue(operand);
- try self.code.append(wasm.opcode(.@"return"));
- return .none;
- }
-
- fn airCall(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- const pl_op = self.air.instructions.items(.data)[inst].pl_op;
- const extra = self.air.extraData(Air.Call, pl_op.payload);
- const args = self.air.extra[extra.end..][0..extra.data.args_len];
-
- const target: *Decl = blk: {
- const func_val = self.air.value(pl_op.operand).?;
-
- if (func_val.castTag(.function)) |func| {
- break :blk func.data.owner_decl;
- } else if (func_val.castTag(.extern_fn)) |ext_fn| {
- break :blk ext_fn.data;
- }
- return self.fail("Expected a function, but instead found type '{s}'", .{func_val.tag()});
- };
-
- for (args) |arg| {
- const arg_val = self.resolveInst(@intToEnum(Air.Inst.Ref, arg));
- try self.emitWValue(arg_val);
- }
-
- try self.code.append(wasm.opcode(.call));
-
- // The function index immediate argument will be filled in using this data
- // in link.Wasm.flush().
- try self.decl.fn_link.wasm.idx_refs.append(self.gpa, .{
- .offset = @intCast(u32, self.code.items.len),
- .decl = target,
- });
-
- return .none;
- }
-
- fn airAlloc(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- const elem_type = self.air.typeOfIndex(inst).elemType();
- return self.allocLocal(elem_type);
- }
-
- fn airStore(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- const bin_op = self.air.instructions.items(.data)[inst].bin_op;
- const writer = self.code.writer();
-
- const lhs = self.resolveInst(bin_op.lhs);
- const rhs = self.resolveInst(bin_op.rhs);
-
- switch (lhs) {
- .multi_value => |multi_value| switch (rhs) {
- // When assigning a value to a multi_value such as a struct,
- // we simply assign the local_index to the rhs one.
- // This allows us to update struct fields without having to individually
- // set each local as each field's index will be calculated off the struct's base index
- .multi_value => self.values.put(self.gpa, Air.refToIndex(bin_op.lhs).?, rhs) catch unreachable, // Instruction does not dominate all uses!
- .constant, .none => {
- // emit all values onto the stack if constant
- try self.emitWValue(rhs);
-
- // for each local, pop the stack value into the local
- // As the last element is on top of the stack, we must populate the locals
- // in reverse.
- var i: u32 = multi_value.count;
- while (i > 0) : (i -= 1) {
- try writer.writeByte(wasm.opcode(.local_set));
- try leb.writeULEB128(writer, multi_value.index + i - 1);
- }
- },
- .local => {
- // This can occur when we wrap a single value into a multi-value,
- // such as wrapping a non-optional value into an optional.
- // This means we must zero the null-tag, and set the payload.
- assert(multi_value.count == 2);
- // set null-tag
- try writer.writeByte(wasm.opcode(.i32_const));
- try leb.writeULEB128(writer, @as(u32, 0));
- try writer.writeByte(wasm.opcode(.local_set));
- try leb.writeULEB128(writer, multi_value.index);
-
- // set payload
- try self.emitWValue(rhs);
- try writer.writeByte(wasm.opcode(.local_set));
- try leb.writeULEB128(writer, multi_value.index + 1);
- },
- else => unreachable,
- },
- .local => |local| {
- try self.emitWValue(rhs);
- try writer.writeByte(wasm.opcode(.local_set));
- try leb.writeULEB128(writer, local);
- },
- else => unreachable,
- }
- return .none;
- }
-
- fn airLoad(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- const ty_op = self.air.instructions.items(.data)[inst].ty_op;
- return self.resolveInst(ty_op.operand);
- }
-
- fn airArg(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- _ = inst;
- // arguments share the index with locals
- defer self.local_index += 1;
- return WValue{ .local = self.local_index };
- }
-
- fn airBinOp(self: *Context, inst: Air.Inst.Index, op: Op) InnerError!WValue {
- const bin_op = self.air.instructions.items(.data)[inst].bin_op;
- const lhs = self.resolveInst(bin_op.lhs);
- const rhs = self.resolveInst(bin_op.rhs);
-
- // it's possible for both lhs and/or rhs to return an offset as well,
- // in which case we return the first offset occurrence we find.
- const offset = blk: {
- if (lhs == .code_offset) break :blk lhs.code_offset;
- if (rhs == .code_offset) break :blk rhs.code_offset;
- break :blk self.code.items.len;
- };
-
- try self.emitWValue(lhs);
- try self.emitWValue(rhs);
-
- const bin_ty = self.air.typeOf(bin_op.lhs);
- const opcode: wasm.Opcode = buildOpcode(.{
- .op = op,
- .valtype1 = try self.typeToValtype(bin_ty),
- .signedness = if (bin_ty.isSignedInt()) .signed else .unsigned,
- });
- try self.code.append(wasm.opcode(opcode));
- return WValue{ .code_offset = offset };
- }
-
- fn airWrapBinOp(self: *Context, inst: Air.Inst.Index, op: Op) InnerError!WValue {
- const bin_op = self.air.instructions.items(.data)[inst].bin_op;
- const lhs = self.resolveInst(bin_op.lhs);
- const rhs = self.resolveInst(bin_op.rhs);
-
- // it's possible for both lhs and/or rhs to return an offset as well,
- // in which case we return the first offset occurrence we find.
- const offset = blk: {
- if (lhs == .code_offset) break :blk lhs.code_offset;
- if (rhs == .code_offset) break :blk rhs.code_offset;
- break :blk self.code.items.len;
- };
-
- try self.emitWValue(lhs);
- try self.emitWValue(rhs);
-
- const bin_ty = self.air.typeOf(bin_op.lhs);
- const opcode: wasm.Opcode = buildOpcode(.{
- .op = op,
- .valtype1 = try self.typeToValtype(bin_ty),
- .signedness = if (bin_ty.isSignedInt()) .signed else .unsigned,
- });
- try self.code.append(wasm.opcode(opcode));
-
- const int_info = bin_ty.intInfo(self.target);
- const bitsize = int_info.bits;
- const is_signed = int_info.signedness == .signed;
- // if target type bitsize is x < 32 and 32 > x < 64, we perform
- // result & ((1<<N)-1) where N = bitsize or bitsize -1 incase of signed.
- if (bitsize != 32 and bitsize < 64) {
- // first check if we can use a single instruction,
- // wasm provides those if the integers are signed and 8/16-bit.
- // For arbitrary integer sizes, we use the algorithm mentioned above.
- if (is_signed and bitsize == 8) {
- try self.code.append(wasm.opcode(.i32_extend8_s));
- } else if (is_signed and bitsize == 16) {
- try self.code.append(wasm.opcode(.i32_extend16_s));
- } else {
- const result = (@as(u64, 1) << @intCast(u6, bitsize - @boolToInt(is_signed))) - 1;
- if (bitsize < 32) {
- try self.code.append(wasm.opcode(.i32_const));
- try leb.writeILEB128(self.code.writer(), @bitCast(i32, @intCast(u32, result)));
- try self.code.append(wasm.opcode(.i32_and));
- } else {
- try self.code.append(wasm.opcode(.i64_const));
- try leb.writeILEB128(self.code.writer(), @bitCast(i64, result));
- try self.code.append(wasm.opcode(.i64_and));
- }
- }
- } else if (int_info.bits > 64) {
- return self.fail("TODO wasm: Integer wrapping for bitsizes larger than 64", .{});
- }
-
- return WValue{ .code_offset = offset };
- }
-
- fn emitConstant(self: *Context, val: Value, ty: Type) InnerError!void {
- const writer = self.code.writer();
- switch (ty.zigTypeTag()) {
- .Int => {
- // write opcode
- const opcode: wasm.Opcode = buildOpcode(.{
- .op = .@"const",
- .valtype1 = try self.typeToValtype(ty),
- });
- try writer.writeByte(wasm.opcode(opcode));
- const int_info = ty.intInfo(self.target);
- // write constant
- switch (int_info.signedness) {
- .signed => try leb.writeILEB128(writer, val.toSignedInt()),
- .unsigned => switch (int_info.bits) {
- 0...32 => try leb.writeILEB128(writer, @bitCast(i32, @intCast(u32, val.toUnsignedInt()))),
- 33...64 => try leb.writeILEB128(writer, @bitCast(i64, val.toUnsignedInt())),
- else => |bits| return self.fail("Wasm TODO: emitConstant for integer with {d} bits", .{bits}),
- },
- }
- },
- .Bool => {
- // write opcode
- try writer.writeByte(wasm.opcode(.i32_const));
- // write constant
- try leb.writeILEB128(writer, val.toSignedInt());
- },
- .Float => {
- // write opcode
- const opcode: wasm.Opcode = buildOpcode(.{
- .op = .@"const",
- .valtype1 = try self.typeToValtype(ty),
- });
- try writer.writeByte(wasm.opcode(opcode));
- // write constant
- switch (ty.floatBits(self.target)) {
- 0...32 => try writer.writeIntLittle(u32, @bitCast(u32, val.toFloat(f32))),
- 64 => try writer.writeIntLittle(u64, @bitCast(u64, val.toFloat(f64))),
- else => |bits| return self.fail("Wasm TODO: emitConstant for float with {d} bits", .{bits}),
- }
- },
- .Pointer => {
- if (val.castTag(.decl_ref)) |payload| {
- const decl = payload.data;
- decl.alive = true;
-
- // offset into the offset table within the 'data' section
- const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8;
- try writer.writeByte(wasm.opcode(.i32_const));
- try leb.writeULEB128(writer, decl.link.wasm.offset_index * ptr_width);
-
- // memory instruction followed by their memarg immediate
- // memarg ::== x:u32, y:u32 => {align x, offset y}
- try writer.writeByte(wasm.opcode(.i32_load));
- try leb.writeULEB128(writer, @as(u32, 0));
- try leb.writeULEB128(writer, @as(u32, 0));
- } else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{val.tag()});
- },
- .Void => {},
- .Enum => {
- if (val.castTag(.enum_field_index)) |field_index| {
- switch (ty.tag()) {
- .enum_simple => {
- try writer.writeByte(wasm.opcode(.i32_const));
- try leb.writeULEB128(writer, field_index.data);
- },
- .enum_full, .enum_nonexhaustive => {
- const enum_full = ty.cast(Type.Payload.EnumFull).?.data;
- if (enum_full.values.count() != 0) {
- const tag_val = enum_full.values.keys()[field_index.data];
- try self.emitConstant(tag_val, enum_full.tag_ty);
- } else {
- try writer.writeByte(wasm.opcode(.i32_const));
- try leb.writeULEB128(writer, field_index.data);
- }
- },
- else => unreachable,
- }
- } else {
- var int_tag_buffer: Type.Payload.Bits = undefined;
- const int_tag_ty = ty.intTagType(&int_tag_buffer);
- try self.emitConstant(val, int_tag_ty);
- }
- },
- .ErrorSet => {
- const error_index = self.global_error_set.get(val.getError().?).?;
- try writer.writeByte(wasm.opcode(.i32_const));
- try leb.writeULEB128(writer, error_index);
- },
- .ErrorUnion => {
- const error_type = ty.errorUnionSet();
- const payload_type = ty.errorUnionPayload();
- if (val.castTag(.eu_payload)) |pl| {
- const payload_val = pl.data;
- // no error, so write a '0' const
- try writer.writeByte(wasm.opcode(.i32_const));
- try leb.writeULEB128(writer, @as(u32, 0));
- // after the error code, we emit the payload
- try self.emitConstant(payload_val, payload_type);
- } else {
- // write the error val
- try self.emitConstant(val, error_type);
-
- // no payload, so write a '0' const
- const opcode: wasm.Opcode = buildOpcode(.{
- .op = .@"const",
- .valtype1 = try self.typeToValtype(payload_type),
- });
- try writer.writeByte(wasm.opcode(opcode));
- try leb.writeULEB128(writer, @as(u32, 0));
- }
- },
- .Optional => {
- var buf: Type.Payload.ElemType = undefined;
- const payload_type = ty.optionalChild(&buf);
- if (ty.isPtrLikeOptional()) {
- return self.fail("Wasm TODO: emitConstant for optional pointer", .{});
- }
-
- // When constant has value 'null', set is_null local to '1'
- // and payload to '0'
- if (val.castTag(.opt_payload)) |pl| {
- const payload_val = pl.data;
- try writer.writeByte(wasm.opcode(.i32_const));
- try leb.writeILEB128(writer, @as(i32, 0));
- try self.emitConstant(payload_val, payload_type);
- } else {
- try writer.writeByte(wasm.opcode(.i32_const));
- try leb.writeILEB128(writer, @as(i32, 1));
-
- const opcode: wasm.Opcode = buildOpcode(.{
- .op = .@"const",
- .valtype1 = try self.typeToValtype(payload_type),
- });
- try writer.writeByte(wasm.opcode(opcode));
- try leb.writeULEB128(writer, @as(u32, 0));
- }
- },
- else => |zig_type| return self.fail("Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}),
- }
- }
-
- /// Returns a `Value` as a signed 32 bit value.
- /// It's illegal to provide a value with a type that cannot be represented
- /// as an integer value.
- fn valueAsI32(self: Context, val: Value, ty: Type) i32 {
- switch (ty.zigTypeTag()) {
- .Enum => {
- if (val.castTag(.enum_field_index)) |field_index| {
- switch (ty.tag()) {
- .enum_simple => return @bitCast(i32, field_index.data),
- .enum_full, .enum_nonexhaustive => {
- const enum_full = ty.cast(Type.Payload.EnumFull).?.data;
- if (enum_full.values.count() != 0) {
- const tag_val = enum_full.values.keys()[field_index.data];
- return self.valueAsI32(tag_val, enum_full.tag_ty);
- } else return @bitCast(i32, field_index.data);
- },
- else => unreachable,
- }
- } else {
- var int_tag_buffer: Type.Payload.Bits = undefined;
- const int_tag_ty = ty.intTagType(&int_tag_buffer);
- return self.valueAsI32(val, int_tag_ty);
- }
- },
- .Int => switch (ty.intInfo(self.target).signedness) {
- .signed => return @truncate(i32, val.toSignedInt()),
- .unsigned => return @bitCast(i32, @truncate(u32, val.toUnsignedInt())),
- },
- .ErrorSet => {
- const error_index = self.global_error_set.get(val.getError().?).?;
- return @bitCast(i32, error_index);
- },
- else => unreachable, // Programmer called this function for an illegal type
- }
- }
-
- fn airBlock(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
- const block_ty = try self.genBlockType(self.air.getRefType(ty_pl.ty));
- const extra = self.air.extraData(Air.Block, ty_pl.payload);
- const body = self.air.extra[extra.end..][0..extra.data.body_len];
-
- try self.startBlock(.block, block_ty, null);
- // Here we set the current block idx, so breaks know the depth to jump
- // to when breaking out.
- try self.blocks.putNoClobber(self.gpa, inst, self.block_depth);
- try self.genBody(body);
- try self.endBlock();
-
- return .none;
- }
-
- /// appends a new wasm block to the code section and increases the `block_depth` by 1
- fn startBlock(self: *Context, block_type: wasm.Opcode, valtype: u8, with_offset: ?usize) !void {
- self.block_depth += 1;
- if (with_offset) |offset| {
- try self.code.insert(offset, wasm.opcode(block_type));
- try self.code.insert(offset + 1, valtype);
- } else {
- try self.code.append(wasm.opcode(block_type));
- try self.code.append(valtype);
- }
- }
-
- /// Ends the current wasm block and decreases the `block_depth` by 1
- fn endBlock(self: *Context) !void {
- try self.code.append(wasm.opcode(.end));
- self.block_depth -= 1;
- }
-
- fn airLoop(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
- const loop = self.air.extraData(Air.Block, ty_pl.payload);
- const body = self.air.extra[loop.end..][0..loop.data.body_len];
-
- // result type of loop is always 'noreturn', meaning we can always
- // emit the wasm type 'block_empty'.
- try self.startBlock(.loop, wasm.block_empty, null);
- try self.genBody(body);
-
- // breaking to the index of a loop block will continue the loop instead
- try self.code.append(wasm.opcode(.br));
- try leb.writeULEB128(self.code.writer(), @as(u32, 0));
-
- try self.endBlock();
-
- return .none;
- }
-
- fn airCondBr(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- const pl_op = self.air.instructions.items(.data)[inst].pl_op;
- const condition = self.resolveInst(pl_op.operand);
- const extra = self.air.extraData(Air.CondBr, pl_op.payload);
- const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len];
- const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
- const writer = self.code.writer();
- // TODO: Handle death instructions for then and else body
-
- // insert blocks at the position of `offset` so
- // the condition can jump to it
- const offset = switch (condition) {
- .code_offset => |offset| offset,
- else => blk: {
- const offset = self.code.items.len;
- try self.emitWValue(condition);
- break :blk offset;
- },
- };
-
- // result type is always noreturn, so use `block_empty` as type.
- try self.startBlock(.block, wasm.block_empty, offset);
-
- // we inserted the block in front of the condition
- // so now check if condition matches. If not, break outside this block
- // and continue with the then codepath
- try writer.writeByte(wasm.opcode(.br_if));
- try leb.writeULEB128(writer, @as(u32, 0));
-
- try self.genBody(else_body);
- try self.endBlock();
-
- // Outer block that matches the condition
- try self.genBody(then_body);
-
- return .none;
- }
-
- fn airCmp(self: *Context, inst: Air.Inst.Index, op: std.math.CompareOperator) InnerError!WValue {
- // save offset, so potential conditions can insert blocks in front of
- // the comparison that we can later jump back to
- const offset = self.code.items.len;
-
- const data: Air.Inst.Data = self.air.instructions.items(.data)[inst];
- const lhs = self.resolveInst(data.bin_op.lhs);
- const rhs = self.resolveInst(data.bin_op.rhs);
- const lhs_ty = self.air.typeOf(data.bin_op.lhs);
-
- try self.emitWValue(lhs);
- try self.emitWValue(rhs);
-
- const signedness: std.builtin.Signedness = blk: {
- // by default we tell the operand type is unsigned (i.e. bools and enum values)
- if (lhs_ty.zigTypeTag() != .Int) break :blk .unsigned;
-
- // incase of an actual integer, we emit the correct signedness
- break :blk lhs_ty.intInfo(self.target).signedness;
- };
- const opcode: wasm.Opcode = buildOpcode(.{
- .valtype1 = try self.typeToValtype(lhs_ty),
- .op = switch (op) {
- .lt => .lt,
- .lte => .le,
- .eq => .eq,
- .neq => .ne,
- .gte => .ge,
- .gt => .gt,
- },
- .signedness = signedness,
- });
- try self.code.append(wasm.opcode(opcode));
- return WValue{ .code_offset = offset };
- }
-
- fn airBr(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- const br = self.air.instructions.items(.data)[inst].br;
-
- // if operand has codegen bits we should break with a value
- if (self.air.typeOf(br.operand).hasCodeGenBits()) {
- try self.emitWValue(self.resolveInst(br.operand));
- }
-
- // We map every block to its block index.
- // We then determine how far we have to jump to it by subtracting it from current block depth
- const idx: u32 = self.block_depth - self.blocks.get(br.block_inst).?;
- const writer = self.code.writer();
- try writer.writeByte(wasm.opcode(.br));
- try leb.writeULEB128(writer, idx);
-
- return .none;
- }
-
- fn airNot(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- const ty_op = self.air.instructions.items(.data)[inst].ty_op;
- const offset = self.code.items.len;
-
- const operand = self.resolveInst(ty_op.operand);
- try self.emitWValue(operand);
-
- // wasm does not have booleans nor the `not` instruction, therefore compare with 0
- // to create the same logic
- const writer = self.code.writer();
- try writer.writeByte(wasm.opcode(.i32_const));
- try leb.writeILEB128(writer, @as(i32, 0));
-
- try writer.writeByte(wasm.opcode(.i32_eq));
-
- return WValue{ .code_offset = offset };
- }
-
- fn airBreakpoint(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- _ = self;
- _ = inst;
- // unsupported by wasm itself. Can be implemented once we support DWARF
- // for wasm
- return .none;
- }
-
- fn airUnreachable(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- _ = inst;
- try self.code.append(wasm.opcode(.@"unreachable"));
- return .none;
- }
-
- fn airBitcast(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- const ty_op = self.air.instructions.items(.data)[inst].ty_op;
- return self.resolveInst(ty_op.operand);
- }
-
- fn airStructFieldPtr(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
- const extra = self.air.extraData(Air.StructField, ty_pl.payload);
- const struct_ptr = self.resolveInst(extra.data.struct_operand);
- return structFieldPtr(struct_ptr, extra.data.field_index);
- }
- fn airStructFieldPtrIndex(self: *Context, inst: Air.Inst.Index, index: u32) InnerError!WValue {
- const ty_op = self.air.instructions.items(.data)[inst].ty_op;
- const struct_ptr = self.resolveInst(ty_op.operand);
- return structFieldPtr(struct_ptr, index);
- }
- fn structFieldPtr(struct_ptr: WValue, index: u32) InnerError!WValue {
- return WValue{ .local = struct_ptr.multi_value.index + index };
- }
-
- fn airStructFieldVal(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- if (self.liveness.isUnused(inst)) return WValue.none;
-
- const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
- const extra = self.air.extraData(Air.StructField, ty_pl.payload).data;
- const struct_multivalue = self.resolveInst(extra.struct_operand).multi_value;
- return WValue{ .local = struct_multivalue.index + extra.field_index };
- }
-
- fn airSwitchBr(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- // result type is always 'noreturn'
- const blocktype = wasm.block_empty;
- const pl_op = self.air.instructions.items(.data)[inst].pl_op;
- const target = self.resolveInst(pl_op.operand);
- const target_ty = self.air.typeOf(pl_op.operand);
- const switch_br = self.air.extraData(Air.SwitchBr, pl_op.payload);
- var extra_index: usize = switch_br.end;
- var case_i: u32 = 0;
-
- // a list that maps each value with its value and body based on the order inside the list.
- const CaseValue = struct { integer: i32, value: Value };
- var case_list = try std.ArrayList(struct {
- values: []const CaseValue,
- body: []const Air.Inst.Index,
- }).initCapacity(self.gpa, switch_br.data.cases_len);
- defer for (case_list.items) |case| {
- self.gpa.free(case.values);
- } else case_list.deinit();
-
- var lowest: i32 = 0;
- var highest: i32 = 0;
- while (case_i < switch_br.data.cases_len) : (case_i += 1) {
- const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
- const items = @bitCast([]const Air.Inst.Ref, self.air.extra[case.end..][0..case.data.items_len]);
- const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len];
- extra_index = case.end + items.len + case_body.len;
- const values = try self.gpa.alloc(CaseValue, items.len);
- errdefer self.gpa.free(values);
-
- for (items) |ref, i| {
- const item_val = self.air.value(ref).?;
- const int_val = self.valueAsI32(item_val, target_ty);
- if (int_val < lowest) {
- lowest = int_val;
- }
- if (int_val > highest) {
- highest = int_val;
- }
- values[i] = .{ .integer = int_val, .value = item_val };
- }
-
- case_list.appendAssumeCapacity(.{ .values = values, .body = case_body });
- try self.startBlock(.block, blocktype, null);
- }
-
- // When the highest and lowest values are seperated by '50',
- // we define it as sparse and use an if/else-chain, rather than a jump table.
- // When the target is an integer size larger than u32, we have no way to use the value
- // as an index, therefore we also use an if/else-chain for those cases.
- // TODO: Benchmark this to find a proper value, LLVM seems to draw the line at '40~45'.
- const is_sparse = highest - lowest > 50 or target_ty.bitSize(self.target) > 32;
-
- const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len];
- const has_else_body = else_body.len != 0;
- if (has_else_body) {
- try self.startBlock(.block, blocktype, null);
- }
-
- if (!is_sparse) {
- // Generate the jump table 'br_table' when the prongs are not sparse.
- // The value 'target' represents the index into the table.
- // Each index in the table represents a label to the branch
- // to jump to.
- try self.startBlock(.block, blocktype, null);
- try self.emitWValue(target);
- if (lowest < 0) {
- // since br_table works using indexes, starting from '0', we must ensure all values
- // we put inside, are atleast 0.
- try self.code.append(wasm.opcode(.i32_const));
- try leb.writeILEB128(self.code.writer(), lowest * -1);
- try self.code.append(wasm.opcode(.i32_add));
- }
- try self.code.append(wasm.opcode(.br_table));
- const depth = highest - lowest + @boolToInt(has_else_body);
- try leb.writeILEB128(self.code.writer(), depth);
- while (lowest <= highest) : (lowest += 1) {
- // idx represents the branch we jump to
- const idx = blk: {
- for (case_list.items) |case, idx| {
- for (case.values) |case_value| {
- if (case_value.integer == lowest) break :blk @intCast(u32, idx);
- }
- }
- break :blk if (has_else_body) case_i else unreachable;
- };
- try leb.writeULEB128(self.code.writer(), idx);
- } else if (has_else_body) {
- try leb.writeULEB128(self.code.writer(), @as(u32, case_i)); // default branch
- }
- try self.endBlock();
- }
-
- const signedness: std.builtin.Signedness = blk: {
- // by default we tell the operand type is unsigned (i.e. bools and enum values)
- if (target_ty.zigTypeTag() != .Int) break :blk .unsigned;
-
- // incase of an actual integer, we emit the correct signedness
- break :blk target_ty.intInfo(self.target).signedness;
- };
-
- for (case_list.items) |case| {
- // when sparse, we use if/else-chain, so emit conditional checks
- if (is_sparse) {
- // for single value prong we can emit a simple if
- if (case.values.len == 1) {
- try self.emitWValue(target);
- try self.emitConstant(case.values[0].value, target_ty);
- const opcode = buildOpcode(.{
- .valtype1 = try self.typeToValtype(target_ty),
- .op = .ne, // not equal, because we want to jump out of this block if it does not match the condition.
- .signedness = signedness,
- });
- try self.code.append(wasm.opcode(opcode));
- try self.code.append(wasm.opcode(.br_if));
- try leb.writeULEB128(self.code.writer(), @as(u32, 0));
- } else {
- // in multi-value prongs we must check if any prongs match the target value.
- try self.startBlock(.block, blocktype, null);
- for (case.values) |value| {
- try self.emitWValue(target);
- try self.emitConstant(value.value, target_ty);
- const opcode = buildOpcode(.{
- .valtype1 = try self.typeToValtype(target_ty),
- .op = .eq,
- .signedness = signedness,
- });
- try self.code.append(wasm.opcode(opcode));
- try self.code.append(wasm.opcode(.br_if));
- try leb.writeULEB128(self.code.writer(), @as(u32, 0));
- }
- // value did not match any of the prong values
- try self.code.append(wasm.opcode(.br));
- try leb.writeULEB128(self.code.writer(), @as(u32, 1));
- try self.endBlock();
- }
- }
- try self.genBody(case.body);
- try self.endBlock();
- }
-
- if (has_else_body) {
- try self.genBody(else_body);
- try self.endBlock();
- }
- return .none;
- }
-
- fn airIsErr(self: *Context, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {
- const un_op = self.air.instructions.items(.data)[inst].un_op;
- const operand = self.resolveInst(un_op);
- const offset = self.code.items.len;
- const writer = self.code.writer();
-
- // load the error value which is positioned at multi_value's index
- try self.emitWValue(.{ .local = operand.multi_value.index });
- // Compare the error value with '0'
- try writer.writeByte(wasm.opcode(.i32_const));
- try leb.writeILEB128(writer, @as(i32, 0));
-
- try writer.writeByte(@enumToInt(opcode));
-
- return WValue{ .code_offset = offset };
- }
-
- fn airUnwrapErrUnionPayload(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- const ty_op = self.air.instructions.items(.data)[inst].ty_op;
- const operand = self.resolveInst(ty_op.operand);
- // The index of multi_value contains the error code. To get the initial index of the payload we get
- // the following index. Next, convert it to a `WValue.local`
- //
- // TODO: Check if payload is a type that requires a multi_value as well and emit that instead. i.e. a struct.
- return WValue{ .local = operand.multi_value.index + 1 };
- }
-
- fn airWrapErrUnionPayload(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- const ty_op = self.air.instructions.items(.data)[inst].ty_op;
- return self.resolveInst(ty_op.operand);
- }
-
- fn airIntcast(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- const ty_op = self.air.instructions.items(.data)[inst].ty_op;
- const ty = self.air.getRefType(ty_op.ty);
- const operand = self.resolveInst(ty_op.operand);
- const ref_ty = self.air.typeOf(ty_op.operand);
- const ref_info = ref_ty.intInfo(self.target);
- const op_bits = ref_info.bits;
- const wanted_bits = ty.intInfo(self.target).bits;
-
- try self.emitWValue(operand);
- if (op_bits > 32 and wanted_bits <= 32) {
- try self.code.append(wasm.opcode(.i32_wrap_i64));
- } else if (op_bits <= 32 and wanted_bits > 32) {
- try self.code.append(wasm.opcode(switch (ref_info.signedness) {
- .signed => .i64_extend_i32_s,
- .unsigned => .i64_extend_i32_u,
- }));
- }
-
- // other cases are no-op
- return .none;
- }
-
- fn airIsNull(self: *Context, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {
- const un_op = self.air.instructions.items(.data)[inst].un_op;
- const operand = self.resolveInst(un_op);
- // const offset = self.code.items.len;
- const writer = self.code.writer();
-
- // load the null value which is positioned at multi_value's index
- try self.emitWValue(.{ .local = operand.multi_value.index });
- // Compare the null value with '0'
- try writer.writeByte(wasm.opcode(.i32_const));
- try leb.writeILEB128(writer, @as(i32, 0));
-
- try writer.writeByte(@enumToInt(opcode));
-
- // we save the result in a new local
- const local = try self.allocLocal(Type.initTag(.i32));
- try writer.writeByte(wasm.opcode(.local_set));
- try leb.writeULEB128(writer, local.local);
-
- return local;
- }
-
- fn airOptionalPayload(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- const ty_op = self.air.instructions.items(.data)[inst].ty_op;
- const operand = self.resolveInst(ty_op.operand);
- return WValue{ .local = operand.multi_value.index + 1 };
- }
-
- fn airOptionalPayloadPtrSet(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- const ty_op = self.air.instructions.items(.data)[inst].ty_op;
- const operand = self.resolveInst(ty_op.operand);
- _ = operand;
- return self.fail("TODO - wasm codegen for optional_payload_ptr_set", .{});
- }
-
- fn airWrapOptional(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
- const ty_op = self.air.instructions.items(.data)[inst].ty_op;
- return self.resolveInst(ty_op.operand);
- }
-};
src/link/Wasm.zig
@@ -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,
CMakeLists.txt
@@ -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"