Commit d3135f7682

Luuk de Gram <luuk@degram.dev>
2021-11-15 18:02:24
Stage2: wasm - Implement the MIR pass (#10153)
* wasm: Move wasm's codegen to arch/wasm/CodeGen.zig * wasm: Define Wasm's Mir This declares the initial most-used instructions for wasm as well as the data that represents them. TODO: Add binary operand opcodes. By re-using the wasm opcode values, we can emit each opcode very easily by simply using `@enumToInt()`. However, this poses a possible problem: If we use all of wasm's opcodes, it leaves us no room to use synthetic opcodes such as debugging instructions. We could use reserved opcodes, but the wasm spec may use them at some point. TODO: Check if we should perhaps use a 16bit tag where the highest bits are used for synthetic opcodes. * wasm: Define basic Emit structure * wasm: Implement corresponding Emit functions for MIR * wasm: Initial lowering to MIR - This implements lowering to MIR from AIR for storing and loading of locals as well as emitting immediates. - Relocating function indexes has been simplified a lot as well as we no longer need to patch offsets and we write a relocatable value instead. - Locals are now emitted at the beginning of the function section entry meaning all offsets we generate are stable. * wasm: Lower all AIR instructions to MIR * wasm: Implement remaining MIR instructions * wasm: Fix function relocations * wasm: Get all tests working * wasm: Make `Data` 4 bytes instead of 8. - 64bit immediates are now stored in 2 seperate u32's. - 64bit floats are now stored in 2 seperate u32's. - `mem_arg` is now stored as a seperate payload in extra.
1 parent b26b72f
Changed files (6)
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"