zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

blob fa3ca6a2 (327079B) - Raw


      1 const std = @import("std");
      2 const builtin = @import("builtin");
      3 const Allocator = std.mem.Allocator;
      4 const ArrayList = std.ArrayList;
      5 const assert = std.debug.assert;
      6 const testing = std.testing;
      7 const leb = std.leb;
      8 const mem = std.mem;
      9 const wasm = std.wasm;
     10 const log = std.log.scoped(.codegen);
     11 
     12 const codegen = @import("../../codegen.zig");
     13 const Module = @import("../../Module.zig");
     14 const InternPool = @import("../../InternPool.zig");
     15 const Decl = Module.Decl;
     16 const Type = @import("../../type.zig").Type;
     17 const Value = @import("../../Value.zig");
     18 const Compilation = @import("../../Compilation.zig");
     19 const link = @import("../../link.zig");
     20 const Air = @import("../../Air.zig");
     21 const Liveness = @import("../../Liveness.zig");
     22 const target_util = @import("../../target.zig");
     23 const Mir = @import("Mir.zig");
     24 const Emit = @import("Emit.zig");
     25 const abi = @import("abi.zig");
     26 const Alignment = InternPool.Alignment;
     27 const errUnionPayloadOffset = codegen.errUnionPayloadOffset;
     28 const errUnionErrorOffset = codegen.errUnionErrorOffset;
     29 
     30 /// Wasm Value, created when generating an instruction
     31 const WValue = union(enum) {
     32     /// `WValue` which has been freed and may no longer hold
     33     /// any references.
     34     dead: void,
     35     /// May be referenced but is unused
     36     none: void,
     37     /// The value lives on top of the stack
     38     stack: void,
     39     /// Index of the local
     40     local: struct {
     41         /// Contains the index to the local
     42         value: u32,
     43         /// The amount of instructions referencing this `WValue`
     44         references: u32,
     45     },
     46     /// An immediate 32bit value
     47     imm32: u32,
     48     /// An immediate 64bit value
     49     imm64: u64,
     50     /// Index into the list of simd128 immediates. This `WValue` is
     51     /// only possible in very rare cases, therefore it would be
     52     /// a waste of memory to store the value in a 128 bit integer.
     53     imm128: u32,
     54     /// A constant 32bit float value
     55     float32: f32,
     56     /// A constant 64bit float value
     57     float64: f64,
     58     /// A value that represents a pointer to the data section
     59     /// Note: The value contains the symbol index, rather than the actual address
     60     /// as we use this to perform the relocation.
     61     memory: u32,
     62     /// A value that represents a parent pointer and an offset
     63     /// from that pointer. i.e. when slicing with constant values.
     64     memory_offset: struct {
     65         /// The symbol of the parent pointer
     66         pointer: u32,
     67         /// Offset will be set as addend when relocating
     68         offset: u32,
     69     },
     70     /// Represents a function pointer
     71     /// In wasm function pointers are indexes into a function table,
     72     /// rather than an address in the data section.
     73     function_index: u32,
     74     /// Offset from the bottom of the virtual stack, with the offset
     75     /// pointing to where the value lives.
     76     stack_offset: struct {
     77         /// Contains the actual value of the offset
     78         value: u32,
     79         /// The amount of instructions referencing this `WValue`
     80         references: u32,
     81     },
     82 
     83     /// Returns the offset from the bottom of the stack. This is useful when
     84     /// we use the load or store instruction to ensure we retrieve the value
     85     /// from the correct position, rather than the value that lives at the
     86     /// bottom of the stack. For instances where `WValue` is not `stack_value`
     87     /// this will return 0, which allows us to simply call this function for all
     88     /// loads and stores without requiring checks everywhere.
     89     fn offset(value: WValue) u32 {
     90         switch (value) {
     91             .stack_offset => |stack_offset| return stack_offset.value,
     92             .dead => unreachable,
     93             else => return 0,
     94         }
     95     }
     96 
     97     /// Promotes a `WValue` to a local when given value is on top of the stack.
     98     /// When encountering a `local` or `stack_offset` this is essentially a no-op.
     99     /// All other tags are illegal.
    100     fn toLocal(value: WValue, gen: *CodeGen, ty: Type) InnerError!WValue {
    101         switch (value) {
    102             .stack => {
    103                 const new_local = try gen.allocLocal(ty);
    104                 try gen.addLabel(.local_set, new_local.local.value);
    105                 return new_local;
    106             },
    107             .local, .stack_offset => return value,
    108             else => unreachable,
    109         }
    110     }
    111 
    112     /// Marks a local as no longer being referenced and essentially allows
    113     /// us to re-use it somewhere else within the function.
    114     /// The valtype of the local is deducted by using the index of the given `WValue`.
    115     fn free(value: *WValue, gen: *CodeGen) void {
    116         if (value.* != .local) return;
    117         const local_value = value.local.value;
    118         const reserved = gen.args.len + @intFromBool(gen.return_value != .none);
    119         if (local_value < reserved + 2) return; // reserved locals may never be re-used. Also accounts for 2 stack locals.
    120 
    121         const index = local_value - reserved;
    122         const valtype = @as(wasm.Valtype, @enumFromInt(gen.locals.items[index]));
    123         switch (valtype) {
    124             .i32 => gen.free_locals_i32.append(gen.gpa, local_value) catch return, // It's ok to fail any of those, a new local can be allocated instead
    125             .i64 => gen.free_locals_i64.append(gen.gpa, local_value) catch return,
    126             .f32 => gen.free_locals_f32.append(gen.gpa, local_value) catch return,
    127             .f64 => gen.free_locals_f64.append(gen.gpa, local_value) catch return,
    128             .v128 => gen.free_locals_v128.append(gen.gpa, local_value) catch return,
    129         }
    130         log.debug("freed local ({d}) of type {}", .{ local_value, valtype });
    131         value.* = .dead;
    132     }
    133 };
    134 
    135 /// Wasm ops, but without input/output/signedness information
    136 /// Used for `buildOpcode`
    137 const Op = enum {
    138     @"unreachable",
    139     nop,
    140     block,
    141     loop,
    142     @"if",
    143     @"else",
    144     end,
    145     br,
    146     br_if,
    147     br_table,
    148     @"return",
    149     call,
    150     call_indirect,
    151     drop,
    152     select,
    153     local_get,
    154     local_set,
    155     local_tee,
    156     global_get,
    157     global_set,
    158     load,
    159     store,
    160     memory_size,
    161     memory_grow,
    162     @"const",
    163     eqz,
    164     eq,
    165     ne,
    166     lt,
    167     gt,
    168     le,
    169     ge,
    170     clz,
    171     ctz,
    172     popcnt,
    173     add,
    174     sub,
    175     mul,
    176     div,
    177     rem,
    178     @"and",
    179     @"or",
    180     xor,
    181     shl,
    182     shr,
    183     rotl,
    184     rotr,
    185     abs,
    186     neg,
    187     ceil,
    188     floor,
    189     trunc,
    190     nearest,
    191     sqrt,
    192     min,
    193     max,
    194     copysign,
    195     wrap,
    196     convert,
    197     demote,
    198     promote,
    199     reinterpret,
    200     extend,
    201 };
    202 
    203 /// Contains the settings needed to create an `Opcode` using `buildOpcode`.
    204 ///
    205 /// The fields correspond to the opcode name. Here is an example
    206 ///          i32_trunc_f32_s
    207 ///          ^   ^     ^   ^
    208 ///          |   |     |   |
    209 ///   valtype1   |     |   |
    210 ///     = .i32   |     |   |
    211 ///              |     |   |
    212 ///             op     |   |
    213 ///       = .trunc     |   |
    214 ///                    |   |
    215 ///             valtype2   |
    216 ///               = .f32   |
    217 ///                        |
    218 ///                width   |
    219 ///               = null   |
    220 ///                        |
    221 ///                   signed
    222 ///                   = true
    223 ///
    224 /// There can be missing fields, here are some more examples:
    225 ///   i64_load8_u
    226 ///     --> .{ .valtype1 = .i64, .op = .load, .width = 8, signed = false }
    227 ///   i32_mul
    228 ///     --> .{ .valtype1 = .i32, .op = .trunc }
    229 ///   nop
    230 ///     --> .{ .op = .nop }
    231 const OpcodeBuildArguments = struct {
    232     /// First valtype in the opcode (usually represents the type of the output)
    233     valtype1: ?wasm.Valtype = null,
    234     /// The operation (e.g. call, unreachable, div, min, sqrt, etc.)
    235     op: Op,
    236     /// Width of the operation (e.g. 8 for i32_load8_s, 16 for i64_extend16_i32_s)
    237     width: ?u8 = null,
    238     /// Second valtype in the opcode name (usually represents the type of the input)
    239     valtype2: ?wasm.Valtype = null,
    240     /// Signedness of the op
    241     signedness: ?std.builtin.Signedness = null,
    242 };
    243 
    244 /// Helper function that builds an Opcode given the arguments needed
    245 fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
    246     switch (args.op) {
    247         .@"unreachable" => return .@"unreachable",
    248         .nop => return .nop,
    249         .block => return .block,
    250         .loop => return .loop,
    251         .@"if" => return .@"if",
    252         .@"else" => return .@"else",
    253         .end => return .end,
    254         .br => return .br,
    255         .br_if => return .br_if,
    256         .br_table => return .br_table,
    257         .@"return" => return .@"return",
    258         .call => return .call,
    259         .call_indirect => return .call_indirect,
    260         .drop => return .drop,
    261         .select => return .select,
    262         .local_get => return .local_get,
    263         .local_set => return .local_set,
    264         .local_tee => return .local_tee,
    265         .global_get => return .global_get,
    266         .global_set => return .global_set,
    267 
    268         .load => if (args.width) |width| switch (width) {
    269             8 => switch (args.valtype1.?) {
    270                 .i32 => if (args.signedness.? == .signed) return .i32_load8_s else return .i32_load8_u,
    271                 .i64 => if (args.signedness.? == .signed) return .i64_load8_s else return .i64_load8_u,
    272                 .f32, .f64, .v128 => unreachable,
    273             },
    274             16 => switch (args.valtype1.?) {
    275                 .i32 => if (args.signedness.? == .signed) return .i32_load16_s else return .i32_load16_u,
    276                 .i64 => if (args.signedness.? == .signed) return .i64_load16_s else return .i64_load16_u,
    277                 .f32, .f64, .v128 => unreachable,
    278             },
    279             32 => switch (args.valtype1.?) {
    280                 .i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u,
    281                 .i32 => return .i32_load,
    282                 .f32 => return .f32_load,
    283                 .f64, .v128 => unreachable,
    284             },
    285             64 => switch (args.valtype1.?) {
    286                 .i64 => return .i64_load,
    287                 .f64 => return .f64_load,
    288                 else => unreachable,
    289             },
    290             else => unreachable,
    291         } else switch (args.valtype1.?) {
    292             .i32 => return .i32_load,
    293             .i64 => return .i64_load,
    294             .f32 => return .f32_load,
    295             .f64 => return .f64_load,
    296             .v128 => unreachable, // handled independently
    297         },
    298         .store => if (args.width) |width| {
    299             switch (width) {
    300                 8 => switch (args.valtype1.?) {
    301                     .i32 => return .i32_store8,
    302                     .i64 => return .i64_store8,
    303                     .f32, .f64, .v128 => unreachable,
    304                 },
    305                 16 => switch (args.valtype1.?) {
    306                     .i32 => return .i32_store16,
    307                     .i64 => return .i64_store16,
    308                     .f32, .f64, .v128 => unreachable,
    309                 },
    310                 32 => switch (args.valtype1.?) {
    311                     .i64 => return .i64_store32,
    312                     .i32 => return .i32_store,
    313                     .f32 => return .f32_store,
    314                     .f64, .v128 => unreachable,
    315                 },
    316                 64 => switch (args.valtype1.?) {
    317                     .i64 => return .i64_store,
    318                     .f64 => return .f64_store,
    319                     else => unreachable,
    320                 },
    321                 else => unreachable,
    322             }
    323         } else {
    324             switch (args.valtype1.?) {
    325                 .i32 => return .i32_store,
    326                 .i64 => return .i64_store,
    327                 .f32 => return .f32_store,
    328                 .f64 => return .f64_store,
    329                 .v128 => unreachable, // handled independently
    330             }
    331         },
    332 
    333         .memory_size => return .memory_size,
    334         .memory_grow => return .memory_grow,
    335 
    336         .@"const" => switch (args.valtype1.?) {
    337             .i32 => return .i32_const,
    338             .i64 => return .i64_const,
    339             .f32 => return .f32_const,
    340             .f64 => return .f64_const,
    341             .v128 => unreachable, // handled independently
    342         },
    343 
    344         .eqz => switch (args.valtype1.?) {
    345             .i32 => return .i32_eqz,
    346             .i64 => return .i64_eqz,
    347             .f32, .f64, .v128 => unreachable,
    348         },
    349         .eq => switch (args.valtype1.?) {
    350             .i32 => return .i32_eq,
    351             .i64 => return .i64_eq,
    352             .f32 => return .f32_eq,
    353             .f64 => return .f64_eq,
    354             .v128 => unreachable, // handled independently
    355         },
    356         .ne => switch (args.valtype1.?) {
    357             .i32 => return .i32_ne,
    358             .i64 => return .i64_ne,
    359             .f32 => return .f32_ne,
    360             .f64 => return .f64_ne,
    361             .v128 => unreachable, // handled independently
    362         },
    363 
    364         .lt => switch (args.valtype1.?) {
    365             .i32 => if (args.signedness.? == .signed) return .i32_lt_s else return .i32_lt_u,
    366             .i64 => if (args.signedness.? == .signed) return .i64_lt_s else return .i64_lt_u,
    367             .f32 => return .f32_lt,
    368             .f64 => return .f64_lt,
    369             .v128 => unreachable, // handled independently
    370         },
    371         .gt => switch (args.valtype1.?) {
    372             .i32 => if (args.signedness.? == .signed) return .i32_gt_s else return .i32_gt_u,
    373             .i64 => if (args.signedness.? == .signed) return .i64_gt_s else return .i64_gt_u,
    374             .f32 => return .f32_gt,
    375             .f64 => return .f64_gt,
    376             .v128 => unreachable, // handled independently
    377         },
    378         .le => switch (args.valtype1.?) {
    379             .i32 => if (args.signedness.? == .signed) return .i32_le_s else return .i32_le_u,
    380             .i64 => if (args.signedness.? == .signed) return .i64_le_s else return .i64_le_u,
    381             .f32 => return .f32_le,
    382             .f64 => return .f64_le,
    383             .v128 => unreachable, // handled independently
    384         },
    385         .ge => switch (args.valtype1.?) {
    386             .i32 => if (args.signedness.? == .signed) return .i32_ge_s else return .i32_ge_u,
    387             .i64 => if (args.signedness.? == .signed) return .i64_ge_s else return .i64_ge_u,
    388             .f32 => return .f32_ge,
    389             .f64 => return .f64_ge,
    390             .v128 => unreachable, // handled independently
    391         },
    392 
    393         .clz => switch (args.valtype1.?) {
    394             .i32 => return .i32_clz,
    395             .i64 => return .i64_clz,
    396             .f32, .f64 => unreachable,
    397             .v128 => unreachable, // handled independently
    398         },
    399         .ctz => switch (args.valtype1.?) {
    400             .i32 => return .i32_ctz,
    401             .i64 => return .i64_ctz,
    402             .f32, .f64 => unreachable,
    403             .v128 => unreachable, // handled independently
    404         },
    405         .popcnt => switch (args.valtype1.?) {
    406             .i32 => return .i32_popcnt,
    407             .i64 => return .i64_popcnt,
    408             .f32, .f64 => unreachable,
    409             .v128 => unreachable, // handled independently
    410         },
    411 
    412         .add => switch (args.valtype1.?) {
    413             .i32 => return .i32_add,
    414             .i64 => return .i64_add,
    415             .f32 => return .f32_add,
    416             .f64 => return .f64_add,
    417             .v128 => unreachable, // handled independently
    418         },
    419         .sub => switch (args.valtype1.?) {
    420             .i32 => return .i32_sub,
    421             .i64 => return .i64_sub,
    422             .f32 => return .f32_sub,
    423             .f64 => return .f64_sub,
    424             .v128 => unreachable, // handled independently
    425         },
    426         .mul => switch (args.valtype1.?) {
    427             .i32 => return .i32_mul,
    428             .i64 => return .i64_mul,
    429             .f32 => return .f32_mul,
    430             .f64 => return .f64_mul,
    431             .v128 => unreachable, // handled independently
    432         },
    433 
    434         .div => switch (args.valtype1.?) {
    435             .i32 => if (args.signedness.? == .signed) return .i32_div_s else return .i32_div_u,
    436             .i64 => if (args.signedness.? == .signed) return .i64_div_s else return .i64_div_u,
    437             .f32 => return .f32_div,
    438             .f64 => return .f64_div,
    439             .v128 => unreachable, // handled independently
    440         },
    441         .rem => switch (args.valtype1.?) {
    442             .i32 => if (args.signedness.? == .signed) return .i32_rem_s else return .i32_rem_u,
    443             .i64 => if (args.signedness.? == .signed) return .i64_rem_s else return .i64_rem_u,
    444             .f32, .f64 => unreachable,
    445             .v128 => unreachable, // handled independently
    446         },
    447 
    448         .@"and" => switch (args.valtype1.?) {
    449             .i32 => return .i32_and,
    450             .i64 => return .i64_and,
    451             .f32, .f64 => unreachable,
    452             .v128 => unreachable, // handled independently
    453         },
    454         .@"or" => switch (args.valtype1.?) {
    455             .i32 => return .i32_or,
    456             .i64 => return .i64_or,
    457             .f32, .f64 => unreachable,
    458             .v128 => unreachable, // handled independently
    459         },
    460         .xor => switch (args.valtype1.?) {
    461             .i32 => return .i32_xor,
    462             .i64 => return .i64_xor,
    463             .f32, .f64 => unreachable,
    464             .v128 => unreachable, // handled independently
    465         },
    466 
    467         .shl => switch (args.valtype1.?) {
    468             .i32 => return .i32_shl,
    469             .i64 => return .i64_shl,
    470             .f32, .f64 => unreachable,
    471             .v128 => unreachable, // handled independently
    472         },
    473         .shr => switch (args.valtype1.?) {
    474             .i32 => if (args.signedness.? == .signed) return .i32_shr_s else return .i32_shr_u,
    475             .i64 => if (args.signedness.? == .signed) return .i64_shr_s else return .i64_shr_u,
    476             .f32, .f64 => unreachable,
    477             .v128 => unreachable, // handled independently
    478         },
    479         .rotl => switch (args.valtype1.?) {
    480             .i32 => return .i32_rotl,
    481             .i64 => return .i64_rotl,
    482             .f32, .f64 => unreachable,
    483             .v128 => unreachable, // handled independently
    484         },
    485         .rotr => switch (args.valtype1.?) {
    486             .i32 => return .i32_rotr,
    487             .i64 => return .i64_rotr,
    488             .f32, .f64 => unreachable,
    489             .v128 => unreachable, // handled independently
    490         },
    491 
    492         .abs => switch (args.valtype1.?) {
    493             .i32, .i64 => unreachable,
    494             .f32 => return .f32_abs,
    495             .f64 => return .f64_abs,
    496             .v128 => unreachable, // handled independently
    497         },
    498         .neg => switch (args.valtype1.?) {
    499             .i32, .i64 => unreachable,
    500             .f32 => return .f32_neg,
    501             .f64 => return .f64_neg,
    502             .v128 => unreachable, // handled independently
    503         },
    504         .ceil => switch (args.valtype1.?) {
    505             .i64 => unreachable,
    506             .i32 => return .f32_ceil, // when valtype is f16, we store it in i32.
    507             .f32 => return .f32_ceil,
    508             .f64 => return .f64_ceil,
    509             .v128 => unreachable, // handled independently
    510         },
    511         .floor => switch (args.valtype1.?) {
    512             .i64 => unreachable,
    513             .i32 => return .f32_floor, // when valtype is f16, we store it in i32.
    514             .f32 => return .f32_floor,
    515             .f64 => return .f64_floor,
    516             .v128 => unreachable, // handled independently
    517         },
    518         .trunc => switch (args.valtype1.?) {
    519             .i32 => if (args.valtype2) |valty| switch (valty) {
    520                 .i32 => unreachable,
    521                 .i64 => unreachable,
    522                 .f32 => if (args.signedness.? == .signed) return .i32_trunc_f32_s else return .i32_trunc_f32_u,
    523                 .f64 => if (args.signedness.? == .signed) return .i32_trunc_f64_s else return .i32_trunc_f64_u,
    524                 .v128 => unreachable, // handled independently
    525             } else return .f32_trunc, // when no valtype2, it's an f16 instead which is stored in an i32.
    526             .i64 => switch (args.valtype2.?) {
    527                 .i32 => unreachable,
    528                 .i64 => unreachable,
    529                 .f32 => if (args.signedness.? == .signed) return .i64_trunc_f32_s else return .i64_trunc_f32_u,
    530                 .f64 => if (args.signedness.? == .signed) return .i64_trunc_f64_s else return .i64_trunc_f64_u,
    531                 .v128 => unreachable, // handled independently
    532             },
    533             .f32 => return .f32_trunc,
    534             .f64 => return .f64_trunc,
    535             .v128 => unreachable, // handled independently
    536         },
    537         .nearest => switch (args.valtype1.?) {
    538             .i32, .i64 => unreachable,
    539             .f32 => return .f32_nearest,
    540             .f64 => return .f64_nearest,
    541             .v128 => unreachable, // handled independently
    542         },
    543         .sqrt => switch (args.valtype1.?) {
    544             .i32, .i64 => unreachable,
    545             .f32 => return .f32_sqrt,
    546             .f64 => return .f64_sqrt,
    547             .v128 => unreachable, // handled independently
    548         },
    549         .min => switch (args.valtype1.?) {
    550             .i32, .i64 => unreachable,
    551             .f32 => return .f32_min,
    552             .f64 => return .f64_min,
    553             .v128 => unreachable, // handled independently
    554         },
    555         .max => switch (args.valtype1.?) {
    556             .i32, .i64 => unreachable,
    557             .f32 => return .f32_max,
    558             .f64 => return .f64_max,
    559             .v128 => unreachable, // handled independently
    560         },
    561         .copysign => switch (args.valtype1.?) {
    562             .i32, .i64 => unreachable,
    563             .f32 => return .f32_copysign,
    564             .f64 => return .f64_copysign,
    565             .v128 => unreachable, // handled independently
    566         },
    567 
    568         .wrap => switch (args.valtype1.?) {
    569             .i32 => switch (args.valtype2.?) {
    570                 .i32 => unreachable,
    571                 .i64 => return .i32_wrap_i64,
    572                 .f32, .f64 => unreachable,
    573                 .v128 => unreachable, // handled independently
    574             },
    575             .i64, .f32, .f64 => unreachable,
    576             .v128 => unreachable, // handled independently
    577         },
    578         .convert => switch (args.valtype1.?) {
    579             .i32, .i64 => unreachable,
    580             .f32 => switch (args.valtype2.?) {
    581                 .i32 => if (args.signedness.? == .signed) return .f32_convert_i32_s else return .f32_convert_i32_u,
    582                 .i64 => if (args.signedness.? == .signed) return .f32_convert_i64_s else return .f32_convert_i64_u,
    583                 .f32, .f64 => unreachable,
    584                 .v128 => unreachable, // handled independently
    585             },
    586             .f64 => switch (args.valtype2.?) {
    587                 .i32 => if (args.signedness.? == .signed) return .f64_convert_i32_s else return .f64_convert_i32_u,
    588                 .i64 => if (args.signedness.? == .signed) return .f64_convert_i64_s else return .f64_convert_i64_u,
    589                 .f32, .f64 => unreachable,
    590                 .v128 => unreachable, // handled independently
    591             },
    592             .v128 => unreachable, // handled independently
    593         },
    594         .demote => if (args.valtype1.? == .f32 and args.valtype2.? == .f64) return .f32_demote_f64 else unreachable,
    595         .promote => if (args.valtype1.? == .f64 and args.valtype2.? == .f32) return .f64_promote_f32 else unreachable,
    596         .reinterpret => switch (args.valtype1.?) {
    597             .i32 => if (args.valtype2.? == .f32) return .i32_reinterpret_f32 else unreachable,
    598             .i64 => if (args.valtype2.? == .f64) return .i64_reinterpret_f64 else unreachable,
    599             .f32 => if (args.valtype2.? == .i32) return .f32_reinterpret_i32 else unreachable,
    600             .f64 => if (args.valtype2.? == .i64) return .f64_reinterpret_i64 else unreachable,
    601             .v128 => unreachable, // handled independently
    602         },
    603         .extend => switch (args.valtype1.?) {
    604             .i32 => switch (args.width.?) {
    605                 8 => if (args.signedness.? == .signed) return .i32_extend8_s else unreachable,
    606                 16 => if (args.signedness.? == .signed) return .i32_extend16_s else unreachable,
    607                 else => unreachable,
    608             },
    609             .i64 => switch (args.width.?) {
    610                 8 => if (args.signedness.? == .signed) return .i64_extend8_s else unreachable,
    611                 16 => if (args.signedness.? == .signed) return .i64_extend16_s else unreachable,
    612                 32 => if (args.signedness.? == .signed) return .i64_extend32_s else unreachable,
    613                 else => unreachable,
    614             },
    615             .f32, .f64 => unreachable,
    616             .v128 => unreachable, // handled independently
    617         },
    618     }
    619 }
    620 
    621 test "Wasm - buildOpcode" {
    622     // Make sure buildOpcode is referenced, and test some examples
    623     const i32_const = buildOpcode(.{ .op = .@"const", .valtype1 = .i32 });
    624     const end = buildOpcode(.{ .op = .end });
    625     const local_get = buildOpcode(.{ .op = .local_get });
    626     const i64_extend32_s = buildOpcode(.{ .op = .extend, .valtype1 = .i64, .width = 32, .signedness = .signed });
    627     const f64_reinterpret_i64 = buildOpcode(.{ .op = .reinterpret, .valtype1 = .f64, .valtype2 = .i64 });
    628 
    629     try testing.expectEqual(@as(wasm.Opcode, .i32_const), i32_const);
    630     try testing.expectEqual(@as(wasm.Opcode, .end), end);
    631     try testing.expectEqual(@as(wasm.Opcode, .local_get), local_get);
    632     try testing.expectEqual(@as(wasm.Opcode, .i64_extend32_s), i64_extend32_s);
    633     try testing.expectEqual(@as(wasm.Opcode, .f64_reinterpret_i64), f64_reinterpret_i64);
    634 }
    635 
    636 /// Hashmap to store generated `WValue` for each `Air.Inst.Ref`
    637 pub const ValueTable = std.AutoArrayHashMapUnmanaged(Air.Inst.Ref, WValue);
    638 
    639 const CodeGen = @This();
    640 
    641 /// Reference to the function declaration the code
    642 /// section belongs to
    643 decl: *Decl,
    644 decl_index: InternPool.DeclIndex,
    645 /// Current block depth. Used to calculate the relative difference between a break
    646 /// and block
    647 block_depth: u32 = 0,
    648 air: Air,
    649 liveness: Liveness,
    650 gpa: mem.Allocator,
    651 debug_output: codegen.DebugInfoOutput,
    652 func_index: InternPool.Index,
    653 /// Contains a list of current branches.
    654 /// When we return from a branch, the branch will be popped from this list,
    655 /// which means branches can only contain references from within its own branch,
    656 /// or a branch higher (lower index) in the tree.
    657 branches: std.ArrayListUnmanaged(Branch) = .{},
    658 /// Table to save `WValue`'s generated by an `Air.Inst`
    659 // values: ValueTable,
    660 /// Mapping from Air.Inst.Index to block ids
    661 blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, struct {
    662     label: u32,
    663     value: WValue,
    664 }) = .{},
    665 /// `bytes` contains the wasm bytecode belonging to the 'code' section.
    666 code: *ArrayList(u8),
    667 /// The index the next local generated will have
    668 /// NOTE: arguments share the index with locals therefore the first variable
    669 /// will have the index that comes after the last argument's index
    670 local_index: u32 = 0,
    671 /// The index of the current argument.
    672 /// Used to track which argument is being referenced in `airArg`.
    673 arg_index: u32 = 0,
    674 /// If codegen fails, an error messages will be allocated and saved in `err_msg`
    675 err_msg: *Module.ErrorMsg,
    676 /// List of all locals' types generated throughout this declaration
    677 /// used to emit locals count at start of 'code' section.
    678 locals: std.ArrayListUnmanaged(u8),
    679 /// List of simd128 immediates. Each value is stored as an array of bytes.
    680 /// This list will only be populated for 128bit-simd values when the target features
    681 /// are enabled also.
    682 simd_immediates: std.ArrayListUnmanaged([16]u8) = .{},
    683 /// The Target we're emitting (used to call intInfo)
    684 target: std.Target,
    685 /// Represents the wasm binary file that is being linked.
    686 bin_file: *link.File.Wasm,
    687 /// List of MIR Instructions
    688 mir_instructions: std.MultiArrayList(Mir.Inst) = .{},
    689 /// Contains extra data for MIR
    690 mir_extra: std.ArrayListUnmanaged(u32) = .{},
    691 /// When a function is executing, we store the the current stack pointer's value within this local.
    692 /// This value is then used to restore the stack pointer to the original value at the return of the function.
    693 initial_stack_value: WValue = .none,
    694 /// The current stack pointer substracted with the stack size. From this value, we will calculate
    695 /// all offsets of the stack values.
    696 bottom_stack_value: WValue = .none,
    697 /// Arguments of this function declaration
    698 /// This will be set after `resolveCallingConventionValues`
    699 args: []WValue = &.{},
    700 /// This will only be `.none` if the function returns void, or returns an immediate.
    701 /// When it returns a pointer to the stack, the `.local` tag will be active and must be populated
    702 /// before this function returns its execution to the caller.
    703 return_value: WValue = .none,
    704 /// The size of the stack this function occupies. In the function prologue
    705 /// we will move the stack pointer by this number, forward aligned with the `stack_alignment`.
    706 stack_size: u32 = 0,
    707 /// The stack alignment, which is 16 bytes by default. This is specified by the
    708 /// tool-conventions: https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md
    709 /// and also what the llvm backend will emit.
    710 /// However, local variables or the usage of `@setAlignStack` can overwrite this default.
    711 stack_alignment: Alignment = .@"16",
    712 
    713 // For each individual Wasm valtype we store a seperate free list which
    714 // allows us to re-use locals that are no longer used. e.g. a temporary local.
    715 /// A list of indexes which represents a local of valtype `i32`.
    716 /// It is illegal to store a non-i32 valtype in this list.
    717 free_locals_i32: std.ArrayListUnmanaged(u32) = .{},
    718 /// A list of indexes which represents a local of valtype `i64`.
    719 /// It is illegal to store a non-i64 valtype in this list.
    720 free_locals_i64: std.ArrayListUnmanaged(u32) = .{},
    721 /// A list of indexes which represents a local of valtype `f32`.
    722 /// It is illegal to store a non-f32 valtype in this list.
    723 free_locals_f32: std.ArrayListUnmanaged(u32) = .{},
    724 /// A list of indexes which represents a local of valtype `f64`.
    725 /// It is illegal to store a non-f64 valtype in this list.
    726 free_locals_f64: std.ArrayListUnmanaged(u32) = .{},
    727 /// A list of indexes which represents a local of valtype `v127`.
    728 /// It is illegal to store a non-v128 valtype in this list.
    729 free_locals_v128: std.ArrayListUnmanaged(u32) = .{},
    730 
    731 /// When in debug mode, this tracks if no `finishAir` was missed.
    732 /// Forgetting to call `finishAir` will cause the result to not be
    733 /// stored in our `values` map and therefore cause bugs.
    734 air_bookkeeping: @TypeOf(bookkeeping_init) = bookkeeping_init,
    735 
    736 const bookkeeping_init = if (std.debug.runtime_safety) @as(usize, 0) else {};
    737 
    738 const InnerError = error{
    739     OutOfMemory,
    740     /// An error occurred when trying to lower AIR to MIR.
    741     CodegenFail,
    742     /// Compiler implementation could not handle a large integer.
    743     Overflow,
    744 };
    745 
    746 pub fn deinit(func: *CodeGen) void {
    747     // in case of an error and we still have branches
    748     for (func.branches.items) |*branch| {
    749         branch.deinit(func.gpa);
    750     }
    751     func.branches.deinit(func.gpa);
    752     func.blocks.deinit(func.gpa);
    753     func.locals.deinit(func.gpa);
    754     func.simd_immediates.deinit(func.gpa);
    755     func.mir_instructions.deinit(func.gpa);
    756     func.mir_extra.deinit(func.gpa);
    757     func.free_locals_i32.deinit(func.gpa);
    758     func.free_locals_i64.deinit(func.gpa);
    759     func.free_locals_f32.deinit(func.gpa);
    760     func.free_locals_f64.deinit(func.gpa);
    761     func.free_locals_v128.deinit(func.gpa);
    762     func.* = undefined;
    763 }
    764 
    765 /// Sets `err_msg` on `CodeGen` and returns `error.CodegenFail` which is caught in link/Wasm.zig
    766 fn fail(func: *CodeGen, comptime fmt: []const u8, args: anytype) InnerError {
    767     const mod = func.bin_file.base.comp.module.?;
    768     const src_loc = func.decl.navSrcLoc(mod).upgrade(mod);
    769     func.err_msg = try Module.ErrorMsg.create(func.gpa, src_loc, fmt, args);
    770     return error.CodegenFail;
    771 }
    772 
    773 /// Resolves the `WValue` for the given instruction `inst`
    774 /// When the given instruction has a `Value`, it returns a constant instead
    775 fn resolveInst(func: *CodeGen, ref: Air.Inst.Ref) InnerError!WValue {
    776     var branch_index = func.branches.items.len;
    777     while (branch_index > 0) : (branch_index -= 1) {
    778         const branch = func.branches.items[branch_index - 1];
    779         if (branch.values.get(ref)) |value| {
    780             return value;
    781         }
    782     }
    783 
    784     // when we did not find an existing instruction, it
    785     // means we must generate it from a constant.
    786     // We always store constants in the most outer branch as they must never
    787     // be removed. The most outer branch is always at index 0.
    788     const gop = try func.branches.items[0].values.getOrPut(func.gpa, ref);
    789     assert(!gop.found_existing);
    790 
    791     const mod = func.bin_file.base.comp.module.?;
    792     const val = (try func.air.value(ref, mod)).?;
    793     const ty = func.typeOf(ref);
    794     if (!ty.hasRuntimeBitsIgnoreComptime(mod) and !ty.isInt(mod) and !ty.isError(mod)) {
    795         gop.value_ptr.* = WValue{ .none = {} };
    796         return gop.value_ptr.*;
    797     }
    798 
    799     // When we need to pass the value by reference (such as a struct), we will
    800     // leverage `generateSymbol` to lower the constant to bytes and emit it
    801     // to the 'rodata' section. We then return the index into the section as `WValue`.
    802     //
    803     // In the other cases, we will simply lower the constant to a value that fits
    804     // into a single local (such as a pointer, integer, bool, etc).
    805     const result = if (isByRef(ty, mod)) blk: {
    806         const sym_index = try func.bin_file.lowerUnnamedConst(val, func.decl_index);
    807         break :blk WValue{ .memory = sym_index };
    808     } else try func.lowerConstant(val, ty);
    809 
    810     gop.value_ptr.* = result;
    811     return result;
    812 }
    813 
    814 fn finishAir(func: *CodeGen, inst: Air.Inst.Index, result: WValue, operands: []const Air.Inst.Ref) void {
    815     assert(operands.len <= Liveness.bpi - 1);
    816     var tomb_bits = func.liveness.getTombBits(inst);
    817     for (operands) |operand| {
    818         const dies = @as(u1, @truncate(tomb_bits)) != 0;
    819         tomb_bits >>= 1;
    820         if (!dies) continue;
    821         processDeath(func, operand);
    822     }
    823 
    824     // results of `none` can never be referenced.
    825     if (result != .none) {
    826         assert(result != .stack); // it's illegal to store a stack value as we cannot track its position
    827         const branch = func.currentBranch();
    828         branch.values.putAssumeCapacityNoClobber(inst.toRef(), result);
    829     }
    830 
    831     if (std.debug.runtime_safety) {
    832         func.air_bookkeeping += 1;
    833     }
    834 }
    835 
    836 const Branch = struct {
    837     values: ValueTable = .{},
    838 
    839     fn deinit(branch: *Branch, gpa: Allocator) void {
    840         branch.values.deinit(gpa);
    841         branch.* = undefined;
    842     }
    843 };
    844 
    845 inline fn currentBranch(func: *CodeGen) *Branch {
    846     return &func.branches.items[func.branches.items.len - 1];
    847 }
    848 
    849 const BigTomb = struct {
    850     gen: *CodeGen,
    851     inst: Air.Inst.Index,
    852     lbt: Liveness.BigTomb,
    853 
    854     fn feed(bt: *BigTomb, op_ref: Air.Inst.Ref) void {
    855         const dies = bt.lbt.feed();
    856         if (!dies) return;
    857         // This will be a nop for interned constants.
    858         processDeath(bt.gen, op_ref);
    859     }
    860 
    861     fn finishAir(bt: *BigTomb, result: WValue) void {
    862         assert(result != .stack);
    863         if (result != .none) {
    864             bt.gen.currentBranch().values.putAssumeCapacityNoClobber(bt.inst.toRef(), result);
    865         }
    866 
    867         if (std.debug.runtime_safety) {
    868             bt.gen.air_bookkeeping += 1;
    869         }
    870     }
    871 };
    872 
    873 fn iterateBigTomb(func: *CodeGen, inst: Air.Inst.Index, operand_count: usize) !BigTomb {
    874     try func.currentBranch().values.ensureUnusedCapacity(func.gpa, operand_count + 1);
    875     return BigTomb{
    876         .gen = func,
    877         .inst = inst,
    878         .lbt = func.liveness.iterateBigTomb(inst),
    879     };
    880 }
    881 
    882 fn processDeath(func: *CodeGen, ref: Air.Inst.Ref) void {
    883     if (ref.toIndex() == null) return;
    884     // Branches are currently only allowed to free locals allocated
    885     // within their own branch.
    886     // TODO: Upon branch consolidation free any locals if needed.
    887     const value = func.currentBranch().values.getPtr(ref) orelse return;
    888     if (value.* != .local) return;
    889     const reserved_indexes = func.args.len + @intFromBool(func.return_value != .none);
    890     if (value.local.value < reserved_indexes) {
    891         return; // function arguments can never be re-used
    892     }
    893     log.debug("Decreasing reference for ref: %{d}, using local '{d}'", .{ @intFromEnum(ref.toIndex().?), value.local.value });
    894     value.local.references -= 1; // if this panics, a call to `reuseOperand` was forgotten by the developer
    895     if (value.local.references == 0) {
    896         value.free(func);
    897     }
    898 }
    899 
    900 /// Appends a MIR instruction and returns its index within the list of instructions
    901 fn addInst(func: *CodeGen, inst: Mir.Inst) error{OutOfMemory}!void {
    902     try func.mir_instructions.append(func.gpa, inst);
    903 }
    904 
    905 fn addTag(func: *CodeGen, tag: Mir.Inst.Tag) error{OutOfMemory}!void {
    906     try func.addInst(.{ .tag = tag, .data = .{ .tag = {} } });
    907 }
    908 
    909 fn addExtended(func: *CodeGen, opcode: wasm.MiscOpcode) error{OutOfMemory}!void {
    910     const extra_index = @as(u32, @intCast(func.mir_extra.items.len));
    911     try func.mir_extra.append(func.gpa, @intFromEnum(opcode));
    912     try func.addInst(.{ .tag = .misc_prefix, .data = .{ .payload = extra_index } });
    913 }
    914 
    915 fn addLabel(func: *CodeGen, tag: Mir.Inst.Tag, label: u32) error{OutOfMemory}!void {
    916     try func.addInst(.{ .tag = tag, .data = .{ .label = label } });
    917 }
    918 
    919 fn addImm32(func: *CodeGen, imm: i32) error{OutOfMemory}!void {
    920     try func.addInst(.{ .tag = .i32_const, .data = .{ .imm32 = imm } });
    921 }
    922 
    923 /// Accepts an unsigned 64bit integer rather than a signed integer to
    924 /// prevent us from having to bitcast multiple times as most values
    925 /// within codegen are represented as unsigned rather than signed.
    926 fn addImm64(func: *CodeGen, imm: u64) error{OutOfMemory}!void {
    927     const extra_index = try func.addExtra(Mir.Imm64.fromU64(imm));
    928     try func.addInst(.{ .tag = .i64_const, .data = .{ .payload = extra_index } });
    929 }
    930 
    931 /// Accepts the index into the list of 128bit-immediates
    932 fn addImm128(func: *CodeGen, index: u32) error{OutOfMemory}!void {
    933     const simd_values = func.simd_immediates.items[index];
    934     const extra_index = @as(u32, @intCast(func.mir_extra.items.len));
    935     // tag + 128bit value
    936     try func.mir_extra.ensureUnusedCapacity(func.gpa, 5);
    937     func.mir_extra.appendAssumeCapacity(std.wasm.simdOpcode(.v128_const));
    938     func.mir_extra.appendSliceAssumeCapacity(@alignCast(mem.bytesAsSlice(u32, &simd_values)));
    939     try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } });
    940 }
    941 
    942 fn addFloat64(func: *CodeGen, float: f64) error{OutOfMemory}!void {
    943     const extra_index = try func.addExtra(Mir.Float64.fromFloat64(float));
    944     try func.addInst(.{ .tag = .f64_const, .data = .{ .payload = extra_index } });
    945 }
    946 
    947 /// Inserts an instruction to load/store from/to wasm's linear memory dependent on the given `tag`.
    948 fn addMemArg(func: *CodeGen, tag: Mir.Inst.Tag, mem_arg: Mir.MemArg) error{OutOfMemory}!void {
    949     const extra_index = try func.addExtra(mem_arg);
    950     try func.addInst(.{ .tag = tag, .data = .{ .payload = extra_index } });
    951 }
    952 
    953 /// Inserts an instruction from the 'atomics' feature which accesses wasm's linear memory dependent on the
    954 /// given `tag`.
    955 fn addAtomicMemArg(func: *CodeGen, tag: wasm.AtomicsOpcode, mem_arg: Mir.MemArg) error{OutOfMemory}!void {
    956     const extra_index = try func.addExtra(@as(struct { val: u32 }, .{ .val = wasm.atomicsOpcode(tag) }));
    957     _ = try func.addExtra(mem_arg);
    958     try func.addInst(.{ .tag = .atomics_prefix, .data = .{ .payload = extra_index } });
    959 }
    960 
    961 /// Helper function to emit atomic mir opcodes.
    962 fn addAtomicTag(func: *CodeGen, tag: wasm.AtomicsOpcode) error{OutOfMemory}!void {
    963     const extra_index = try func.addExtra(@as(struct { val: u32 }, .{ .val = wasm.atomicsOpcode(tag) }));
    964     try func.addInst(.{ .tag = .atomics_prefix, .data = .{ .payload = extra_index } });
    965 }
    966 
    967 /// Appends entries to `mir_extra` based on the type of `extra`.
    968 /// Returns the index into `mir_extra`
    969 fn addExtra(func: *CodeGen, extra: anytype) error{OutOfMemory}!u32 {
    970     const fields = std.meta.fields(@TypeOf(extra));
    971     try func.mir_extra.ensureUnusedCapacity(func.gpa, fields.len);
    972     return func.addExtraAssumeCapacity(extra);
    973 }
    974 
    975 /// Appends entries to `mir_extra` based on the type of `extra`.
    976 /// Returns the index into `mir_extra`
    977 fn addExtraAssumeCapacity(func: *CodeGen, extra: anytype) error{OutOfMemory}!u32 {
    978     const fields = std.meta.fields(@TypeOf(extra));
    979     const result = @as(u32, @intCast(func.mir_extra.items.len));
    980     inline for (fields) |field| {
    981         func.mir_extra.appendAssumeCapacity(switch (field.type) {
    982             u32 => @field(extra, field.name),
    983             else => |field_type| @compileError("Unsupported field type " ++ @typeName(field_type)),
    984         });
    985     }
    986     return result;
    987 }
    988 
    989 /// Using a given `Type`, returns the corresponding type
    990 fn typeToValtype(ty: Type, mod: *Module) wasm.Valtype {
    991     const target = mod.getTarget();
    992     const ip = &mod.intern_pool;
    993     return switch (ty.zigTypeTag(mod)) {
    994         .Float => switch (ty.floatBits(target)) {
    995             16 => wasm.Valtype.i32, // stored/loaded as u16
    996             32 => wasm.Valtype.f32,
    997             64 => wasm.Valtype.f64,
    998             80, 128 => wasm.Valtype.i64,
    999             else => unreachable,
   1000         },
   1001         .Int, .Enum => blk: {
   1002             const info = ty.intInfo(mod);
   1003             if (info.bits <= 32) break :blk wasm.Valtype.i32;
   1004             if (info.bits > 32 and info.bits <= 128) break :blk wasm.Valtype.i64;
   1005             break :blk wasm.Valtype.i32; // represented as pointer to stack
   1006         },
   1007         .Struct => {
   1008             if (mod.typeToPackedStruct(ty)) |packed_struct| {
   1009                 return typeToValtype(Type.fromInterned(packed_struct.backingIntType(ip).*), mod);
   1010             } else {
   1011                 return wasm.Valtype.i32;
   1012             }
   1013         },
   1014         .Vector => switch (determineSimdStoreStrategy(ty, mod)) {
   1015             .direct => wasm.Valtype.v128,
   1016             .unrolled => wasm.Valtype.i32,
   1017         },
   1018         .Union => switch (ty.containerLayout(mod)) {
   1019             .@"packed" => {
   1020                 const int_ty = mod.intType(.unsigned, @as(u16, @intCast(ty.bitSize(mod)))) catch @panic("out of memory");
   1021                 return typeToValtype(int_ty, mod);
   1022             },
   1023             else => wasm.Valtype.i32,
   1024         },
   1025         else => wasm.Valtype.i32, // all represented as reference/immediate
   1026     };
   1027 }
   1028 
   1029 /// Using a given `Type`, returns the byte representation of its wasm value type
   1030 fn genValtype(ty: Type, mod: *Module) u8 {
   1031     return wasm.valtype(typeToValtype(ty, mod));
   1032 }
   1033 
   1034 /// Using a given `Type`, returns the corresponding wasm value type
   1035 /// Differently from `genValtype` this also allows `void` to create a block
   1036 /// with no return type
   1037 fn genBlockType(ty: Type, mod: *Module) u8 {
   1038     return switch (ty.ip_index) {
   1039         .void_type, .noreturn_type => wasm.block_empty,
   1040         else => genValtype(ty, mod),
   1041     };
   1042 }
   1043 
   1044 /// Writes the bytecode depending on the given `WValue` in `val`
   1045 fn emitWValue(func: *CodeGen, value: WValue) InnerError!void {
   1046     switch (value) {
   1047         .dead => unreachable, // reference to free'd `WValue` (missing reuseOperand?)
   1048         .none, .stack => {}, // no-op
   1049         .local => |idx| try func.addLabel(.local_get, idx.value),
   1050         .imm32 => |val| try func.addImm32(@as(i32, @bitCast(val))),
   1051         .imm64 => |val| try func.addImm64(val),
   1052         .imm128 => |val| try func.addImm128(val),
   1053         .float32 => |val| try func.addInst(.{ .tag = .f32_const, .data = .{ .float32 = val } }),
   1054         .float64 => |val| try func.addFloat64(val),
   1055         .memory => |ptr| {
   1056             const extra_index = try func.addExtra(Mir.Memory{ .pointer = ptr, .offset = 0 });
   1057             try func.addInst(.{ .tag = .memory_address, .data = .{ .payload = extra_index } });
   1058         },
   1059         .memory_offset => |mem_off| {
   1060             const extra_index = try func.addExtra(Mir.Memory{ .pointer = mem_off.pointer, .offset = mem_off.offset });
   1061             try func.addInst(.{ .tag = .memory_address, .data = .{ .payload = extra_index } });
   1062         },
   1063         .function_index => |index| try func.addLabel(.function_index, index), // write function index and generate relocation
   1064         .stack_offset => try func.addLabel(.local_get, func.bottom_stack_value.local.value), // caller must ensure to address the offset
   1065     }
   1066 }
   1067 
   1068 /// If given a local or stack-offset, increases the reference count by 1.
   1069 /// The old `WValue` found at instruction `ref` is then replaced by the
   1070 /// modified `WValue` and returned. When given a non-local or non-stack-offset,
   1071 /// returns the given `operand` itfunc instead.
   1072 fn reuseOperand(func: *CodeGen, ref: Air.Inst.Ref, operand: WValue) WValue {
   1073     if (operand != .local and operand != .stack_offset) return operand;
   1074     var new_value = operand;
   1075     switch (new_value) {
   1076         .local => |*local| local.references += 1,
   1077         .stack_offset => |*stack_offset| stack_offset.references += 1,
   1078         else => unreachable,
   1079     }
   1080     const old_value = func.getResolvedInst(ref);
   1081     old_value.* = new_value;
   1082     return new_value;
   1083 }
   1084 
   1085 /// From a reference, returns its resolved `WValue`.
   1086 /// It's illegal to provide a `Air.Inst.Ref` that hasn't been resolved yet.
   1087 fn getResolvedInst(func: *CodeGen, ref: Air.Inst.Ref) *WValue {
   1088     var index = func.branches.items.len;
   1089     while (index > 0) : (index -= 1) {
   1090         const branch = func.branches.items[index - 1];
   1091         if (branch.values.getPtr(ref)) |value| {
   1092             return value;
   1093         }
   1094     }
   1095     unreachable; // developer-error: This can only be called on resolved instructions. Use `resolveInst` instead.
   1096 }
   1097 
   1098 /// Creates one locals for a given `Type`.
   1099 /// Returns a corresponding `Wvalue` with `local` as active tag
   1100 fn allocLocal(func: *CodeGen, ty: Type) InnerError!WValue {
   1101     const mod = func.bin_file.base.comp.module.?;
   1102     const valtype = typeToValtype(ty, mod);
   1103     switch (valtype) {
   1104         .i32 => if (func.free_locals_i32.popOrNull()) |index| {
   1105             log.debug("reusing local ({d}) of type {}", .{ index, valtype });
   1106             return WValue{ .local = .{ .value = index, .references = 1 } };
   1107         },
   1108         .i64 => if (func.free_locals_i64.popOrNull()) |index| {
   1109             log.debug("reusing local ({d}) of type {}", .{ index, valtype });
   1110             return WValue{ .local = .{ .value = index, .references = 1 } };
   1111         },
   1112         .f32 => if (func.free_locals_f32.popOrNull()) |index| {
   1113             log.debug("reusing local ({d}) of type {}", .{ index, valtype });
   1114             return WValue{ .local = .{ .value = index, .references = 1 } };
   1115         },
   1116         .f64 => if (func.free_locals_f64.popOrNull()) |index| {
   1117             log.debug("reusing local ({d}) of type {}", .{ index, valtype });
   1118             return WValue{ .local = .{ .value = index, .references = 1 } };
   1119         },
   1120         .v128 => if (func.free_locals_v128.popOrNull()) |index| {
   1121             log.debug("reusing local ({d}) of type {}", .{ index, valtype });
   1122             return WValue{ .local = .{ .value = index, .references = 1 } };
   1123         },
   1124     }
   1125     log.debug("new local of type {}", .{valtype});
   1126     // no local was free to be re-used, so allocate a new local instead
   1127     return func.ensureAllocLocal(ty);
   1128 }
   1129 
   1130 /// Ensures a new local will be created. This is useful when it's useful
   1131 /// to use a zero-initialized local.
   1132 fn ensureAllocLocal(func: *CodeGen, ty: Type) InnerError!WValue {
   1133     const mod = func.bin_file.base.comp.module.?;
   1134     try func.locals.append(func.gpa, genValtype(ty, mod));
   1135     const initial_index = func.local_index;
   1136     func.local_index += 1;
   1137     return WValue{ .local = .{ .value = initial_index, .references = 1 } };
   1138 }
   1139 
   1140 /// Generates a `wasm.Type` from a given function type.
   1141 /// Memory is owned by the caller.
   1142 fn genFunctype(
   1143     gpa: Allocator,
   1144     cc: std.builtin.CallingConvention,
   1145     params: []const InternPool.Index,
   1146     return_type: Type,
   1147     mod: *Module,
   1148 ) !wasm.Type {
   1149     var temp_params = std.ArrayList(wasm.Valtype).init(gpa);
   1150     defer temp_params.deinit();
   1151     var returns = std.ArrayList(wasm.Valtype).init(gpa);
   1152     defer returns.deinit();
   1153 
   1154     if (firstParamSRet(cc, return_type, mod)) {
   1155         try temp_params.append(.i32); // memory address is always a 32-bit handle
   1156     } else if (return_type.hasRuntimeBitsIgnoreComptime(mod)) {
   1157         if (cc == .C) {
   1158             const res_classes = abi.classifyType(return_type, mod);
   1159             assert(res_classes[0] == .direct and res_classes[1] == .none);
   1160             const scalar_type = abi.scalarType(return_type, mod);
   1161             try returns.append(typeToValtype(scalar_type, mod));
   1162         } else {
   1163             try returns.append(typeToValtype(return_type, mod));
   1164         }
   1165     } else if (return_type.isError(mod)) {
   1166         try returns.append(.i32);
   1167     }
   1168 
   1169     // param types
   1170     for (params) |param_type_ip| {
   1171         const param_type = Type.fromInterned(param_type_ip);
   1172         if (!param_type.hasRuntimeBitsIgnoreComptime(mod)) continue;
   1173 
   1174         switch (cc) {
   1175             .C => {
   1176                 const param_classes = abi.classifyType(param_type, mod);
   1177                 for (param_classes) |class| {
   1178                     if (class == .none) continue;
   1179                     if (class == .direct) {
   1180                         const scalar_type = abi.scalarType(param_type, mod);
   1181                         try temp_params.append(typeToValtype(scalar_type, mod));
   1182                     } else {
   1183                         try temp_params.append(typeToValtype(param_type, mod));
   1184                     }
   1185                 }
   1186             },
   1187             else => if (isByRef(param_type, mod))
   1188                 try temp_params.append(.i32)
   1189             else
   1190                 try temp_params.append(typeToValtype(param_type, mod)),
   1191         }
   1192     }
   1193 
   1194     return wasm.Type{
   1195         .params = try temp_params.toOwnedSlice(),
   1196         .returns = try returns.toOwnedSlice(),
   1197     };
   1198 }
   1199 
   1200 pub fn generate(
   1201     bin_file: *link.File,
   1202     src_loc: Module.SrcLoc,
   1203     func_index: InternPool.Index,
   1204     air: Air,
   1205     liveness: Liveness,
   1206     code: *std.ArrayList(u8),
   1207     debug_output: codegen.DebugInfoOutput,
   1208 ) codegen.CodeGenError!codegen.Result {
   1209     _ = src_loc;
   1210     const comp = bin_file.comp;
   1211     const gpa = comp.gpa;
   1212     const mod = comp.module.?;
   1213     const func = mod.funcInfo(func_index);
   1214     const decl = mod.declPtr(func.owner_decl);
   1215     const namespace = mod.namespacePtr(decl.src_namespace);
   1216     const target = namespace.file_scope.mod.resolved_target.result;
   1217     var code_gen: CodeGen = .{
   1218         .gpa = gpa,
   1219         .air = air,
   1220         .liveness = liveness,
   1221         .code = code,
   1222         .decl_index = func.owner_decl,
   1223         .decl = decl,
   1224         .err_msg = undefined,
   1225         .locals = .{},
   1226         .target = target,
   1227         .bin_file = bin_file.cast(link.File.Wasm).?,
   1228         .debug_output = debug_output,
   1229         .func_index = func_index,
   1230     };
   1231     defer code_gen.deinit();
   1232 
   1233     genFunc(&code_gen) catch |err| switch (err) {
   1234         error.CodegenFail => return codegen.Result{ .fail = code_gen.err_msg },
   1235         else => |e| return e,
   1236     };
   1237 
   1238     return codegen.Result.ok;
   1239 }
   1240 
   1241 fn genFunc(func: *CodeGen) InnerError!void {
   1242     const mod = func.bin_file.base.comp.module.?;
   1243     const ip = &mod.intern_pool;
   1244     const fn_info = mod.typeToFunc(func.decl.typeOf(mod)).?;
   1245     var func_type = try genFunctype(func.gpa, fn_info.cc, fn_info.param_types.get(ip), Type.fromInterned(fn_info.return_type), mod);
   1246     defer func_type.deinit(func.gpa);
   1247     _ = try func.bin_file.storeDeclType(func.decl_index, func_type);
   1248 
   1249     var cc_result = try func.resolveCallingConventionValues(func.decl.typeOf(mod));
   1250     defer cc_result.deinit(func.gpa);
   1251 
   1252     func.args = cc_result.args;
   1253     func.return_value = cc_result.return_value;
   1254 
   1255     try func.addTag(.dbg_prologue_end);
   1256 
   1257     try func.branches.append(func.gpa, .{});
   1258     // clean up outer branch
   1259     defer {
   1260         var outer_branch = func.branches.pop();
   1261         outer_branch.deinit(func.gpa);
   1262         assert(func.branches.items.len == 0); // missing branch merge
   1263     }
   1264     // Generate MIR for function body
   1265     try func.genBody(func.air.getMainBody());
   1266 
   1267     // In case we have a return value, but the last instruction is a noreturn (such as a while loop)
   1268     // we emit an unreachable instruction to tell the stack validator that part will never be reached.
   1269     if (func_type.returns.len != 0 and func.air.instructions.len > 0) {
   1270         const inst: Air.Inst.Index = @enumFromInt(func.air.instructions.len - 1);
   1271         const last_inst_ty = func.typeOfIndex(inst);
   1272         if (!last_inst_ty.hasRuntimeBitsIgnoreComptime(mod) or last_inst_ty.isNoReturn(mod)) {
   1273             try func.addTag(.@"unreachable");
   1274         }
   1275     }
   1276     // End of function body
   1277     try func.addTag(.end);
   1278 
   1279     try func.addTag(.dbg_epilogue_begin);
   1280 
   1281     // check if we have to initialize and allocate anything into the stack frame.
   1282     // If so, create enough stack space and insert the instructions at the front of the list.
   1283     if (func.initial_stack_value != .none) {
   1284         var prologue = std.ArrayList(Mir.Inst).init(func.gpa);
   1285         defer prologue.deinit();
   1286 
   1287         const sp = @intFromEnum(func.bin_file.zigObjectPtr().?.stack_pointer_sym);
   1288         // load stack pointer
   1289         try prologue.append(.{ .tag = .global_get, .data = .{ .label = sp } });
   1290         // store stack pointer so we can restore it when we return from the function
   1291         try prologue.append(.{ .tag = .local_tee, .data = .{ .label = func.initial_stack_value.local.value } });
   1292         // get the total stack size
   1293         const aligned_stack = func.stack_alignment.forward(func.stack_size);
   1294         try prologue.append(.{ .tag = .i32_const, .data = .{ .imm32 = @intCast(aligned_stack) } });
   1295         // subtract it from the current stack pointer
   1296         try prologue.append(.{ .tag = .i32_sub, .data = .{ .tag = {} } });
   1297         // Get negative stack aligment
   1298         try prologue.append(.{ .tag = .i32_const, .data = .{ .imm32 = @as(i32, @intCast(func.stack_alignment.toByteUnits().?)) * -1 } });
   1299         // Bitwise-and the value to get the new stack pointer to ensure the pointers are aligned with the abi alignment
   1300         try prologue.append(.{ .tag = .i32_and, .data = .{ .tag = {} } });
   1301         // store the current stack pointer as the bottom, which will be used to calculate all stack pointer offsets
   1302         try prologue.append(.{ .tag = .local_tee, .data = .{ .label = func.bottom_stack_value.local.value } });
   1303         // Store the current stack pointer value into the global stack pointer so other function calls will
   1304         // start from this value instead and not overwrite the current stack.
   1305         try prologue.append(.{ .tag = .global_set, .data = .{ .label = sp } });
   1306 
   1307         // reserve space and insert all prologue instructions at the front of the instruction list
   1308         // We insert them in reserve order as there is no insertSlice in multiArrayList.
   1309         try func.mir_instructions.ensureUnusedCapacity(func.gpa, prologue.items.len);
   1310         for (prologue.items, 0..) |_, index| {
   1311             const inst = prologue.items[prologue.items.len - 1 - index];
   1312             func.mir_instructions.insertAssumeCapacity(0, inst);
   1313         }
   1314     }
   1315 
   1316     var mir: Mir = .{
   1317         .instructions = func.mir_instructions.toOwnedSlice(),
   1318         .extra = try func.mir_extra.toOwnedSlice(func.gpa),
   1319     };
   1320     defer mir.deinit(func.gpa);
   1321 
   1322     var emit: Emit = .{
   1323         .mir = mir,
   1324         .bin_file = func.bin_file,
   1325         .code = func.code,
   1326         .locals = func.locals.items,
   1327         .decl_index = func.decl_index,
   1328         .dbg_output = func.debug_output,
   1329         .prev_di_line = 0,
   1330         .prev_di_column = 0,
   1331         .prev_di_offset = 0,
   1332     };
   1333 
   1334     emit.emitMir() catch |err| switch (err) {
   1335         error.EmitFail => {
   1336             func.err_msg = emit.error_msg.?;
   1337             return error.CodegenFail;
   1338         },
   1339         else => |e| return e,
   1340     };
   1341 }
   1342 
   1343 const CallWValues = struct {
   1344     args: []WValue,
   1345     return_value: WValue,
   1346 
   1347     fn deinit(values: *CallWValues, gpa: Allocator) void {
   1348         gpa.free(values.args);
   1349         values.* = undefined;
   1350     }
   1351 };
   1352 
   1353 fn resolveCallingConventionValues(func: *CodeGen, fn_ty: Type) InnerError!CallWValues {
   1354     const mod = func.bin_file.base.comp.module.?;
   1355     const ip = &mod.intern_pool;
   1356     const fn_info = mod.typeToFunc(fn_ty).?;
   1357     const cc = fn_info.cc;
   1358     var result: CallWValues = .{
   1359         .args = &.{},
   1360         .return_value = .none,
   1361     };
   1362     if (cc == .Naked) return result;
   1363 
   1364     var args = std.ArrayList(WValue).init(func.gpa);
   1365     defer args.deinit();
   1366 
   1367     // Check if we store the result as a pointer to the stack rather than
   1368     // by value
   1369     if (firstParamSRet(fn_info.cc, Type.fromInterned(fn_info.return_type), mod)) {
   1370         // the sret arg will be passed as first argument, therefore we
   1371         // set the `return_value` before allocating locals for regular args.
   1372         result.return_value = .{ .local = .{ .value = func.local_index, .references = 1 } };
   1373         func.local_index += 1;
   1374     }
   1375 
   1376     switch (cc) {
   1377         .Unspecified => {
   1378             for (fn_info.param_types.get(ip)) |ty| {
   1379                 if (!Type.fromInterned(ty).hasRuntimeBitsIgnoreComptime(mod)) {
   1380                     continue;
   1381                 }
   1382 
   1383                 try args.append(.{ .local = .{ .value = func.local_index, .references = 1 } });
   1384                 func.local_index += 1;
   1385             }
   1386         },
   1387         .C => {
   1388             for (fn_info.param_types.get(ip)) |ty| {
   1389                 const ty_classes = abi.classifyType(Type.fromInterned(ty), mod);
   1390                 for (ty_classes) |class| {
   1391                     if (class == .none) continue;
   1392                     try args.append(.{ .local = .{ .value = func.local_index, .references = 1 } });
   1393                     func.local_index += 1;
   1394                 }
   1395             }
   1396         },
   1397         else => return func.fail("calling convention '{s}' not supported for Wasm", .{@tagName(cc)}),
   1398     }
   1399     result.args = try args.toOwnedSlice();
   1400     return result;
   1401 }
   1402 
   1403 fn firstParamSRet(cc: std.builtin.CallingConvention, return_type: Type, mod: *Module) bool {
   1404     switch (cc) {
   1405         .Unspecified, .Inline => return isByRef(return_type, mod),
   1406         .C => {
   1407             const ty_classes = abi.classifyType(return_type, mod);
   1408             if (ty_classes[0] == .indirect) return true;
   1409             if (ty_classes[0] == .direct and ty_classes[1] == .direct) return true;
   1410             return false;
   1411         },
   1412         else => return false,
   1413     }
   1414 }
   1415 
   1416 /// Lowers a Zig type and its value based on a given calling convention to ensure
   1417 /// it matches the ABI.
   1418 fn lowerArg(func: *CodeGen, cc: std.builtin.CallingConvention, ty: Type, value: WValue) !void {
   1419     if (cc != .C) {
   1420         return func.lowerToStack(value);
   1421     }
   1422 
   1423     const mod = func.bin_file.base.comp.module.?;
   1424     const ty_classes = abi.classifyType(ty, mod);
   1425     assert(ty_classes[0] != .none);
   1426     switch (ty.zigTypeTag(mod)) {
   1427         .Struct, .Union => {
   1428             if (ty_classes[0] == .indirect) {
   1429                 return func.lowerToStack(value);
   1430             }
   1431             assert(ty_classes[0] == .direct);
   1432             const scalar_type = abi.scalarType(ty, mod);
   1433             switch (value) {
   1434                 .memory,
   1435                 .memory_offset,
   1436                 .stack_offset,
   1437                 => _ = try func.load(value, scalar_type, 0),
   1438                 .dead => unreachable,
   1439                 else => try func.emitWValue(value),
   1440             }
   1441         },
   1442         .Int, .Float => {
   1443             if (ty_classes[1] == .none) {
   1444                 return func.lowerToStack(value);
   1445             }
   1446             assert(ty_classes[0] == .direct and ty_classes[1] == .direct);
   1447             assert(ty.abiSize(mod) == 16);
   1448             // in this case we have an integer or float that must be lowered as 2 i64's.
   1449             try func.emitWValue(value);
   1450             try func.addMemArg(.i64_load, .{ .offset = value.offset(), .alignment = 8 });
   1451             try func.emitWValue(value);
   1452             try func.addMemArg(.i64_load, .{ .offset = value.offset() + 8, .alignment = 8 });
   1453         },
   1454         else => return func.lowerToStack(value),
   1455     }
   1456 }
   1457 
   1458 /// Lowers a `WValue` to the stack. This means when the `value` results in
   1459 /// `.stack_offset` we calculate the pointer of this offset and use that.
   1460 /// The value is left on the stack, and not stored in any temporary.
   1461 fn lowerToStack(func: *CodeGen, value: WValue) !void {
   1462     switch (value) {
   1463         .stack_offset => |offset| {
   1464             try func.emitWValue(value);
   1465             if (offset.value > 0) {
   1466                 switch (func.arch()) {
   1467                     .wasm32 => {
   1468                         try func.addImm32(@as(i32, @bitCast(offset.value)));
   1469                         try func.addTag(.i32_add);
   1470                     },
   1471                     .wasm64 => {
   1472                         try func.addImm64(offset.value);
   1473                         try func.addTag(.i64_add);
   1474                     },
   1475                     else => unreachable,
   1476                 }
   1477             }
   1478         },
   1479         else => try func.emitWValue(value),
   1480     }
   1481 }
   1482 
   1483 /// Creates a local for the initial stack value
   1484 /// Asserts `initial_stack_value` is `.none`
   1485 fn initializeStack(func: *CodeGen) !void {
   1486     assert(func.initial_stack_value == .none);
   1487     // Reserve a local to store the current stack pointer
   1488     // We can later use this local to set the stack pointer back to the value
   1489     // we have stored here.
   1490     func.initial_stack_value = try func.ensureAllocLocal(Type.usize);
   1491     // Also reserve a local to store the bottom stack value
   1492     func.bottom_stack_value = try func.ensureAllocLocal(Type.usize);
   1493 }
   1494 
   1495 /// Reads the stack pointer from `Context.initial_stack_value` and writes it
   1496 /// to the global stack pointer variable
   1497 fn restoreStackPointer(func: *CodeGen) !void {
   1498     // only restore the pointer if it was initialized
   1499     if (func.initial_stack_value == .none) return;
   1500     // Get the original stack pointer's value
   1501     try func.emitWValue(func.initial_stack_value);
   1502 
   1503     // save its value in the global stack pointer
   1504     try func.addLabel(.global_set, @intFromEnum(func.bin_file.zigObjectPtr().?.stack_pointer_sym));
   1505 }
   1506 
   1507 /// From a given type, will create space on the virtual stack to store the value of such type.
   1508 /// This returns a `WValue` with its active tag set to `local`, containing the index to the local
   1509 /// that points to the position on the virtual stack. This function should be used instead of
   1510 /// moveStack unless a local was already created to store the pointer.
   1511 ///
   1512 /// Asserts Type has codegenbits
   1513 fn allocStack(func: *CodeGen, ty: Type) !WValue {
   1514     const mod = func.bin_file.base.comp.module.?;
   1515     assert(ty.hasRuntimeBitsIgnoreComptime(mod));
   1516     if (func.initial_stack_value == .none) {
   1517         try func.initializeStack();
   1518     }
   1519 
   1520     const abi_size = std.math.cast(u32, ty.abiSize(mod)) orelse {
   1521         return func.fail("Type {} with ABI size of {d} exceeds stack frame size", .{
   1522             ty.fmt(mod), ty.abiSize(mod),
   1523         });
   1524     };
   1525     const abi_align = ty.abiAlignment(mod);
   1526 
   1527     func.stack_alignment = func.stack_alignment.max(abi_align);
   1528 
   1529     const offset: u32 = @intCast(abi_align.forward(func.stack_size));
   1530     defer func.stack_size = offset + abi_size;
   1531 
   1532     return WValue{ .stack_offset = .{ .value = offset, .references = 1 } };
   1533 }
   1534 
   1535 /// From a given AIR instruction generates a pointer to the stack where
   1536 /// the value of its type will live.
   1537 /// This is different from allocStack where this will use the pointer's alignment
   1538 /// if it is set, to ensure the stack alignment will be set correctly.
   1539 fn allocStackPtr(func: *CodeGen, inst: Air.Inst.Index) !WValue {
   1540     const mod = func.bin_file.base.comp.module.?;
   1541     const ptr_ty = func.typeOfIndex(inst);
   1542     const pointee_ty = ptr_ty.childType(mod);
   1543 
   1544     if (func.initial_stack_value == .none) {
   1545         try func.initializeStack();
   1546     }
   1547 
   1548     if (!pointee_ty.hasRuntimeBitsIgnoreComptime(mod)) {
   1549         return func.allocStack(Type.usize); // create a value containing just the stack pointer.
   1550     }
   1551 
   1552     const abi_alignment = ptr_ty.ptrAlignment(mod);
   1553     const abi_size = std.math.cast(u32, pointee_ty.abiSize(mod)) orelse {
   1554         return func.fail("Type {} with ABI size of {d} exceeds stack frame size", .{
   1555             pointee_ty.fmt(mod), pointee_ty.abiSize(mod),
   1556         });
   1557     };
   1558     func.stack_alignment = func.stack_alignment.max(abi_alignment);
   1559 
   1560     const offset: u32 = @intCast(abi_alignment.forward(func.stack_size));
   1561     defer func.stack_size = offset + abi_size;
   1562 
   1563     return WValue{ .stack_offset = .{ .value = offset, .references = 1 } };
   1564 }
   1565 
   1566 /// From given zig bitsize, returns the wasm bitsize
   1567 fn toWasmBits(bits: u16) ?u16 {
   1568     return for ([_]u16{ 32, 64, 128 }) |wasm_bits| {
   1569         if (bits <= wasm_bits) return wasm_bits;
   1570     } else null;
   1571 }
   1572 
   1573 /// Performs a copy of bytes for a given type. Copying all bytes
   1574 /// from rhs to lhs.
   1575 fn memcpy(func: *CodeGen, dst: WValue, src: WValue, len: WValue) !void {
   1576     // When bulk_memory is enabled, we lower it to wasm's memcpy instruction.
   1577     // If not, we lower it ourselves manually
   1578     if (std.Target.wasm.featureSetHas(func.target.cpu.features, .bulk_memory)) {
   1579         try func.lowerToStack(dst);
   1580         try func.lowerToStack(src);
   1581         try func.emitWValue(len);
   1582         try func.addExtended(.memory_copy);
   1583         return;
   1584     }
   1585 
   1586     // when the length is comptime-known, rather than a runtime value, we can optimize the generated code by having
   1587     // the loop during codegen, rather than inserting a runtime loop into the binary.
   1588     switch (len) {
   1589         .imm32, .imm64 => blk: {
   1590             const length = switch (len) {
   1591                 .imm32 => |val| val,
   1592                 .imm64 => |val| val,
   1593                 else => unreachable,
   1594             };
   1595             // if the size (length) is more than 32 bytes, we use a runtime loop instead to prevent
   1596             // binary size bloat.
   1597             if (length > 32) break :blk;
   1598             var offset: u32 = 0;
   1599             const lhs_base = dst.offset();
   1600             const rhs_base = src.offset();
   1601             while (offset < length) : (offset += 1) {
   1602                 // get dst's address to store the result
   1603                 try func.emitWValue(dst);
   1604                 // load byte from src's address
   1605                 try func.emitWValue(src);
   1606                 switch (func.arch()) {
   1607                     .wasm32 => {
   1608                         try func.addMemArg(.i32_load8_u, .{ .offset = rhs_base + offset, .alignment = 1 });
   1609                         try func.addMemArg(.i32_store8, .{ .offset = lhs_base + offset, .alignment = 1 });
   1610                     },
   1611                     .wasm64 => {
   1612                         try func.addMemArg(.i64_load8_u, .{ .offset = rhs_base + offset, .alignment = 1 });
   1613                         try func.addMemArg(.i64_store8, .{ .offset = lhs_base + offset, .alignment = 1 });
   1614                     },
   1615                     else => unreachable,
   1616                 }
   1617             }
   1618             return;
   1619         },
   1620         else => {},
   1621     }
   1622 
   1623     // allocate a local for the offset, and set it to 0.
   1624     // This to ensure that inside loops we correctly re-set the counter.
   1625     var offset = try func.allocLocal(Type.usize); // local for counter
   1626     defer offset.free(func);
   1627     switch (func.arch()) {
   1628         .wasm32 => try func.addImm32(0),
   1629         .wasm64 => try func.addImm64(0),
   1630         else => unreachable,
   1631     }
   1632     try func.addLabel(.local_set, offset.local.value);
   1633 
   1634     // outer block to jump to when loop is done
   1635     try func.startBlock(.block, wasm.block_empty);
   1636     try func.startBlock(.loop, wasm.block_empty);
   1637 
   1638     // loop condition (offset == length -> break)
   1639     {
   1640         try func.emitWValue(offset);
   1641         try func.emitWValue(len);
   1642         switch (func.arch()) {
   1643             .wasm32 => try func.addTag(.i32_eq),
   1644             .wasm64 => try func.addTag(.i64_eq),
   1645             else => unreachable,
   1646         }
   1647         try func.addLabel(.br_if, 1); // jump out of loop into outer block (finished)
   1648     }
   1649 
   1650     // get dst ptr
   1651     {
   1652         try func.emitWValue(dst);
   1653         try func.emitWValue(offset);
   1654         switch (func.arch()) {
   1655             .wasm32 => try func.addTag(.i32_add),
   1656             .wasm64 => try func.addTag(.i64_add),
   1657             else => unreachable,
   1658         }
   1659     }
   1660 
   1661     // get src value and also store in dst
   1662     {
   1663         try func.emitWValue(src);
   1664         try func.emitWValue(offset);
   1665         switch (func.arch()) {
   1666             .wasm32 => {
   1667                 try func.addTag(.i32_add);
   1668                 try func.addMemArg(.i32_load8_u, .{ .offset = src.offset(), .alignment = 1 });
   1669                 try func.addMemArg(.i32_store8, .{ .offset = dst.offset(), .alignment = 1 });
   1670             },
   1671             .wasm64 => {
   1672                 try func.addTag(.i64_add);
   1673                 try func.addMemArg(.i64_load8_u, .{ .offset = src.offset(), .alignment = 1 });
   1674                 try func.addMemArg(.i64_store8, .{ .offset = dst.offset(), .alignment = 1 });
   1675             },
   1676             else => unreachable,
   1677         }
   1678     }
   1679 
   1680     // increment loop counter
   1681     {
   1682         try func.emitWValue(offset);
   1683         switch (func.arch()) {
   1684             .wasm32 => {
   1685                 try func.addImm32(1);
   1686                 try func.addTag(.i32_add);
   1687             },
   1688             .wasm64 => {
   1689                 try func.addImm64(1);
   1690                 try func.addTag(.i64_add);
   1691             },
   1692             else => unreachable,
   1693         }
   1694         try func.addLabel(.local_set, offset.local.value);
   1695         try func.addLabel(.br, 0); // jump to start of loop
   1696     }
   1697     try func.endBlock(); // close off loop block
   1698     try func.endBlock(); // close off outer block
   1699 }
   1700 
   1701 fn ptrSize(func: *const CodeGen) u16 {
   1702     return @divExact(func.target.ptrBitWidth(), 8);
   1703 }
   1704 
   1705 fn arch(func: *const CodeGen) std.Target.Cpu.Arch {
   1706     return func.target.cpu.arch;
   1707 }
   1708 
   1709 /// For a given `Type`, will return true when the type will be passed
   1710 /// by reference, rather than by value
   1711 fn isByRef(ty: Type, mod: *Module) bool {
   1712     const ip = &mod.intern_pool;
   1713     const target = mod.getTarget();
   1714     switch (ty.zigTypeTag(mod)) {
   1715         .Type,
   1716         .ComptimeInt,
   1717         .ComptimeFloat,
   1718         .EnumLiteral,
   1719         .Undefined,
   1720         .Null,
   1721         .Opaque,
   1722         => unreachable,
   1723 
   1724         .NoReturn,
   1725         .Void,
   1726         .Bool,
   1727         .ErrorSet,
   1728         .Fn,
   1729         .Enum,
   1730         .AnyFrame,
   1731         => return false,
   1732 
   1733         .Array,
   1734         .Frame,
   1735         => return ty.hasRuntimeBitsIgnoreComptime(mod),
   1736         .Union => {
   1737             if (mod.typeToUnion(ty)) |union_obj| {
   1738                 if (union_obj.getLayout(ip) == .@"packed") {
   1739                     return ty.abiSize(mod) > 8;
   1740                 }
   1741             }
   1742             return ty.hasRuntimeBitsIgnoreComptime(mod);
   1743         },
   1744         .Struct => {
   1745             if (mod.typeToPackedStruct(ty)) |packed_struct| {
   1746                 return isByRef(Type.fromInterned(packed_struct.backingIntType(ip).*), mod);
   1747             }
   1748             return ty.hasRuntimeBitsIgnoreComptime(mod);
   1749         },
   1750         .Vector => return determineSimdStoreStrategy(ty, mod) == .unrolled,
   1751         .Int => return ty.intInfo(mod).bits > 64,
   1752         .Float => return ty.floatBits(target) > 64,
   1753         .ErrorUnion => {
   1754             const pl_ty = ty.errorUnionPayload(mod);
   1755             if (!pl_ty.hasRuntimeBitsIgnoreComptime(mod)) {
   1756                 return false;
   1757             }
   1758             return true;
   1759         },
   1760         .Optional => {
   1761             if (ty.isPtrLikeOptional(mod)) return false;
   1762             const pl_type = ty.optionalChild(mod);
   1763             if (pl_type.zigTypeTag(mod) == .ErrorSet) return false;
   1764             return pl_type.hasRuntimeBitsIgnoreComptime(mod);
   1765         },
   1766         .Pointer => {
   1767             // Slices act like struct and will be passed by reference
   1768             if (ty.isSlice(mod)) return true;
   1769             return false;
   1770         },
   1771     }
   1772 }
   1773 
   1774 const SimdStoreStrategy = enum {
   1775     direct,
   1776     unrolled,
   1777 };
   1778 
   1779 /// For a given vector type, returns the `SimdStoreStrategy`.
   1780 /// This means when a given type is 128 bits and either the simd128 or relaxed-simd
   1781 /// features are enabled, the function will return `.direct`. This would allow to store
   1782 /// it using a instruction, rather than an unrolled version.
   1783 fn determineSimdStoreStrategy(ty: Type, mod: *Module) SimdStoreStrategy {
   1784     std.debug.assert(ty.zigTypeTag(mod) == .Vector);
   1785     if (ty.bitSize(mod) != 128) return .unrolled;
   1786     const hasFeature = std.Target.wasm.featureSetHas;
   1787     const target = mod.getTarget();
   1788     const features = target.cpu.features;
   1789     if (hasFeature(features, .relaxed_simd) or hasFeature(features, .simd128)) {
   1790         return .direct;
   1791     }
   1792     return .unrolled;
   1793 }
   1794 
   1795 /// Creates a new local for a pointer that points to memory with given offset.
   1796 /// This can be used to get a pointer to a struct field, error payload, etc.
   1797 /// By providing `modify` as action, it will modify the given `ptr_value` instead of making a new
   1798 /// local value to store the pointer. This allows for local re-use and improves binary size.
   1799 fn buildPointerOffset(func: *CodeGen, ptr_value: WValue, offset: u64, action: enum { modify, new }) InnerError!WValue {
   1800     // do not perform arithmetic when offset is 0.
   1801     if (offset == 0 and ptr_value.offset() == 0 and action == .modify) return ptr_value;
   1802     const result_ptr: WValue = switch (action) {
   1803         .new => try func.ensureAllocLocal(Type.usize),
   1804         .modify => ptr_value,
   1805     };
   1806     try func.emitWValue(ptr_value);
   1807     if (offset + ptr_value.offset() > 0) {
   1808         switch (func.arch()) {
   1809             .wasm32 => {
   1810                 try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(offset + ptr_value.offset())))));
   1811                 try func.addTag(.i32_add);
   1812             },
   1813             .wasm64 => {
   1814                 try func.addImm64(offset + ptr_value.offset());
   1815                 try func.addTag(.i64_add);
   1816             },
   1817             else => unreachable,
   1818         }
   1819     }
   1820     try func.addLabel(.local_set, result_ptr.local.value);
   1821     return result_ptr;
   1822 }
   1823 
   1824 fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   1825     const air_tags = func.air.instructions.items(.tag);
   1826     return switch (air_tags[@intFromEnum(inst)]) {
   1827         .inferred_alloc, .inferred_alloc_comptime => unreachable,
   1828 
   1829         .add => func.airBinOp(inst, .add),
   1830         .add_sat => func.airSatBinOp(inst, .add),
   1831         .add_wrap => func.airWrapBinOp(inst, .add),
   1832         .sub => func.airBinOp(inst, .sub),
   1833         .sub_sat => func.airSatBinOp(inst, .sub),
   1834         .sub_wrap => func.airWrapBinOp(inst, .sub),
   1835         .mul => func.airBinOp(inst, .mul),
   1836         .mul_wrap => func.airWrapBinOp(inst, .mul),
   1837         .div_float, .div_exact => func.airDiv(inst),
   1838         .div_trunc => func.airDivTrunc(inst),
   1839         .div_floor => func.airDivFloor(inst),
   1840         .bit_and => func.airBinOp(inst, .@"and"),
   1841         .bit_or => func.airBinOp(inst, .@"or"),
   1842         .bool_and => func.airBinOp(inst, .@"and"),
   1843         .bool_or => func.airBinOp(inst, .@"or"),
   1844         .rem => func.airBinOp(inst, .rem),
   1845         .mod => func.airMod(inst),
   1846         .shl => func.airWrapBinOp(inst, .shl),
   1847         .shl_exact => func.airBinOp(inst, .shl),
   1848         .shl_sat => func.airShlSat(inst),
   1849         .shr, .shr_exact => func.airBinOp(inst, .shr),
   1850         .xor => func.airBinOp(inst, .xor),
   1851         .max => func.airMaxMin(inst, .max),
   1852         .min => func.airMaxMin(inst, .min),
   1853         .mul_add => func.airMulAdd(inst),
   1854 
   1855         .sqrt => func.airUnaryFloatOp(inst, .sqrt),
   1856         .sin => func.airUnaryFloatOp(inst, .sin),
   1857         .cos => func.airUnaryFloatOp(inst, .cos),
   1858         .tan => func.airUnaryFloatOp(inst, .tan),
   1859         .exp => func.airUnaryFloatOp(inst, .exp),
   1860         .exp2 => func.airUnaryFloatOp(inst, .exp2),
   1861         .log => func.airUnaryFloatOp(inst, .log),
   1862         .log2 => func.airUnaryFloatOp(inst, .log2),
   1863         .log10 => func.airUnaryFloatOp(inst, .log10),
   1864         .floor => func.airUnaryFloatOp(inst, .floor),
   1865         .ceil => func.airUnaryFloatOp(inst, .ceil),
   1866         .round => func.airUnaryFloatOp(inst, .round),
   1867         .trunc_float => func.airUnaryFloatOp(inst, .trunc),
   1868         .neg => func.airUnaryFloatOp(inst, .neg),
   1869 
   1870         .abs => func.airAbs(inst),
   1871 
   1872         .add_with_overflow => func.airAddSubWithOverflow(inst, .add),
   1873         .sub_with_overflow => func.airAddSubWithOverflow(inst, .sub),
   1874         .shl_with_overflow => func.airShlWithOverflow(inst),
   1875         .mul_with_overflow => func.airMulWithOverflow(inst),
   1876 
   1877         .clz => func.airClz(inst),
   1878         .ctz => func.airCtz(inst),
   1879 
   1880         .cmp_eq => func.airCmp(inst, .eq),
   1881         .cmp_gte => func.airCmp(inst, .gte),
   1882         .cmp_gt => func.airCmp(inst, .gt),
   1883         .cmp_lte => func.airCmp(inst, .lte),
   1884         .cmp_lt => func.airCmp(inst, .lt),
   1885         .cmp_neq => func.airCmp(inst, .neq),
   1886 
   1887         .cmp_vector => func.airCmpVector(inst),
   1888         .cmp_lt_errors_len => func.airCmpLtErrorsLen(inst),
   1889 
   1890         .array_elem_val => func.airArrayElemVal(inst),
   1891         .array_to_slice => func.airArrayToSlice(inst),
   1892         .alloc => func.airAlloc(inst),
   1893         .arg => func.airArg(inst),
   1894         .bitcast => func.airBitcast(inst),
   1895         .block => func.airBlock(inst),
   1896         .trap => func.airTrap(inst),
   1897         .breakpoint => func.airBreakpoint(inst),
   1898         .br => func.airBr(inst),
   1899         .int_from_bool => func.airIntFromBool(inst),
   1900         .cond_br => func.airCondBr(inst),
   1901         .intcast => func.airIntcast(inst),
   1902         .fptrunc => func.airFptrunc(inst),
   1903         .fpext => func.airFpext(inst),
   1904         .int_from_float => func.airIntFromFloat(inst),
   1905         .float_from_int => func.airFloatFromInt(inst),
   1906         .get_union_tag => func.airGetUnionTag(inst),
   1907 
   1908         .@"try" => func.airTry(inst),
   1909         .try_ptr => func.airTryPtr(inst),
   1910 
   1911         .dbg_stmt => func.airDbgStmt(inst),
   1912         .dbg_inline_block => func.airDbgInlineBlock(inst),
   1913         .dbg_var_ptr => func.airDbgVar(inst, true),
   1914         .dbg_var_val => func.airDbgVar(inst, false),
   1915 
   1916         .call => func.airCall(inst, .auto),
   1917         .call_always_tail => func.airCall(inst, .always_tail),
   1918         .call_never_tail => func.airCall(inst, .never_tail),
   1919         .call_never_inline => func.airCall(inst, .never_inline),
   1920 
   1921         .is_err => func.airIsErr(inst, .i32_ne),
   1922         .is_non_err => func.airIsErr(inst, .i32_eq),
   1923 
   1924         .is_null => func.airIsNull(inst, .i32_eq, .value),
   1925         .is_non_null => func.airIsNull(inst, .i32_ne, .value),
   1926         .is_null_ptr => func.airIsNull(inst, .i32_eq, .ptr),
   1927         .is_non_null_ptr => func.airIsNull(inst, .i32_ne, .ptr),
   1928 
   1929         .load => func.airLoad(inst),
   1930         .loop => func.airLoop(inst),
   1931         .memset => func.airMemset(inst, false),
   1932         .memset_safe => func.airMemset(inst, true),
   1933         .not => func.airNot(inst),
   1934         .optional_payload => func.airOptionalPayload(inst),
   1935         .optional_payload_ptr => func.airOptionalPayloadPtr(inst),
   1936         .optional_payload_ptr_set => func.airOptionalPayloadPtrSet(inst),
   1937         .ptr_add => func.airPtrBinOp(inst, .add),
   1938         .ptr_sub => func.airPtrBinOp(inst, .sub),
   1939         .ptr_elem_ptr => func.airPtrElemPtr(inst),
   1940         .ptr_elem_val => func.airPtrElemVal(inst),
   1941         .int_from_ptr => func.airIntFromPtr(inst),
   1942         .ret => func.airRet(inst),
   1943         .ret_safe => func.airRet(inst), // TODO
   1944         .ret_ptr => func.airRetPtr(inst),
   1945         .ret_load => func.airRetLoad(inst),
   1946         .splat => func.airSplat(inst),
   1947         .select => func.airSelect(inst),
   1948         .shuffle => func.airShuffle(inst),
   1949         .reduce => func.airReduce(inst),
   1950         .aggregate_init => func.airAggregateInit(inst),
   1951         .union_init => func.airUnionInit(inst),
   1952         .prefetch => func.airPrefetch(inst),
   1953         .popcount => func.airPopcount(inst),
   1954         .byte_swap => func.airByteSwap(inst),
   1955 
   1956         .slice => func.airSlice(inst),
   1957         .slice_len => func.airSliceLen(inst),
   1958         .slice_elem_val => func.airSliceElemVal(inst),
   1959         .slice_elem_ptr => func.airSliceElemPtr(inst),
   1960         .slice_ptr => func.airSlicePtr(inst),
   1961         .ptr_slice_len_ptr => func.airPtrSliceFieldPtr(inst, func.ptrSize()),
   1962         .ptr_slice_ptr_ptr => func.airPtrSliceFieldPtr(inst, 0),
   1963         .store => func.airStore(inst, false),
   1964         .store_safe => func.airStore(inst, true),
   1965 
   1966         .set_union_tag => func.airSetUnionTag(inst),
   1967         .struct_field_ptr => func.airStructFieldPtr(inst),
   1968         .struct_field_ptr_index_0 => func.airStructFieldPtrIndex(inst, 0),
   1969         .struct_field_ptr_index_1 => func.airStructFieldPtrIndex(inst, 1),
   1970         .struct_field_ptr_index_2 => func.airStructFieldPtrIndex(inst, 2),
   1971         .struct_field_ptr_index_3 => func.airStructFieldPtrIndex(inst, 3),
   1972         .struct_field_val => func.airStructFieldVal(inst),
   1973         .field_parent_ptr => func.airFieldParentPtr(inst),
   1974 
   1975         .switch_br => func.airSwitchBr(inst),
   1976         .trunc => func.airTrunc(inst),
   1977         .unreach => func.airUnreachable(inst),
   1978 
   1979         .wrap_optional => func.airWrapOptional(inst),
   1980         .unwrap_errunion_payload => func.airUnwrapErrUnionPayload(inst, false),
   1981         .unwrap_errunion_payload_ptr => func.airUnwrapErrUnionPayload(inst, true),
   1982         .unwrap_errunion_err => func.airUnwrapErrUnionError(inst, false),
   1983         .unwrap_errunion_err_ptr => func.airUnwrapErrUnionError(inst, true),
   1984         .wrap_errunion_payload => func.airWrapErrUnionPayload(inst),
   1985         .wrap_errunion_err => func.airWrapErrUnionErr(inst),
   1986         .errunion_payload_ptr_set => func.airErrUnionPayloadPtrSet(inst),
   1987         .error_name => func.airErrorName(inst),
   1988 
   1989         .wasm_memory_size => func.airWasmMemorySize(inst),
   1990         .wasm_memory_grow => func.airWasmMemoryGrow(inst),
   1991 
   1992         .memcpy => func.airMemcpy(inst),
   1993 
   1994         .ret_addr => func.airRetAddr(inst),
   1995         .tag_name => func.airTagName(inst),
   1996 
   1997         .error_set_has_value => func.airErrorSetHasValue(inst),
   1998         .frame_addr => func.airFrameAddress(inst),
   1999 
   2000         .mul_sat,
   2001         .assembly,
   2002         .bit_reverse,
   2003         .is_err_ptr,
   2004         .is_non_err_ptr,
   2005 
   2006         .err_return_trace,
   2007         .set_err_return_trace,
   2008         .save_err_return_trace_index,
   2009         .is_named_enum_value,
   2010         .addrspace_cast,
   2011         .vector_store_elem,
   2012         .c_va_arg,
   2013         .c_va_copy,
   2014         .c_va_end,
   2015         .c_va_start,
   2016         => |tag| return func.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),
   2017 
   2018         .atomic_load => func.airAtomicLoad(inst),
   2019         .atomic_store_unordered,
   2020         .atomic_store_monotonic,
   2021         .atomic_store_release,
   2022         .atomic_store_seq_cst,
   2023         // in WebAssembly, all atomic instructions are sequentially ordered.
   2024         => func.airAtomicStore(inst),
   2025         .atomic_rmw => func.airAtomicRmw(inst),
   2026         .cmpxchg_weak => func.airCmpxchg(inst),
   2027         .cmpxchg_strong => func.airCmpxchg(inst),
   2028         .fence => func.airFence(inst),
   2029 
   2030         .add_optimized,
   2031         .sub_optimized,
   2032         .mul_optimized,
   2033         .div_float_optimized,
   2034         .div_trunc_optimized,
   2035         .div_floor_optimized,
   2036         .div_exact_optimized,
   2037         .rem_optimized,
   2038         .mod_optimized,
   2039         .neg_optimized,
   2040         .cmp_lt_optimized,
   2041         .cmp_lte_optimized,
   2042         .cmp_eq_optimized,
   2043         .cmp_gte_optimized,
   2044         .cmp_gt_optimized,
   2045         .cmp_neq_optimized,
   2046         .cmp_vector_optimized,
   2047         .reduce_optimized,
   2048         .int_from_float_optimized,
   2049         => return func.fail("TODO implement optimized float mode", .{}),
   2050 
   2051         .add_safe,
   2052         .sub_safe,
   2053         .mul_safe,
   2054         => return func.fail("TODO implement safety_checked_instructions", .{}),
   2055 
   2056         .work_item_id,
   2057         .work_group_size,
   2058         .work_group_id,
   2059         => unreachable,
   2060     };
   2061 }
   2062 
   2063 fn genBody(func: *CodeGen, body: []const Air.Inst.Index) InnerError!void {
   2064     const mod = func.bin_file.base.comp.module.?;
   2065     const ip = &mod.intern_pool;
   2066 
   2067     for (body) |inst| {
   2068         if (func.liveness.isUnused(inst) and !func.air.mustLower(inst, ip)) {
   2069             continue;
   2070         }
   2071         const old_bookkeeping_value = func.air_bookkeeping;
   2072         try func.currentBranch().values.ensureUnusedCapacity(func.gpa, Liveness.bpi);
   2073         try func.genInst(inst);
   2074 
   2075         if (std.debug.runtime_safety and func.air_bookkeeping < old_bookkeeping_value + 1) {
   2076             std.debug.panic("Missing call to `finishAir` in AIR instruction %{d} ('{}')", .{
   2077                 inst,
   2078                 func.air.instructions.items(.tag)[@intFromEnum(inst)],
   2079             });
   2080         }
   2081     }
   2082 }
   2083 
   2084 fn airRet(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   2085     const mod = func.bin_file.base.comp.module.?;
   2086     const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
   2087     const operand = try func.resolveInst(un_op);
   2088     const fn_info = mod.typeToFunc(func.decl.typeOf(mod)).?;
   2089     const ret_ty = Type.fromInterned(fn_info.return_type);
   2090 
   2091     // result must be stored in the stack and we return a pointer
   2092     // to the stack instead
   2093     if (func.return_value != .none) {
   2094         try func.store(func.return_value, operand, ret_ty, 0);
   2095     } else if (fn_info.cc == .C and ret_ty.hasRuntimeBitsIgnoreComptime(mod)) {
   2096         switch (ret_ty.zigTypeTag(mod)) {
   2097             // Aggregate types can be lowered as a singular value
   2098             .Struct, .Union => {
   2099                 const scalar_type = abi.scalarType(ret_ty, mod);
   2100                 try func.emitWValue(operand);
   2101                 const opcode = buildOpcode(.{
   2102                     .op = .load,
   2103                     .width = @as(u8, @intCast(scalar_type.abiSize(mod) * 8)),
   2104                     .signedness = if (scalar_type.isSignedInt(mod)) .signed else .unsigned,
   2105                     .valtype1 = typeToValtype(scalar_type, mod),
   2106                 });
   2107                 try func.addMemArg(Mir.Inst.Tag.fromOpcode(opcode), .{
   2108                     .offset = operand.offset(),
   2109                     .alignment = @intCast(scalar_type.abiAlignment(mod).toByteUnits().?),
   2110                 });
   2111             },
   2112             else => try func.emitWValue(operand),
   2113         }
   2114     } else {
   2115         if (!ret_ty.hasRuntimeBitsIgnoreComptime(mod) and ret_ty.isError(mod)) {
   2116             try func.addImm32(0);
   2117         } else {
   2118             try func.emitWValue(operand);
   2119         }
   2120     }
   2121     try func.restoreStackPointer();
   2122     try func.addTag(.@"return");
   2123 
   2124     func.finishAir(inst, .none, &.{un_op});
   2125 }
   2126 
   2127 fn airRetPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   2128     const mod = func.bin_file.base.comp.module.?;
   2129     const child_type = func.typeOfIndex(inst).childType(mod);
   2130 
   2131     const result = result: {
   2132         if (!child_type.isFnOrHasRuntimeBitsIgnoreComptime(mod)) {
   2133             break :result try func.allocStack(Type.usize); // create pointer to void
   2134         }
   2135 
   2136         const fn_info = mod.typeToFunc(func.decl.typeOf(mod)).?;
   2137         if (firstParamSRet(fn_info.cc, Type.fromInterned(fn_info.return_type), mod)) {
   2138             break :result func.return_value;
   2139         }
   2140 
   2141         break :result try func.allocStackPtr(inst);
   2142     };
   2143 
   2144     func.finishAir(inst, result, &.{});
   2145 }
   2146 
   2147 fn airRetLoad(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   2148     const mod = func.bin_file.base.comp.module.?;
   2149     const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
   2150     const operand = try func.resolveInst(un_op);
   2151     const ret_ty = func.typeOf(un_op).childType(mod);
   2152 
   2153     const fn_info = mod.typeToFunc(func.decl.typeOf(mod)).?;
   2154     if (!ret_ty.hasRuntimeBitsIgnoreComptime(mod)) {
   2155         if (ret_ty.isError(mod)) {
   2156             try func.addImm32(0);
   2157         }
   2158     } else if (!firstParamSRet(fn_info.cc, Type.fromInterned(fn_info.return_type), mod)) {
   2159         // leave on the stack
   2160         _ = try func.load(operand, ret_ty, 0);
   2161     }
   2162 
   2163     try func.restoreStackPointer();
   2164     try func.addTag(.@"return");
   2165     return func.finishAir(inst, .none, &.{un_op});
   2166 }
   2167 
   2168 fn airCall(func: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) InnerError!void {
   2169     if (modifier == .always_tail) return func.fail("TODO implement tail calls for wasm", .{});
   2170     const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
   2171     const extra = func.air.extraData(Air.Call, pl_op.payload);
   2172     const args = @as([]const Air.Inst.Ref, @ptrCast(func.air.extra[extra.end..][0..extra.data.args_len]));
   2173     const ty = func.typeOf(pl_op.operand);
   2174 
   2175     const mod = func.bin_file.base.comp.module.?;
   2176     const ip = &mod.intern_pool;
   2177     const fn_ty = switch (ty.zigTypeTag(mod)) {
   2178         .Fn => ty,
   2179         .Pointer => ty.childType(mod),
   2180         else => unreachable,
   2181     };
   2182     const ret_ty = fn_ty.fnReturnType(mod);
   2183     const fn_info = mod.typeToFunc(fn_ty).?;
   2184     const first_param_sret = firstParamSRet(fn_info.cc, Type.fromInterned(fn_info.return_type), mod);
   2185 
   2186     const callee: ?InternPool.DeclIndex = blk: {
   2187         const func_val = (try func.air.value(pl_op.operand, mod)) orelse break :blk null;
   2188 
   2189         if (func_val.getFunction(mod)) |function| {
   2190             _ = try func.bin_file.getOrCreateAtomForDecl(function.owner_decl);
   2191             break :blk function.owner_decl;
   2192         } else if (func_val.getExternFunc(mod)) |extern_func| {
   2193             const ext_decl = mod.declPtr(extern_func.decl);
   2194             const ext_info = mod.typeToFunc(ext_decl.typeOf(mod)).?;
   2195             var func_type = try genFunctype(func.gpa, ext_info.cc, ext_info.param_types.get(ip), Type.fromInterned(ext_info.return_type), mod);
   2196             defer func_type.deinit(func.gpa);
   2197             const atom_index = try func.bin_file.getOrCreateAtomForDecl(extern_func.decl);
   2198             const atom = func.bin_file.getAtomPtr(atom_index);
   2199             const type_index = try func.bin_file.storeDeclType(extern_func.decl, func_type);
   2200             try func.bin_file.addOrUpdateImport(
   2201                 ext_decl.name.toSlice(&mod.intern_pool),
   2202                 atom.sym_index,
   2203                 ext_decl.getOwnedExternFunc(mod).?.lib_name.toSlice(&mod.intern_pool),
   2204                 type_index,
   2205             );
   2206             break :blk extern_func.decl;
   2207         } else switch (mod.intern_pool.indexToKey(func_val.ip_index)) {
   2208             .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) {
   2209                 .decl => |decl| {
   2210                     _ = try func.bin_file.getOrCreateAtomForDecl(decl);
   2211                     break :blk decl;
   2212                 },
   2213                 else => {},
   2214             },
   2215             else => {},
   2216         }
   2217         return func.fail("Expected a function, but instead found '{s}'", .{@tagName(ip.indexToKey(func_val.toIntern()))});
   2218     };
   2219 
   2220     const sret = if (first_param_sret) blk: {
   2221         const sret_local = try func.allocStack(ret_ty);
   2222         try func.lowerToStack(sret_local);
   2223         break :blk sret_local;
   2224     } else WValue{ .none = {} };
   2225 
   2226     for (args) |arg| {
   2227         const arg_val = try func.resolveInst(arg);
   2228 
   2229         const arg_ty = func.typeOf(arg);
   2230         if (!arg_ty.hasRuntimeBitsIgnoreComptime(mod)) continue;
   2231 
   2232         try func.lowerArg(mod.typeToFunc(fn_ty).?.cc, arg_ty, arg_val);
   2233     }
   2234 
   2235     if (callee) |direct| {
   2236         const atom_index = func.bin_file.zigObjectPtr().?.decls_map.get(direct).?.atom;
   2237         try func.addLabel(.call, @intFromEnum(func.bin_file.getAtom(atom_index).sym_index));
   2238     } else {
   2239         // in this case we call a function pointer
   2240         // so load its value onto the stack
   2241         std.debug.assert(ty.zigTypeTag(mod) == .Pointer);
   2242         const operand = try func.resolveInst(pl_op.operand);
   2243         try func.emitWValue(operand);
   2244 
   2245         var fn_type = try genFunctype(func.gpa, fn_info.cc, fn_info.param_types.get(ip), Type.fromInterned(fn_info.return_type), mod);
   2246         defer fn_type.deinit(func.gpa);
   2247 
   2248         const fn_type_index = try func.bin_file.zigObjectPtr().?.putOrGetFuncType(func.gpa, fn_type);
   2249         try func.addLabel(.call_indirect, fn_type_index);
   2250     }
   2251 
   2252     const result_value = result_value: {
   2253         if (!ret_ty.hasRuntimeBitsIgnoreComptime(mod) and !ret_ty.isError(mod)) {
   2254             break :result_value WValue{ .none = {} };
   2255         } else if (ret_ty.isNoReturn(mod)) {
   2256             try func.addTag(.@"unreachable");
   2257             break :result_value WValue{ .none = {} };
   2258         } else if (first_param_sret) {
   2259             break :result_value sret;
   2260             // TODO: Make this less fragile and optimize
   2261         } else if (mod.typeToFunc(fn_ty).?.cc == .C and ret_ty.zigTypeTag(mod) == .Struct or ret_ty.zigTypeTag(mod) == .Union) {
   2262             const result_local = try func.allocLocal(ret_ty);
   2263             try func.addLabel(.local_set, result_local.local.value);
   2264             const scalar_type = abi.scalarType(ret_ty, mod);
   2265             const result = try func.allocStack(scalar_type);
   2266             try func.store(result, result_local, scalar_type, 0);
   2267             break :result_value result;
   2268         } else {
   2269             const result_local = try func.allocLocal(ret_ty);
   2270             try func.addLabel(.local_set, result_local.local.value);
   2271             break :result_value result_local;
   2272         }
   2273     };
   2274 
   2275     var bt = try func.iterateBigTomb(inst, 1 + args.len);
   2276     bt.feed(pl_op.operand);
   2277     for (args) |arg| bt.feed(arg);
   2278     return bt.finishAir(result_value);
   2279 }
   2280 
   2281 fn airAlloc(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   2282     const value = try func.allocStackPtr(inst);
   2283     func.finishAir(inst, value, &.{});
   2284 }
   2285 
   2286 fn airStore(func: *CodeGen, inst: Air.Inst.Index, safety: bool) InnerError!void {
   2287     const mod = func.bin_file.base.comp.module.?;
   2288     if (safety) {
   2289         // TODO if the value is undef, write 0xaa bytes to dest
   2290     } else {
   2291         // TODO if the value is undef, don't lower this instruction
   2292     }
   2293     const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
   2294 
   2295     const lhs = try func.resolveInst(bin_op.lhs);
   2296     const rhs = try func.resolveInst(bin_op.rhs);
   2297     const ptr_ty = func.typeOf(bin_op.lhs);
   2298     const ptr_info = ptr_ty.ptrInfo(mod);
   2299     const ty = ptr_ty.childType(mod);
   2300 
   2301     if (ptr_info.packed_offset.host_size == 0) {
   2302         try func.store(lhs, rhs, ty, 0);
   2303     } else {
   2304         // at this point we have a non-natural alignment, we must
   2305         // load the value, and then shift+or the rhs into the result location.
   2306         const int_elem_ty = try mod.intType(.unsigned, ptr_info.packed_offset.host_size * 8);
   2307 
   2308         if (isByRef(int_elem_ty, mod)) {
   2309             return func.fail("TODO: airStore for pointers to bitfields with backing type larger than 64bits", .{});
   2310         }
   2311 
   2312         var mask = @as(u64, @intCast((@as(u65, 1) << @as(u7, @intCast(ty.bitSize(mod)))) - 1));
   2313         mask <<= @as(u6, @intCast(ptr_info.packed_offset.bit_offset));
   2314         mask ^= ~@as(u64, 0);
   2315         const shift_val = if (ptr_info.packed_offset.host_size <= 4)
   2316             WValue{ .imm32 = ptr_info.packed_offset.bit_offset }
   2317         else
   2318             WValue{ .imm64 = ptr_info.packed_offset.bit_offset };
   2319         const mask_val = if (ptr_info.packed_offset.host_size <= 4)
   2320             WValue{ .imm32 = @as(u32, @truncate(mask)) }
   2321         else
   2322             WValue{ .imm64 = mask };
   2323 
   2324         try func.emitWValue(lhs);
   2325         const loaded = try func.load(lhs, int_elem_ty, 0);
   2326         const anded = try func.binOp(loaded, mask_val, int_elem_ty, .@"and");
   2327         const extended_value = try func.intcast(rhs, ty, int_elem_ty);
   2328         const shifted_value = if (ptr_info.packed_offset.bit_offset > 0) shifted: {
   2329             break :shifted try func.binOp(extended_value, shift_val, int_elem_ty, .shl);
   2330         } else extended_value;
   2331         const result = try func.binOp(anded, shifted_value, int_elem_ty, .@"or");
   2332         // lhs is still on the stack
   2333         try func.store(.stack, result, int_elem_ty, lhs.offset());
   2334     }
   2335 
   2336     func.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs });
   2337 }
   2338 
   2339 fn store(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerError!void {
   2340     assert(!(lhs != .stack and rhs == .stack));
   2341     const mod = func.bin_file.base.comp.module.?;
   2342     const abi_size = ty.abiSize(mod);
   2343     switch (ty.zigTypeTag(mod)) {
   2344         .ErrorUnion => {
   2345             const pl_ty = ty.errorUnionPayload(mod);
   2346             if (!pl_ty.hasRuntimeBitsIgnoreComptime(mod)) {
   2347                 return func.store(lhs, rhs, Type.anyerror, 0);
   2348             }
   2349 
   2350             const len = @as(u32, @intCast(abi_size));
   2351             return func.memcpy(lhs, rhs, .{ .imm32 = len });
   2352         },
   2353         .Optional => {
   2354             if (ty.isPtrLikeOptional(mod)) {
   2355                 return func.store(lhs, rhs, Type.usize, 0);
   2356             }
   2357             const pl_ty = ty.optionalChild(mod);
   2358             if (!pl_ty.hasRuntimeBitsIgnoreComptime(mod)) {
   2359                 return func.store(lhs, rhs, Type.u8, 0);
   2360             }
   2361             if (pl_ty.zigTypeTag(mod) == .ErrorSet) {
   2362                 return func.store(lhs, rhs, Type.anyerror, 0);
   2363             }
   2364 
   2365             const len = @as(u32, @intCast(abi_size));
   2366             return func.memcpy(lhs, rhs, .{ .imm32 = len });
   2367         },
   2368         .Struct, .Array, .Union => if (isByRef(ty, mod)) {
   2369             const len = @as(u32, @intCast(abi_size));
   2370             return func.memcpy(lhs, rhs, .{ .imm32 = len });
   2371         },
   2372         .Vector => switch (determineSimdStoreStrategy(ty, mod)) {
   2373             .unrolled => {
   2374                 const len: u32 = @intCast(abi_size);
   2375                 return func.memcpy(lhs, rhs, .{ .imm32 = len });
   2376             },
   2377             .direct => {
   2378                 try func.emitWValue(lhs);
   2379                 try func.lowerToStack(rhs);
   2380                 // TODO: Add helper functions for simd opcodes
   2381                 const extra_index: u32 = @intCast(func.mir_extra.items.len);
   2382                 // stores as := opcode, offset, alignment (opcode::memarg)
   2383                 try func.mir_extra.appendSlice(func.gpa, &[_]u32{
   2384                     std.wasm.simdOpcode(.v128_store),
   2385                     offset + lhs.offset(),
   2386                     @intCast(ty.abiAlignment(mod).toByteUnits() orelse 0),
   2387                 });
   2388                 return func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } });
   2389             },
   2390         },
   2391         .Pointer => {
   2392             if (ty.isSlice(mod)) {
   2393                 // store pointer first
   2394                 // lower it to the stack so we do not have to store rhs into a local first
   2395                 try func.emitWValue(lhs);
   2396                 const ptr_local = try func.load(rhs, Type.usize, 0);
   2397                 try func.store(.{ .stack = {} }, ptr_local, Type.usize, 0 + lhs.offset());
   2398 
   2399                 // retrieve length from rhs, and store that alongside lhs as well
   2400                 try func.emitWValue(lhs);
   2401                 const len_local = try func.load(rhs, Type.usize, func.ptrSize());
   2402                 try func.store(.{ .stack = {} }, len_local, Type.usize, func.ptrSize() + lhs.offset());
   2403                 return;
   2404             }
   2405         },
   2406         .Int, .Float => if (abi_size > 8 and abi_size <= 16) {
   2407             try func.emitWValue(lhs);
   2408             const lsb = try func.load(rhs, Type.u64, 0);
   2409             try func.store(.{ .stack = {} }, lsb, Type.u64, 0 + lhs.offset());
   2410 
   2411             try func.emitWValue(lhs);
   2412             const msb = try func.load(rhs, Type.u64, 8);
   2413             try func.store(.{ .stack = {} }, msb, Type.u64, 8 + lhs.offset());
   2414             return;
   2415         } else if (abi_size > 16) {
   2416             try func.memcpy(lhs, rhs, .{ .imm32 = @as(u32, @intCast(ty.abiSize(mod))) });
   2417         },
   2418         else => if (abi_size > 8) {
   2419             return func.fail("TODO: `store` for type `{}` with abisize `{d}`", .{
   2420                 ty.fmt(func.bin_file.base.comp.module.?),
   2421                 abi_size,
   2422             });
   2423         },
   2424     }
   2425     try func.emitWValue(lhs);
   2426     // In this case we're actually interested in storing the stack position
   2427     // into lhs, so we calculate that and emit that instead
   2428     try func.lowerToStack(rhs);
   2429 
   2430     const valtype = typeToValtype(ty, mod);
   2431     const opcode = buildOpcode(.{
   2432         .valtype1 = valtype,
   2433         .width = @as(u8, @intCast(abi_size * 8)),
   2434         .op = .store,
   2435     });
   2436 
   2437     // store rhs value at stack pointer's location in memory
   2438     try func.addMemArg(
   2439         Mir.Inst.Tag.fromOpcode(opcode),
   2440         .{
   2441             .offset = offset + lhs.offset(),
   2442             .alignment = @intCast(ty.abiAlignment(mod).toByteUnits().?),
   2443         },
   2444     );
   2445 }
   2446 
   2447 fn airLoad(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   2448     const mod = func.bin_file.base.comp.module.?;
   2449     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   2450     const operand = try func.resolveInst(ty_op.operand);
   2451     const ty = ty_op.ty.toType();
   2452     const ptr_ty = func.typeOf(ty_op.operand);
   2453     const ptr_info = ptr_ty.ptrInfo(mod);
   2454 
   2455     if (!ty.hasRuntimeBitsIgnoreComptime(mod)) return func.finishAir(inst, .none, &.{ty_op.operand});
   2456 
   2457     const result = result: {
   2458         if (isByRef(ty, mod)) {
   2459             const new_local = try func.allocStack(ty);
   2460             try func.store(new_local, operand, ty, 0);
   2461             break :result new_local;
   2462         }
   2463 
   2464         if (ptr_info.packed_offset.host_size == 0) {
   2465             const stack_loaded = try func.load(operand, ty, 0);
   2466             break :result try stack_loaded.toLocal(func, ty);
   2467         }
   2468 
   2469         // at this point we have a non-natural alignment, we must
   2470         // shift the value to obtain the correct bit.
   2471         const int_elem_ty = try mod.intType(.unsigned, ptr_info.packed_offset.host_size * 8);
   2472         const shift_val = if (ptr_info.packed_offset.host_size <= 4)
   2473             WValue{ .imm32 = ptr_info.packed_offset.bit_offset }
   2474         else if (ptr_info.packed_offset.host_size <= 8)
   2475             WValue{ .imm64 = ptr_info.packed_offset.bit_offset }
   2476         else
   2477             return func.fail("TODO: airLoad where ptr to bitfield exceeds 64 bits", .{});
   2478 
   2479         const stack_loaded = try func.load(operand, int_elem_ty, 0);
   2480         const shifted = try func.binOp(stack_loaded, shift_val, int_elem_ty, .shr);
   2481         const result = try func.trunc(shifted, ty, int_elem_ty);
   2482         // const wrapped = try func.wrapOperand(shifted, ty);
   2483         break :result try result.toLocal(func, ty);
   2484     };
   2485     func.finishAir(inst, result, &.{ty_op.operand});
   2486 }
   2487 
   2488 /// Loads an operand from the linear memory section.
   2489 /// NOTE: Leaves the value on the stack.
   2490 fn load(func: *CodeGen, operand: WValue, ty: Type, offset: u32) InnerError!WValue {
   2491     const mod = func.bin_file.base.comp.module.?;
   2492     // load local's value from memory by its stack position
   2493     try func.emitWValue(operand);
   2494 
   2495     if (ty.zigTypeTag(mod) == .Vector) {
   2496         // TODO: Add helper functions for simd opcodes
   2497         const extra_index = @as(u32, @intCast(func.mir_extra.items.len));
   2498         // stores as := opcode, offset, alignment (opcode::memarg)
   2499         try func.mir_extra.appendSlice(func.gpa, &[_]u32{
   2500             std.wasm.simdOpcode(.v128_load),
   2501             offset + operand.offset(),
   2502             @intCast(ty.abiAlignment(mod).toByteUnits().?),
   2503         });
   2504         try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } });
   2505         return WValue{ .stack = {} };
   2506     }
   2507 
   2508     const abi_size: u8 = @intCast(ty.abiSize(mod));
   2509     const opcode = buildOpcode(.{
   2510         .valtype1 = typeToValtype(ty, mod),
   2511         .width = abi_size * 8,
   2512         .op = .load,
   2513         .signedness = .unsigned,
   2514     });
   2515 
   2516     try func.addMemArg(
   2517         Mir.Inst.Tag.fromOpcode(opcode),
   2518         .{
   2519             .offset = offset + operand.offset(),
   2520             .alignment = @intCast(ty.abiAlignment(mod).toByteUnits().?),
   2521         },
   2522     );
   2523 
   2524     return WValue{ .stack = {} };
   2525 }
   2526 
   2527 fn airArg(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   2528     const mod = func.bin_file.base.comp.module.?;
   2529     const arg_index = func.arg_index;
   2530     const arg = func.args[arg_index];
   2531     const cc = mod.typeToFunc(func.decl.typeOf(mod)).?.cc;
   2532     const arg_ty = func.typeOfIndex(inst);
   2533     if (cc == .C) {
   2534         const arg_classes = abi.classifyType(arg_ty, mod);
   2535         for (arg_classes) |class| {
   2536             if (class != .none) {
   2537                 func.arg_index += 1;
   2538             }
   2539         }
   2540 
   2541         // When we have an argument that's passed using more than a single parameter,
   2542         // we combine them into a single stack value
   2543         if (arg_classes[0] == .direct and arg_classes[1] == .direct) {
   2544             if (arg_ty.zigTypeTag(mod) != .Int and arg_ty.zigTypeTag(mod) != .Float) {
   2545                 return func.fail(
   2546                     "TODO: Implement C-ABI argument for type '{}'",
   2547                     .{arg_ty.fmt(func.bin_file.base.comp.module.?)},
   2548                 );
   2549             }
   2550             const result = try func.allocStack(arg_ty);
   2551             try func.store(result, arg, Type.u64, 0);
   2552             try func.store(result, func.args[arg_index + 1], Type.u64, 8);
   2553             return func.finishAir(inst, result, &.{});
   2554         }
   2555     } else {
   2556         func.arg_index += 1;
   2557     }
   2558 
   2559     switch (func.debug_output) {
   2560         .dwarf => |dwarf| {
   2561             const src_index = func.air.instructions.items(.data)[@intFromEnum(inst)].arg.src_index;
   2562             const name = mod.getParamName(func.func_index, src_index);
   2563             try dwarf.genArgDbgInfo(name, arg_ty, mod.funcOwnerDeclIndex(func.func_index), .{
   2564                 .wasm_local = arg.local.value,
   2565             });
   2566         },
   2567         else => {},
   2568     }
   2569 
   2570     func.finishAir(inst, arg, &.{});
   2571 }
   2572 
   2573 fn airBinOp(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerError!void {
   2574     const mod = func.bin_file.base.comp.module.?;
   2575     const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
   2576     const lhs = try func.resolveInst(bin_op.lhs);
   2577     const rhs = try func.resolveInst(bin_op.rhs);
   2578     const lhs_ty = func.typeOf(bin_op.lhs);
   2579     const rhs_ty = func.typeOf(bin_op.rhs);
   2580 
   2581     // For certain operations, such as shifting, the types are different.
   2582     // When converting this to a WebAssembly type, they *must* match to perform
   2583     // an operation. For this reason we verify if the WebAssembly type is different, in which
   2584     // case we first coerce the operands to the same type before performing the operation.
   2585     // For big integers we can ignore this as we will call into compiler-rt which handles this.
   2586     const result = switch (op) {
   2587         .shr, .shl => res: {
   2588             const lhs_wasm_bits = toWasmBits(@as(u16, @intCast(lhs_ty.bitSize(mod)))) orelse {
   2589                 return func.fail("TODO: implement '{s}' for types larger than 128 bits", .{@tagName(op)});
   2590             };
   2591             const rhs_wasm_bits = toWasmBits(@as(u16, @intCast(rhs_ty.bitSize(mod)))).?;
   2592             const new_rhs = if (lhs_wasm_bits != rhs_wasm_bits and lhs_wasm_bits != 128) blk: {
   2593                 const tmp = try func.intcast(rhs, rhs_ty, lhs_ty);
   2594                 break :blk try tmp.toLocal(func, lhs_ty);
   2595             } else rhs;
   2596             const stack_result = try func.binOp(lhs, new_rhs, lhs_ty, op);
   2597             break :res try stack_result.toLocal(func, lhs_ty);
   2598         },
   2599         else => res: {
   2600             const stack_result = try func.binOp(lhs, rhs, lhs_ty, op);
   2601             break :res try stack_result.toLocal(func, lhs_ty);
   2602         },
   2603     };
   2604 
   2605     func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
   2606 }
   2607 
   2608 /// Performs a binary operation on the given `WValue`'s
   2609 /// NOTE: THis leaves the value on top of the stack.
   2610 fn binOp(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError!WValue {
   2611     const mod = func.bin_file.base.comp.module.?;
   2612     assert(!(lhs != .stack and rhs == .stack));
   2613 
   2614     if (ty.isAnyFloat()) {
   2615         const float_op = FloatOp.fromOp(op);
   2616         return func.floatOp(float_op, ty, &.{ lhs, rhs });
   2617     }
   2618 
   2619     if (isByRef(ty, mod)) {
   2620         if (ty.zigTypeTag(mod) == .Int) {
   2621             return func.binOpBigInt(lhs, rhs, ty, op);
   2622         } else {
   2623             return func.fail(
   2624                 "TODO: Implement binary operation for type: {}",
   2625                 .{ty.fmt(func.bin_file.base.comp.module.?)},
   2626             );
   2627         }
   2628     }
   2629 
   2630     const opcode: wasm.Opcode = buildOpcode(.{
   2631         .op = op,
   2632         .valtype1 = typeToValtype(ty, mod),
   2633         .signedness = if (ty.isSignedInt(mod)) .signed else .unsigned,
   2634     });
   2635     try func.emitWValue(lhs);
   2636     try func.emitWValue(rhs);
   2637 
   2638     try func.addTag(Mir.Inst.Tag.fromOpcode(opcode));
   2639 
   2640     return WValue{ .stack = {} };
   2641 }
   2642 
   2643 fn binOpBigInt(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError!WValue {
   2644     const mod = func.bin_file.base.comp.module.?;
   2645     const int_info = ty.intInfo(mod);
   2646     if (int_info.bits > 128) {
   2647         return func.fail("TODO: Implement binary operation for big integers larger than 128 bits", .{});
   2648     }
   2649 
   2650     switch (op) {
   2651         .mul => return func.callIntrinsic("__multi3", &.{ ty.toIntern(), ty.toIntern() }, ty, &.{ lhs, rhs }),
   2652         .div => switch (int_info.signedness) {
   2653             .signed => return func.callIntrinsic("__udivti3", &.{ ty.toIntern(), ty.toIntern() }, ty, &.{ lhs, rhs }),
   2654             .unsigned => return func.callIntrinsic("__divti3", &.{ ty.toIntern(), ty.toIntern() }, ty, &.{ lhs, rhs }),
   2655         },
   2656         .rem => return func.callIntrinsic("__umodti3", &.{ ty.toIntern(), ty.toIntern() }, ty, &.{ lhs, rhs }),
   2657         .shr => return func.callIntrinsic("__lshrti3", &.{ ty.toIntern(), .i32_type }, ty, &.{ lhs, rhs }),
   2658         .shl => return func.callIntrinsic("__ashlti3", &.{ ty.toIntern(), .i32_type }, ty, &.{ lhs, rhs }),
   2659         .@"and", .@"or", .xor => {
   2660             const result = try func.allocStack(ty);
   2661             try func.emitWValue(result);
   2662             const lhs_high_bit = try func.load(lhs, Type.u64, 0);
   2663             const rhs_high_bit = try func.load(rhs, Type.u64, 0);
   2664             const op_high_bit = try func.binOp(lhs_high_bit, rhs_high_bit, Type.u64, op);
   2665             try func.store(.stack, op_high_bit, Type.u64, result.offset());
   2666 
   2667             try func.emitWValue(result);
   2668             const lhs_low_bit = try func.load(lhs, Type.u64, 8);
   2669             const rhs_low_bit = try func.load(rhs, Type.u64, 8);
   2670             const op_low_bit = try func.binOp(lhs_low_bit, rhs_low_bit, Type.u64, op);
   2671             try func.store(.stack, op_low_bit, Type.u64, result.offset() + 8);
   2672             return result;
   2673         },
   2674         .add, .sub => {
   2675             const result = try func.allocStack(ty);
   2676             var lhs_high_bit = try (try func.load(lhs, Type.u64, 0)).toLocal(func, Type.u64);
   2677             defer lhs_high_bit.free(func);
   2678             var rhs_high_bit = try (try func.load(rhs, Type.u64, 0)).toLocal(func, Type.u64);
   2679             defer rhs_high_bit.free(func);
   2680             var high_op_res = try (try func.binOp(lhs_high_bit, rhs_high_bit, Type.u64, op)).toLocal(func, Type.u64);
   2681             defer high_op_res.free(func);
   2682 
   2683             const lhs_low_bit = try func.load(lhs, Type.u64, 8);
   2684             const rhs_low_bit = try func.load(rhs, Type.u64, 8);
   2685             const low_op_res = try func.binOp(lhs_low_bit, rhs_low_bit, Type.u64, op);
   2686 
   2687             const lt = if (op == .add) blk: {
   2688                 break :blk try func.cmp(high_op_res, rhs_high_bit, Type.u64, .lt);
   2689             } else if (op == .sub) blk: {
   2690                 break :blk try func.cmp(lhs_high_bit, rhs_high_bit, Type.u64, .lt);
   2691             } else unreachable;
   2692             const tmp = try func.intcast(lt, Type.u32, Type.u64);
   2693             var tmp_op = try (try func.binOp(low_op_res, tmp, Type.u64, op)).toLocal(func, Type.u64);
   2694             defer tmp_op.free(func);
   2695 
   2696             try func.store(result, high_op_res, Type.u64, 0);
   2697             try func.store(result, tmp_op, Type.u64, 8);
   2698             return result;
   2699         },
   2700         else => return func.fail("TODO: Implement binary operation for big integers: '{s}'", .{@tagName(op)}),
   2701     }
   2702 }
   2703 
   2704 const FloatOp = enum {
   2705     add,
   2706     ceil,
   2707     cos,
   2708     div,
   2709     exp,
   2710     exp2,
   2711     fabs,
   2712     floor,
   2713     fma,
   2714     fmax,
   2715     fmin,
   2716     fmod,
   2717     log,
   2718     log10,
   2719     log2,
   2720     mul,
   2721     neg,
   2722     round,
   2723     sin,
   2724     sqrt,
   2725     sub,
   2726     tan,
   2727     trunc,
   2728 
   2729     pub fn fromOp(op: Op) FloatOp {
   2730         return switch (op) {
   2731             .add => .add,
   2732             .ceil => .ceil,
   2733             .div => .div,
   2734             .abs => .fabs,
   2735             .floor => .floor,
   2736             .max => .fmax,
   2737             .min => .fmin,
   2738             .mul => .mul,
   2739             .neg => .neg,
   2740             .nearest => .round,
   2741             .sqrt => .sqrt,
   2742             .sub => .sub,
   2743             .trunc => .trunc,
   2744             else => unreachable,
   2745         };
   2746     }
   2747 
   2748     pub fn toOp(float_op: FloatOp) ?Op {
   2749         return switch (float_op) {
   2750             .add => .add,
   2751             .ceil => .ceil,
   2752             .div => .div,
   2753             .fabs => .abs,
   2754             .floor => .floor,
   2755             .fmax => .max,
   2756             .fmin => .min,
   2757             .mul => .mul,
   2758             .neg => .neg,
   2759             .round => .nearest,
   2760             .sqrt => .sqrt,
   2761             .sub => .sub,
   2762             .trunc => .trunc,
   2763 
   2764             .cos,
   2765             .exp,
   2766             .exp2,
   2767             .fma,
   2768             .fmod,
   2769             .log,
   2770             .log10,
   2771             .log2,
   2772             .sin,
   2773             .tan,
   2774             => null,
   2775         };
   2776     }
   2777 };
   2778 
   2779 fn airAbs(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   2780     const mod = func.bin_file.base.comp.module.?;
   2781     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   2782     const operand = try func.resolveInst(ty_op.operand);
   2783     const ty = func.typeOf(ty_op.operand);
   2784     const scalar_ty = ty.scalarType(mod);
   2785 
   2786     switch (scalar_ty.zigTypeTag(mod)) {
   2787         .Int => if (ty.zigTypeTag(mod) == .Vector) {
   2788             return func.fail("TODO implement airAbs for {}", .{ty.fmt(mod)});
   2789         } else {
   2790             const int_bits = ty.intInfo(mod).bits;
   2791             const wasm_bits = toWasmBits(int_bits) orelse {
   2792                 return func.fail("TODO: airAbs for signed integers larger than '{d}' bits", .{int_bits});
   2793             };
   2794 
   2795             const op = try operand.toLocal(func, ty);
   2796 
   2797             try func.emitWValue(op);
   2798             switch (wasm_bits) {
   2799                 32 => {
   2800                     if (wasm_bits != int_bits) {
   2801                         try func.addImm32(wasm_bits - int_bits);
   2802                         try func.addTag(.i32_shl);
   2803                     }
   2804                     try func.addImm32(31);
   2805                     try func.addTag(.i32_shr_s);
   2806 
   2807                     const tmp = try func.allocLocal(ty);
   2808                     try func.addLabel(.local_tee, tmp.local.value);
   2809 
   2810                     try func.emitWValue(op);
   2811                     try func.addTag(.i32_xor);
   2812                     try func.emitWValue(tmp);
   2813                     try func.addTag(.i32_sub);
   2814 
   2815                     if (int_bits != wasm_bits) {
   2816                         try func.emitWValue(WValue{ .imm32 = (@as(u32, 1) << @intCast(int_bits)) - 1 });
   2817                         try func.addTag(.i32_and);
   2818                     }
   2819                 },
   2820                 64 => {
   2821                     if (wasm_bits != int_bits) {
   2822                         try func.addImm64(wasm_bits - int_bits);
   2823                         try func.addTag(.i64_shl);
   2824                     }
   2825                     try func.addImm64(63);
   2826                     try func.addTag(.i64_shr_s);
   2827 
   2828                     const tmp = try func.allocLocal(ty);
   2829                     try func.addLabel(.local_tee, tmp.local.value);
   2830 
   2831                     try func.emitWValue(op);
   2832                     try func.addTag(.i64_xor);
   2833                     try func.emitWValue(tmp);
   2834                     try func.addTag(.i64_sub);
   2835 
   2836                     if (int_bits != wasm_bits) {
   2837                         try func.emitWValue(WValue{ .imm64 = (@as(u64, 1) << @intCast(int_bits)) - 1 });
   2838                         try func.addTag(.i64_and);
   2839                     }
   2840                 },
   2841                 else => return func.fail("TODO: Implement airAbs for {}", .{ty.fmt(mod)}),
   2842             }
   2843 
   2844             const result = try (WValue{ .stack = {} }).toLocal(func, ty);
   2845             func.finishAir(inst, result, &.{ty_op.operand});
   2846         },
   2847         .Float => {
   2848             const result = try (try func.floatOp(.fabs, ty, &.{operand})).toLocal(func, ty);
   2849             func.finishAir(inst, result, &.{ty_op.operand});
   2850         },
   2851         else => unreachable,
   2852     }
   2853 }
   2854 
   2855 fn airUnaryFloatOp(func: *CodeGen, inst: Air.Inst.Index, op: FloatOp) InnerError!void {
   2856     const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
   2857     const operand = try func.resolveInst(un_op);
   2858     const ty = func.typeOf(un_op);
   2859 
   2860     const result = try (try func.floatOp(op, ty, &.{operand})).toLocal(func, ty);
   2861     func.finishAir(inst, result, &.{un_op});
   2862 }
   2863 
   2864 fn floatOp(func: *CodeGen, float_op: FloatOp, ty: Type, args: []const WValue) InnerError!WValue {
   2865     const mod = func.bin_file.base.comp.module.?;
   2866     if (ty.zigTypeTag(mod) == .Vector) {
   2867         return func.fail("TODO: Implement floatOps for vectors", .{});
   2868     }
   2869 
   2870     const float_bits = ty.floatBits(func.target);
   2871 
   2872     if (float_op == .neg) {
   2873         return func.floatNeg(ty, args[0]);
   2874     }
   2875 
   2876     if (float_bits == 32 or float_bits == 64) {
   2877         if (float_op.toOp()) |op| {
   2878             for (args) |operand| {
   2879                 try func.emitWValue(operand);
   2880             }
   2881             const opcode = buildOpcode(.{ .op = op, .valtype1 = typeToValtype(ty, mod) });
   2882             try func.addTag(Mir.Inst.Tag.fromOpcode(opcode));
   2883             return .stack;
   2884         }
   2885     }
   2886 
   2887     var fn_name_buf: [64]u8 = undefined;
   2888     const fn_name = switch (float_op) {
   2889         .add,
   2890         .sub,
   2891         .div,
   2892         .mul,
   2893         => std.fmt.bufPrint(&fn_name_buf, "__{s}{s}f3", .{
   2894             @tagName(float_op), target_util.compilerRtFloatAbbrev(float_bits),
   2895         }) catch unreachable,
   2896 
   2897         .ceil,
   2898         .cos,
   2899         .exp,
   2900         .exp2,
   2901         .fabs,
   2902         .floor,
   2903         .fma,
   2904         .fmax,
   2905         .fmin,
   2906         .fmod,
   2907         .log,
   2908         .log10,
   2909         .log2,
   2910         .round,
   2911         .sin,
   2912         .sqrt,
   2913         .tan,
   2914         .trunc,
   2915         => std.fmt.bufPrint(&fn_name_buf, "{s}{s}{s}", .{
   2916             target_util.libcFloatPrefix(float_bits), @tagName(float_op), target_util.libcFloatSuffix(float_bits),
   2917         }) catch unreachable,
   2918         .neg => unreachable, // handled above
   2919     };
   2920 
   2921     // fma requires three operands
   2922     var param_types_buffer: [3]InternPool.Index = .{ ty.ip_index, ty.ip_index, ty.ip_index };
   2923     const param_types = param_types_buffer[0..args.len];
   2924     return func.callIntrinsic(fn_name, param_types, ty, args);
   2925 }
   2926 
   2927 /// NOTE: The result value remains on top of the stack.
   2928 fn floatNeg(func: *CodeGen, ty: Type, arg: WValue) InnerError!WValue {
   2929     const float_bits = ty.floatBits(func.target);
   2930     switch (float_bits) {
   2931         16 => {
   2932             try func.emitWValue(arg);
   2933             try func.addImm32(std.math.minInt(i16));
   2934             try func.addTag(.i32_xor);
   2935             return .stack;
   2936         },
   2937         32, 64 => {
   2938             try func.emitWValue(arg);
   2939             const val_type: wasm.Valtype = if (float_bits == 32) .f32 else .f64;
   2940             const opcode = buildOpcode(.{ .op = .neg, .valtype1 = val_type });
   2941             try func.addTag(Mir.Inst.Tag.fromOpcode(opcode));
   2942             return .stack;
   2943         },
   2944         80, 128 => {
   2945             const result = try func.allocStack(ty);
   2946             try func.emitWValue(result);
   2947             try func.emitWValue(arg);
   2948             try func.addMemArg(.i64_load, .{ .offset = 0 + arg.offset(), .alignment = 2 });
   2949             try func.addMemArg(.i64_store, .{ .offset = 0 + result.offset(), .alignment = 2 });
   2950 
   2951             try func.emitWValue(result);
   2952             try func.emitWValue(arg);
   2953             try func.addMemArg(.i64_load, .{ .offset = 8 + arg.offset(), .alignment = 2 });
   2954 
   2955             if (float_bits == 80) {
   2956                 try func.addImm64(0x8000);
   2957                 try func.addTag(.i64_xor);
   2958                 try func.addMemArg(.i64_store16, .{ .offset = 8 + result.offset(), .alignment = 2 });
   2959             } else {
   2960                 try func.addImm64(0x8000000000000000);
   2961                 try func.addTag(.i64_xor);
   2962                 try func.addMemArg(.i64_store, .{ .offset = 8 + result.offset(), .alignment = 2 });
   2963             }
   2964             return result;
   2965         },
   2966         else => unreachable,
   2967     }
   2968 }
   2969 
   2970 fn airWrapBinOp(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerError!void {
   2971     const mod = func.bin_file.base.comp.module.?;
   2972     const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
   2973 
   2974     const lhs = try func.resolveInst(bin_op.lhs);
   2975     const rhs = try func.resolveInst(bin_op.rhs);
   2976     const lhs_ty = func.typeOf(bin_op.lhs);
   2977     const rhs_ty = func.typeOf(bin_op.rhs);
   2978 
   2979     if (lhs_ty.zigTypeTag(mod) == .Vector or rhs_ty.zigTypeTag(mod) == .Vector) {
   2980         return func.fail("TODO: Implement wrapping arithmetic for vectors", .{});
   2981     }
   2982 
   2983     // For certain operations, such as shifting, the types are different.
   2984     // When converting this to a WebAssembly type, they *must* match to perform
   2985     // an operation. For this reason we verify if the WebAssembly type is different, in which
   2986     // case we first coerce the operands to the same type before performing the operation.
   2987     // For big integers we can ignore this as we will call into compiler-rt which handles this.
   2988     const result = switch (op) {
   2989         .shr, .shl => res: {
   2990             const lhs_wasm_bits = toWasmBits(@as(u16, @intCast(lhs_ty.bitSize(mod)))) orelse {
   2991                 return func.fail("TODO: implement '{s}' for types larger than 128 bits", .{@tagName(op)});
   2992             };
   2993             const rhs_wasm_bits = toWasmBits(@as(u16, @intCast(rhs_ty.bitSize(mod)))).?;
   2994             const new_rhs = if (lhs_wasm_bits != rhs_wasm_bits and lhs_wasm_bits != 128) blk: {
   2995                 const tmp = try func.intcast(rhs, rhs_ty, lhs_ty);
   2996                 break :blk try tmp.toLocal(func, lhs_ty);
   2997             } else rhs;
   2998             const stack_result = try func.wrapBinOp(lhs, new_rhs, lhs_ty, op);
   2999             break :res try stack_result.toLocal(func, lhs_ty);
   3000         },
   3001         else => res: {
   3002             const stack_result = try func.wrapBinOp(lhs, rhs, lhs_ty, op);
   3003             break :res try stack_result.toLocal(func, lhs_ty);
   3004         },
   3005     };
   3006 
   3007     return func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
   3008 }
   3009 
   3010 /// Performs a wrapping binary operation.
   3011 /// Asserts rhs is not a stack value when lhs also isn't.
   3012 /// NOTE: Leaves the result on the stack when its Type is <= 64 bits
   3013 fn wrapBinOp(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError!WValue {
   3014     const bin_local = try func.binOp(lhs, rhs, ty, op);
   3015     return func.wrapOperand(bin_local, ty);
   3016 }
   3017 
   3018 /// Wraps an operand based on a given type's bitsize.
   3019 /// Asserts `Type` is <= 128 bits.
   3020 /// NOTE: When the Type is <= 64 bits, leaves the value on top of the stack.
   3021 fn wrapOperand(func: *CodeGen, operand: WValue, ty: Type) InnerError!WValue {
   3022     const mod = func.bin_file.base.comp.module.?;
   3023     assert(ty.abiSize(mod) <= 16);
   3024     const bitsize = @as(u16, @intCast(ty.bitSize(mod)));
   3025     const wasm_bits = toWasmBits(bitsize) orelse {
   3026         return func.fail("TODO: Implement wrapOperand for bitsize '{d}'", .{bitsize});
   3027     };
   3028 
   3029     if (wasm_bits == bitsize) return operand;
   3030 
   3031     if (wasm_bits == 128) {
   3032         assert(operand != .stack);
   3033         const lsb = try func.load(operand, Type.u64, 8);
   3034 
   3035         const result_ptr = try func.allocStack(ty);
   3036         try func.emitWValue(result_ptr);
   3037         try func.store(.{ .stack = {} }, lsb, Type.u64, 8 + result_ptr.offset());
   3038         const result = (@as(u64, 1) << @as(u6, @intCast(64 - (wasm_bits - bitsize)))) - 1;
   3039         try func.emitWValue(result_ptr);
   3040         _ = try func.load(operand, Type.u64, 0);
   3041         try func.addImm64(result);
   3042         try func.addTag(.i64_and);
   3043         try func.addMemArg(.i64_store, .{ .offset = result_ptr.offset(), .alignment = 8 });
   3044         return result_ptr;
   3045     }
   3046 
   3047     const result = (@as(u64, 1) << @as(u6, @intCast(bitsize))) - 1;
   3048     try func.emitWValue(operand);
   3049     if (bitsize <= 32) {
   3050         try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(result)))));
   3051         try func.addTag(.i32_and);
   3052     } else if (bitsize <= 64) {
   3053         try func.addImm64(result);
   3054         try func.addTag(.i64_and);
   3055     } else unreachable;
   3056 
   3057     return WValue{ .stack = {} };
   3058 }
   3059 
   3060 fn lowerPtr(func: *CodeGen, ptr_val: InternPool.Index, prev_offset: u64) InnerError!WValue {
   3061     const zcu = func.bin_file.base.comp.module.?;
   3062     const ptr = zcu.intern_pool.indexToKey(ptr_val).ptr;
   3063     const offset: u64 = prev_offset + ptr.byte_offset;
   3064     return switch (ptr.base_addr) {
   3065         .decl => |decl| return func.lowerDeclRefValue(decl, @intCast(offset)),
   3066         .anon_decl => |ad| return func.lowerAnonDeclRef(ad, @intCast(offset)),
   3067         .int => return func.lowerConstant(try zcu.intValue(Type.usize, offset), Type.usize),
   3068         .eu_payload => return func.fail("Wasm TODO: lower error union payload pointer", .{}),
   3069         .opt_payload => |opt_ptr| return func.lowerPtr(opt_ptr, offset),
   3070         .field => |field| {
   3071             const base_ptr = Value.fromInterned(field.base);
   3072             const base_ty = base_ptr.typeOf(zcu).childType(zcu);
   3073             const field_off: u64 = switch (base_ty.zigTypeTag(zcu)) {
   3074                 .Pointer => off: {
   3075                     assert(base_ty.isSlice(zcu));
   3076                     break :off switch (field.index) {
   3077                         Value.slice_ptr_index => 0,
   3078                         Value.slice_len_index => @divExact(zcu.getTarget().ptrBitWidth(), 8),
   3079                         else => unreachable,
   3080                     };
   3081                 },
   3082                 .Struct => switch (base_ty.containerLayout(zcu)) {
   3083                     .auto => base_ty.structFieldOffset(@intCast(field.index), zcu),
   3084                     .@"extern", .@"packed" => unreachable,
   3085                 },
   3086                 .Union => switch (base_ty.containerLayout(zcu)) {
   3087                     .auto => off: {
   3088                         // Keep in sync with the `un` case of `generateSymbol`.
   3089                         const layout = base_ty.unionGetLayout(zcu);
   3090                         if (layout.payload_size == 0) break :off 0;
   3091                         if (layout.tag_size == 0) break :off 0;
   3092                         if (layout.tag_align.compare(.gte, layout.payload_align)) {
   3093                             // Tag first.
   3094                             break :off layout.tag_size;
   3095                         } else {
   3096                             // Payload first.
   3097                             break :off 0;
   3098                         }
   3099                     },
   3100                     .@"extern", .@"packed" => unreachable,
   3101                 },
   3102                 else => unreachable,
   3103             };
   3104             return func.lowerPtr(field.base, offset + field_off);
   3105         },
   3106         .arr_elem, .comptime_field, .comptime_alloc => unreachable,
   3107     };
   3108 }
   3109 
   3110 fn lowerAnonDeclRef(
   3111     func: *CodeGen,
   3112     anon_decl: InternPool.Key.Ptr.BaseAddr.AnonDecl,
   3113     offset: u32,
   3114 ) InnerError!WValue {
   3115     const mod = func.bin_file.base.comp.module.?;
   3116     const decl_val = anon_decl.val;
   3117     const ty = Type.fromInterned(mod.intern_pool.typeOf(decl_val));
   3118 
   3119     const is_fn_body = ty.zigTypeTag(mod) == .Fn;
   3120     if (!is_fn_body and !ty.hasRuntimeBitsIgnoreComptime(mod)) {
   3121         return WValue{ .imm32 = 0xaaaaaaaa };
   3122     }
   3123 
   3124     const decl_align = mod.intern_pool.indexToKey(anon_decl.orig_ty).ptr_type.flags.alignment;
   3125     const res = try func.bin_file.lowerAnonDecl(decl_val, decl_align, func.decl.navSrcLoc(mod).upgrade(mod));
   3126     switch (res) {
   3127         .ok => {},
   3128         .fail => |em| {
   3129             func.err_msg = em;
   3130             return error.CodegenFail;
   3131         },
   3132     }
   3133     const target_atom_index = func.bin_file.zigObjectPtr().?.anon_decls.get(decl_val).?;
   3134     const target_sym_index = @intFromEnum(func.bin_file.getAtom(target_atom_index).sym_index);
   3135     if (is_fn_body) {
   3136         return WValue{ .function_index = target_sym_index };
   3137     } else if (offset == 0) {
   3138         return WValue{ .memory = target_sym_index };
   3139     } else return WValue{ .memory_offset = .{ .pointer = target_sym_index, .offset = offset } };
   3140 }
   3141 
   3142 fn lowerDeclRefValue(func: *CodeGen, decl_index: InternPool.DeclIndex, offset: u32) InnerError!WValue {
   3143     const mod = func.bin_file.base.comp.module.?;
   3144 
   3145     const decl = mod.declPtr(decl_index);
   3146     // check if decl is an alias to a function, in which case we
   3147     // want to lower the actual decl, rather than the alias itself.
   3148     if (decl.val.getFunction(mod)) |func_val| {
   3149         if (func_val.owner_decl != decl_index) {
   3150             return func.lowerDeclRefValue(func_val.owner_decl, offset);
   3151         }
   3152     } else if (decl.val.getExternFunc(mod)) |func_val| {
   3153         if (func_val.decl != decl_index) {
   3154             return func.lowerDeclRefValue(func_val.decl, offset);
   3155         }
   3156     }
   3157     const decl_ty = decl.typeOf(mod);
   3158     if (decl_ty.zigTypeTag(mod) != .Fn and !decl_ty.hasRuntimeBitsIgnoreComptime(mod)) {
   3159         return WValue{ .imm32 = 0xaaaaaaaa };
   3160     }
   3161 
   3162     const atom_index = try func.bin_file.getOrCreateAtomForDecl(decl_index);
   3163     const atom = func.bin_file.getAtom(atom_index);
   3164 
   3165     const target_sym_index = @intFromEnum(atom.sym_index);
   3166     if (decl_ty.zigTypeTag(mod) == .Fn) {
   3167         return WValue{ .function_index = target_sym_index };
   3168     } else if (offset == 0) {
   3169         return WValue{ .memory = target_sym_index };
   3170     } else return WValue{ .memory_offset = .{ .pointer = target_sym_index, .offset = offset } };
   3171 }
   3172 
   3173 /// Converts a signed integer to its 2's complement form and returns
   3174 /// an unsigned integer instead.
   3175 /// Asserts bitsize <= 64
   3176 fn toTwosComplement(value: anytype, bits: u7) std.meta.Int(.unsigned, @typeInfo(@TypeOf(value)).Int.bits) {
   3177     const T = @TypeOf(value);
   3178     comptime assert(@typeInfo(T) == .Int);
   3179     comptime assert(@typeInfo(T).Int.signedness == .signed);
   3180     assert(bits <= 64);
   3181     const WantedT = std.meta.Int(.unsigned, @typeInfo(T).Int.bits);
   3182     if (value >= 0) return @as(WantedT, @bitCast(value));
   3183     const max_value = @as(u64, @intCast((@as(u65, 1) << bits) - 1));
   3184     const flipped = @as(T, @intCast((~-@as(i65, value)) + 1));
   3185     const result = @as(WantedT, @bitCast(flipped)) & max_value;
   3186     return @as(WantedT, @intCast(result));
   3187 }
   3188 
   3189 /// This function is intended to assert that `isByRef` returns `false` for `ty`.
   3190 /// However such an assertion fails on the behavior tests currently.
   3191 fn lowerConstant(func: *CodeGen, val: Value, ty: Type) InnerError!WValue {
   3192     const mod = func.bin_file.base.comp.module.?;
   3193     // TODO: enable this assertion
   3194     //assert(!isByRef(ty, mod));
   3195     const ip = &mod.intern_pool;
   3196     if (val.isUndefDeep(mod)) return func.emitUndefined(ty);
   3197 
   3198     switch (ip.indexToKey(val.ip_index)) {
   3199         .int_type,
   3200         .ptr_type,
   3201         .array_type,
   3202         .vector_type,
   3203         .opt_type,
   3204         .anyframe_type,
   3205         .error_union_type,
   3206         .simple_type,
   3207         .struct_type,
   3208         .anon_struct_type,
   3209         .union_type,
   3210         .opaque_type,
   3211         .enum_type,
   3212         .func_type,
   3213         .error_set_type,
   3214         .inferred_error_set_type,
   3215         => unreachable, // types, not values
   3216 
   3217         .undef => unreachable, // handled above
   3218         .simple_value => |simple_value| switch (simple_value) {
   3219             .undefined,
   3220             .void,
   3221             .null,
   3222             .empty_struct,
   3223             .@"unreachable",
   3224             .generic_poison,
   3225             => unreachable, // non-runtime values
   3226             .false, .true => return WValue{ .imm32 = switch (simple_value) {
   3227                 .false => 0,
   3228                 .true => 1,
   3229                 else => unreachable,
   3230             } },
   3231         },
   3232         .variable,
   3233         .extern_func,
   3234         .func,
   3235         .enum_literal,
   3236         .empty_enum_value,
   3237         => unreachable, // non-runtime values
   3238         .int => {
   3239             const int_info = ty.intInfo(mod);
   3240             switch (int_info.signedness) {
   3241                 .signed => switch (int_info.bits) {
   3242                     0...32 => return WValue{ .imm32 = @as(u32, @intCast(toTwosComplement(
   3243                         val.toSignedInt(mod),
   3244                         @as(u6, @intCast(int_info.bits)),
   3245                     ))) },
   3246                     33...64 => return WValue{ .imm64 = toTwosComplement(
   3247                         val.toSignedInt(mod),
   3248                         @as(u7, @intCast(int_info.bits)),
   3249                     ) },
   3250                     else => unreachable,
   3251                 },
   3252                 .unsigned => switch (int_info.bits) {
   3253                     0...32 => return WValue{ .imm32 = @as(u32, @intCast(val.toUnsignedInt(mod))) },
   3254                     33...64 => return WValue{ .imm64 = val.toUnsignedInt(mod) },
   3255                     else => unreachable,
   3256                 },
   3257             }
   3258         },
   3259         .err => |err| {
   3260             const int = try mod.getErrorValue(err.name);
   3261             return WValue{ .imm32 = int };
   3262         },
   3263         .error_union => |error_union| {
   3264             const err_int_ty = try mod.errorIntType();
   3265             const err_ty, const err_val = switch (error_union.val) {
   3266                 .err_name => |err_name| .{
   3267                     ty.errorUnionSet(mod),
   3268                     Value.fromInterned((try mod.intern(.{ .err = .{
   3269                         .ty = ty.errorUnionSet(mod).toIntern(),
   3270                         .name = err_name,
   3271                     } }))),
   3272                 },
   3273                 .payload => .{
   3274                     err_int_ty,
   3275                     try mod.intValue(err_int_ty, 0),
   3276                 },
   3277             };
   3278             const payload_type = ty.errorUnionPayload(mod);
   3279             if (!payload_type.hasRuntimeBitsIgnoreComptime(mod)) {
   3280                 // We use the error type directly as the type.
   3281                 return func.lowerConstant(err_val, err_ty);
   3282             }
   3283 
   3284             return func.fail("Wasm TODO: lowerConstant error union with non-zero-bit payload type", .{});
   3285         },
   3286         .enum_tag => |enum_tag| {
   3287             const int_tag_ty = ip.typeOf(enum_tag.int);
   3288             return func.lowerConstant(Value.fromInterned(enum_tag.int), Type.fromInterned(int_tag_ty));
   3289         },
   3290         .float => |float| switch (float.storage) {
   3291             .f16 => |f16_val| return WValue{ .imm32 = @as(u16, @bitCast(f16_val)) },
   3292             .f32 => |f32_val| return WValue{ .float32 = f32_val },
   3293             .f64 => |f64_val| return WValue{ .float64 = f64_val },
   3294             else => unreachable,
   3295         },
   3296         .slice => |slice| {
   3297             var ptr = ip.indexToKey(slice.ptr).ptr;
   3298             const owner_decl = while (true) switch (ptr.base_addr) {
   3299                 .decl => |decl| break decl,
   3300                 .int, .anon_decl => return func.fail("Wasm TODO: lower slice where ptr is not owned by decl", .{}),
   3301                 .opt_payload, .eu_payload => |base| ptr = ip.indexToKey(base).ptr,
   3302                 .field => |base_index| ptr = ip.indexToKey(base_index.base).ptr,
   3303                 .arr_elem, .comptime_field, .comptime_alloc => unreachable,
   3304             };
   3305             return .{ .memory = try func.bin_file.lowerUnnamedConst(val, owner_decl) };
   3306         },
   3307         .ptr => return func.lowerPtr(val.toIntern(), 0),
   3308         .opt => if (ty.optionalReprIsPayload(mod)) {
   3309             const pl_ty = ty.optionalChild(mod);
   3310             if (val.optionalValue(mod)) |payload| {
   3311                 return func.lowerConstant(payload, pl_ty);
   3312             } else {
   3313                 return WValue{ .imm32 = 0 };
   3314             }
   3315         } else {
   3316             return WValue{ .imm32 = @intFromBool(!val.isNull(mod)) };
   3317         },
   3318         .aggregate => switch (ip.indexToKey(ty.ip_index)) {
   3319             .array_type => return func.fail("Wasm TODO: LowerConstant for {}", .{ty.fmt(mod)}),
   3320             .vector_type => {
   3321                 assert(determineSimdStoreStrategy(ty, mod) == .direct);
   3322                 var buf: [16]u8 = undefined;
   3323                 val.writeToMemory(ty, mod, &buf) catch unreachable;
   3324                 return func.storeSimdImmd(buf);
   3325             },
   3326             .struct_type => {
   3327                 const struct_type = ip.loadStructType(ty.toIntern());
   3328                 // non-packed structs are not handled in this function because they
   3329                 // are by-ref types.
   3330                 assert(struct_type.layout == .@"packed");
   3331                 var buf: [8]u8 = .{0} ** 8; // zero the buffer so we do not read 0xaa as integer
   3332                 val.writeToPackedMemory(ty, mod, &buf, 0) catch unreachable;
   3333                 const backing_int_ty = Type.fromInterned(struct_type.backingIntType(ip).*);
   3334                 const int_val = try mod.intValue(
   3335                     backing_int_ty,
   3336                     mem.readInt(u64, &buf, .little),
   3337                 );
   3338                 return func.lowerConstant(int_val, backing_int_ty);
   3339             },
   3340             else => unreachable,
   3341         },
   3342         .un => |un| {
   3343             // in this case we have a packed union which will not be passed by reference.
   3344             const constant_ty = if (un.tag == .none)
   3345                 try ty.unionBackingType(mod)
   3346             else field_ty: {
   3347                 const union_obj = mod.typeToUnion(ty).?;
   3348                 const field_index = mod.unionTagFieldIndex(union_obj, Value.fromInterned(un.tag)).?;
   3349                 break :field_ty Type.fromInterned(union_obj.field_types.get(ip)[field_index]);
   3350             };
   3351             return func.lowerConstant(Value.fromInterned(un.val), constant_ty);
   3352         },
   3353         .memoized_call => unreachable,
   3354     }
   3355 }
   3356 
   3357 /// Stores the value as a 128bit-immediate value by storing it inside
   3358 /// the list and returning the index into this list as `WValue`.
   3359 fn storeSimdImmd(func: *CodeGen, value: [16]u8) !WValue {
   3360     const index = @as(u32, @intCast(func.simd_immediates.items.len));
   3361     try func.simd_immediates.append(func.gpa, value);
   3362     return WValue{ .imm128 = index };
   3363 }
   3364 
   3365 fn emitUndefined(func: *CodeGen, ty: Type) InnerError!WValue {
   3366     const mod = func.bin_file.base.comp.module.?;
   3367     const ip = &mod.intern_pool;
   3368     switch (ty.zigTypeTag(mod)) {
   3369         .Bool, .ErrorSet => return WValue{ .imm32 = 0xaaaaaaaa },
   3370         .Int, .Enum => switch (ty.intInfo(mod).bits) {
   3371             0...32 => return WValue{ .imm32 = 0xaaaaaaaa },
   3372             33...64 => return WValue{ .imm64 = 0xaaaaaaaaaaaaaaaa },
   3373             else => unreachable,
   3374         },
   3375         .Float => switch (ty.floatBits(func.target)) {
   3376             16 => return WValue{ .imm32 = 0xaaaaaaaa },
   3377             32 => return WValue{ .float32 = @as(f32, @bitCast(@as(u32, 0xaaaaaaaa))) },
   3378             64 => return WValue{ .float64 = @as(f64, @bitCast(@as(u64, 0xaaaaaaaaaaaaaaaa))) },
   3379             else => unreachable,
   3380         },
   3381         .Pointer => switch (func.arch()) {
   3382             .wasm32 => return WValue{ .imm32 = 0xaaaaaaaa },
   3383             .wasm64 => return WValue{ .imm64 = 0xaaaaaaaaaaaaaaaa },
   3384             else => unreachable,
   3385         },
   3386         .Optional => {
   3387             const pl_ty = ty.optionalChild(mod);
   3388             if (ty.optionalReprIsPayload(mod)) {
   3389                 return func.emitUndefined(pl_ty);
   3390             }
   3391             return WValue{ .imm32 = 0xaaaaaaaa };
   3392         },
   3393         .ErrorUnion => {
   3394             return WValue{ .imm32 = 0xaaaaaaaa };
   3395         },
   3396         .Struct => {
   3397             const packed_struct = mod.typeToPackedStruct(ty).?;
   3398             return func.emitUndefined(Type.fromInterned(packed_struct.backingIntType(ip).*));
   3399         },
   3400         else => return func.fail("Wasm TODO: emitUndefined for type: {}\n", .{ty.zigTypeTag(mod)}),
   3401     }
   3402 }
   3403 
   3404 /// Returns a `Value` as a signed 32 bit value.
   3405 /// It's illegal to provide a value with a type that cannot be represented
   3406 /// as an integer value.
   3407 fn valueAsI32(func: *const CodeGen, val: Value, ty: Type) i32 {
   3408     const mod = func.bin_file.base.comp.module.?;
   3409 
   3410     switch (val.ip_index) {
   3411         .none => {},
   3412         .bool_true => return 1,
   3413         .bool_false => return 0,
   3414         else => return switch (mod.intern_pool.indexToKey(val.ip_index)) {
   3415             .enum_tag => |enum_tag| intIndexAsI32(&mod.intern_pool, enum_tag.int, mod),
   3416             .int => |int| intStorageAsI32(int.storage, mod),
   3417             .ptr => |ptr| {
   3418                 assert(ptr.base_addr == .int);
   3419                 return @intCast(ptr.byte_offset);
   3420             },
   3421             .err => |err| @as(i32, @bitCast(@as(Module.ErrorInt, @intCast(mod.global_error_set.getIndex(err.name).?)))),
   3422             else => unreachable,
   3423         },
   3424     }
   3425 
   3426     return switch (ty.zigTypeTag(mod)) {
   3427         .ErrorSet => @as(i32, @bitCast(val.getErrorInt(mod))),
   3428         else => unreachable, // Programmer called this function for an illegal type
   3429     };
   3430 }
   3431 
   3432 fn intIndexAsI32(ip: *const InternPool, int: InternPool.Index, mod: *Module) i32 {
   3433     return intStorageAsI32(ip.indexToKey(int).int.storage, mod);
   3434 }
   3435 
   3436 fn intStorageAsI32(storage: InternPool.Key.Int.Storage, mod: *Module) i32 {
   3437     return switch (storage) {
   3438         .i64 => |x| @as(i32, @intCast(x)),
   3439         .u64 => |x| @as(i32, @bitCast(@as(u32, @intCast(x)))),
   3440         .big_int => unreachable,
   3441         .lazy_align => |ty| @as(i32, @bitCast(@as(u32, @intCast(Type.fromInterned(ty).abiAlignment(mod).toByteUnits() orelse 0)))),
   3442         .lazy_size => |ty| @as(i32, @bitCast(@as(u32, @intCast(Type.fromInterned(ty).abiSize(mod))))),
   3443     };
   3444 }
   3445 
   3446 fn airBlock(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   3447     const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
   3448     const extra = func.air.extraData(Air.Block, ty_pl.payload);
   3449     try func.lowerBlock(inst, ty_pl.ty.toType(), @ptrCast(func.air.extra[extra.end..][0..extra.data.body_len]));
   3450 }
   3451 
   3452 fn lowerBlock(func: *CodeGen, inst: Air.Inst.Index, block_ty: Type, body: []const Air.Inst.Index) InnerError!void {
   3453     const mod = func.bin_file.base.comp.module.?;
   3454     const wasm_block_ty = genBlockType(block_ty, mod);
   3455 
   3456     // if wasm_block_ty is non-empty, we create a register to store the temporary value
   3457     const block_result: WValue = if (wasm_block_ty != wasm.block_empty) blk: {
   3458         const ty: Type = if (isByRef(block_ty, mod)) Type.u32 else block_ty;
   3459         break :blk try func.ensureAllocLocal(ty); // make sure it's a clean local as it may never get overwritten
   3460     } else WValue.none;
   3461 
   3462     try func.startBlock(.block, wasm.block_empty);
   3463     // Here we set the current block idx, so breaks know the depth to jump
   3464     // to when breaking out.
   3465     try func.blocks.putNoClobber(func.gpa, inst, .{
   3466         .label = func.block_depth,
   3467         .value = block_result,
   3468     });
   3469 
   3470     try func.genBody(body);
   3471     try func.endBlock();
   3472 
   3473     const liveness = func.liveness.getBlock(inst);
   3474     try func.currentBranch().values.ensureUnusedCapacity(func.gpa, liveness.deaths.len);
   3475 
   3476     func.finishAir(inst, block_result, &.{});
   3477 }
   3478 
   3479 /// appends a new wasm block to the code section and increases the `block_depth` by 1
   3480 fn startBlock(func: *CodeGen, block_tag: wasm.Opcode, valtype: u8) !void {
   3481     func.block_depth += 1;
   3482     try func.addInst(.{
   3483         .tag = Mir.Inst.Tag.fromOpcode(block_tag),
   3484         .data = .{ .block_type = valtype },
   3485     });
   3486 }
   3487 
   3488 /// Ends the current wasm block and decreases the `block_depth` by 1
   3489 fn endBlock(func: *CodeGen) !void {
   3490     try func.addTag(.end);
   3491     func.block_depth -= 1;
   3492 }
   3493 
   3494 fn airLoop(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   3495     const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
   3496     const loop = func.air.extraData(Air.Block, ty_pl.payload);
   3497     const body: []const Air.Inst.Index = @ptrCast(func.air.extra[loop.end..][0..loop.data.body_len]);
   3498 
   3499     // result type of loop is always 'noreturn', meaning we can always
   3500     // emit the wasm type 'block_empty'.
   3501     try func.startBlock(.loop, wasm.block_empty);
   3502     try func.genBody(body);
   3503 
   3504     // breaking to the index of a loop block will continue the loop instead
   3505     try func.addLabel(.br, 0);
   3506     try func.endBlock();
   3507 
   3508     func.finishAir(inst, .none, &.{});
   3509 }
   3510 
   3511 fn airCondBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   3512     const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
   3513     const condition = try func.resolveInst(pl_op.operand);
   3514     const extra = func.air.extraData(Air.CondBr, pl_op.payload);
   3515     const then_body: []const Air.Inst.Index = @ptrCast(func.air.extra[extra.end..][0..extra.data.then_body_len]);
   3516     const else_body: []const Air.Inst.Index = @ptrCast(func.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]);
   3517     const liveness_condbr = func.liveness.getCondBr(inst);
   3518 
   3519     // result type is always noreturn, so use `block_empty` as type.
   3520     try func.startBlock(.block, wasm.block_empty);
   3521     // emit the conditional value
   3522     try func.emitWValue(condition);
   3523 
   3524     // we inserted the block in front of the condition
   3525     // so now check if condition matches. If not, break outside this block
   3526     // and continue with the then codepath
   3527     try func.addLabel(.br_if, 0);
   3528 
   3529     try func.branches.ensureUnusedCapacity(func.gpa, 2);
   3530     {
   3531         func.branches.appendAssumeCapacity(.{});
   3532         try func.currentBranch().values.ensureUnusedCapacity(func.gpa, @as(u32, @intCast(liveness_condbr.else_deaths.len)));
   3533         defer {
   3534             var else_stack = func.branches.pop();
   3535             else_stack.deinit(func.gpa);
   3536         }
   3537         try func.genBody(else_body);
   3538         try func.endBlock();
   3539     }
   3540 
   3541     // Outer block that matches the condition
   3542     {
   3543         func.branches.appendAssumeCapacity(.{});
   3544         try func.currentBranch().values.ensureUnusedCapacity(func.gpa, @as(u32, @intCast(liveness_condbr.then_deaths.len)));
   3545         defer {
   3546             var then_stack = func.branches.pop();
   3547             then_stack.deinit(func.gpa);
   3548         }
   3549         try func.genBody(then_body);
   3550     }
   3551 
   3552     func.finishAir(inst, .none, &.{});
   3553 }
   3554 
   3555 fn airCmp(func: *CodeGen, inst: Air.Inst.Index, op: std.math.CompareOperator) InnerError!void {
   3556     const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
   3557 
   3558     const lhs = try func.resolveInst(bin_op.lhs);
   3559     const rhs = try func.resolveInst(bin_op.rhs);
   3560     const operand_ty = func.typeOf(bin_op.lhs);
   3561     const result = try (try func.cmp(lhs, rhs, operand_ty, op)).toLocal(func, Type.u32); // comparison result is always 32 bits
   3562     func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
   3563 }
   3564 
   3565 /// Compares two operands.
   3566 /// Asserts rhs is not a stack value when the lhs isn't a stack value either
   3567 /// NOTE: This leaves the result on top of the stack, rather than a new local.
   3568 fn cmp(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, op: std.math.CompareOperator) InnerError!WValue {
   3569     assert(!(lhs != .stack and rhs == .stack));
   3570     const mod = func.bin_file.base.comp.module.?;
   3571     if (ty.zigTypeTag(mod) == .Optional and !ty.optionalReprIsPayload(mod)) {
   3572         const payload_ty = ty.optionalChild(mod);
   3573         if (payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
   3574             // When we hit this case, we must check the value of optionals
   3575             // that are not pointers. This means first checking against non-null for
   3576             // both lhs and rhs, as well as checking the payload are matching of lhs and rhs
   3577             return func.cmpOptionals(lhs, rhs, ty, op);
   3578         }
   3579     } else if (ty.isAnyFloat()) {
   3580         return func.cmpFloat(ty, lhs, rhs, op);
   3581     } else if (isByRef(ty, mod)) {
   3582         return func.cmpBigInt(lhs, rhs, ty, op);
   3583     }
   3584 
   3585     const signedness: std.builtin.Signedness = blk: {
   3586         // by default we tell the operand type is unsigned (i.e. bools and enum values)
   3587         if (ty.zigTypeTag(mod) != .Int) break :blk .unsigned;
   3588 
   3589         // incase of an actual integer, we emit the correct signedness
   3590         break :blk ty.intInfo(mod).signedness;
   3591     };
   3592     const extend_sign = blk: {
   3593         // do we need to extend the sign bit?
   3594         if (signedness != .signed) break :blk false;
   3595         if (op == .eq or op == .neq) break :blk false;
   3596         const int_bits = ty.intInfo(mod).bits;
   3597         const wasm_bits = toWasmBits(int_bits) orelse unreachable;
   3598         break :blk (wasm_bits != int_bits);
   3599     };
   3600 
   3601     const lhs_wasm = if (extend_sign)
   3602         try func.signExtendInt(lhs, ty)
   3603     else
   3604         lhs;
   3605 
   3606     const rhs_wasm = if (extend_sign)
   3607         try func.signExtendInt(rhs, ty)
   3608     else
   3609         rhs;
   3610 
   3611     // ensure that when we compare pointers, we emit
   3612     // the true pointer of a stack value, rather than the stack pointer.
   3613     try func.lowerToStack(lhs_wasm);
   3614     try func.lowerToStack(rhs_wasm);
   3615 
   3616     const opcode: wasm.Opcode = buildOpcode(.{
   3617         .valtype1 = typeToValtype(ty, mod),
   3618         .op = switch (op) {
   3619             .lt => .lt,
   3620             .lte => .le,
   3621             .eq => .eq,
   3622             .neq => .ne,
   3623             .gte => .ge,
   3624             .gt => .gt,
   3625         },
   3626         .signedness = signedness,
   3627     });
   3628     try func.addTag(Mir.Inst.Tag.fromOpcode(opcode));
   3629 
   3630     return WValue{ .stack = {} };
   3631 }
   3632 
   3633 /// Compares two floats.
   3634 /// NOTE: Leaves the result of the comparison on top of the stack.
   3635 fn cmpFloat(func: *CodeGen, ty: Type, lhs: WValue, rhs: WValue, cmp_op: std.math.CompareOperator) InnerError!WValue {
   3636     const float_bits = ty.floatBits(func.target);
   3637 
   3638     const op: Op = switch (cmp_op) {
   3639         .lt => .lt,
   3640         .lte => .le,
   3641         .eq => .eq,
   3642         .neq => .ne,
   3643         .gte => .ge,
   3644         .gt => .gt,
   3645     };
   3646 
   3647     switch (float_bits) {
   3648         16 => {
   3649             _ = try func.fpext(lhs, Type.f16, Type.f32);
   3650             _ = try func.fpext(rhs, Type.f16, Type.f32);
   3651             const opcode = buildOpcode(.{ .op = op, .valtype1 = .f32 });
   3652             try func.addTag(Mir.Inst.Tag.fromOpcode(opcode));
   3653             return .stack;
   3654         },
   3655         32, 64 => {
   3656             try func.emitWValue(lhs);
   3657             try func.emitWValue(rhs);
   3658             const val_type: wasm.Valtype = if (float_bits == 32) .f32 else .f64;
   3659             const opcode = buildOpcode(.{ .op = op, .valtype1 = val_type });
   3660             try func.addTag(Mir.Inst.Tag.fromOpcode(opcode));
   3661             return .stack;
   3662         },
   3663         80, 128 => {
   3664             var fn_name_buf: [32]u8 = undefined;
   3665             const fn_name = std.fmt.bufPrint(&fn_name_buf, "__{s}{s}f2", .{
   3666                 @tagName(op), target_util.compilerRtFloatAbbrev(float_bits),
   3667             }) catch unreachable;
   3668 
   3669             const result = try func.callIntrinsic(fn_name, &.{ ty.ip_index, ty.ip_index }, Type.bool, &.{ lhs, rhs });
   3670             return func.cmp(result, WValue{ .imm32 = 0 }, Type.i32, cmp_op);
   3671         },
   3672         else => unreachable,
   3673     }
   3674 }
   3675 
   3676 fn airCmpVector(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   3677     _ = inst;
   3678     return func.fail("TODO implement airCmpVector for wasm", .{});
   3679 }
   3680 
   3681 fn airCmpLtErrorsLen(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   3682     const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
   3683     const operand = try func.resolveInst(un_op);
   3684     const sym_index = try func.bin_file.getGlobalSymbol("__zig_errors_len", null);
   3685     const errors_len = WValue{ .memory = @intFromEnum(sym_index) };
   3686 
   3687     try func.emitWValue(operand);
   3688     const mod = func.bin_file.base.comp.module.?;
   3689     const err_int_ty = try mod.errorIntType();
   3690     const errors_len_val = try func.load(errors_len, err_int_ty, 0);
   3691     const result = try func.cmp(.stack, errors_len_val, err_int_ty, .lt);
   3692 
   3693     return func.finishAir(inst, try result.toLocal(func, Type.bool), &.{un_op});
   3694 }
   3695 
   3696 fn airBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   3697     const mod = func.bin_file.base.comp.module.?;
   3698     const br = func.air.instructions.items(.data)[@intFromEnum(inst)].br;
   3699     const block = func.blocks.get(br.block_inst).?;
   3700 
   3701     // if operand has codegen bits we should break with a value
   3702     if (func.typeOf(br.operand).hasRuntimeBitsIgnoreComptime(mod)) {
   3703         const operand = try func.resolveInst(br.operand);
   3704         try func.lowerToStack(operand);
   3705 
   3706         if (block.value != .none) {
   3707             try func.addLabel(.local_set, block.value.local.value);
   3708         }
   3709     }
   3710 
   3711     // We map every block to its block index.
   3712     // We then determine how far we have to jump to it by subtracting it from current block depth
   3713     const idx: u32 = func.block_depth - block.label;
   3714     try func.addLabel(.br, idx);
   3715 
   3716     func.finishAir(inst, .none, &.{br.operand});
   3717 }
   3718 
   3719 fn airNot(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   3720     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   3721 
   3722     const operand = try func.resolveInst(ty_op.operand);
   3723     const operand_ty = func.typeOf(ty_op.operand);
   3724     const mod = func.bin_file.base.comp.module.?;
   3725 
   3726     const result = result: {
   3727         if (operand_ty.zigTypeTag(mod) == .Bool) {
   3728             try func.emitWValue(operand);
   3729             try func.addTag(.i32_eqz);
   3730             const not_tmp = try func.allocLocal(operand_ty);
   3731             try func.addLabel(.local_set, not_tmp.local.value);
   3732             break :result not_tmp;
   3733         } else {
   3734             const operand_bits = operand_ty.intInfo(mod).bits;
   3735             const wasm_bits = toWasmBits(operand_bits) orelse {
   3736                 return func.fail("TODO: Implement binary NOT for integer with bitsize '{d}'", .{operand_bits});
   3737             };
   3738 
   3739             switch (wasm_bits) {
   3740                 32 => {
   3741                     const bin_op = try func.binOp(operand, .{ .imm32 = ~@as(u32, 0) }, operand_ty, .xor);
   3742                     break :result try (try func.wrapOperand(bin_op, operand_ty)).toLocal(func, operand_ty);
   3743                 },
   3744                 64 => {
   3745                     const bin_op = try func.binOp(operand, .{ .imm64 = ~@as(u64, 0) }, operand_ty, .xor);
   3746                     break :result try (try func.wrapOperand(bin_op, operand_ty)).toLocal(func, operand_ty);
   3747                 },
   3748                 128 => {
   3749                     const result_ptr = try func.allocStack(operand_ty);
   3750                     try func.emitWValue(result_ptr);
   3751                     const msb = try func.load(operand, Type.u64, 0);
   3752                     const msb_xor = try func.binOp(msb, .{ .imm64 = ~@as(u64, 0) }, Type.u64, .xor);
   3753                     try func.store(.{ .stack = {} }, msb_xor, Type.u64, 0 + result_ptr.offset());
   3754 
   3755                     try func.emitWValue(result_ptr);
   3756                     const lsb = try func.load(operand, Type.u64, 8);
   3757                     const lsb_xor = try func.binOp(lsb, .{ .imm64 = ~@as(u64, 0) }, Type.u64, .xor);
   3758                     try func.store(result_ptr, lsb_xor, Type.u64, 8 + result_ptr.offset());
   3759                     break :result result_ptr;
   3760                 },
   3761                 else => unreachable,
   3762             }
   3763         }
   3764     };
   3765     func.finishAir(inst, result, &.{ty_op.operand});
   3766 }
   3767 
   3768 fn airTrap(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   3769     try func.addTag(.@"unreachable");
   3770     func.finishAir(inst, .none, &.{});
   3771 }
   3772 
   3773 fn airBreakpoint(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   3774     // unsupported by wasm itfunc. Can be implemented once we support DWARF
   3775     // for wasm
   3776     try func.addTag(.@"unreachable");
   3777     func.finishAir(inst, .none, &.{});
   3778 }
   3779 
   3780 fn airUnreachable(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   3781     try func.addTag(.@"unreachable");
   3782     func.finishAir(inst, .none, &.{});
   3783 }
   3784 
   3785 fn airBitcast(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   3786     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   3787     const result = result: {
   3788         const operand = try func.resolveInst(ty_op.operand);
   3789         const wanted_ty = func.typeOfIndex(inst);
   3790         const given_ty = func.typeOf(ty_op.operand);
   3791         if (given_ty.isAnyFloat() or wanted_ty.isAnyFloat()) {
   3792             const bitcast_result = try func.bitcast(wanted_ty, given_ty, operand);
   3793             break :result try bitcast_result.toLocal(func, wanted_ty);
   3794         }
   3795         const mod = func.bin_file.base.comp.module.?;
   3796         if (isByRef(given_ty, mod) and !isByRef(wanted_ty, mod)) {
   3797             const loaded_memory = try func.load(operand, wanted_ty, 0);
   3798             break :result try loaded_memory.toLocal(func, wanted_ty);
   3799         }
   3800         if (!isByRef(given_ty, mod) and isByRef(wanted_ty, mod)) {
   3801             const stack_memory = try func.allocStack(wanted_ty);
   3802             try func.store(stack_memory, operand, given_ty, 0);
   3803             break :result stack_memory;
   3804         }
   3805         break :result func.reuseOperand(ty_op.operand, operand);
   3806     };
   3807     func.finishAir(inst, result, &.{ty_op.operand});
   3808 }
   3809 
   3810 fn bitcast(func: *CodeGen, wanted_ty: Type, given_ty: Type, operand: WValue) InnerError!WValue {
   3811     const mod = func.bin_file.base.comp.module.?;
   3812     // if we bitcast a float to or from an integer we must use the 'reinterpret' instruction
   3813     if (!(wanted_ty.isAnyFloat() or given_ty.isAnyFloat())) return operand;
   3814     if (wanted_ty.ip_index == .f16_type or given_ty.ip_index == .f16_type) return operand;
   3815     if (wanted_ty.bitSize(mod) > 64) return operand;
   3816     assert((wanted_ty.isInt(mod) and given_ty.isAnyFloat()) or (wanted_ty.isAnyFloat() and given_ty.isInt(mod)));
   3817 
   3818     const opcode = buildOpcode(.{
   3819         .op = .reinterpret,
   3820         .valtype1 = typeToValtype(wanted_ty, mod),
   3821         .valtype2 = typeToValtype(given_ty, mod),
   3822     });
   3823     try func.emitWValue(operand);
   3824     try func.addTag(Mir.Inst.Tag.fromOpcode(opcode));
   3825     return WValue{ .stack = {} };
   3826 }
   3827 
   3828 fn airStructFieldPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   3829     const mod = func.bin_file.base.comp.module.?;
   3830     const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
   3831     const extra = func.air.extraData(Air.StructField, ty_pl.payload);
   3832 
   3833     const struct_ptr = try func.resolveInst(extra.data.struct_operand);
   3834     const struct_ptr_ty = func.typeOf(extra.data.struct_operand);
   3835     const struct_ty = struct_ptr_ty.childType(mod);
   3836     const result = try func.structFieldPtr(inst, extra.data.struct_operand, struct_ptr, struct_ptr_ty, struct_ty, extra.data.field_index);
   3837     func.finishAir(inst, result, &.{extra.data.struct_operand});
   3838 }
   3839 
   3840 fn airStructFieldPtrIndex(func: *CodeGen, inst: Air.Inst.Index, index: u32) InnerError!void {
   3841     const mod = func.bin_file.base.comp.module.?;
   3842     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   3843     const struct_ptr = try func.resolveInst(ty_op.operand);
   3844     const struct_ptr_ty = func.typeOf(ty_op.operand);
   3845     const struct_ty = struct_ptr_ty.childType(mod);
   3846 
   3847     const result = try func.structFieldPtr(inst, ty_op.operand, struct_ptr, struct_ptr_ty, struct_ty, index);
   3848     func.finishAir(inst, result, &.{ty_op.operand});
   3849 }
   3850 
   3851 fn structFieldPtr(
   3852     func: *CodeGen,
   3853     inst: Air.Inst.Index,
   3854     ref: Air.Inst.Ref,
   3855     struct_ptr: WValue,
   3856     struct_ptr_ty: Type,
   3857     struct_ty: Type,
   3858     index: u32,
   3859 ) InnerError!WValue {
   3860     const mod = func.bin_file.base.comp.module.?;
   3861     const result_ty = func.typeOfIndex(inst);
   3862     const struct_ptr_ty_info = struct_ptr_ty.ptrInfo(mod);
   3863 
   3864     const offset = switch (struct_ty.containerLayout(mod)) {
   3865         .@"packed" => switch (struct_ty.zigTypeTag(mod)) {
   3866             .Struct => offset: {
   3867                 if (result_ty.ptrInfo(mod).packed_offset.host_size != 0) {
   3868                     break :offset @as(u32, 0);
   3869                 }
   3870                 const struct_type = mod.typeToStruct(struct_ty).?;
   3871                 break :offset @divExact(mod.structPackedFieldBitOffset(struct_type, index) + struct_ptr_ty_info.packed_offset.bit_offset, 8);
   3872             },
   3873             .Union => 0,
   3874             else => unreachable,
   3875         },
   3876         else => struct_ty.structFieldOffset(index, mod),
   3877     };
   3878     // save a load and store when we can simply reuse the operand
   3879     if (offset == 0) {
   3880         return func.reuseOperand(ref, struct_ptr);
   3881     }
   3882     switch (struct_ptr) {
   3883         .stack_offset => |stack_offset| {
   3884             return WValue{ .stack_offset = .{ .value = stack_offset.value + @as(u32, @intCast(offset)), .references = 1 } };
   3885         },
   3886         else => return func.buildPointerOffset(struct_ptr, offset, .new),
   3887     }
   3888 }
   3889 
   3890 fn airStructFieldVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   3891     const mod = func.bin_file.base.comp.module.?;
   3892     const ip = &mod.intern_pool;
   3893     const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
   3894     const struct_field = func.air.extraData(Air.StructField, ty_pl.payload).data;
   3895 
   3896     const struct_ty = func.typeOf(struct_field.struct_operand);
   3897     const operand = try func.resolveInst(struct_field.struct_operand);
   3898     const field_index = struct_field.field_index;
   3899     const field_ty = struct_ty.structFieldType(field_index, mod);
   3900     if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) return func.finishAir(inst, .none, &.{struct_field.struct_operand});
   3901 
   3902     const result = switch (struct_ty.containerLayout(mod)) {
   3903         .@"packed" => switch (struct_ty.zigTypeTag(mod)) {
   3904             .Struct => result: {
   3905                 const packed_struct = mod.typeToPackedStruct(struct_ty).?;
   3906                 const offset = mod.structPackedFieldBitOffset(packed_struct, field_index);
   3907                 const backing_ty = Type.fromInterned(packed_struct.backingIntType(ip).*);
   3908                 const wasm_bits = toWasmBits(backing_ty.intInfo(mod).bits) orelse {
   3909                     return func.fail("TODO: airStructFieldVal for packed structs larger than 128 bits", .{});
   3910                 };
   3911                 const const_wvalue = if (wasm_bits == 32)
   3912                     WValue{ .imm32 = offset }
   3913                 else if (wasm_bits == 64)
   3914                     WValue{ .imm64 = offset }
   3915                 else
   3916                     return func.fail("TODO: airStructFieldVal for packed structs larger than 64 bits", .{});
   3917 
   3918                 // for first field we don't require any shifting
   3919                 const shifted_value = if (offset == 0)
   3920                     operand
   3921                 else
   3922                     try func.binOp(operand, const_wvalue, backing_ty, .shr);
   3923 
   3924                 if (field_ty.zigTypeTag(mod) == .Float) {
   3925                     const int_type = try mod.intType(.unsigned, @as(u16, @intCast(field_ty.bitSize(mod))));
   3926                     const truncated = try func.trunc(shifted_value, int_type, backing_ty);
   3927                     const bitcasted = try func.bitcast(field_ty, int_type, truncated);
   3928                     break :result try bitcasted.toLocal(func, field_ty);
   3929                 } else if (field_ty.isPtrAtRuntime(mod) and packed_struct.field_types.len == 1) {
   3930                     // In this case we do not have to perform any transformations,
   3931                     // we can simply reuse the operand.
   3932                     break :result func.reuseOperand(struct_field.struct_operand, operand);
   3933                 } else if (field_ty.isPtrAtRuntime(mod)) {
   3934                     const int_type = try mod.intType(.unsigned, @as(u16, @intCast(field_ty.bitSize(mod))));
   3935                     const truncated = try func.trunc(shifted_value, int_type, backing_ty);
   3936                     break :result try truncated.toLocal(func, field_ty);
   3937                 }
   3938                 const truncated = try func.trunc(shifted_value, field_ty, backing_ty);
   3939                 break :result try truncated.toLocal(func, field_ty);
   3940             },
   3941             .Union => result: {
   3942                 if (isByRef(struct_ty, mod)) {
   3943                     if (!isByRef(field_ty, mod)) {
   3944                         const val = try func.load(operand, field_ty, 0);
   3945                         break :result try val.toLocal(func, field_ty);
   3946                     } else {
   3947                         const new_stack_val = try func.allocStack(field_ty);
   3948                         try func.store(new_stack_val, operand, field_ty, 0);
   3949                         break :result new_stack_val;
   3950                     }
   3951                 }
   3952 
   3953                 const union_int_type = try mod.intType(.unsigned, @as(u16, @intCast(struct_ty.bitSize(mod))));
   3954                 if (field_ty.zigTypeTag(mod) == .Float) {
   3955                     const int_type = try mod.intType(.unsigned, @as(u16, @intCast(field_ty.bitSize(mod))));
   3956                     const truncated = try func.trunc(operand, int_type, union_int_type);
   3957                     const bitcasted = try func.bitcast(field_ty, int_type, truncated);
   3958                     break :result try bitcasted.toLocal(func, field_ty);
   3959                 } else if (field_ty.isPtrAtRuntime(mod)) {
   3960                     const int_type = try mod.intType(.unsigned, @as(u16, @intCast(field_ty.bitSize(mod))));
   3961                     const truncated = try func.trunc(operand, int_type, union_int_type);
   3962                     break :result try truncated.toLocal(func, field_ty);
   3963                 }
   3964                 const truncated = try func.trunc(operand, field_ty, union_int_type);
   3965                 break :result try truncated.toLocal(func, field_ty);
   3966             },
   3967             else => unreachable,
   3968         },
   3969         else => result: {
   3970             const offset = std.math.cast(u32, struct_ty.structFieldOffset(field_index, mod)) orelse {
   3971                 return func.fail("Field type '{}' too big to fit into stack frame", .{field_ty.fmt(mod)});
   3972             };
   3973             if (isByRef(field_ty, mod)) {
   3974                 switch (operand) {
   3975                     .stack_offset => |stack_offset| {
   3976                         break :result WValue{ .stack_offset = .{ .value = stack_offset.value + offset, .references = 1 } };
   3977                     },
   3978                     else => break :result try func.buildPointerOffset(operand, offset, .new),
   3979                 }
   3980             }
   3981             const field = try func.load(operand, field_ty, offset);
   3982             break :result try field.toLocal(func, field_ty);
   3983         },
   3984     };
   3985 
   3986     func.finishAir(inst, result, &.{struct_field.struct_operand});
   3987 }
   3988 
   3989 fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   3990     const mod = func.bin_file.base.comp.module.?;
   3991     // result type is always 'noreturn'
   3992     const blocktype = wasm.block_empty;
   3993     const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
   3994     const target = try func.resolveInst(pl_op.operand);
   3995     const target_ty = func.typeOf(pl_op.operand);
   3996     const switch_br = func.air.extraData(Air.SwitchBr, pl_op.payload);
   3997     const liveness = try func.liveness.getSwitchBr(func.gpa, inst, switch_br.data.cases_len + 1);
   3998     defer func.gpa.free(liveness.deaths);
   3999 
   4000     var extra_index: usize = switch_br.end;
   4001     var case_i: u32 = 0;
   4002 
   4003     // a list that maps each value with its value and body based on the order inside the list.
   4004     const CaseValue = struct { integer: i32, value: Value };
   4005     var case_list = try std.ArrayList(struct {
   4006         values: []const CaseValue,
   4007         body: []const Air.Inst.Index,
   4008     }).initCapacity(func.gpa, switch_br.data.cases_len);
   4009     defer for (case_list.items) |case| {
   4010         func.gpa.free(case.values);
   4011     } else case_list.deinit();
   4012 
   4013     var lowest_maybe: ?i32 = null;
   4014     var highest_maybe: ?i32 = null;
   4015     while (case_i < switch_br.data.cases_len) : (case_i += 1) {
   4016         const case = func.air.extraData(Air.SwitchBr.Case, extra_index);
   4017         const items: []const Air.Inst.Ref = @ptrCast(func.air.extra[case.end..][0..case.data.items_len]);
   4018         const case_body: []const Air.Inst.Index = @ptrCast(func.air.extra[case.end + items.len ..][0..case.data.body_len]);
   4019         extra_index = case.end + items.len + case_body.len;
   4020         const values = try func.gpa.alloc(CaseValue, items.len);
   4021         errdefer func.gpa.free(values);
   4022 
   4023         for (items, 0..) |ref, i| {
   4024             const item_val = (try func.air.value(ref, mod)).?;
   4025             const int_val = func.valueAsI32(item_val, target_ty);
   4026             if (lowest_maybe == null or int_val < lowest_maybe.?) {
   4027                 lowest_maybe = int_val;
   4028             }
   4029             if (highest_maybe == null or int_val > highest_maybe.?) {
   4030                 highest_maybe = int_val;
   4031             }
   4032             values[i] = .{ .integer = int_val, .value = item_val };
   4033         }
   4034 
   4035         case_list.appendAssumeCapacity(.{ .values = values, .body = case_body });
   4036         try func.startBlock(.block, blocktype);
   4037     }
   4038 
   4039     // When highest and lowest are null, we have no cases and can use a jump table
   4040     const lowest = lowest_maybe orelse 0;
   4041     const highest = highest_maybe orelse 0;
   4042     // When the highest and lowest values are seperated by '50',
   4043     // we define it as sparse and use an if/else-chain, rather than a jump table.
   4044     // When the target is an integer size larger than u32, we have no way to use the value
   4045     // as an index, therefore we also use an if/else-chain for those cases.
   4046     // TODO: Benchmark this to find a proper value, LLVM seems to draw the line at '40~45'.
   4047     const is_sparse = highest - lowest > 50 or target_ty.bitSize(mod) > 32;
   4048 
   4049     const else_body: []const Air.Inst.Index = @ptrCast(func.air.extra[extra_index..][0..switch_br.data.else_body_len]);
   4050     const has_else_body = else_body.len != 0;
   4051     if (has_else_body) {
   4052         try func.startBlock(.block, blocktype);
   4053     }
   4054 
   4055     if (!is_sparse) {
   4056         // Generate the jump table 'br_table' when the prongs are not sparse.
   4057         // The value 'target' represents the index into the table.
   4058         // Each index in the table represents a label to the branch
   4059         // to jump to.
   4060         try func.startBlock(.block, blocktype);
   4061         try func.emitWValue(target);
   4062         if (lowest < 0) {
   4063             // since br_table works using indexes, starting from '0', we must ensure all values
   4064             // we put inside, are atleast 0.
   4065             try func.addImm32(lowest * -1);
   4066             try func.addTag(.i32_add);
   4067         } else if (lowest > 0) {
   4068             // make the index start from 0 by substracting the lowest value
   4069             try func.addImm32(lowest);
   4070             try func.addTag(.i32_sub);
   4071         }
   4072 
   4073         // Account for default branch so always add '1'
   4074         const depth = @as(u32, @intCast(highest - lowest + @intFromBool(has_else_body))) + 1;
   4075         const jump_table: Mir.JumpTable = .{ .length = depth };
   4076         const table_extra_index = try func.addExtra(jump_table);
   4077         try func.addInst(.{ .tag = .br_table, .data = .{ .payload = table_extra_index } });
   4078         try func.mir_extra.ensureUnusedCapacity(func.gpa, depth);
   4079         var value = lowest;
   4080         while (value <= highest) : (value += 1) {
   4081             // idx represents the branch we jump to
   4082             const idx = blk: {
   4083                 for (case_list.items, 0..) |case, idx| {
   4084                     for (case.values) |case_value| {
   4085                         if (case_value.integer == value) break :blk @as(u32, @intCast(idx));
   4086                     }
   4087                 }
   4088                 // error sets are almost always sparse so we use the default case
   4089                 // for errors that are not present in any branch. This is fine as this default
   4090                 // case will never be hit for those cases but we do save runtime cost and size
   4091                 // by using a jump table for this instead of if-else chains.
   4092                 break :blk if (has_else_body or target_ty.zigTypeTag(mod) == .ErrorSet) case_i else unreachable;
   4093             };
   4094             func.mir_extra.appendAssumeCapacity(idx);
   4095         } else if (has_else_body) {
   4096             func.mir_extra.appendAssumeCapacity(case_i); // default branch
   4097         }
   4098         try func.endBlock();
   4099     }
   4100 
   4101     const signedness: std.builtin.Signedness = blk: {
   4102         // by default we tell the operand type is unsigned (i.e. bools and enum values)
   4103         if (target_ty.zigTypeTag(mod) != .Int) break :blk .unsigned;
   4104 
   4105         // incase of an actual integer, we emit the correct signedness
   4106         break :blk target_ty.intInfo(mod).signedness;
   4107     };
   4108 
   4109     try func.branches.ensureUnusedCapacity(func.gpa, case_list.items.len + @intFromBool(has_else_body));
   4110     for (case_list.items, 0..) |case, index| {
   4111         // when sparse, we use if/else-chain, so emit conditional checks
   4112         if (is_sparse) {
   4113             // for single value prong we can emit a simple if
   4114             if (case.values.len == 1) {
   4115                 try func.emitWValue(target);
   4116                 const val = try func.lowerConstant(case.values[0].value, target_ty);
   4117                 try func.emitWValue(val);
   4118                 const opcode = buildOpcode(.{
   4119                     .valtype1 = typeToValtype(target_ty, mod),
   4120                     .op = .ne, // not equal, because we want to jump out of this block if it does not match the condition.
   4121                     .signedness = signedness,
   4122                 });
   4123                 try func.addTag(Mir.Inst.Tag.fromOpcode(opcode));
   4124                 try func.addLabel(.br_if, 0);
   4125             } else {
   4126                 // in multi-value prongs we must check if any prongs match the target value.
   4127                 try func.startBlock(.block, blocktype);
   4128                 for (case.values) |value| {
   4129                     try func.emitWValue(target);
   4130                     const val = try func.lowerConstant(value.value, target_ty);
   4131                     try func.emitWValue(val);
   4132                     const opcode = buildOpcode(.{
   4133                         .valtype1 = typeToValtype(target_ty, mod),
   4134                         .op = .eq,
   4135                         .signedness = signedness,
   4136                     });
   4137                     try func.addTag(Mir.Inst.Tag.fromOpcode(opcode));
   4138                     try func.addLabel(.br_if, 0);
   4139                 }
   4140                 // value did not match any of the prong values
   4141                 try func.addLabel(.br, 1);
   4142                 try func.endBlock();
   4143             }
   4144         }
   4145         func.branches.appendAssumeCapacity(.{});
   4146         try func.currentBranch().values.ensureUnusedCapacity(func.gpa, liveness.deaths[index].len);
   4147         defer {
   4148             var case_branch = func.branches.pop();
   4149             case_branch.deinit(func.gpa);
   4150         }
   4151         try func.genBody(case.body);
   4152         try func.endBlock();
   4153     }
   4154 
   4155     if (has_else_body) {
   4156         func.branches.appendAssumeCapacity(.{});
   4157         const else_deaths = liveness.deaths.len - 1;
   4158         try func.currentBranch().values.ensureUnusedCapacity(func.gpa, liveness.deaths[else_deaths].len);
   4159         defer {
   4160             var else_branch = func.branches.pop();
   4161             else_branch.deinit(func.gpa);
   4162         }
   4163         try func.genBody(else_body);
   4164         try func.endBlock();
   4165     }
   4166     func.finishAir(inst, .none, &.{});
   4167 }
   4168 
   4169 fn airIsErr(func: *CodeGen, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!void {
   4170     const mod = func.bin_file.base.comp.module.?;
   4171     const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
   4172     const operand = try func.resolveInst(un_op);
   4173     const err_union_ty = func.typeOf(un_op);
   4174     const pl_ty = err_union_ty.errorUnionPayload(mod);
   4175 
   4176     const result = result: {
   4177         if (err_union_ty.errorUnionSet(mod).errorSetIsEmpty(mod)) {
   4178             switch (opcode) {
   4179                 .i32_ne => break :result WValue{ .imm32 = 0 },
   4180                 .i32_eq => break :result WValue{ .imm32 = 1 },
   4181                 else => unreachable,
   4182             }
   4183         }
   4184 
   4185         try func.emitWValue(operand);
   4186         if (pl_ty.hasRuntimeBitsIgnoreComptime(mod)) {
   4187             try func.addMemArg(.i32_load16_u, .{
   4188                 .offset = operand.offset() + @as(u32, @intCast(errUnionErrorOffset(pl_ty, mod))),
   4189                 .alignment = @intCast(Type.anyerror.abiAlignment(mod).toByteUnits().?),
   4190             });
   4191         }
   4192 
   4193         // Compare the error value with '0'
   4194         try func.addImm32(0);
   4195         try func.addTag(Mir.Inst.Tag.fromOpcode(opcode));
   4196 
   4197         const is_err_tmp = try func.allocLocal(Type.i32);
   4198         try func.addLabel(.local_set, is_err_tmp.local.value);
   4199         break :result is_err_tmp;
   4200     };
   4201     func.finishAir(inst, result, &.{un_op});
   4202 }
   4203 
   4204 fn airUnwrapErrUnionPayload(func: *CodeGen, inst: Air.Inst.Index, op_is_ptr: bool) InnerError!void {
   4205     const mod = func.bin_file.base.comp.module.?;
   4206     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   4207 
   4208     const operand = try func.resolveInst(ty_op.operand);
   4209     const op_ty = func.typeOf(ty_op.operand);
   4210     const err_ty = if (op_is_ptr) op_ty.childType(mod) else op_ty;
   4211     const payload_ty = err_ty.errorUnionPayload(mod);
   4212 
   4213     const result = result: {
   4214         if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
   4215             if (op_is_ptr) {
   4216                 break :result func.reuseOperand(ty_op.operand, operand);
   4217             }
   4218             break :result WValue{ .none = {} };
   4219         }
   4220 
   4221         const pl_offset = @as(u32, @intCast(errUnionPayloadOffset(payload_ty, mod)));
   4222         if (op_is_ptr or isByRef(payload_ty, mod)) {
   4223             break :result try func.buildPointerOffset(operand, pl_offset, .new);
   4224         }
   4225 
   4226         const payload = try func.load(operand, payload_ty, pl_offset);
   4227         break :result try payload.toLocal(func, payload_ty);
   4228     };
   4229     func.finishAir(inst, result, &.{ty_op.operand});
   4230 }
   4231 
   4232 fn airUnwrapErrUnionError(func: *CodeGen, inst: Air.Inst.Index, op_is_ptr: bool) InnerError!void {
   4233     const mod = func.bin_file.base.comp.module.?;
   4234     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   4235 
   4236     const operand = try func.resolveInst(ty_op.operand);
   4237     const op_ty = func.typeOf(ty_op.operand);
   4238     const err_ty = if (op_is_ptr) op_ty.childType(mod) else op_ty;
   4239     const payload_ty = err_ty.errorUnionPayload(mod);
   4240 
   4241     const result = result: {
   4242         if (err_ty.errorUnionSet(mod).errorSetIsEmpty(mod)) {
   4243             break :result WValue{ .imm32 = 0 };
   4244         }
   4245 
   4246         if (op_is_ptr or !payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
   4247             break :result func.reuseOperand(ty_op.operand, operand);
   4248         }
   4249 
   4250         const error_val = try func.load(operand, Type.anyerror, @as(u32, @intCast(errUnionErrorOffset(payload_ty, mod))));
   4251         break :result try error_val.toLocal(func, Type.anyerror);
   4252     };
   4253     func.finishAir(inst, result, &.{ty_op.operand});
   4254 }
   4255 
   4256 fn airWrapErrUnionPayload(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   4257     const mod = func.bin_file.base.comp.module.?;
   4258     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   4259 
   4260     const operand = try func.resolveInst(ty_op.operand);
   4261     const err_ty = func.typeOfIndex(inst);
   4262 
   4263     const pl_ty = func.typeOf(ty_op.operand);
   4264     const result = result: {
   4265         if (!pl_ty.hasRuntimeBitsIgnoreComptime(mod)) {
   4266             break :result func.reuseOperand(ty_op.operand, operand);
   4267         }
   4268 
   4269         const err_union = try func.allocStack(err_ty);
   4270         const payload_ptr = try func.buildPointerOffset(err_union, @as(u32, @intCast(errUnionPayloadOffset(pl_ty, mod))), .new);
   4271         try func.store(payload_ptr, operand, pl_ty, 0);
   4272 
   4273         // ensure we also write '0' to the error part, so any present stack value gets overwritten by it.
   4274         try func.emitWValue(err_union);
   4275         try func.addImm32(0);
   4276         const err_val_offset = @as(u32, @intCast(errUnionErrorOffset(pl_ty, mod)));
   4277         try func.addMemArg(.i32_store16, .{
   4278             .offset = err_union.offset() + err_val_offset,
   4279             .alignment = 2,
   4280         });
   4281         break :result err_union;
   4282     };
   4283     func.finishAir(inst, result, &.{ty_op.operand});
   4284 }
   4285 
   4286 fn airWrapErrUnionErr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   4287     const mod = func.bin_file.base.comp.module.?;
   4288     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   4289 
   4290     const operand = try func.resolveInst(ty_op.operand);
   4291     const err_ty = ty_op.ty.toType();
   4292     const pl_ty = err_ty.errorUnionPayload(mod);
   4293 
   4294     const result = result: {
   4295         if (!pl_ty.hasRuntimeBitsIgnoreComptime(mod)) {
   4296             break :result func.reuseOperand(ty_op.operand, operand);
   4297         }
   4298 
   4299         const err_union = try func.allocStack(err_ty);
   4300         // store error value
   4301         try func.store(err_union, operand, Type.anyerror, @as(u32, @intCast(errUnionErrorOffset(pl_ty, mod))));
   4302 
   4303         // write 'undefined' to the payload
   4304         const payload_ptr = try func.buildPointerOffset(err_union, @as(u32, @intCast(errUnionPayloadOffset(pl_ty, mod))), .new);
   4305         const len = @as(u32, @intCast(err_ty.errorUnionPayload(mod).abiSize(mod)));
   4306         try func.memset(Type.u8, payload_ptr, .{ .imm32 = len }, .{ .imm32 = 0xaa });
   4307 
   4308         break :result err_union;
   4309     };
   4310     func.finishAir(inst, result, &.{ty_op.operand});
   4311 }
   4312 
   4313 fn airIntcast(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   4314     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   4315 
   4316     const ty = ty_op.ty.toType();
   4317     const operand = try func.resolveInst(ty_op.operand);
   4318     const operand_ty = func.typeOf(ty_op.operand);
   4319     const mod = func.bin_file.base.comp.module.?;
   4320     if (ty.zigTypeTag(mod) == .Vector or operand_ty.zigTypeTag(mod) == .Vector) {
   4321         return func.fail("todo Wasm intcast for vectors", .{});
   4322     }
   4323     if (ty.abiSize(mod) > 16 or operand_ty.abiSize(mod) > 16) {
   4324         return func.fail("todo Wasm intcast for bitsize > 128", .{});
   4325     }
   4326 
   4327     const op_bits = toWasmBits(@as(u16, @intCast(operand_ty.bitSize(mod)))).?;
   4328     const wanted_bits = toWasmBits(@as(u16, @intCast(ty.bitSize(mod)))).?;
   4329     const result = if (op_bits == wanted_bits and !ty.isSignedInt(mod))
   4330         func.reuseOperand(ty_op.operand, operand)
   4331     else
   4332         try (try func.intcast(operand, operand_ty, ty)).toLocal(func, ty);
   4333 
   4334     func.finishAir(inst, result, &.{});
   4335 }
   4336 
   4337 /// Upcasts or downcasts an integer based on the given and wanted types,
   4338 /// and stores the result in a new operand.
   4339 /// Asserts type's bitsize <= 128
   4340 /// NOTE: May leave the result on the top of the stack.
   4341 fn intcast(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerError!WValue {
   4342     const mod = func.bin_file.base.comp.module.?;
   4343     const given_bitsize = @as(u16, @intCast(given.bitSize(mod)));
   4344     const wanted_bitsize = @as(u16, @intCast(wanted.bitSize(mod)));
   4345     assert(given_bitsize <= 128);
   4346     assert(wanted_bitsize <= 128);
   4347 
   4348     const op_bits = toWasmBits(given_bitsize).?;
   4349     const wanted_bits = toWasmBits(wanted_bitsize).?;
   4350     if (op_bits == wanted_bits) {
   4351         if (given.isSignedInt(mod)) {
   4352             if (given_bitsize < wanted_bitsize) {
   4353                 // signed integers are stored as two's complement,
   4354                 // when we upcast from a smaller integer to larger
   4355                 // integers, we must get its absolute value similar to
   4356                 // i64_extend_i32_s instruction.
   4357                 return func.signExtendInt(operand, given);
   4358             }
   4359             return func.wrapOperand(operand, wanted);
   4360         }
   4361         return operand;
   4362     }
   4363 
   4364     if (op_bits > 32 and op_bits <= 64 and wanted_bits == 32) {
   4365         try func.emitWValue(operand);
   4366         try func.addTag(.i32_wrap_i64);
   4367         if (given.isSignedInt(mod) and wanted_bitsize < 32)
   4368             return func.wrapOperand(.{ .stack = {} }, wanted)
   4369         else
   4370             return WValue{ .stack = {} };
   4371     } else if (op_bits == 32 and wanted_bits > 32 and wanted_bits <= 64) {
   4372         const operand32 = if (given_bitsize < 32 and wanted.isSignedInt(mod))
   4373             try func.signExtendInt(operand, given)
   4374         else
   4375             operand;
   4376         try func.emitWValue(operand32);
   4377         try func.addTag(if (wanted.isSignedInt(mod)) .i64_extend_i32_s else .i64_extend_i32_u);
   4378         if (given.isSignedInt(mod) and wanted_bitsize < 64)
   4379             return func.wrapOperand(.{ .stack = {} }, wanted)
   4380         else
   4381             return WValue{ .stack = {} };
   4382     } else if (wanted_bits == 128) {
   4383         // for 128bit integers we store the integer in the virtual stack, rather than a local
   4384         const stack_ptr = try func.allocStack(wanted);
   4385         try func.emitWValue(stack_ptr);
   4386 
   4387         // for 32 bit integers, we first coerce the value into a 64 bit integer before storing it
   4388         // meaning less store operations are required.
   4389         const lhs = if (op_bits == 32) blk: {
   4390             break :blk try func.intcast(operand, given, if (wanted.isSignedInt(mod)) Type.i64 else Type.u64);
   4391         } else operand;
   4392 
   4393         // store msb first
   4394         try func.store(.{ .stack = {} }, lhs, Type.u64, 0 + stack_ptr.offset());
   4395 
   4396         // For signed integers we shift msb by 63 (64bit integer - 1 sign bit) and store remaining value
   4397         if (wanted.isSignedInt(mod)) {
   4398             try func.emitWValue(stack_ptr);
   4399             const shr = try func.binOp(lhs, .{ .imm64 = 63 }, Type.i64, .shr);
   4400             try func.store(.{ .stack = {} }, shr, Type.u64, 8 + stack_ptr.offset());
   4401         } else {
   4402             // Ensure memory of lsb is zero'd
   4403             try func.store(stack_ptr, .{ .imm64 = 0 }, Type.u64, 8);
   4404         }
   4405         return stack_ptr;
   4406     } else return func.load(operand, wanted, 0);
   4407 }
   4408 
   4409 fn airIsNull(func: *CodeGen, inst: Air.Inst.Index, opcode: wasm.Opcode, op_kind: enum { value, ptr }) InnerError!void {
   4410     const mod = func.bin_file.base.comp.module.?;
   4411     const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
   4412     const operand = try func.resolveInst(un_op);
   4413 
   4414     const op_ty = func.typeOf(un_op);
   4415     const optional_ty = if (op_kind == .ptr) op_ty.childType(mod) else op_ty;
   4416     const is_null = try func.isNull(operand, optional_ty, opcode);
   4417     const result = try is_null.toLocal(func, optional_ty);
   4418     func.finishAir(inst, result, &.{un_op});
   4419 }
   4420 
   4421 /// For a given type and operand, checks if it's considered `null`.
   4422 /// NOTE: Leaves the result on the stack
   4423 fn isNull(func: *CodeGen, operand: WValue, optional_ty: Type, opcode: wasm.Opcode) InnerError!WValue {
   4424     const mod = func.bin_file.base.comp.module.?;
   4425     try func.emitWValue(operand);
   4426     const payload_ty = optional_ty.optionalChild(mod);
   4427     if (!optional_ty.optionalReprIsPayload(mod)) {
   4428         // When payload is zero-bits, we can treat operand as a value, rather than
   4429         // a pointer to the stack value
   4430         if (payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
   4431             const offset = std.math.cast(u32, payload_ty.abiSize(mod)) orelse {
   4432                 return func.fail("Optional type {} too big to fit into stack frame", .{optional_ty.fmt(mod)});
   4433             };
   4434             try func.addMemArg(.i32_load8_u, .{ .offset = operand.offset() + offset, .alignment = 1 });
   4435         }
   4436     } else if (payload_ty.isSlice(mod)) {
   4437         switch (func.arch()) {
   4438             .wasm32 => try func.addMemArg(.i32_load, .{ .offset = operand.offset(), .alignment = 4 }),
   4439             .wasm64 => try func.addMemArg(.i64_load, .{ .offset = operand.offset(), .alignment = 8 }),
   4440             else => unreachable,
   4441         }
   4442     }
   4443 
   4444     // Compare the null value with '0'
   4445     try func.addImm32(0);
   4446     try func.addTag(Mir.Inst.Tag.fromOpcode(opcode));
   4447 
   4448     return WValue{ .stack = {} };
   4449 }
   4450 
   4451 fn airOptionalPayload(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   4452     const mod = func.bin_file.base.comp.module.?;
   4453     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   4454     const opt_ty = func.typeOf(ty_op.operand);
   4455     const payload_ty = func.typeOfIndex(inst);
   4456     if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
   4457         return func.finishAir(inst, .none, &.{ty_op.operand});
   4458     }
   4459 
   4460     const result = result: {
   4461         const operand = try func.resolveInst(ty_op.operand);
   4462         if (opt_ty.optionalReprIsPayload(mod)) break :result func.reuseOperand(ty_op.operand, operand);
   4463 
   4464         if (isByRef(payload_ty, mod)) {
   4465             break :result try func.buildPointerOffset(operand, 0, .new);
   4466         }
   4467 
   4468         const payload = try func.load(operand, payload_ty, 0);
   4469         break :result try payload.toLocal(func, payload_ty);
   4470     };
   4471     func.finishAir(inst, result, &.{ty_op.operand});
   4472 }
   4473 
   4474 fn airOptionalPayloadPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   4475     const mod = func.bin_file.base.comp.module.?;
   4476     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   4477     const operand = try func.resolveInst(ty_op.operand);
   4478     const opt_ty = func.typeOf(ty_op.operand).childType(mod);
   4479 
   4480     const result = result: {
   4481         const payload_ty = opt_ty.optionalChild(mod);
   4482         if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod) or opt_ty.optionalReprIsPayload(mod)) {
   4483             break :result func.reuseOperand(ty_op.operand, operand);
   4484         }
   4485 
   4486         break :result try func.buildPointerOffset(operand, 0, .new);
   4487     };
   4488     func.finishAir(inst, result, &.{ty_op.operand});
   4489 }
   4490 
   4491 fn airOptionalPayloadPtrSet(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   4492     const mod = func.bin_file.base.comp.module.?;
   4493     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   4494     const operand = try func.resolveInst(ty_op.operand);
   4495     const opt_ty = func.typeOf(ty_op.operand).childType(mod);
   4496     const payload_ty = opt_ty.optionalChild(mod);
   4497     if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
   4498         return func.fail("TODO: Implement OptionalPayloadPtrSet for optional with zero-sized type {}", .{payload_ty.fmtDebug()});
   4499     }
   4500 
   4501     if (opt_ty.optionalReprIsPayload(mod)) {
   4502         return func.finishAir(inst, operand, &.{ty_op.operand});
   4503     }
   4504 
   4505     const offset = std.math.cast(u32, payload_ty.abiSize(mod)) orelse {
   4506         return func.fail("Optional type {} too big to fit into stack frame", .{opt_ty.fmt(mod)});
   4507     };
   4508 
   4509     try func.emitWValue(operand);
   4510     try func.addImm32(1);
   4511     try func.addMemArg(.i32_store8, .{ .offset = operand.offset() + offset, .alignment = 1 });
   4512 
   4513     const result = try func.buildPointerOffset(operand, 0, .new);
   4514     return func.finishAir(inst, result, &.{ty_op.operand});
   4515 }
   4516 
   4517 fn airWrapOptional(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   4518     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   4519     const payload_ty = func.typeOf(ty_op.operand);
   4520     const mod = func.bin_file.base.comp.module.?;
   4521 
   4522     const result = result: {
   4523         if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
   4524             const non_null_bit = try func.allocStack(Type.u1);
   4525             try func.emitWValue(non_null_bit);
   4526             try func.addImm32(1);
   4527             try func.addMemArg(.i32_store8, .{ .offset = non_null_bit.offset(), .alignment = 1 });
   4528             break :result non_null_bit;
   4529         }
   4530 
   4531         const operand = try func.resolveInst(ty_op.operand);
   4532         const op_ty = func.typeOfIndex(inst);
   4533         if (op_ty.optionalReprIsPayload(mod)) {
   4534             break :result func.reuseOperand(ty_op.operand, operand);
   4535         }
   4536         const offset = std.math.cast(u32, payload_ty.abiSize(mod)) orelse {
   4537             return func.fail("Optional type {} too big to fit into stack frame", .{op_ty.fmt(mod)});
   4538         };
   4539 
   4540         // Create optional type, set the non-null bit, and store the operand inside the optional type
   4541         const result_ptr = try func.allocStack(op_ty);
   4542         try func.emitWValue(result_ptr);
   4543         try func.addImm32(1);
   4544         try func.addMemArg(.i32_store8, .{ .offset = result_ptr.offset() + offset, .alignment = 1 });
   4545 
   4546         const payload_ptr = try func.buildPointerOffset(result_ptr, 0, .new);
   4547         try func.store(payload_ptr, operand, payload_ty, 0);
   4548         break :result result_ptr;
   4549     };
   4550 
   4551     func.finishAir(inst, result, &.{ty_op.operand});
   4552 }
   4553 
   4554 fn airSlice(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   4555     const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
   4556     const bin_op = func.air.extraData(Air.Bin, ty_pl.payload).data;
   4557 
   4558     const lhs = try func.resolveInst(bin_op.lhs);
   4559     const rhs = try func.resolveInst(bin_op.rhs);
   4560     const slice_ty = func.typeOfIndex(inst);
   4561 
   4562     const slice = try func.allocStack(slice_ty);
   4563     try func.store(slice, lhs, Type.usize, 0);
   4564     try func.store(slice, rhs, Type.usize, func.ptrSize());
   4565 
   4566     func.finishAir(inst, slice, &.{ bin_op.lhs, bin_op.rhs });
   4567 }
   4568 
   4569 fn airSliceLen(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   4570     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   4571 
   4572     const operand = try func.resolveInst(ty_op.operand);
   4573     func.finishAir(inst, try func.sliceLen(operand), &.{ty_op.operand});
   4574 }
   4575 
   4576 fn airSliceElemVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   4577     const mod = func.bin_file.base.comp.module.?;
   4578     const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
   4579 
   4580     const slice_ty = func.typeOf(bin_op.lhs);
   4581     const slice = try func.resolveInst(bin_op.lhs);
   4582     const index = try func.resolveInst(bin_op.rhs);
   4583     const elem_ty = slice_ty.childType(mod);
   4584     const elem_size = elem_ty.abiSize(mod);
   4585 
   4586     // load pointer onto stack
   4587     _ = try func.load(slice, Type.usize, 0);
   4588 
   4589     // calculate index into slice
   4590     try func.emitWValue(index);
   4591     try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(elem_size)))));
   4592     try func.addTag(.i32_mul);
   4593     try func.addTag(.i32_add);
   4594 
   4595     const result_ptr = try func.allocLocal(Type.usize);
   4596     try func.addLabel(.local_set, result_ptr.local.value);
   4597 
   4598     const result = if (!isByRef(elem_ty, mod)) result: {
   4599         const elem_val = try func.load(result_ptr, elem_ty, 0);
   4600         break :result try elem_val.toLocal(func, elem_ty);
   4601     } else result_ptr;
   4602 
   4603     func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
   4604 }
   4605 
   4606 fn airSliceElemPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   4607     const mod = func.bin_file.base.comp.module.?;
   4608     const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
   4609     const bin_op = func.air.extraData(Air.Bin, ty_pl.payload).data;
   4610 
   4611     const elem_ty = ty_pl.ty.toType().childType(mod);
   4612     const elem_size = elem_ty.abiSize(mod);
   4613 
   4614     const slice = try func.resolveInst(bin_op.lhs);
   4615     const index = try func.resolveInst(bin_op.rhs);
   4616 
   4617     _ = try func.load(slice, Type.usize, 0);
   4618 
   4619     // calculate index into slice
   4620     try func.emitWValue(index);
   4621     try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(elem_size)))));
   4622     try func.addTag(.i32_mul);
   4623     try func.addTag(.i32_add);
   4624 
   4625     const result = try func.allocLocal(Type.i32);
   4626     try func.addLabel(.local_set, result.local.value);
   4627     func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
   4628 }
   4629 
   4630 fn airSlicePtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   4631     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   4632     const operand = try func.resolveInst(ty_op.operand);
   4633     func.finishAir(inst, try func.slicePtr(operand), &.{ty_op.operand});
   4634 }
   4635 
   4636 fn slicePtr(func: *CodeGen, operand: WValue) InnerError!WValue {
   4637     const ptr = try func.load(operand, Type.usize, 0);
   4638     return ptr.toLocal(func, Type.usize);
   4639 }
   4640 
   4641 fn sliceLen(func: *CodeGen, operand: WValue) InnerError!WValue {
   4642     const len = try func.load(operand, Type.usize, func.ptrSize());
   4643     return len.toLocal(func, Type.usize);
   4644 }
   4645 
   4646 fn airTrunc(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   4647     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   4648 
   4649     const operand = try func.resolveInst(ty_op.operand);
   4650     const wanted_ty = ty_op.ty.toType();
   4651     const op_ty = func.typeOf(ty_op.operand);
   4652 
   4653     const result = try func.trunc(operand, wanted_ty, op_ty);
   4654     func.finishAir(inst, try result.toLocal(func, wanted_ty), &.{ty_op.operand});
   4655 }
   4656 
   4657 /// Truncates a given operand to a given type, discarding any overflown bits.
   4658 /// NOTE: Resulting value is left on the stack.
   4659 fn trunc(func: *CodeGen, operand: WValue, wanted_ty: Type, given_ty: Type) InnerError!WValue {
   4660     const mod = func.bin_file.base.comp.module.?;
   4661     const given_bits = @as(u16, @intCast(given_ty.bitSize(mod)));
   4662     if (toWasmBits(given_bits) == null) {
   4663         return func.fail("TODO: Implement wasm integer truncation for integer bitsize: {d}", .{given_bits});
   4664     }
   4665 
   4666     var result = try func.intcast(operand, given_ty, wanted_ty);
   4667     const wanted_bits = @as(u16, @intCast(wanted_ty.bitSize(mod)));
   4668     const wasm_bits = toWasmBits(wanted_bits).?;
   4669     if (wasm_bits != wanted_bits) {
   4670         result = try func.wrapOperand(result, wanted_ty);
   4671     }
   4672     return result;
   4673 }
   4674 
   4675 fn airIntFromBool(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   4676     const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
   4677     const operand = try func.resolveInst(un_op);
   4678     const result = func.reuseOperand(un_op, operand);
   4679 
   4680     func.finishAir(inst, result, &.{un_op});
   4681 }
   4682 
   4683 fn airArrayToSlice(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   4684     const mod = func.bin_file.base.comp.module.?;
   4685     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   4686 
   4687     const operand = try func.resolveInst(ty_op.operand);
   4688     const array_ty = func.typeOf(ty_op.operand).childType(mod);
   4689     const slice_ty = ty_op.ty.toType();
   4690 
   4691     // create a slice on the stack
   4692     const slice_local = try func.allocStack(slice_ty);
   4693 
   4694     // store the array ptr in the slice
   4695     if (array_ty.hasRuntimeBitsIgnoreComptime(mod)) {
   4696         try func.store(slice_local, operand, Type.usize, 0);
   4697     }
   4698 
   4699     // store the length of the array in the slice
   4700     const len = WValue{ .imm32 = @as(u32, @intCast(array_ty.arrayLen(mod))) };
   4701     try func.store(slice_local, len, Type.usize, func.ptrSize());
   4702 
   4703     func.finishAir(inst, slice_local, &.{ty_op.operand});
   4704 }
   4705 
   4706 fn airIntFromPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   4707     const mod = func.bin_file.base.comp.module.?;
   4708     const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
   4709     const operand = try func.resolveInst(un_op);
   4710     const ptr_ty = func.typeOf(un_op);
   4711     const result = if (ptr_ty.isSlice(mod))
   4712         try func.slicePtr(operand)
   4713     else switch (operand) {
   4714         // for stack offset, return a pointer to this offset.
   4715         .stack_offset => try func.buildPointerOffset(operand, 0, .new),
   4716         else => func.reuseOperand(un_op, operand),
   4717     };
   4718     func.finishAir(inst, result, &.{un_op});
   4719 }
   4720 
   4721 fn airPtrElemVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   4722     const mod = func.bin_file.base.comp.module.?;
   4723     const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
   4724 
   4725     const ptr_ty = func.typeOf(bin_op.lhs);
   4726     const ptr = try func.resolveInst(bin_op.lhs);
   4727     const index = try func.resolveInst(bin_op.rhs);
   4728     const elem_ty = ptr_ty.childType(mod);
   4729     const elem_size = elem_ty.abiSize(mod);
   4730 
   4731     // load pointer onto the stack
   4732     if (ptr_ty.isSlice(mod)) {
   4733         _ = try func.load(ptr, Type.usize, 0);
   4734     } else {
   4735         try func.lowerToStack(ptr);
   4736     }
   4737 
   4738     // calculate index into slice
   4739     try func.emitWValue(index);
   4740     try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(elem_size)))));
   4741     try func.addTag(.i32_mul);
   4742     try func.addTag(.i32_add);
   4743 
   4744     const elem_result = val: {
   4745         var result = try func.allocLocal(Type.usize);
   4746         try func.addLabel(.local_set, result.local.value);
   4747         if (isByRef(elem_ty, mod)) {
   4748             break :val result;
   4749         }
   4750         defer result.free(func); // only free if it's not returned like above
   4751 
   4752         const elem_val = try func.load(result, elem_ty, 0);
   4753         break :val try elem_val.toLocal(func, elem_ty);
   4754     };
   4755     func.finishAir(inst, elem_result, &.{ bin_op.lhs, bin_op.rhs });
   4756 }
   4757 
   4758 fn airPtrElemPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   4759     const mod = func.bin_file.base.comp.module.?;
   4760     const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
   4761     const bin_op = func.air.extraData(Air.Bin, ty_pl.payload).data;
   4762 
   4763     const ptr_ty = func.typeOf(bin_op.lhs);
   4764     const elem_ty = ty_pl.ty.toType().childType(mod);
   4765     const elem_size = elem_ty.abiSize(mod);
   4766 
   4767     const ptr = try func.resolveInst(bin_op.lhs);
   4768     const index = try func.resolveInst(bin_op.rhs);
   4769 
   4770     // load pointer onto the stack
   4771     if (ptr_ty.isSlice(mod)) {
   4772         _ = try func.load(ptr, Type.usize, 0);
   4773     } else {
   4774         try func.lowerToStack(ptr);
   4775     }
   4776 
   4777     // calculate index into ptr
   4778     try func.emitWValue(index);
   4779     try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(elem_size)))));
   4780     try func.addTag(.i32_mul);
   4781     try func.addTag(.i32_add);
   4782 
   4783     const result = try func.allocLocal(Type.i32);
   4784     try func.addLabel(.local_set, result.local.value);
   4785     func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
   4786 }
   4787 
   4788 fn airPtrBinOp(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerError!void {
   4789     const mod = func.bin_file.base.comp.module.?;
   4790     const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
   4791     const bin_op = func.air.extraData(Air.Bin, ty_pl.payload).data;
   4792 
   4793     const ptr = try func.resolveInst(bin_op.lhs);
   4794     const offset = try func.resolveInst(bin_op.rhs);
   4795     const ptr_ty = func.typeOf(bin_op.lhs);
   4796     const pointee_ty = switch (ptr_ty.ptrSize(mod)) {
   4797         .One => ptr_ty.childType(mod).childType(mod), // ptr to array, so get array element type
   4798         else => ptr_ty.childType(mod),
   4799     };
   4800 
   4801     const valtype = typeToValtype(Type.usize, mod);
   4802     const mul_opcode = buildOpcode(.{ .valtype1 = valtype, .op = .mul });
   4803     const bin_opcode = buildOpcode(.{ .valtype1 = valtype, .op = op });
   4804 
   4805     try func.lowerToStack(ptr);
   4806     try func.emitWValue(offset);
   4807     try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(pointee_ty.abiSize(mod))))));
   4808     try func.addTag(Mir.Inst.Tag.fromOpcode(mul_opcode));
   4809     try func.addTag(Mir.Inst.Tag.fromOpcode(bin_opcode));
   4810 
   4811     const result = try func.allocLocal(Type.usize);
   4812     try func.addLabel(.local_set, result.local.value);
   4813     func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
   4814 }
   4815 
   4816 fn airMemset(func: *CodeGen, inst: Air.Inst.Index, safety: bool) InnerError!void {
   4817     const mod = func.bin_file.base.comp.module.?;
   4818     if (safety) {
   4819         // TODO if the value is undef, write 0xaa bytes to dest
   4820     } else {
   4821         // TODO if the value is undef, don't lower this instruction
   4822     }
   4823     const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
   4824 
   4825     const ptr = try func.resolveInst(bin_op.lhs);
   4826     const ptr_ty = func.typeOf(bin_op.lhs);
   4827     const value = try func.resolveInst(bin_op.rhs);
   4828     const len = switch (ptr_ty.ptrSize(mod)) {
   4829         .Slice => try func.sliceLen(ptr),
   4830         .One => @as(WValue, .{ .imm32 = @as(u32, @intCast(ptr_ty.childType(mod).arrayLen(mod))) }),
   4831         .C, .Many => unreachable,
   4832     };
   4833 
   4834     const elem_ty = if (ptr_ty.ptrSize(mod) == .One)
   4835         ptr_ty.childType(mod).childType(mod)
   4836     else
   4837         ptr_ty.childType(mod);
   4838 
   4839     const dst_ptr = try func.sliceOrArrayPtr(ptr, ptr_ty);
   4840     try func.memset(elem_ty, dst_ptr, len, value);
   4841 
   4842     func.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs });
   4843 }
   4844 
   4845 /// Sets a region of memory at `ptr` to the value of `value`
   4846 /// When the user has enabled the bulk_memory feature, we lower
   4847 /// this to wasm's memset instruction. When the feature is not present,
   4848 /// we implement it manually.
   4849 fn memset(func: *CodeGen, elem_ty: Type, ptr: WValue, len: WValue, value: WValue) InnerError!void {
   4850     const mod = func.bin_file.base.comp.module.?;
   4851     const abi_size = @as(u32, @intCast(elem_ty.abiSize(mod)));
   4852 
   4853     // When bulk_memory is enabled, we lower it to wasm's memset instruction.
   4854     // If not, we lower it ourselves.
   4855     if (std.Target.wasm.featureSetHas(func.target.cpu.features, .bulk_memory) and abi_size == 1) {
   4856         try func.lowerToStack(ptr);
   4857         try func.emitWValue(value);
   4858         try func.emitWValue(len);
   4859         try func.addExtended(.memory_fill);
   4860         return;
   4861     }
   4862 
   4863     const final_len = switch (len) {
   4864         .imm32 => |val| WValue{ .imm32 = val * abi_size },
   4865         .imm64 => |val| WValue{ .imm64 = val * abi_size },
   4866         else => if (abi_size != 1) blk: {
   4867             const new_len = try func.ensureAllocLocal(Type.usize);
   4868             try func.emitWValue(len);
   4869             switch (func.arch()) {
   4870                 .wasm32 => {
   4871                     try func.emitWValue(.{ .imm32 = abi_size });
   4872                     try func.addTag(.i32_mul);
   4873                 },
   4874                 .wasm64 => {
   4875                     try func.emitWValue(.{ .imm64 = abi_size });
   4876                     try func.addTag(.i64_mul);
   4877                 },
   4878                 else => unreachable,
   4879             }
   4880             try func.addLabel(.local_set, new_len.local.value);
   4881             break :blk new_len;
   4882         } else len,
   4883     };
   4884 
   4885     var end_ptr = try func.allocLocal(Type.usize);
   4886     defer end_ptr.free(func);
   4887     var new_ptr = try func.buildPointerOffset(ptr, 0, .new);
   4888     defer new_ptr.free(func);
   4889 
   4890     // get the loop conditional: if current pointer address equals final pointer's address
   4891     try func.lowerToStack(ptr);
   4892     try func.emitWValue(final_len);
   4893     switch (func.arch()) {
   4894         .wasm32 => try func.addTag(.i32_add),
   4895         .wasm64 => try func.addTag(.i64_add),
   4896         else => unreachable,
   4897     }
   4898     try func.addLabel(.local_set, end_ptr.local.value);
   4899 
   4900     // outer block to jump to when loop is done
   4901     try func.startBlock(.block, wasm.block_empty);
   4902     try func.startBlock(.loop, wasm.block_empty);
   4903 
   4904     // check for codition for loop end
   4905     try func.emitWValue(new_ptr);
   4906     try func.emitWValue(end_ptr);
   4907     switch (func.arch()) {
   4908         .wasm32 => try func.addTag(.i32_eq),
   4909         .wasm64 => try func.addTag(.i64_eq),
   4910         else => unreachable,
   4911     }
   4912     try func.addLabel(.br_if, 1); // jump out of loop into outer block (finished)
   4913 
   4914     // store the value at the current position of the pointer
   4915     try func.store(new_ptr, value, elem_ty, 0);
   4916 
   4917     // move the pointer to the next element
   4918     try func.emitWValue(new_ptr);
   4919     switch (func.arch()) {
   4920         .wasm32 => {
   4921             try func.emitWValue(.{ .imm32 = abi_size });
   4922             try func.addTag(.i32_add);
   4923         },
   4924         .wasm64 => {
   4925             try func.emitWValue(.{ .imm64 = abi_size });
   4926             try func.addTag(.i64_add);
   4927         },
   4928         else => unreachable,
   4929     }
   4930     try func.addLabel(.local_set, new_ptr.local.value);
   4931 
   4932     // end of loop
   4933     try func.addLabel(.br, 0); // jump to start of loop
   4934     try func.endBlock();
   4935     try func.endBlock();
   4936 }
   4937 
   4938 fn airArrayElemVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   4939     const mod = func.bin_file.base.comp.module.?;
   4940     const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
   4941 
   4942     const array_ty = func.typeOf(bin_op.lhs);
   4943     const array = try func.resolveInst(bin_op.lhs);
   4944     const index = try func.resolveInst(bin_op.rhs);
   4945     const elem_ty = array_ty.childType(mod);
   4946     const elem_size = elem_ty.abiSize(mod);
   4947 
   4948     if (isByRef(array_ty, mod)) {
   4949         try func.lowerToStack(array);
   4950         try func.emitWValue(index);
   4951         try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(elem_size)))));
   4952         try func.addTag(.i32_mul);
   4953         try func.addTag(.i32_add);
   4954     } else {
   4955         std.debug.assert(array_ty.zigTypeTag(mod) == .Vector);
   4956 
   4957         switch (index) {
   4958             inline .imm32, .imm64 => |lane| {
   4959                 const opcode: wasm.SimdOpcode = switch (elem_ty.bitSize(mod)) {
   4960                     8 => if (elem_ty.isSignedInt(mod)) .i8x16_extract_lane_s else .i8x16_extract_lane_u,
   4961                     16 => if (elem_ty.isSignedInt(mod)) .i16x8_extract_lane_s else .i16x8_extract_lane_u,
   4962                     32 => if (elem_ty.isInt(mod)) .i32x4_extract_lane else .f32x4_extract_lane,
   4963                     64 => if (elem_ty.isInt(mod)) .i64x2_extract_lane else .f64x2_extract_lane,
   4964                     else => unreachable,
   4965                 };
   4966 
   4967                 var operands = [_]u32{ std.wasm.simdOpcode(opcode), @as(u8, @intCast(lane)) };
   4968 
   4969                 try func.emitWValue(array);
   4970 
   4971                 const extra_index = @as(u32, @intCast(func.mir_extra.items.len));
   4972                 try func.mir_extra.appendSlice(func.gpa, &operands);
   4973                 try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } });
   4974 
   4975                 return func.finishAir(inst, try WValue.toLocal(.stack, func, elem_ty), &.{ bin_op.lhs, bin_op.rhs });
   4976             },
   4977             else => {
   4978                 const stack_vec = try func.allocStack(array_ty);
   4979                 try func.store(stack_vec, array, array_ty, 0);
   4980 
   4981                 // Is a non-unrolled vector (v128)
   4982                 try func.lowerToStack(stack_vec);
   4983                 try func.emitWValue(index);
   4984                 try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(elem_size)))));
   4985                 try func.addTag(.i32_mul);
   4986                 try func.addTag(.i32_add);
   4987             },
   4988         }
   4989     }
   4990 
   4991     const elem_result = val: {
   4992         var result = try func.allocLocal(Type.usize);
   4993         try func.addLabel(.local_set, result.local.value);
   4994 
   4995         if (isByRef(elem_ty, mod)) {
   4996             break :val result;
   4997         }
   4998         defer result.free(func); // only free if no longer needed and not returned like above
   4999 
   5000         const elem_val = try func.load(result, elem_ty, 0);
   5001         break :val try elem_val.toLocal(func, elem_ty);
   5002     };
   5003 
   5004     func.finishAir(inst, elem_result, &.{ bin_op.lhs, bin_op.rhs });
   5005 }
   5006 
   5007 fn airIntFromFloat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   5008     const mod = func.bin_file.base.comp.module.?;
   5009     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   5010 
   5011     const operand = try func.resolveInst(ty_op.operand);
   5012     const op_ty = func.typeOf(ty_op.operand);
   5013     const op_bits = op_ty.floatBits(func.target);
   5014 
   5015     const dest_ty = func.typeOfIndex(inst);
   5016     const dest_info = dest_ty.intInfo(mod);
   5017 
   5018     if (dest_info.bits > 128) {
   5019         return func.fail("TODO: intFromFloat for integers/floats with bitsize {}", .{dest_info.bits});
   5020     }
   5021 
   5022     if ((op_bits != 32 and op_bits != 64) or dest_info.bits > 64) {
   5023         const dest_bitsize = if (dest_info.bits <= 16) 16 else std.math.ceilPowerOfTwoAssert(u16, dest_info.bits);
   5024 
   5025         var fn_name_buf: [16]u8 = undefined;
   5026         const fn_name = std.fmt.bufPrint(&fn_name_buf, "__fix{s}{s}f{s}i", .{
   5027             switch (dest_info.signedness) {
   5028                 .signed => "",
   5029                 .unsigned => "uns",
   5030             },
   5031             target_util.compilerRtFloatAbbrev(op_bits),
   5032             target_util.compilerRtIntAbbrev(dest_bitsize),
   5033         }) catch unreachable;
   5034 
   5035         const result = try (try func.callIntrinsic(fn_name, &.{op_ty.ip_index}, dest_ty, &.{operand})).toLocal(func, dest_ty);
   5036         return func.finishAir(inst, result, &.{ty_op.operand});
   5037     }
   5038 
   5039     try func.emitWValue(operand);
   5040     const op = buildOpcode(.{
   5041         .op = .trunc,
   5042         .valtype1 = typeToValtype(dest_ty, mod),
   5043         .valtype2 = typeToValtype(op_ty, mod),
   5044         .signedness = dest_info.signedness,
   5045     });
   5046     try func.addTag(Mir.Inst.Tag.fromOpcode(op));
   5047     const wrapped = try func.wrapOperand(.{ .stack = {} }, dest_ty);
   5048     const result = try wrapped.toLocal(func, dest_ty);
   5049     func.finishAir(inst, result, &.{ty_op.operand});
   5050 }
   5051 
   5052 fn airFloatFromInt(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   5053     const mod = func.bin_file.base.comp.module.?;
   5054     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   5055 
   5056     const operand = try func.resolveInst(ty_op.operand);
   5057     const op_ty = func.typeOf(ty_op.operand);
   5058     const op_info = op_ty.intInfo(mod);
   5059 
   5060     const dest_ty = func.typeOfIndex(inst);
   5061     const dest_bits = dest_ty.floatBits(func.target);
   5062 
   5063     if (op_info.bits > 128) {
   5064         return func.fail("TODO: floatFromInt for integers/floats with bitsize {d} bits", .{op_info.bits});
   5065     }
   5066 
   5067     if (op_info.bits > 64 or (dest_bits > 64 or dest_bits < 32)) {
   5068         const op_bitsize = if (op_info.bits <= 16) 16 else std.math.ceilPowerOfTwoAssert(u16, op_info.bits);
   5069 
   5070         var fn_name_buf: [16]u8 = undefined;
   5071         const fn_name = std.fmt.bufPrint(&fn_name_buf, "__float{s}{s}i{s}f", .{
   5072             switch (op_info.signedness) {
   5073                 .signed => "",
   5074                 .unsigned => "un",
   5075             },
   5076             target_util.compilerRtIntAbbrev(op_bitsize),
   5077             target_util.compilerRtFloatAbbrev(dest_bits),
   5078         }) catch unreachable;
   5079 
   5080         const result = try (try func.callIntrinsic(fn_name, &.{op_ty.ip_index}, dest_ty, &.{operand})).toLocal(func, dest_ty);
   5081         return func.finishAir(inst, result, &.{ty_op.operand});
   5082     }
   5083 
   5084     try func.emitWValue(operand);
   5085     const op = buildOpcode(.{
   5086         .op = .convert,
   5087         .valtype1 = typeToValtype(dest_ty, mod),
   5088         .valtype2 = typeToValtype(op_ty, mod),
   5089         .signedness = op_info.signedness,
   5090     });
   5091     try func.addTag(Mir.Inst.Tag.fromOpcode(op));
   5092 
   5093     const result = try func.allocLocal(dest_ty);
   5094     try func.addLabel(.local_set, result.local.value);
   5095     func.finishAir(inst, result, &.{ty_op.operand});
   5096 }
   5097 
   5098 fn airSplat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   5099     const mod = func.bin_file.base.comp.module.?;
   5100     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   5101     const operand = try func.resolveInst(ty_op.operand);
   5102     const ty = func.typeOfIndex(inst);
   5103     const elem_ty = ty.childType(mod);
   5104 
   5105     if (determineSimdStoreStrategy(ty, mod) == .direct) blk: {
   5106         switch (operand) {
   5107             // when the operand lives in the linear memory section, we can directly
   5108             // load and splat the value at once. Meaning we do not first have to load
   5109             // the scalar value onto the stack.
   5110             .stack_offset, .memory, .memory_offset => {
   5111                 const opcode = switch (elem_ty.bitSize(mod)) {
   5112                     8 => std.wasm.simdOpcode(.v128_load8_splat),
   5113                     16 => std.wasm.simdOpcode(.v128_load16_splat),
   5114                     32 => std.wasm.simdOpcode(.v128_load32_splat),
   5115                     64 => std.wasm.simdOpcode(.v128_load64_splat),
   5116                     else => break :blk, // Cannot make use of simd-instructions
   5117                 };
   5118                 const result = try func.allocLocal(ty);
   5119                 try func.emitWValue(operand);
   5120                 // TODO: Add helper functions for simd opcodes
   5121                 const extra_index = @as(u32, @intCast(func.mir_extra.items.len));
   5122                 // stores as := opcode, offset, alignment (opcode::memarg)
   5123                 try func.mir_extra.appendSlice(func.gpa, &[_]u32{
   5124                     opcode,
   5125                     operand.offset(),
   5126                     @intCast(elem_ty.abiAlignment(mod).toByteUnits().?),
   5127                 });
   5128                 try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } });
   5129                 try func.addLabel(.local_set, result.local.value);
   5130                 return func.finishAir(inst, result, &.{ty_op.operand});
   5131             },
   5132             .local => {
   5133                 const opcode = switch (elem_ty.bitSize(mod)) {
   5134                     8 => std.wasm.simdOpcode(.i8x16_splat),
   5135                     16 => std.wasm.simdOpcode(.i16x8_splat),
   5136                     32 => if (elem_ty.isInt(mod)) std.wasm.simdOpcode(.i32x4_splat) else std.wasm.simdOpcode(.f32x4_splat),
   5137                     64 => if (elem_ty.isInt(mod)) std.wasm.simdOpcode(.i64x2_splat) else std.wasm.simdOpcode(.f64x2_splat),
   5138                     else => break :blk, // Cannot make use of simd-instructions
   5139                 };
   5140                 const result = try func.allocLocal(ty);
   5141                 try func.emitWValue(operand);
   5142                 const extra_index = @as(u32, @intCast(func.mir_extra.items.len));
   5143                 try func.mir_extra.append(func.gpa, opcode);
   5144                 try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } });
   5145                 try func.addLabel(.local_set, result.local.value);
   5146                 return func.finishAir(inst, result, &.{ty_op.operand});
   5147             },
   5148             else => unreachable,
   5149         }
   5150     }
   5151     const elem_size = elem_ty.bitSize(mod);
   5152     const vector_len = @as(usize, @intCast(ty.vectorLen(mod)));
   5153     if ((!std.math.isPowerOfTwo(elem_size) or elem_size % 8 != 0) and vector_len > 1) {
   5154         return func.fail("TODO: WebAssembly `@splat` for arbitrary element bitsize {d}", .{elem_size});
   5155     }
   5156 
   5157     const result = try func.allocStack(ty);
   5158     const elem_byte_size = @as(u32, @intCast(elem_ty.abiSize(mod)));
   5159     var index: usize = 0;
   5160     var offset: u32 = 0;
   5161     while (index < vector_len) : (index += 1) {
   5162         try func.store(result, operand, elem_ty, offset);
   5163         offset += elem_byte_size;
   5164     }
   5165 
   5166     return func.finishAir(inst, result, &.{ty_op.operand});
   5167 }
   5168 
   5169 fn airSelect(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   5170     const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
   5171     const operand = try func.resolveInst(pl_op.operand);
   5172 
   5173     _ = operand;
   5174     return func.fail("TODO: Implement wasm airSelect", .{});
   5175 }
   5176 
   5177 fn airShuffle(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   5178     const mod = func.bin_file.base.comp.module.?;
   5179     const inst_ty = func.typeOfIndex(inst);
   5180     const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
   5181     const extra = func.air.extraData(Air.Shuffle, ty_pl.payload).data;
   5182 
   5183     const a = try func.resolveInst(extra.a);
   5184     const b = try func.resolveInst(extra.b);
   5185     const mask = Value.fromInterned(extra.mask);
   5186     const mask_len = extra.mask_len;
   5187 
   5188     const child_ty = inst_ty.childType(mod);
   5189     const elem_size = child_ty.abiSize(mod);
   5190 
   5191     // TODO: One of them could be by ref; handle in loop
   5192     if (isByRef(func.typeOf(extra.a), mod) or isByRef(inst_ty, mod)) {
   5193         const result = try func.allocStack(inst_ty);
   5194 
   5195         for (0..mask_len) |index| {
   5196             const value = (try mask.elemValue(mod, index)).toSignedInt(mod);
   5197 
   5198             try func.emitWValue(result);
   5199 
   5200             const loaded = if (value >= 0)
   5201                 try func.load(a, child_ty, @as(u32, @intCast(@as(i64, @intCast(elem_size)) * value)))
   5202             else
   5203                 try func.load(b, child_ty, @as(u32, @intCast(@as(i64, @intCast(elem_size)) * ~value)));
   5204 
   5205             try func.store(.stack, loaded, child_ty, result.stack_offset.value + @as(u32, @intCast(elem_size)) * @as(u32, @intCast(index)));
   5206         }
   5207 
   5208         return func.finishAir(inst, result, &.{ extra.a, extra.b });
   5209     } else {
   5210         var operands = [_]u32{
   5211             std.wasm.simdOpcode(.i8x16_shuffle),
   5212         } ++ [1]u32{undefined} ** 4;
   5213 
   5214         var lanes = mem.asBytes(operands[1..]);
   5215         for (0..@as(usize, @intCast(mask_len))) |index| {
   5216             const mask_elem = (try mask.elemValue(mod, index)).toSignedInt(mod);
   5217             const base_index = if (mask_elem >= 0)
   5218                 @as(u8, @intCast(@as(i64, @intCast(elem_size)) * mask_elem))
   5219             else
   5220                 16 + @as(u8, @intCast(@as(i64, @intCast(elem_size)) * ~mask_elem));
   5221 
   5222             for (0..@as(usize, @intCast(elem_size))) |byte_offset| {
   5223                 lanes[index * @as(usize, @intCast(elem_size)) + byte_offset] = base_index + @as(u8, @intCast(byte_offset));
   5224             }
   5225         }
   5226 
   5227         try func.emitWValue(a);
   5228         try func.emitWValue(b);
   5229 
   5230         const extra_index = @as(u32, @intCast(func.mir_extra.items.len));
   5231         try func.mir_extra.appendSlice(func.gpa, &operands);
   5232         try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } });
   5233 
   5234         return func.finishAir(inst, try WValue.toLocal(.stack, func, inst_ty), &.{ extra.a, extra.b });
   5235     }
   5236 }
   5237 
   5238 fn airReduce(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   5239     const reduce = func.air.instructions.items(.data)[@intFromEnum(inst)].reduce;
   5240     const operand = try func.resolveInst(reduce.operand);
   5241 
   5242     _ = operand;
   5243     return func.fail("TODO: Implement wasm airReduce", .{});
   5244 }
   5245 
   5246 fn airAggregateInit(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   5247     const mod = func.bin_file.base.comp.module.?;
   5248     const ip = &mod.intern_pool;
   5249     const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
   5250     const result_ty = func.typeOfIndex(inst);
   5251     const len = @as(usize, @intCast(result_ty.arrayLen(mod)));
   5252     const elements = @as([]const Air.Inst.Ref, @ptrCast(func.air.extra[ty_pl.payload..][0..len]));
   5253 
   5254     const result: WValue = result_value: {
   5255         switch (result_ty.zigTypeTag(mod)) {
   5256             .Array => {
   5257                 const result = try func.allocStack(result_ty);
   5258                 const elem_ty = result_ty.childType(mod);
   5259                 const elem_size = @as(u32, @intCast(elem_ty.abiSize(mod)));
   5260                 const sentinel = if (result_ty.sentinel(mod)) |sent| blk: {
   5261                     break :blk try func.lowerConstant(sent, elem_ty);
   5262                 } else null;
   5263 
   5264                 // When the element type is by reference, we must copy the entire
   5265                 // value. It is therefore safer to move the offset pointer and store
   5266                 // each value individually, instead of using store offsets.
   5267                 if (isByRef(elem_ty, mod)) {
   5268                     // copy stack pointer into a temporary local, which is
   5269                     // moved for each element to store each value in the right position.
   5270                     const offset = try func.buildPointerOffset(result, 0, .new);
   5271                     for (elements, 0..) |elem, elem_index| {
   5272                         const elem_val = try func.resolveInst(elem);
   5273                         try func.store(offset, elem_val, elem_ty, 0);
   5274 
   5275                         if (elem_index < elements.len - 1 and sentinel == null) {
   5276                             _ = try func.buildPointerOffset(offset, elem_size, .modify);
   5277                         }
   5278                     }
   5279                     if (sentinel) |sent| {
   5280                         try func.store(offset, sent, elem_ty, 0);
   5281                     }
   5282                 } else {
   5283                     var offset: u32 = 0;
   5284                     for (elements) |elem| {
   5285                         const elem_val = try func.resolveInst(elem);
   5286                         try func.store(result, elem_val, elem_ty, offset);
   5287                         offset += elem_size;
   5288                     }
   5289                     if (sentinel) |sent| {
   5290                         try func.store(result, sent, elem_ty, offset);
   5291                     }
   5292                 }
   5293                 break :result_value result;
   5294             },
   5295             .Struct => switch (result_ty.containerLayout(mod)) {
   5296                 .@"packed" => {
   5297                     if (isByRef(result_ty, mod)) {
   5298                         return func.fail("TODO: airAggregateInit for packed structs larger than 64 bits", .{});
   5299                     }
   5300                     const packed_struct = mod.typeToPackedStruct(result_ty).?;
   5301                     const field_types = packed_struct.field_types;
   5302                     const backing_type = Type.fromInterned(packed_struct.backingIntType(ip).*);
   5303 
   5304                     // ensure the result is zero'd
   5305                     const result = try func.allocLocal(backing_type);
   5306                     if (backing_type.bitSize(mod) <= 32)
   5307                         try func.addImm32(0)
   5308                     else
   5309                         try func.addImm64(0);
   5310                     try func.addLabel(.local_set, result.local.value);
   5311 
   5312                     var current_bit: u16 = 0;
   5313                     for (elements, 0..) |elem, elem_index| {
   5314                         const field_ty = Type.fromInterned(field_types.get(ip)[elem_index]);
   5315                         if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) continue;
   5316 
   5317                         const shift_val = if (backing_type.bitSize(mod) <= 32)
   5318                             WValue{ .imm32 = current_bit }
   5319                         else
   5320                             WValue{ .imm64 = current_bit };
   5321 
   5322                         const value = try func.resolveInst(elem);
   5323                         const value_bit_size: u16 = @intCast(field_ty.bitSize(mod));
   5324                         const int_ty = try mod.intType(.unsigned, value_bit_size);
   5325 
   5326                         // load our current result on stack so we can perform all transformations
   5327                         // using only stack values. Saving the cost of loads and stores.
   5328                         try func.emitWValue(result);
   5329                         const bitcasted = try func.bitcast(int_ty, field_ty, value);
   5330                         const extended_val = try func.intcast(bitcasted, int_ty, backing_type);
   5331                         // no need to shift any values when the current offset is 0
   5332                         const shifted = if (current_bit != 0) shifted: {
   5333                             break :shifted try func.binOp(extended_val, shift_val, backing_type, .shl);
   5334                         } else extended_val;
   5335                         // we ignore the result as we keep it on the stack to assign it directly to `result`
   5336                         _ = try func.binOp(.stack, shifted, backing_type, .@"or");
   5337                         try func.addLabel(.local_set, result.local.value);
   5338                         current_bit += value_bit_size;
   5339                     }
   5340                     break :result_value result;
   5341                 },
   5342                 else => {
   5343                     const result = try func.allocStack(result_ty);
   5344                     const offset = try func.buildPointerOffset(result, 0, .new); // pointer to offset
   5345                     var prev_field_offset: u64 = 0;
   5346                     for (elements, 0..) |elem, elem_index| {
   5347                         if ((try result_ty.structFieldValueComptime(mod, elem_index)) != null) continue;
   5348 
   5349                         const elem_ty = result_ty.structFieldType(elem_index, mod);
   5350                         const field_offset = result_ty.structFieldOffset(elem_index, mod);
   5351                         _ = try func.buildPointerOffset(offset, @intCast(field_offset - prev_field_offset), .modify);
   5352                         prev_field_offset = field_offset;
   5353 
   5354                         const value = try func.resolveInst(elem);
   5355                         try func.store(offset, value, elem_ty, 0);
   5356                     }
   5357 
   5358                     break :result_value result;
   5359                 },
   5360             },
   5361             .Vector => return func.fail("TODO: Wasm backend: implement airAggregateInit for vectors", .{}),
   5362             else => unreachable,
   5363         }
   5364     };
   5365 
   5366     if (elements.len <= Liveness.bpi - 1) {
   5367         var buf = [1]Air.Inst.Ref{.none} ** (Liveness.bpi - 1);
   5368         @memcpy(buf[0..elements.len], elements);
   5369         return func.finishAir(inst, result, &buf);
   5370     }
   5371     var bt = try func.iterateBigTomb(inst, elements.len);
   5372     for (elements) |arg| bt.feed(arg);
   5373     return bt.finishAir(result);
   5374 }
   5375 
   5376 fn airUnionInit(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   5377     const mod = func.bin_file.base.comp.module.?;
   5378     const ip = &mod.intern_pool;
   5379     const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
   5380     const extra = func.air.extraData(Air.UnionInit, ty_pl.payload).data;
   5381 
   5382     const result = result: {
   5383         const union_ty = func.typeOfIndex(inst);
   5384         const layout = union_ty.unionGetLayout(mod);
   5385         const union_obj = mod.typeToUnion(union_ty).?;
   5386         const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[extra.field_index]);
   5387         const field_name = union_obj.loadTagType(ip).names.get(ip)[extra.field_index];
   5388 
   5389         const tag_int = blk: {
   5390             const tag_ty = union_ty.unionTagTypeHypothetical(mod);
   5391             const enum_field_index = tag_ty.enumFieldIndex(field_name, mod).?;
   5392             const tag_val = try mod.enumValueFieldIndex(tag_ty, enum_field_index);
   5393             break :blk try func.lowerConstant(tag_val, tag_ty);
   5394         };
   5395         if (layout.payload_size == 0) {
   5396             if (layout.tag_size == 0) {
   5397                 break :result WValue{ .none = {} };
   5398             }
   5399             assert(!isByRef(union_ty, mod));
   5400             break :result tag_int;
   5401         }
   5402 
   5403         if (isByRef(union_ty, mod)) {
   5404             const result_ptr = try func.allocStack(union_ty);
   5405             const payload = try func.resolveInst(extra.init);
   5406             if (layout.tag_align.compare(.gte, layout.payload_align)) {
   5407                 if (isByRef(field_ty, mod)) {
   5408                     const payload_ptr = try func.buildPointerOffset(result_ptr, layout.tag_size, .new);
   5409                     try func.store(payload_ptr, payload, field_ty, 0);
   5410                 } else {
   5411                     try func.store(result_ptr, payload, field_ty, @intCast(layout.tag_size));
   5412                 }
   5413 
   5414                 if (layout.tag_size > 0) {
   5415                     try func.store(result_ptr, tag_int, Type.fromInterned(union_obj.enum_tag_ty), 0);
   5416                 }
   5417             } else {
   5418                 try func.store(result_ptr, payload, field_ty, 0);
   5419                 if (layout.tag_size > 0) {
   5420                     try func.store(
   5421                         result_ptr,
   5422                         tag_int,
   5423                         Type.fromInterned(union_obj.enum_tag_ty),
   5424                         @intCast(layout.payload_size),
   5425                     );
   5426                 }
   5427             }
   5428             break :result result_ptr;
   5429         } else {
   5430             const operand = try func.resolveInst(extra.init);
   5431             const union_int_type = try mod.intType(.unsigned, @as(u16, @intCast(union_ty.bitSize(mod))));
   5432             if (field_ty.zigTypeTag(mod) == .Float) {
   5433                 const int_type = try mod.intType(.unsigned, @intCast(field_ty.bitSize(mod)));
   5434                 const bitcasted = try func.bitcast(field_ty, int_type, operand);
   5435                 const casted = try func.trunc(bitcasted, int_type, union_int_type);
   5436                 break :result try casted.toLocal(func, field_ty);
   5437             } else if (field_ty.isPtrAtRuntime(mod)) {
   5438                 const int_type = try mod.intType(.unsigned, @intCast(field_ty.bitSize(mod)));
   5439                 const casted = try func.intcast(operand, int_type, union_int_type);
   5440                 break :result try casted.toLocal(func, field_ty);
   5441             }
   5442             const casted = try func.intcast(operand, field_ty, union_int_type);
   5443             break :result try casted.toLocal(func, field_ty);
   5444         }
   5445     };
   5446 
   5447     return func.finishAir(inst, result, &.{extra.init});
   5448 }
   5449 
   5450 fn airPrefetch(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   5451     const prefetch = func.air.instructions.items(.data)[@intFromEnum(inst)].prefetch;
   5452     func.finishAir(inst, .none, &.{prefetch.ptr});
   5453 }
   5454 
   5455 fn airWasmMemorySize(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   5456     const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
   5457 
   5458     const result = try func.allocLocal(func.typeOfIndex(inst));
   5459     try func.addLabel(.memory_size, pl_op.payload);
   5460     try func.addLabel(.local_set, result.local.value);
   5461     func.finishAir(inst, result, &.{pl_op.operand});
   5462 }
   5463 
   5464 fn airWasmMemoryGrow(func: *CodeGen, inst: Air.Inst.Index) !void {
   5465     const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
   5466 
   5467     const operand = try func.resolveInst(pl_op.operand);
   5468     const result = try func.allocLocal(func.typeOfIndex(inst));
   5469     try func.emitWValue(operand);
   5470     try func.addLabel(.memory_grow, pl_op.payload);
   5471     try func.addLabel(.local_set, result.local.value);
   5472     func.finishAir(inst, result, &.{pl_op.operand});
   5473 }
   5474 
   5475 fn cmpOptionals(func: *CodeGen, lhs: WValue, rhs: WValue, operand_ty: Type, op: std.math.CompareOperator) InnerError!WValue {
   5476     const mod = func.bin_file.base.comp.module.?;
   5477     assert(operand_ty.hasRuntimeBitsIgnoreComptime(mod));
   5478     assert(op == .eq or op == .neq);
   5479     const payload_ty = operand_ty.optionalChild(mod);
   5480 
   5481     // We store the final result in here that will be validated
   5482     // if the optional is truly equal.
   5483     var result = try func.ensureAllocLocal(Type.i32);
   5484     defer result.free(func);
   5485 
   5486     try func.startBlock(.block, wasm.block_empty);
   5487     _ = try func.isNull(lhs, operand_ty, .i32_eq);
   5488     _ = try func.isNull(rhs, operand_ty, .i32_eq);
   5489     try func.addTag(.i32_ne); // inverse so we can exit early
   5490     try func.addLabel(.br_if, 0);
   5491 
   5492     _ = try func.load(lhs, payload_ty, 0);
   5493     _ = try func.load(rhs, payload_ty, 0);
   5494     const opcode = buildOpcode(.{ .op = .ne, .valtype1 = typeToValtype(payload_ty, mod) });
   5495     try func.addTag(Mir.Inst.Tag.fromOpcode(opcode));
   5496     try func.addLabel(.br_if, 0);
   5497 
   5498     try func.addImm32(1);
   5499     try func.addLabel(.local_set, result.local.value);
   5500     try func.endBlock();
   5501 
   5502     try func.emitWValue(result);
   5503     try func.addImm32(0);
   5504     try func.addTag(if (op == .eq) .i32_ne else .i32_eq);
   5505     return WValue{ .stack = {} };
   5506 }
   5507 
   5508 /// Compares big integers by checking both its high bits and low bits.
   5509 /// NOTE: Leaves the result of the comparison on top of the stack.
   5510 /// TODO: Lower this to compiler_rt call when bitsize > 128
   5511 fn cmpBigInt(func: *CodeGen, lhs: WValue, rhs: WValue, operand_ty: Type, op: std.math.CompareOperator) InnerError!WValue {
   5512     const mod = func.bin_file.base.comp.module.?;
   5513     assert(operand_ty.abiSize(mod) >= 16);
   5514     assert(!(lhs != .stack and rhs == .stack));
   5515     if (operand_ty.bitSize(mod) > 128) {
   5516         return func.fail("TODO: Support cmpBigInt for integer bitsize: '{d}'", .{operand_ty.bitSize(mod)});
   5517     }
   5518 
   5519     var lhs_high_bit = try (try func.load(lhs, Type.u64, 0)).toLocal(func, Type.u64);
   5520     defer lhs_high_bit.free(func);
   5521     var rhs_high_bit = try (try func.load(rhs, Type.u64, 0)).toLocal(func, Type.u64);
   5522     defer rhs_high_bit.free(func);
   5523 
   5524     switch (op) {
   5525         .eq, .neq => {
   5526             const xor_high = try func.binOp(lhs_high_bit, rhs_high_bit, Type.u64, .xor);
   5527             const lhs_low_bit = try func.load(lhs, Type.u64, 8);
   5528             const rhs_low_bit = try func.load(rhs, Type.u64, 8);
   5529             const xor_low = try func.binOp(lhs_low_bit, rhs_low_bit, Type.u64, .xor);
   5530             const or_result = try func.binOp(xor_high, xor_low, Type.u64, .@"or");
   5531 
   5532             switch (op) {
   5533                 .eq => return func.cmp(or_result, .{ .imm64 = 0 }, Type.u64, .eq),
   5534                 .neq => return func.cmp(or_result, .{ .imm64 = 0 }, Type.u64, .neq),
   5535                 else => unreachable,
   5536             }
   5537         },
   5538         else => {
   5539             const ty = if (operand_ty.isSignedInt(mod)) Type.i64 else Type.u64;
   5540             // leave those value on top of the stack for '.select'
   5541             const lhs_low_bit = try func.load(lhs, Type.u64, 8);
   5542             const rhs_low_bit = try func.load(rhs, Type.u64, 8);
   5543             _ = try func.cmp(lhs_low_bit, rhs_low_bit, ty, op);
   5544             _ = try func.cmp(lhs_high_bit, rhs_high_bit, ty, op);
   5545             _ = try func.cmp(lhs_high_bit, rhs_high_bit, ty, .eq);
   5546             try func.addTag(.select);
   5547         },
   5548     }
   5549 
   5550     return WValue{ .stack = {} };
   5551 }
   5552 
   5553 fn airSetUnionTag(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   5554     const mod = func.bin_file.base.comp.module.?;
   5555     const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
   5556     const un_ty = func.typeOf(bin_op.lhs).childType(mod);
   5557     const tag_ty = func.typeOf(bin_op.rhs);
   5558     const layout = un_ty.unionGetLayout(mod);
   5559     if (layout.tag_size == 0) return func.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs });
   5560 
   5561     const union_ptr = try func.resolveInst(bin_op.lhs);
   5562     const new_tag = try func.resolveInst(bin_op.rhs);
   5563     if (layout.payload_size == 0) {
   5564         try func.store(union_ptr, new_tag, tag_ty, 0);
   5565         return func.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs });
   5566     }
   5567 
   5568     // when the tag alignment is smaller than the payload, the field will be stored
   5569     // after the payload.
   5570     const offset: u32 = if (layout.tag_align.compare(.lt, layout.payload_align)) blk: {
   5571         break :blk @intCast(layout.payload_size);
   5572     } else 0;
   5573     try func.store(union_ptr, new_tag, tag_ty, offset);
   5574     func.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs });
   5575 }
   5576 
   5577 fn airGetUnionTag(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   5578     const mod = func.bin_file.base.comp.module.?;
   5579     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   5580 
   5581     const un_ty = func.typeOf(ty_op.operand);
   5582     const tag_ty = func.typeOfIndex(inst);
   5583     const layout = un_ty.unionGetLayout(mod);
   5584     if (layout.tag_size == 0) return func.finishAir(inst, .none, &.{ty_op.operand});
   5585 
   5586     const operand = try func.resolveInst(ty_op.operand);
   5587     // when the tag alignment is smaller than the payload, the field will be stored
   5588     // after the payload.
   5589     const offset: u32 = if (layout.tag_align.compare(.lt, layout.payload_align)) blk: {
   5590         break :blk @intCast(layout.payload_size);
   5591     } else 0;
   5592     const tag = try func.load(operand, tag_ty, offset);
   5593     const result = try tag.toLocal(func, tag_ty);
   5594     func.finishAir(inst, result, &.{ty_op.operand});
   5595 }
   5596 
   5597 fn airFpext(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   5598     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   5599 
   5600     const dest_ty = func.typeOfIndex(inst);
   5601     const operand = try func.resolveInst(ty_op.operand);
   5602     const extended = try func.fpext(operand, func.typeOf(ty_op.operand), dest_ty);
   5603     const result = try extended.toLocal(func, dest_ty);
   5604     func.finishAir(inst, result, &.{ty_op.operand});
   5605 }
   5606 
   5607 /// Extends a float from a given `Type` to a larger wanted `Type`
   5608 /// NOTE: Leaves the result on the stack
   5609 fn fpext(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerError!WValue {
   5610     const given_bits = given.floatBits(func.target);
   5611     const wanted_bits = wanted.floatBits(func.target);
   5612 
   5613     if (wanted_bits == 64 and given_bits == 32) {
   5614         try func.emitWValue(operand);
   5615         try func.addTag(.f64_promote_f32);
   5616         return WValue{ .stack = {} };
   5617     } else if (given_bits == 16 and wanted_bits <= 64) {
   5618         // call __extendhfsf2(f16) f32
   5619         const f32_result = try func.callIntrinsic(
   5620             "__extendhfsf2",
   5621             &.{.f16_type},
   5622             Type.f32,
   5623             &.{operand},
   5624         );
   5625         std.debug.assert(f32_result == .stack);
   5626 
   5627         if (wanted_bits == 64) {
   5628             try func.addTag(.f64_promote_f32);
   5629         }
   5630         return WValue{ .stack = {} };
   5631     }
   5632 
   5633     var fn_name_buf: [13]u8 = undefined;
   5634     const fn_name = std.fmt.bufPrint(&fn_name_buf, "__extend{s}f{s}f2", .{
   5635         target_util.compilerRtFloatAbbrev(given_bits),
   5636         target_util.compilerRtFloatAbbrev(wanted_bits),
   5637     }) catch unreachable;
   5638 
   5639     return func.callIntrinsic(fn_name, &.{given.ip_index}, wanted, &.{operand});
   5640 }
   5641 
   5642 fn airFptrunc(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   5643     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   5644 
   5645     const dest_ty = func.typeOfIndex(inst);
   5646     const operand = try func.resolveInst(ty_op.operand);
   5647     const truncated = try func.fptrunc(operand, func.typeOf(ty_op.operand), dest_ty);
   5648     const result = try truncated.toLocal(func, dest_ty);
   5649     func.finishAir(inst, result, &.{ty_op.operand});
   5650 }
   5651 
   5652 /// Truncates a float from a given `Type` to its wanted `Type`
   5653 /// NOTE: The result value remains on the stack
   5654 fn fptrunc(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerError!WValue {
   5655     const given_bits = given.floatBits(func.target);
   5656     const wanted_bits = wanted.floatBits(func.target);
   5657 
   5658     if (wanted_bits == 32 and given_bits == 64) {
   5659         try func.emitWValue(operand);
   5660         try func.addTag(.f32_demote_f64);
   5661         return WValue{ .stack = {} };
   5662     } else if (wanted_bits == 16 and given_bits <= 64) {
   5663         const op: WValue = if (given_bits == 64) blk: {
   5664             try func.emitWValue(operand);
   5665             try func.addTag(.f32_demote_f64);
   5666             break :blk WValue{ .stack = {} };
   5667         } else operand;
   5668 
   5669         // call __truncsfhf2(f32) f16
   5670         return func.callIntrinsic("__truncsfhf2", &.{.f32_type}, Type.f16, &.{op});
   5671     }
   5672 
   5673     var fn_name_buf: [12]u8 = undefined;
   5674     const fn_name = std.fmt.bufPrint(&fn_name_buf, "__trunc{s}f{s}f2", .{
   5675         target_util.compilerRtFloatAbbrev(given_bits),
   5676         target_util.compilerRtFloatAbbrev(wanted_bits),
   5677     }) catch unreachable;
   5678 
   5679     return func.callIntrinsic(fn_name, &.{given.ip_index}, wanted, &.{operand});
   5680 }
   5681 
   5682 fn airErrUnionPayloadPtrSet(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   5683     const mod = func.bin_file.base.comp.module.?;
   5684     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   5685 
   5686     const err_set_ty = func.typeOf(ty_op.operand).childType(mod);
   5687     const payload_ty = err_set_ty.errorUnionPayload(mod);
   5688     const operand = try func.resolveInst(ty_op.operand);
   5689 
   5690     // set error-tag to '0' to annotate error union is non-error
   5691     try func.store(
   5692         operand,
   5693         .{ .imm32 = 0 },
   5694         Type.anyerror,
   5695         @as(u32, @intCast(errUnionErrorOffset(payload_ty, mod))),
   5696     );
   5697 
   5698     const result = result: {
   5699         if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
   5700             break :result func.reuseOperand(ty_op.operand, operand);
   5701         }
   5702 
   5703         break :result try func.buildPointerOffset(operand, @as(u32, @intCast(errUnionPayloadOffset(payload_ty, mod))), .new);
   5704     };
   5705     func.finishAir(inst, result, &.{ty_op.operand});
   5706 }
   5707 
   5708 fn airFieldParentPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   5709     const mod = func.bin_file.base.comp.module.?;
   5710     const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
   5711     const extra = func.air.extraData(Air.FieldParentPtr, ty_pl.payload).data;
   5712 
   5713     const field_ptr = try func.resolveInst(extra.field_ptr);
   5714     const parent_ty = ty_pl.ty.toType().childType(mod);
   5715     const field_offset = parent_ty.structFieldOffset(extra.field_index, mod);
   5716 
   5717     const result = if (field_offset != 0) result: {
   5718         const base = try func.buildPointerOffset(field_ptr, 0, .new);
   5719         try func.addLabel(.local_get, base.local.value);
   5720         try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(field_offset)))));
   5721         try func.addTag(.i32_sub);
   5722         try func.addLabel(.local_set, base.local.value);
   5723         break :result base;
   5724     } else func.reuseOperand(extra.field_ptr, field_ptr);
   5725 
   5726     func.finishAir(inst, result, &.{extra.field_ptr});
   5727 }
   5728 
   5729 fn sliceOrArrayPtr(func: *CodeGen, ptr: WValue, ptr_ty: Type) InnerError!WValue {
   5730     const mod = func.bin_file.base.comp.module.?;
   5731     if (ptr_ty.isSlice(mod)) {
   5732         return func.slicePtr(ptr);
   5733     } else {
   5734         return ptr;
   5735     }
   5736 }
   5737 
   5738 fn airMemcpy(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   5739     const mod = func.bin_file.base.comp.module.?;
   5740     const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
   5741     const dst = try func.resolveInst(bin_op.lhs);
   5742     const dst_ty = func.typeOf(bin_op.lhs);
   5743     const ptr_elem_ty = dst_ty.childType(mod);
   5744     const src = try func.resolveInst(bin_op.rhs);
   5745     const src_ty = func.typeOf(bin_op.rhs);
   5746     const len = switch (dst_ty.ptrSize(mod)) {
   5747         .Slice => blk: {
   5748             const slice_len = try func.sliceLen(dst);
   5749             if (ptr_elem_ty.abiSize(mod) != 1) {
   5750                 try func.emitWValue(slice_len);
   5751                 try func.emitWValue(.{ .imm32 = @as(u32, @intCast(ptr_elem_ty.abiSize(mod))) });
   5752                 try func.addTag(.i32_mul);
   5753                 try func.addLabel(.local_set, slice_len.local.value);
   5754             }
   5755             break :blk slice_len;
   5756         },
   5757         .One => @as(WValue, .{
   5758             .imm32 = @as(u32, @intCast(ptr_elem_ty.arrayLen(mod) * ptr_elem_ty.childType(mod).abiSize(mod))),
   5759         }),
   5760         .C, .Many => unreachable,
   5761     };
   5762     const dst_ptr = try func.sliceOrArrayPtr(dst, dst_ty);
   5763     const src_ptr = try func.sliceOrArrayPtr(src, src_ty);
   5764     try func.memcpy(dst_ptr, src_ptr, len);
   5765 
   5766     func.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs });
   5767 }
   5768 
   5769 fn airRetAddr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   5770     // TODO: Implement this properly once stack serialization is solved
   5771     func.finishAir(inst, switch (func.arch()) {
   5772         .wasm32 => .{ .imm32 = 0 },
   5773         .wasm64 => .{ .imm64 = 0 },
   5774         else => unreachable,
   5775     }, &.{});
   5776 }
   5777 
   5778 fn airPopcount(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   5779     const mod = func.bin_file.base.comp.module.?;
   5780     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   5781 
   5782     const operand = try func.resolveInst(ty_op.operand);
   5783     const op_ty = func.typeOf(ty_op.operand);
   5784     const result_ty = func.typeOfIndex(inst);
   5785 
   5786     if (op_ty.zigTypeTag(mod) == .Vector) {
   5787         return func.fail("TODO: Implement @popCount for vectors", .{});
   5788     }
   5789 
   5790     const int_info = op_ty.intInfo(mod);
   5791     const bits = int_info.bits;
   5792     const wasm_bits = toWasmBits(bits) orelse {
   5793         return func.fail("TODO: Implement @popCount for integers with bitsize '{d}'", .{bits});
   5794     };
   5795 
   5796     switch (wasm_bits) {
   5797         128 => {
   5798             _ = try func.load(operand, Type.u64, 0);
   5799             try func.addTag(.i64_popcnt);
   5800             _ = try func.load(operand, Type.u64, 8);
   5801             try func.addTag(.i64_popcnt);
   5802             try func.addTag(.i64_add);
   5803             try func.addTag(.i32_wrap_i64);
   5804         },
   5805         else => {
   5806             try func.emitWValue(operand);
   5807             switch (wasm_bits) {
   5808                 32 => try func.addTag(.i32_popcnt),
   5809                 64 => {
   5810                     try func.addTag(.i64_popcnt);
   5811                     try func.addTag(.i32_wrap_i64);
   5812                 },
   5813                 else => unreachable,
   5814             }
   5815         },
   5816     }
   5817 
   5818     const result = try func.allocLocal(result_ty);
   5819     try func.addLabel(.local_set, result.local.value);
   5820     func.finishAir(inst, result, &.{ty_op.operand});
   5821 }
   5822 
   5823 fn airErrorName(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   5824     const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
   5825 
   5826     const operand = try func.resolveInst(un_op);
   5827     // First retrieve the symbol index to the error name table
   5828     // that will be used to emit a relocation for the pointer
   5829     // to the error name table.
   5830     //
   5831     // Each entry to this table is a slice (ptr+len).
   5832     // The operand in this instruction represents the index within this table.
   5833     // This means to get the final name, we emit the base pointer and then perform
   5834     // pointer arithmetic to find the pointer to this slice and return that.
   5835     //
   5836     // As the names are global and the slice elements are constant, we do not have
   5837     // to make a copy of the ptr+value but can point towards them directly.
   5838     const error_table_symbol = try func.bin_file.getErrorTableSymbol();
   5839     const name_ty = Type.slice_const_u8_sentinel_0;
   5840     const mod = func.bin_file.base.comp.module.?;
   5841     const abi_size = name_ty.abiSize(mod);
   5842 
   5843     const error_name_value: WValue = .{ .memory = error_table_symbol }; // emitting this will create a relocation
   5844     try func.emitWValue(error_name_value);
   5845     try func.emitWValue(operand);
   5846     switch (func.arch()) {
   5847         .wasm32 => {
   5848             try func.addImm32(@as(i32, @bitCast(@as(u32, @intCast(abi_size)))));
   5849             try func.addTag(.i32_mul);
   5850             try func.addTag(.i32_add);
   5851         },
   5852         .wasm64 => {
   5853             try func.addImm64(abi_size);
   5854             try func.addTag(.i64_mul);
   5855             try func.addTag(.i64_add);
   5856         },
   5857         else => unreachable,
   5858     }
   5859 
   5860     const result_ptr = try func.allocLocal(Type.usize);
   5861     try func.addLabel(.local_set, result_ptr.local.value);
   5862     func.finishAir(inst, result_ptr, &.{un_op});
   5863 }
   5864 
   5865 fn airPtrSliceFieldPtr(func: *CodeGen, inst: Air.Inst.Index, offset: u32) InnerError!void {
   5866     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   5867     const slice_ptr = try func.resolveInst(ty_op.operand);
   5868     const result = try func.buildPointerOffset(slice_ptr, offset, .new);
   5869     func.finishAir(inst, result, &.{ty_op.operand});
   5870 }
   5871 
   5872 fn airAddSubWithOverflow(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerError!void {
   5873     assert(op == .add or op == .sub);
   5874     const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
   5875     const extra = func.air.extraData(Air.Bin, ty_pl.payload).data;
   5876 
   5877     const lhs_op = try func.resolveInst(extra.lhs);
   5878     const rhs_op = try func.resolveInst(extra.rhs);
   5879     const lhs_ty = func.typeOf(extra.lhs);
   5880     const mod = func.bin_file.base.comp.module.?;
   5881 
   5882     if (lhs_ty.zigTypeTag(mod) == .Vector) {
   5883         return func.fail("TODO: Implement overflow arithmetic for vectors", .{});
   5884     }
   5885 
   5886     const int_info = lhs_ty.intInfo(mod);
   5887     const is_signed = int_info.signedness == .signed;
   5888     const wasm_bits = toWasmBits(int_info.bits) orelse {
   5889         return func.fail("TODO: Implement {{add/sub}}_with_overflow for integer bitsize: {d}", .{int_info.bits});
   5890     };
   5891 
   5892     if (wasm_bits == 128) {
   5893         const result = try func.addSubWithOverflowBigInt(lhs_op, rhs_op, lhs_ty, func.typeOfIndex(inst), op);
   5894         return func.finishAir(inst, result, &.{ extra.lhs, extra.rhs });
   5895     }
   5896 
   5897     const zero = switch (wasm_bits) {
   5898         32 => WValue{ .imm32 = 0 },
   5899         64 => WValue{ .imm64 = 0 },
   5900         else => unreachable,
   5901     };
   5902 
   5903     // for signed integers, we first apply signed shifts by the difference in bits
   5904     // to get the signed value, as we store it internally as 2's complement.
   5905     var lhs = if (wasm_bits != int_info.bits and is_signed) blk: {
   5906         break :blk try (try func.signExtendInt(lhs_op, lhs_ty)).toLocal(func, lhs_ty);
   5907     } else lhs_op;
   5908     var rhs = if (wasm_bits != int_info.bits and is_signed) blk: {
   5909         break :blk try (try func.signExtendInt(rhs_op, lhs_ty)).toLocal(func, lhs_ty);
   5910     } else rhs_op;
   5911 
   5912     // in this case, we performed a signExtendInt which created a temporary local
   5913     // so let's free this so it can be re-used instead.
   5914     // In the other case we do not want to free it, because that would free the
   5915     // resolved instructions which may be referenced by other instructions.
   5916     defer if (wasm_bits != int_info.bits and is_signed) {
   5917         lhs.free(func);
   5918         rhs.free(func);
   5919     };
   5920 
   5921     const bin_op = try (try func.binOp(lhs, rhs, lhs_ty, op)).toLocal(func, lhs_ty);
   5922     var result = if (wasm_bits != int_info.bits) blk: {
   5923         break :blk try (try func.wrapOperand(bin_op, lhs_ty)).toLocal(func, lhs_ty);
   5924     } else bin_op;
   5925     defer result.free(func);
   5926 
   5927     const cmp_op: std.math.CompareOperator = if (op == .sub) .gt else .lt;
   5928     const overflow_bit: WValue = if (is_signed) blk: {
   5929         if (wasm_bits == int_info.bits) {
   5930             const cmp_zero = try func.cmp(rhs, zero, lhs_ty, cmp_op);
   5931             const lt = try func.cmp(bin_op, lhs, lhs_ty, .lt);
   5932             break :blk try func.binOp(cmp_zero, lt, Type.u32, .xor);
   5933         }
   5934         const abs = try func.signExtendInt(bin_op, lhs_ty);
   5935         break :blk try func.cmp(abs, bin_op, lhs_ty, .neq);
   5936     } else if (wasm_bits == int_info.bits)
   5937         try func.cmp(bin_op, lhs, lhs_ty, cmp_op)
   5938     else
   5939         try func.cmp(bin_op, result, lhs_ty, .neq);
   5940     var overflow_local = try overflow_bit.toLocal(func, Type.u32);
   5941     defer overflow_local.free(func);
   5942 
   5943     const result_ptr = try func.allocStack(func.typeOfIndex(inst));
   5944     try func.store(result_ptr, result, lhs_ty, 0);
   5945     const offset = @as(u32, @intCast(lhs_ty.abiSize(mod)));
   5946     try func.store(result_ptr, overflow_local, Type.u1, offset);
   5947 
   5948     func.finishAir(inst, result_ptr, &.{ extra.lhs, extra.rhs });
   5949 }
   5950 
   5951 fn addSubWithOverflowBigInt(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, result_ty: Type, op: Op) InnerError!WValue {
   5952     const mod = func.bin_file.base.comp.module.?;
   5953     assert(op == .add or op == .sub);
   5954     const int_info = ty.intInfo(mod);
   5955     const is_signed = int_info.signedness == .signed;
   5956     if (int_info.bits != 128) {
   5957         return func.fail("TODO: Implement @{{add/sub}}WithOverflow for integer bitsize '{d}'", .{int_info.bits});
   5958     }
   5959 
   5960     var lhs_high_bit = try (try func.load(lhs, Type.u64, 0)).toLocal(func, Type.u64);
   5961     defer lhs_high_bit.free(func);
   5962     var lhs_low_bit = try (try func.load(lhs, Type.u64, 8)).toLocal(func, Type.u64);
   5963     defer lhs_low_bit.free(func);
   5964     var rhs_high_bit = try (try func.load(rhs, Type.u64, 0)).toLocal(func, Type.u64);
   5965     defer rhs_high_bit.free(func);
   5966     var rhs_low_bit = try (try func.load(rhs, Type.u64, 8)).toLocal(func, Type.u64);
   5967     defer rhs_low_bit.free(func);
   5968 
   5969     var low_op_res = try (try func.binOp(lhs_low_bit, rhs_low_bit, Type.u64, op)).toLocal(func, Type.u64);
   5970     defer low_op_res.free(func);
   5971     var high_op_res = try (try func.binOp(lhs_high_bit, rhs_high_bit, Type.u64, op)).toLocal(func, Type.u64);
   5972     defer high_op_res.free(func);
   5973 
   5974     var lt = if (op == .add) blk: {
   5975         break :blk try (try func.cmp(high_op_res, lhs_high_bit, Type.u64, .lt)).toLocal(func, Type.u32);
   5976     } else if (op == .sub) blk: {
   5977         break :blk try (try func.cmp(lhs_high_bit, rhs_high_bit, Type.u64, .lt)).toLocal(func, Type.u32);
   5978     } else unreachable;
   5979     defer lt.free(func);
   5980     var tmp = try (try func.intcast(lt, Type.u32, Type.u64)).toLocal(func, Type.u64);
   5981     defer tmp.free(func);
   5982     var tmp_op = try (try func.binOp(low_op_res, tmp, Type.u64, op)).toLocal(func, Type.u64);
   5983     defer tmp_op.free(func);
   5984 
   5985     const overflow_bit = if (is_signed) blk: {
   5986         const xor_low = try func.binOp(lhs_low_bit, rhs_low_bit, Type.u64, .xor);
   5987         const to_wrap = if (op == .add) wrap: {
   5988             break :wrap try func.binOp(xor_low, .{ .imm64 = ~@as(u64, 0) }, Type.u64, .xor);
   5989         } else xor_low;
   5990         const xor_op = try func.binOp(lhs_low_bit, tmp_op, Type.u64, .xor);
   5991         const wrap = try func.binOp(to_wrap, xor_op, Type.u64, .@"and");
   5992         break :blk try func.cmp(wrap, .{ .imm64 = 0 }, Type.i64, .lt); // i64 because signed
   5993     } else blk: {
   5994         const first_arg = if (op == .sub) arg: {
   5995             break :arg try func.cmp(high_op_res, lhs_high_bit, Type.u64, .gt);
   5996         } else lt;
   5997 
   5998         try func.emitWValue(first_arg);
   5999         _ = try func.cmp(tmp_op, lhs_low_bit, Type.u64, if (op == .add) .lt else .gt);
   6000         _ = try func.cmp(tmp_op, lhs_low_bit, Type.u64, .eq);
   6001         try func.addTag(.select);
   6002 
   6003         break :blk WValue{ .stack = {} };
   6004     };
   6005     var overflow_local = try overflow_bit.toLocal(func, Type.u1);
   6006     defer overflow_local.free(func);
   6007 
   6008     const result_ptr = try func.allocStack(result_ty);
   6009     try func.store(result_ptr, high_op_res, Type.u64, 0);
   6010     try func.store(result_ptr, tmp_op, Type.u64, 8);
   6011     try func.store(result_ptr, overflow_local, Type.u1, 16);
   6012 
   6013     return result_ptr;
   6014 }
   6015 
   6016 fn airShlWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   6017     const mod = func.bin_file.base.comp.module.?;
   6018     const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
   6019     const extra = func.air.extraData(Air.Bin, ty_pl.payload).data;
   6020 
   6021     const lhs = try func.resolveInst(extra.lhs);
   6022     const rhs = try func.resolveInst(extra.rhs);
   6023     const lhs_ty = func.typeOf(extra.lhs);
   6024     const rhs_ty = func.typeOf(extra.rhs);
   6025 
   6026     if (lhs_ty.zigTypeTag(mod) == .Vector) {
   6027         return func.fail("TODO: Implement overflow arithmetic for vectors", .{});
   6028     }
   6029 
   6030     const int_info = lhs_ty.intInfo(mod);
   6031     const is_signed = int_info.signedness == .signed;
   6032     const wasm_bits = toWasmBits(int_info.bits) orelse {
   6033         return func.fail("TODO: Implement shl_with_overflow for integer bitsize: {d}", .{int_info.bits});
   6034     };
   6035 
   6036     // Ensure rhs is coerced to lhs as they must have the same WebAssembly types
   6037     // before we can perform any binary operation.
   6038     const rhs_wasm_bits = toWasmBits(rhs_ty.intInfo(mod).bits).?;
   6039     const rhs_final = if (wasm_bits != rhs_wasm_bits) blk: {
   6040         const rhs_casted = try func.intcast(rhs, rhs_ty, lhs_ty);
   6041         break :blk try rhs_casted.toLocal(func, lhs_ty);
   6042     } else rhs;
   6043 
   6044     var shl = try (try func.binOp(lhs, rhs_final, lhs_ty, .shl)).toLocal(func, lhs_ty);
   6045     defer shl.free(func);
   6046     var result = if (wasm_bits != int_info.bits) blk: {
   6047         break :blk try (try func.wrapOperand(shl, lhs_ty)).toLocal(func, lhs_ty);
   6048     } else shl;
   6049     defer result.free(func); // it's a no-op to free the same local twice (when wasm_bits == int_info.bits)
   6050 
   6051     const overflow_bit = if (wasm_bits != int_info.bits and is_signed) blk: {
   6052         // emit lhs to stack to we can keep 'wrapped' on the stack also
   6053         try func.emitWValue(lhs);
   6054         const abs = try func.signExtendInt(shl, lhs_ty);
   6055         const wrapped = try func.wrapBinOp(abs, rhs_final, lhs_ty, .shr);
   6056         break :blk try func.cmp(.{ .stack = {} }, wrapped, lhs_ty, .neq);
   6057     } else blk: {
   6058         try func.emitWValue(lhs);
   6059         const shr = try func.binOp(result, rhs_final, lhs_ty, .shr);
   6060         break :blk try func.cmp(.{ .stack = {} }, shr, lhs_ty, .neq);
   6061     };
   6062     var overflow_local = try overflow_bit.toLocal(func, Type.u1);
   6063     defer overflow_local.free(func);
   6064 
   6065     const result_ptr = try func.allocStack(func.typeOfIndex(inst));
   6066     try func.store(result_ptr, result, lhs_ty, 0);
   6067     const offset = @as(u32, @intCast(lhs_ty.abiSize(mod)));
   6068     try func.store(result_ptr, overflow_local, Type.u1, offset);
   6069 
   6070     func.finishAir(inst, result_ptr, &.{ extra.lhs, extra.rhs });
   6071 }
   6072 
   6073 fn airMulWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   6074     const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
   6075     const extra = func.air.extraData(Air.Bin, ty_pl.payload).data;
   6076 
   6077     const lhs = try func.resolveInst(extra.lhs);
   6078     const rhs = try func.resolveInst(extra.rhs);
   6079     const lhs_ty = func.typeOf(extra.lhs);
   6080     const mod = func.bin_file.base.comp.module.?;
   6081 
   6082     if (lhs_ty.zigTypeTag(mod) == .Vector) {
   6083         return func.fail("TODO: Implement overflow arithmetic for vectors", .{});
   6084     }
   6085 
   6086     // We store the bit if it's overflowed or not in this. As it's zero-initialized
   6087     // we only need to update it if an overflow (or underflow) occurred.
   6088     var overflow_bit = try func.ensureAllocLocal(Type.u1);
   6089     defer overflow_bit.free(func);
   6090 
   6091     const int_info = lhs_ty.intInfo(mod);
   6092     const wasm_bits = toWasmBits(int_info.bits) orelse {
   6093         return func.fail("TODO: Implement `@mulWithOverflow` for integer bitsize: {d}", .{int_info.bits});
   6094     };
   6095 
   6096     const zero = switch (wasm_bits) {
   6097         32 => WValue{ .imm32 = 0 },
   6098         64, 128 => WValue{ .imm64 = 0 },
   6099         else => unreachable,
   6100     };
   6101 
   6102     // for 32 bit integers we upcast it to a 64bit integer
   6103     const bin_op = if (int_info.bits == 32) blk: {
   6104         const new_ty = if (int_info.signedness == .signed) Type.i64 else Type.u64;
   6105         const lhs_upcast = try func.intcast(lhs, lhs_ty, new_ty);
   6106         const rhs_upcast = try func.intcast(rhs, lhs_ty, new_ty);
   6107         const bin_op = try (try func.binOp(lhs_upcast, rhs_upcast, new_ty, .mul)).toLocal(func, new_ty);
   6108         if (int_info.signedness == .unsigned) {
   6109             const shr = try func.binOp(bin_op, .{ .imm64 = int_info.bits }, new_ty, .shr);
   6110             const wrap = try func.intcast(shr, new_ty, lhs_ty);
   6111             _ = try func.cmp(wrap, zero, lhs_ty, .neq);
   6112             try func.addLabel(.local_set, overflow_bit.local.value);
   6113             break :blk try func.intcast(bin_op, new_ty, lhs_ty);
   6114         } else {
   6115             const down_cast = try (try func.intcast(bin_op, new_ty, lhs_ty)).toLocal(func, lhs_ty);
   6116             var shr = try (try func.binOp(down_cast, .{ .imm32 = int_info.bits - 1 }, lhs_ty, .shr)).toLocal(func, lhs_ty);
   6117             defer shr.free(func);
   6118 
   6119             const shr_res = try func.binOp(bin_op, .{ .imm64 = int_info.bits }, new_ty, .shr);
   6120             const down_shr_res = try func.intcast(shr_res, new_ty, lhs_ty);
   6121             _ = try func.cmp(down_shr_res, shr, lhs_ty, .neq);
   6122             try func.addLabel(.local_set, overflow_bit.local.value);
   6123             break :blk down_cast;
   6124         }
   6125     } else if (int_info.signedness == .signed and wasm_bits == 32) blk: {
   6126         const lhs_abs = try func.signExtendInt(lhs, lhs_ty);
   6127         const rhs_abs = try func.signExtendInt(rhs, lhs_ty);
   6128         const bin_op = try (try func.binOp(lhs_abs, rhs_abs, lhs_ty, .mul)).toLocal(func, lhs_ty);
   6129         const mul_abs = try func.signExtendInt(bin_op, lhs_ty);
   6130         _ = try func.cmp(mul_abs, bin_op, lhs_ty, .neq);
   6131         try func.addLabel(.local_set, overflow_bit.local.value);
   6132         break :blk try func.wrapOperand(bin_op, lhs_ty);
   6133     } else if (wasm_bits == 32) blk: {
   6134         var bin_op = try (try func.binOp(lhs, rhs, lhs_ty, .mul)).toLocal(func, lhs_ty);
   6135         defer bin_op.free(func);
   6136         const shift_imm = if (wasm_bits == 32)
   6137             WValue{ .imm32 = int_info.bits }
   6138         else
   6139             WValue{ .imm64 = int_info.bits };
   6140         const shr = try func.binOp(bin_op, shift_imm, lhs_ty, .shr);
   6141         _ = try func.cmp(shr, zero, lhs_ty, .neq);
   6142         try func.addLabel(.local_set, overflow_bit.local.value);
   6143         break :blk try func.wrapOperand(bin_op, lhs_ty);
   6144     } else if (int_info.bits == 64 and int_info.signedness == .unsigned) blk: {
   6145         const new_ty = Type.u128;
   6146         var lhs_upcast = try (try func.intcast(lhs, lhs_ty, new_ty)).toLocal(func, lhs_ty);
   6147         defer lhs_upcast.free(func);
   6148         var rhs_upcast = try (try func.intcast(rhs, lhs_ty, new_ty)).toLocal(func, lhs_ty);
   6149         defer rhs_upcast.free(func);
   6150         const bin_op = try func.binOp(lhs_upcast, rhs_upcast, new_ty, .mul);
   6151         const lsb = try func.load(bin_op, lhs_ty, 8);
   6152         _ = try func.cmp(lsb, zero, lhs_ty, .neq);
   6153         try func.addLabel(.local_set, overflow_bit.local.value);
   6154 
   6155         break :blk try func.load(bin_op, lhs_ty, 0);
   6156     } else if (int_info.bits == 64 and int_info.signedness == .signed) blk: {
   6157         const shift_val: WValue = .{ .imm64 = 63 };
   6158         var lhs_shifted = try (try func.binOp(lhs, shift_val, lhs_ty, .shr)).toLocal(func, lhs_ty);
   6159         defer lhs_shifted.free(func);
   6160         var rhs_shifted = try (try func.binOp(rhs, shift_val, lhs_ty, .shr)).toLocal(func, lhs_ty);
   6161         defer rhs_shifted.free(func);
   6162 
   6163         const bin_op = try func.callIntrinsic(
   6164             "__multi3",
   6165             &[_]InternPool.Index{.i64_type} ** 4,
   6166             Type.i128,
   6167             &.{ lhs, lhs_shifted, rhs, rhs_shifted },
   6168         );
   6169         const res = try func.allocLocal(lhs_ty);
   6170         const msb = try func.load(bin_op, lhs_ty, 0);
   6171         try func.addLabel(.local_tee, res.local.value);
   6172         const msb_shifted = try func.binOp(msb, shift_val, lhs_ty, .shr);
   6173         const lsb = try func.load(bin_op, lhs_ty, 8);
   6174         _ = try func.cmp(lsb, msb_shifted, lhs_ty, .neq);
   6175         try func.addLabel(.local_set, overflow_bit.local.value);
   6176         break :blk res;
   6177     } else if (int_info.bits == 128 and int_info.signedness == .unsigned) blk: {
   6178         var lhs_msb = try (try func.load(lhs, Type.u64, 0)).toLocal(func, Type.u64);
   6179         defer lhs_msb.free(func);
   6180         var lhs_lsb = try (try func.load(lhs, Type.u64, 8)).toLocal(func, Type.u64);
   6181         defer lhs_lsb.free(func);
   6182         var rhs_msb = try (try func.load(rhs, Type.u64, 0)).toLocal(func, Type.u64);
   6183         defer rhs_msb.free(func);
   6184         var rhs_lsb = try (try func.load(rhs, Type.u64, 8)).toLocal(func, Type.u64);
   6185         defer rhs_lsb.free(func);
   6186 
   6187         const mul1 = try func.callIntrinsic(
   6188             "__multi3",
   6189             &[_]InternPool.Index{.i64_type} ** 4,
   6190             Type.i128,
   6191             &.{ lhs_lsb, zero, rhs_msb, zero },
   6192         );
   6193         const mul2 = try func.callIntrinsic(
   6194             "__multi3",
   6195             &[_]InternPool.Index{.i64_type} ** 4,
   6196             Type.i128,
   6197             &.{ rhs_lsb, zero, lhs_msb, zero },
   6198         );
   6199         const mul3 = try func.callIntrinsic(
   6200             "__multi3",
   6201             &[_]InternPool.Index{.i64_type} ** 4,
   6202             Type.i128,
   6203             &.{ lhs_msb, zero, rhs_msb, zero },
   6204         );
   6205 
   6206         const rhs_lsb_not_zero = try func.cmp(rhs_lsb, zero, Type.u64, .neq);
   6207         const lhs_lsb_not_zero = try func.cmp(lhs_lsb, zero, Type.u64, .neq);
   6208         const lsb_and = try func.binOp(rhs_lsb_not_zero, lhs_lsb_not_zero, Type.bool, .@"and");
   6209         const mul1_lsb = try func.load(mul1, Type.u64, 8);
   6210         const mul1_lsb_not_zero = try func.cmp(mul1_lsb, zero, Type.u64, .neq);
   6211         const lsb_or1 = try func.binOp(lsb_and, mul1_lsb_not_zero, Type.bool, .@"or");
   6212         const mul2_lsb = try func.load(mul2, Type.u64, 8);
   6213         const mul2_lsb_not_zero = try func.cmp(mul2_lsb, zero, Type.u64, .neq);
   6214         const lsb_or = try func.binOp(lsb_or1, mul2_lsb_not_zero, Type.bool, .@"or");
   6215 
   6216         const mul1_msb = try func.load(mul1, Type.u64, 0);
   6217         const mul2_msb = try func.load(mul2, Type.u64, 0);
   6218         const mul_add1 = try func.binOp(mul1_msb, mul2_msb, Type.u64, .add);
   6219 
   6220         var mul3_lsb = try (try func.load(mul3, Type.u64, 8)).toLocal(func, Type.u64);
   6221         defer mul3_lsb.free(func);
   6222         var mul_add2 = try (try func.binOp(mul_add1, mul3_lsb, Type.u64, .add)).toLocal(func, Type.u64);
   6223         defer mul_add2.free(func);
   6224         const mul_add_lt = try func.cmp(mul_add2, mul3_lsb, Type.u64, .lt);
   6225 
   6226         // result for overflow bit
   6227         _ = try func.binOp(lsb_or, mul_add_lt, Type.bool, .@"or");
   6228         try func.addLabel(.local_set, overflow_bit.local.value);
   6229 
   6230         const tmp_result = try func.allocStack(Type.u128);
   6231         try func.emitWValue(tmp_result);
   6232         const mul3_msb = try func.load(mul3, Type.u64, 0);
   6233         try func.store(.stack, mul3_msb, Type.u64, tmp_result.offset());
   6234         try func.store(tmp_result, mul_add2, Type.u64, 8);
   6235         break :blk tmp_result;
   6236     } else return func.fail("TODO: @mulWithOverflow for integers between 32 and 64 bits", .{});
   6237     var bin_op_local = try bin_op.toLocal(func, lhs_ty);
   6238     defer bin_op_local.free(func);
   6239 
   6240     const result_ptr = try func.allocStack(func.typeOfIndex(inst));
   6241     try func.store(result_ptr, bin_op_local, lhs_ty, 0);
   6242     const offset = @as(u32, @intCast(lhs_ty.abiSize(mod)));
   6243     try func.store(result_ptr, overflow_bit, Type.u1, offset);
   6244 
   6245     func.finishAir(inst, result_ptr, &.{ extra.lhs, extra.rhs });
   6246 }
   6247 
   6248 fn airMaxMin(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerError!void {
   6249     assert(op == .max or op == .min);
   6250     const mod = func.bin_file.base.comp.module.?;
   6251     const target = mod.getTarget();
   6252     const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
   6253 
   6254     const ty = func.typeOfIndex(inst);
   6255     if (ty.zigTypeTag(mod) == .Vector) {
   6256         return func.fail("TODO: `@maximum` and `@minimum` for vectors", .{});
   6257     }
   6258 
   6259     if (ty.abiSize(mod) > 16) {
   6260         return func.fail("TODO: `@maximum` and `@minimum` for types larger than 16 bytes", .{});
   6261     }
   6262 
   6263     const lhs = try func.resolveInst(bin_op.lhs);
   6264     const rhs = try func.resolveInst(bin_op.rhs);
   6265 
   6266     if (ty.zigTypeTag(mod) == .Float) {
   6267         var fn_name_buf: [64]u8 = undefined;
   6268         const float_bits = ty.floatBits(target);
   6269         const fn_name = std.fmt.bufPrint(&fn_name_buf, "{s}f{s}{s}", .{
   6270             target_util.libcFloatPrefix(float_bits),
   6271             @tagName(op),
   6272             target_util.libcFloatSuffix(float_bits),
   6273         }) catch unreachable;
   6274         const result = try func.callIntrinsic(fn_name, &.{ ty.ip_index, ty.ip_index }, ty, &.{ lhs, rhs });
   6275         try func.lowerToStack(result);
   6276     } else {
   6277         // operands to select from
   6278         try func.lowerToStack(lhs);
   6279         try func.lowerToStack(rhs);
   6280         _ = try func.cmp(lhs, rhs, ty, if (op == .max) .gt else .lt);
   6281 
   6282         // based on the result from comparison, return operand 0 or 1.
   6283         try func.addTag(.select);
   6284     }
   6285 
   6286     // store result in local
   6287     const result_ty = if (isByRef(ty, mod)) Type.u32 else ty;
   6288     const result = try func.allocLocal(result_ty);
   6289     try func.addLabel(.local_set, result.local.value);
   6290     func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
   6291 }
   6292 
   6293 fn airMulAdd(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   6294     const mod = func.bin_file.base.comp.module.?;
   6295     const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
   6296     const bin_op = func.air.extraData(Air.Bin, pl_op.payload).data;
   6297 
   6298     const ty = func.typeOfIndex(inst);
   6299     if (ty.zigTypeTag(mod) == .Vector) {
   6300         return func.fail("TODO: `@mulAdd` for vectors", .{});
   6301     }
   6302 
   6303     const addend = try func.resolveInst(pl_op.operand);
   6304     const lhs = try func.resolveInst(bin_op.lhs);
   6305     const rhs = try func.resolveInst(bin_op.rhs);
   6306 
   6307     const result = if (ty.floatBits(func.target) == 16) fl_result: {
   6308         const rhs_ext = try func.fpext(rhs, ty, Type.f32);
   6309         const lhs_ext = try func.fpext(lhs, ty, Type.f32);
   6310         const addend_ext = try func.fpext(addend, ty, Type.f32);
   6311         // call to compiler-rt `fn fmaf(f32, f32, f32) f32`
   6312         const result = try func.callIntrinsic(
   6313             "fmaf",
   6314             &.{ .f32_type, .f32_type, .f32_type },
   6315             Type.f32,
   6316             &.{ rhs_ext, lhs_ext, addend_ext },
   6317         );
   6318         break :fl_result try (try func.fptrunc(result, Type.f32, ty)).toLocal(func, ty);
   6319     } else result: {
   6320         const mul_result = try func.binOp(lhs, rhs, ty, .mul);
   6321         break :result try (try func.binOp(mul_result, addend, ty, .add)).toLocal(func, ty);
   6322     };
   6323 
   6324     func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs, pl_op.operand });
   6325 }
   6326 
   6327 fn airClz(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   6328     const mod = func.bin_file.base.comp.module.?;
   6329     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   6330 
   6331     const ty = func.typeOf(ty_op.operand);
   6332     const result_ty = func.typeOfIndex(inst);
   6333     if (ty.zigTypeTag(mod) == .Vector) {
   6334         return func.fail("TODO: `@clz` for vectors", .{});
   6335     }
   6336 
   6337     const operand = try func.resolveInst(ty_op.operand);
   6338     const int_info = ty.intInfo(mod);
   6339     const wasm_bits = toWasmBits(int_info.bits) orelse {
   6340         return func.fail("TODO: `@clz` for integers with bitsize '{d}'", .{int_info.bits});
   6341     };
   6342 
   6343     switch (wasm_bits) {
   6344         32 => {
   6345             try func.emitWValue(operand);
   6346             try func.addTag(.i32_clz);
   6347         },
   6348         64 => {
   6349             try func.emitWValue(operand);
   6350             try func.addTag(.i64_clz);
   6351             try func.addTag(.i32_wrap_i64);
   6352         },
   6353         128 => {
   6354             var lsb = try (try func.load(operand, Type.u64, 8)).toLocal(func, Type.u64);
   6355             defer lsb.free(func);
   6356 
   6357             try func.emitWValue(lsb);
   6358             try func.addTag(.i64_clz);
   6359             _ = try func.load(operand, Type.u64, 0);
   6360             try func.addTag(.i64_clz);
   6361             try func.emitWValue(.{ .imm64 = 64 });
   6362             try func.addTag(.i64_add);
   6363             _ = try func.cmp(lsb, .{ .imm64 = 0 }, Type.u64, .neq);
   6364             try func.addTag(.select);
   6365             try func.addTag(.i32_wrap_i64);
   6366         },
   6367         else => unreachable,
   6368     }
   6369 
   6370     if (wasm_bits != int_info.bits) {
   6371         try func.emitWValue(.{ .imm32 = wasm_bits - int_info.bits });
   6372         try func.addTag(.i32_sub);
   6373     }
   6374 
   6375     const result = try func.allocLocal(result_ty);
   6376     try func.addLabel(.local_set, result.local.value);
   6377     func.finishAir(inst, result, &.{ty_op.operand});
   6378 }
   6379 
   6380 fn airCtz(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   6381     const mod = func.bin_file.base.comp.module.?;
   6382     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   6383 
   6384     const ty = func.typeOf(ty_op.operand);
   6385     const result_ty = func.typeOfIndex(inst);
   6386 
   6387     if (ty.zigTypeTag(mod) == .Vector) {
   6388         return func.fail("TODO: `@ctz` for vectors", .{});
   6389     }
   6390 
   6391     const operand = try func.resolveInst(ty_op.operand);
   6392     const int_info = ty.intInfo(mod);
   6393     const wasm_bits = toWasmBits(int_info.bits) orelse {
   6394         return func.fail("TODO: `@clz` for integers with bitsize '{d}'", .{int_info.bits});
   6395     };
   6396 
   6397     switch (wasm_bits) {
   6398         32 => {
   6399             if (wasm_bits != int_info.bits) {
   6400                 const val: u32 = @as(u32, 1) << @as(u5, @intCast(int_info.bits));
   6401                 // leave value on the stack
   6402                 _ = try func.binOp(operand, .{ .imm32 = val }, ty, .@"or");
   6403             } else try func.emitWValue(operand);
   6404             try func.addTag(.i32_ctz);
   6405         },
   6406         64 => {
   6407             if (wasm_bits != int_info.bits) {
   6408                 const val: u64 = @as(u64, 1) << @as(u6, @intCast(int_info.bits));
   6409                 // leave value on the stack
   6410                 _ = try func.binOp(operand, .{ .imm64 = val }, ty, .@"or");
   6411             } else try func.emitWValue(operand);
   6412             try func.addTag(.i64_ctz);
   6413             try func.addTag(.i32_wrap_i64);
   6414         },
   6415         128 => {
   6416             var msb = try (try func.load(operand, Type.u64, 0)).toLocal(func, Type.u64);
   6417             defer msb.free(func);
   6418 
   6419             try func.emitWValue(msb);
   6420             try func.addTag(.i64_ctz);
   6421             _ = try func.load(operand, Type.u64, 8);
   6422             if (wasm_bits != int_info.bits) {
   6423                 try func.addImm64(@as(u64, 1) << @as(u6, @intCast(int_info.bits - 64)));
   6424                 try func.addTag(.i64_or);
   6425             }
   6426             try func.addTag(.i64_ctz);
   6427             try func.addImm64(64);
   6428             if (wasm_bits != int_info.bits) {
   6429                 try func.addTag(.i64_or);
   6430             } else {
   6431                 try func.addTag(.i64_add);
   6432             }
   6433             _ = try func.cmp(msb, .{ .imm64 = 0 }, Type.u64, .neq);
   6434             try func.addTag(.select);
   6435             try func.addTag(.i32_wrap_i64);
   6436         },
   6437         else => unreachable,
   6438     }
   6439 
   6440     const result = try func.allocLocal(result_ty);
   6441     try func.addLabel(.local_set, result.local.value);
   6442     func.finishAir(inst, result, &.{ty_op.operand});
   6443 }
   6444 
   6445 fn airDbgStmt(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   6446     if (func.debug_output != .dwarf) return func.finishAir(inst, .none, &.{});
   6447 
   6448     const dbg_stmt = func.air.instructions.items(.data)[@intFromEnum(inst)].dbg_stmt;
   6449     try func.addInst(.{ .tag = .dbg_line, .data = .{
   6450         .payload = try func.addExtra(Mir.DbgLineColumn{
   6451             .line = dbg_stmt.line,
   6452             .column = dbg_stmt.column,
   6453         }),
   6454     } });
   6455     func.finishAir(inst, .none, &.{});
   6456 }
   6457 
   6458 fn airDbgInlineBlock(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   6459     const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
   6460     const extra = func.air.extraData(Air.DbgInlineBlock, ty_pl.payload);
   6461     // TODO
   6462     try func.lowerBlock(inst, ty_pl.ty.toType(), @ptrCast(func.air.extra[extra.end..][0..extra.data.body_len]));
   6463 }
   6464 
   6465 fn airDbgVar(func: *CodeGen, inst: Air.Inst.Index, is_ptr: bool) InnerError!void {
   6466     if (func.debug_output != .dwarf) return func.finishAir(inst, .none, &.{});
   6467 
   6468     const mod = func.bin_file.base.comp.module.?;
   6469     const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
   6470     const ty = func.typeOf(pl_op.operand);
   6471     const operand = try func.resolveInst(pl_op.operand);
   6472 
   6473     log.debug("airDbgVar: %{d}: {}, {}", .{ inst, ty.fmtDebug(), operand });
   6474 
   6475     const name = func.air.nullTerminatedString(pl_op.payload);
   6476     log.debug(" var name = ({s})", .{name});
   6477 
   6478     const loc: link.File.Dwarf.DeclState.DbgInfoLoc = switch (operand) {
   6479         .local => |local| .{ .wasm_local = local.value },
   6480         else => blk: {
   6481             log.debug("TODO generate debug info for {}", .{operand});
   6482             break :blk .nop;
   6483         },
   6484     };
   6485     try func.debug_output.dwarf.genVarDbgInfo(name, ty, mod.funcOwnerDeclIndex(func.func_index), is_ptr, loc);
   6486 
   6487     func.finishAir(inst, .none, &.{});
   6488 }
   6489 
   6490 fn airTry(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   6491     const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
   6492     const err_union = try func.resolveInst(pl_op.operand);
   6493     const extra = func.air.extraData(Air.Try, pl_op.payload);
   6494     const body: []const Air.Inst.Index = @ptrCast(func.air.extra[extra.end..][0..extra.data.body_len]);
   6495     const err_union_ty = func.typeOf(pl_op.operand);
   6496     const result = try lowerTry(func, inst, err_union, body, err_union_ty, false);
   6497     func.finishAir(inst, result, &.{pl_op.operand});
   6498 }
   6499 
   6500 fn airTryPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   6501     const mod = func.bin_file.base.comp.module.?;
   6502     const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
   6503     const extra = func.air.extraData(Air.TryPtr, ty_pl.payload);
   6504     const err_union_ptr = try func.resolveInst(extra.data.ptr);
   6505     const body: []const Air.Inst.Index = @ptrCast(func.air.extra[extra.end..][0..extra.data.body_len]);
   6506     const err_union_ty = func.typeOf(extra.data.ptr).childType(mod);
   6507     const result = try lowerTry(func, inst, err_union_ptr, body, err_union_ty, true);
   6508     func.finishAir(inst, result, &.{extra.data.ptr});
   6509 }
   6510 
   6511 fn lowerTry(
   6512     func: *CodeGen,
   6513     inst: Air.Inst.Index,
   6514     err_union: WValue,
   6515     body: []const Air.Inst.Index,
   6516     err_union_ty: Type,
   6517     operand_is_ptr: bool,
   6518 ) InnerError!WValue {
   6519     const mod = func.bin_file.base.comp.module.?;
   6520     if (operand_is_ptr) {
   6521         return func.fail("TODO: lowerTry for pointers", .{});
   6522     }
   6523 
   6524     const pl_ty = err_union_ty.errorUnionPayload(mod);
   6525     const pl_has_bits = pl_ty.hasRuntimeBitsIgnoreComptime(mod);
   6526 
   6527     if (!err_union_ty.errorUnionSet(mod).errorSetIsEmpty(mod)) {
   6528         // Block we can jump out of when error is not set
   6529         try func.startBlock(.block, wasm.block_empty);
   6530 
   6531         // check if the error tag is set for the error union.
   6532         try func.emitWValue(err_union);
   6533         if (pl_has_bits) {
   6534             const err_offset = @as(u32, @intCast(errUnionErrorOffset(pl_ty, mod)));
   6535             try func.addMemArg(.i32_load16_u, .{
   6536                 .offset = err_union.offset() + err_offset,
   6537                 .alignment = @intCast(Type.anyerror.abiAlignment(mod).toByteUnits().?),
   6538             });
   6539         }
   6540         try func.addTag(.i32_eqz);
   6541         try func.addLabel(.br_if, 0); // jump out of block when error is '0'
   6542 
   6543         const liveness = func.liveness.getCondBr(inst);
   6544         try func.branches.append(func.gpa, .{});
   6545         try func.currentBranch().values.ensureUnusedCapacity(func.gpa, liveness.else_deaths.len + liveness.then_deaths.len);
   6546         defer {
   6547             var branch = func.branches.pop();
   6548             branch.deinit(func.gpa);
   6549         }
   6550         try func.genBody(body);
   6551         try func.endBlock();
   6552     }
   6553 
   6554     // if we reach here it means error was not set, and we want the payload
   6555     if (!pl_has_bits) {
   6556         return WValue{ .none = {} };
   6557     }
   6558 
   6559     const pl_offset = @as(u32, @intCast(errUnionPayloadOffset(pl_ty, mod)));
   6560     if (isByRef(pl_ty, mod)) {
   6561         return buildPointerOffset(func, err_union, pl_offset, .new);
   6562     }
   6563     const payload = try func.load(err_union, pl_ty, pl_offset);
   6564     return payload.toLocal(func, pl_ty);
   6565 }
   6566 
   6567 fn airByteSwap(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   6568     const mod = func.bin_file.base.comp.module.?;
   6569     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   6570 
   6571     const ty = func.typeOfIndex(inst);
   6572     const operand = try func.resolveInst(ty_op.operand);
   6573 
   6574     if (ty.zigTypeTag(mod) == .Vector) {
   6575         return func.fail("TODO: @byteSwap for vectors", .{});
   6576     }
   6577     const int_info = ty.intInfo(mod);
   6578 
   6579     // bytes are no-op
   6580     if (int_info.bits == 8) {
   6581         return func.finishAir(inst, func.reuseOperand(ty_op.operand, operand), &.{ty_op.operand});
   6582     }
   6583 
   6584     const result = result: {
   6585         switch (int_info.bits) {
   6586             16 => {
   6587                 const shl_res = try func.binOp(operand, .{ .imm32 = 8 }, ty, .shl);
   6588                 const lhs = try func.binOp(shl_res, .{ .imm32 = 0xFF00 }, ty, .@"and");
   6589                 const shr_res = try func.binOp(operand, .{ .imm32 = 8 }, ty, .shr);
   6590                 const res = if (int_info.signedness == .signed) blk: {
   6591                     break :blk try func.wrapOperand(shr_res, Type.u8);
   6592                 } else shr_res;
   6593                 break :result try (try func.binOp(lhs, res, ty, .@"or")).toLocal(func, ty);
   6594             },
   6595             24 => {
   6596                 var msb = try (try func.wrapOperand(operand, Type.u16)).toLocal(func, Type.u16);
   6597                 defer msb.free(func);
   6598 
   6599                 const shl_res = try func.binOp(msb, .{ .imm32 = 8 }, Type.u16, .shl);
   6600                 const lhs = try func.binOp(shl_res, .{ .imm32 = 0xFF0000 }, Type.u16, .@"and");
   6601                 const shr_res = try func.binOp(msb, .{ .imm32 = 8 }, ty, .shr);
   6602 
   6603                 const res = if (int_info.signedness == .signed) blk: {
   6604                     break :blk try func.wrapOperand(shr_res, Type.u8);
   6605                 } else shr_res;
   6606                 const lhs_tmp = try func.binOp(lhs, res, ty, .@"or");
   6607                 const lhs_result = try func.binOp(lhs_tmp, .{ .imm32 = 8 }, ty, .shr);
   6608                 const rhs_wrap = try func.wrapOperand(msb, Type.u8);
   6609                 const rhs_result = try func.binOp(rhs_wrap, .{ .imm32 = 16 }, ty, .shl);
   6610 
   6611                 const lsb = try func.wrapBinOp(operand, .{ .imm32 = 16 }, Type.u8, .shr);
   6612                 const tmp = try func.binOp(lhs_result, rhs_result, ty, .@"or");
   6613                 break :result try (try func.binOp(tmp, lsb, ty, .@"or")).toLocal(func, ty);
   6614             },
   6615             32 => {
   6616                 const shl_tmp = try func.binOp(operand, .{ .imm32 = 8 }, Type.u32, .shl);
   6617                 const lhs = try func.binOp(shl_tmp, .{ .imm32 = 0xFF00FF00 }, Type.u32, .@"and");
   6618                 const shr_tmp = try func.binOp(operand, .{ .imm32 = 8 }, Type.u32, .shr);
   6619                 const rhs = try func.binOp(shr_tmp, .{ .imm32 = 0x00FF00FF }, Type.u32, .@"and");
   6620                 var tmp_or = try (try func.binOp(lhs, rhs, Type.u32, .@"or")).toLocal(func, Type.u32);
   6621 
   6622                 const shl = try func.binOp(tmp_or, .{ .imm32 = 16 }, Type.u32, .shl);
   6623                 const shr = try func.binOp(tmp_or, .{ .imm32 = 16 }, Type.u32, .shr);
   6624 
   6625                 tmp_or.free(func);
   6626 
   6627                 break :result try (try func.binOp(shl, shr, Type.u32, .@"or")).toLocal(func, Type.u32);
   6628             },
   6629             64 => {
   6630                 const shl_tmp_1 = try func.binOp(operand, .{ .imm64 = 8 }, Type.u64, .shl);
   6631                 const lhs_1 = try func.binOp(shl_tmp_1, .{ .imm64 = 0xFF00FF00FF00FF00 }, Type.u64, .@"and");
   6632 
   6633                 const shr_tmp_1 = try func.binOp(operand, .{ .imm64 = 8 }, Type.u64, .shr);
   6634                 const rhs_1 = try func.binOp(shr_tmp_1, .{ .imm64 = 0x00FF00FF00FF00FF }, Type.u64, .@"and");
   6635 
   6636                 var tmp_or_1 = try (try func.binOp(lhs_1, rhs_1, Type.u64, .@"or")).toLocal(func, Type.u64);
   6637 
   6638                 const shl_tmp_2 = try func.binOp(tmp_or_1, .{ .imm64 = 16 }, Type.u64, .shl);
   6639                 const lhs_2 = try func.binOp(shl_tmp_2, .{ .imm64 = 0xFFFF0000FFFF0000 }, Type.u64, .@"and");
   6640 
   6641                 const shr_tmp_2 = try func.binOp(tmp_or_1, .{ .imm64 = 16 }, Type.u64, .shr);
   6642                 tmp_or_1.free(func);
   6643                 const rhs_2 = try func.binOp(shr_tmp_2, .{ .imm64 = 0x0000FFFF0000FFFF }, Type.u64, .@"and");
   6644 
   6645                 var tmp_or_2 = try (try func.binOp(lhs_2, rhs_2, Type.u64, .@"or")).toLocal(func, Type.u64);
   6646 
   6647                 const shl = try func.binOp(tmp_or_2, .{ .imm64 = 32 }, Type.u64, .shl);
   6648                 const shr = try func.binOp(tmp_or_2, .{ .imm64 = 32 }, Type.u64, .shr);
   6649                 tmp_or_2.free(func);
   6650 
   6651                 break :result try (try func.binOp(shl, shr, Type.u64, .@"or")).toLocal(func, Type.u64);
   6652             },
   6653             else => return func.fail("TODO: @byteSwap for integers with bitsize {d}", .{int_info.bits}),
   6654         }
   6655     };
   6656     func.finishAir(inst, result, &.{ty_op.operand});
   6657 }
   6658 
   6659 fn airDiv(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   6660     const mod = func.bin_file.base.comp.module.?;
   6661     const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
   6662 
   6663     const ty = func.typeOfIndex(inst);
   6664     const lhs = try func.resolveInst(bin_op.lhs);
   6665     const rhs = try func.resolveInst(bin_op.rhs);
   6666 
   6667     const result = if (ty.isSignedInt(mod))
   6668         try func.divSigned(lhs, rhs, ty)
   6669     else
   6670         try (try func.binOp(lhs, rhs, ty, .div)).toLocal(func, ty);
   6671     func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
   6672 }
   6673 
   6674 fn airDivTrunc(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   6675     const mod = func.bin_file.base.comp.module.?;
   6676     const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
   6677 
   6678     const ty = func.typeOfIndex(inst);
   6679     const lhs = try func.resolveInst(bin_op.lhs);
   6680     const rhs = try func.resolveInst(bin_op.rhs);
   6681 
   6682     const div_result = if (ty.isSignedInt(mod))
   6683         try func.divSigned(lhs, rhs, ty)
   6684     else
   6685         try (try func.binOp(lhs, rhs, ty, .div)).toLocal(func, ty);
   6686 
   6687     if (ty.isAnyFloat()) {
   6688         const trunc_result = try (try func.floatOp(.trunc, ty, &.{div_result})).toLocal(func, ty);
   6689         return func.finishAir(inst, trunc_result, &.{ bin_op.lhs, bin_op.rhs });
   6690     }
   6691 
   6692     return func.finishAir(inst, div_result, &.{ bin_op.lhs, bin_op.rhs });
   6693 }
   6694 
   6695 fn airDivFloor(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   6696     const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
   6697 
   6698     const mod = func.bin_file.base.comp.module.?;
   6699     const ty = func.typeOfIndex(inst);
   6700     const lhs = try func.resolveInst(bin_op.lhs);
   6701     const rhs = try func.resolveInst(bin_op.rhs);
   6702 
   6703     if (ty.isUnsignedInt(mod)) {
   6704         _ = try func.binOp(lhs, rhs, ty, .div);
   6705     } else if (ty.isSignedInt(mod)) {
   6706         const int_bits = ty.intInfo(mod).bits;
   6707         const wasm_bits = toWasmBits(int_bits) orelse {
   6708             return func.fail("TODO: `@divFloor` for signed integers larger than 64 bits ({d} bits requested)", .{int_bits});
   6709         };
   6710 
   6711         if (wasm_bits > 64) {
   6712             return func.fail("TODO: `@divFloor` for signed integers larger than 64 bits ({d} bits requested)", .{int_bits});
   6713         }
   6714 
   6715         const lhs_wasm = if (wasm_bits != int_bits)
   6716             try (try func.signExtendInt(lhs, ty)).toLocal(func, ty)
   6717         else
   6718             lhs;
   6719 
   6720         const rhs_wasm = if (wasm_bits != int_bits)
   6721             try (try func.signExtendInt(rhs, ty)).toLocal(func, ty)
   6722         else
   6723             rhs;
   6724 
   6725         const zero = switch (wasm_bits) {
   6726             32 => WValue{ .imm32 = 0 },
   6727             64 => WValue{ .imm64 = 0 },
   6728             else => unreachable,
   6729         };
   6730 
   6731         // tee leaves the value on the stack and stores it in a local.
   6732         const quotient = try func.allocLocal(ty);
   6733         _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .div);
   6734         try func.addLabel(.local_tee, quotient.local.value);
   6735 
   6736         // select takes a 32 bit value as the condition, so in the 64 bit case we use eqz to narrow
   6737         // the 64 bit value we want to use as the condition to 32 bits.
   6738         // This also inverts the condition (non 0 => 0, 0 => 1), so we put the adjusted and
   6739         // non-adjusted quotients on the stack in the opposite order for 32 vs 64 bits.
   6740         if (wasm_bits == 64) {
   6741             try func.emitWValue(quotient);
   6742         }
   6743 
   6744         // 0 if the signs of rhs_wasm and lhs_wasm are the same, 1 otherwise.
   6745         _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .xor);
   6746         _ = try func.cmp(.stack, zero, ty, .lt);
   6747 
   6748         switch (wasm_bits) {
   6749             32 => {
   6750                 try func.addTag(.i32_sub);
   6751                 try func.emitWValue(quotient);
   6752             },
   6753             64 => {
   6754                 try func.addTag(.i64_extend_i32_u);
   6755                 try func.addTag(.i64_sub);
   6756             },
   6757             else => unreachable,
   6758         }
   6759 
   6760         _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .rem);
   6761 
   6762         if (wasm_bits == 64) {
   6763             try func.addTag(.i64_eqz);
   6764         }
   6765 
   6766         try func.addTag(.select);
   6767 
   6768         // We need to zero the high bits because N bit comparisons consider all 32 or 64 bits, and
   6769         // expect all but the lowest N bits to be 0.
   6770         // TODO: Should we be zeroing the high bits here or should we be ignoring the high bits
   6771         // when performing comparisons?
   6772         if (int_bits != wasm_bits) {
   6773             _ = try func.wrapOperand(.{ .stack = {} }, ty);
   6774         }
   6775     } else {
   6776         const float_bits = ty.floatBits(func.target);
   6777         if (float_bits > 64) {
   6778             return func.fail("TODO: `@divFloor` for floats with bitsize: {d}", .{float_bits});
   6779         }
   6780         const is_f16 = float_bits == 16;
   6781 
   6782         const lhs_wasm = if (is_f16) try func.fpext(lhs, Type.f16, Type.f32) else lhs;
   6783         const rhs_wasm = if (is_f16) try func.fpext(rhs, Type.f16, Type.f32) else rhs;
   6784 
   6785         try func.emitWValue(lhs_wasm);
   6786         try func.emitWValue(rhs_wasm);
   6787 
   6788         switch (float_bits) {
   6789             16, 32 => {
   6790                 try func.addTag(.f32_div);
   6791                 try func.addTag(.f32_floor);
   6792             },
   6793             64 => {
   6794                 try func.addTag(.f64_div);
   6795                 try func.addTag(.f64_floor);
   6796             },
   6797             else => unreachable,
   6798         }
   6799 
   6800         if (is_f16) {
   6801             _ = try func.fptrunc(.{ .stack = {} }, Type.f32, Type.f16);
   6802         }
   6803     }
   6804 
   6805     const result = try func.allocLocal(ty);
   6806     try func.addLabel(.local_set, result.local.value);
   6807     func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
   6808 }
   6809 
   6810 fn divSigned(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type) InnerError!WValue {
   6811     const mod = func.bin_file.base.comp.module.?;
   6812     const int_bits = ty.intInfo(mod).bits;
   6813     const wasm_bits = toWasmBits(int_bits) orelse {
   6814         return func.fail("TODO: Implement signed division for integers with bitsize '{d}'", .{int_bits});
   6815     };
   6816 
   6817     if (wasm_bits == 128) {
   6818         return func.fail("TODO: Implement signed division for 128-bit integerrs", .{});
   6819     }
   6820 
   6821     if (wasm_bits != int_bits) {
   6822         // Leave both values on the stack
   6823         _ = try func.signExtendInt(lhs, ty);
   6824         _ = try func.signExtendInt(rhs, ty);
   6825     } else {
   6826         try func.emitWValue(lhs);
   6827         try func.emitWValue(rhs);
   6828     }
   6829     try func.addTag(.i32_div_s);
   6830 
   6831     const result = try func.allocLocal(ty);
   6832     try func.addLabel(.local_set, result.local.value);
   6833     return result;
   6834 }
   6835 
   6836 /// Remainder after floor division, defined by:
   6837 /// @divFloor(a, b) * b + @mod(a, b) = a
   6838 fn airMod(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   6839     const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
   6840 
   6841     const mod = func.bin_file.base.comp.module.?;
   6842     const ty = func.typeOfIndex(inst);
   6843     const lhs = try func.resolveInst(bin_op.lhs);
   6844     const rhs = try func.resolveInst(bin_op.rhs);
   6845 
   6846     if (ty.isUnsignedInt(mod)) {
   6847         _ = try func.binOp(lhs, rhs, ty, .rem);
   6848     } else if (ty.isSignedInt(mod)) {
   6849         // The wasm rem instruction gives the remainder after truncating division (rounding towards
   6850         // 0), equivalent to @rem.
   6851         // We make use of the fact that:
   6852         // @mod(a, b) = @rem(@rem(a, b) + b, b)
   6853         const int_bits = ty.intInfo(mod).bits;
   6854         const wasm_bits = toWasmBits(int_bits) orelse {
   6855             return func.fail("TODO: `@mod` for signed integers larger than 64 bits ({d} bits requested)", .{int_bits});
   6856         };
   6857 
   6858         if (wasm_bits > 64) {
   6859             return func.fail("TODO: `@mod` for signed integers larger than 64 bits ({d} bits requested)", .{int_bits});
   6860         }
   6861 
   6862         const lhs_wasm = if (wasm_bits != int_bits)
   6863             try (try func.signExtendInt(lhs, ty)).toLocal(func, ty)
   6864         else
   6865             lhs;
   6866 
   6867         const rhs_wasm = if (wasm_bits != int_bits)
   6868             try (try func.signExtendInt(rhs, ty)).toLocal(func, ty)
   6869         else
   6870             rhs;
   6871 
   6872         _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .rem);
   6873         _ = try func.binOp(.stack, rhs_wasm, ty, .add);
   6874         _ = try func.binOp(.stack, rhs_wasm, ty, .rem);
   6875     } else {
   6876         return func.fail("TODO: implement `@mod` on floating point types for {}", .{func.target.cpu.arch});
   6877     }
   6878 
   6879     const result = try func.allocLocal(ty);
   6880     try func.addLabel(.local_set, result.local.value);
   6881     func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
   6882 }
   6883 
   6884 /// Sign extends an N bit signed integer and pushes the result to the stack.
   6885 /// The result will be sign extended to 32 bits if N <= 32 or 64 bits if N <= 64.
   6886 /// Support for integers wider than 64 bits has not yet been implemented.
   6887 fn signExtendInt(func: *CodeGen, operand: WValue, ty: Type) InnerError!WValue {
   6888     const mod = func.bin_file.base.comp.module.?;
   6889     const int_bits = ty.intInfo(mod).bits;
   6890     const wasm_bits = toWasmBits(int_bits) orelse {
   6891         return func.fail("TODO: signExtendInt for signed integers larger than '{d}' bits", .{int_bits});
   6892     };
   6893 
   6894     const shift_val = switch (wasm_bits) {
   6895         32 => WValue{ .imm32 = wasm_bits - int_bits },
   6896         64 => WValue{ .imm64 = wasm_bits - int_bits },
   6897         else => return func.fail("TODO: signExtendInt for i128", .{}),
   6898     };
   6899 
   6900     try func.emitWValue(operand);
   6901     switch (wasm_bits) {
   6902         32 => {
   6903             try func.emitWValue(shift_val);
   6904             try func.addTag(.i32_shl);
   6905             try func.emitWValue(shift_val);
   6906             try func.addTag(.i32_shr_s);
   6907         },
   6908         64 => {
   6909             try func.emitWValue(shift_val);
   6910             try func.addTag(.i64_shl);
   6911             try func.emitWValue(shift_val);
   6912             try func.addTag(.i64_shr_s);
   6913         },
   6914         else => unreachable,
   6915     }
   6916 
   6917     return WValue{ .stack = {} };
   6918 }
   6919 
   6920 fn airSatBinOp(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerError!void {
   6921     assert(op == .add or op == .sub);
   6922     const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
   6923 
   6924     const mod = func.bin_file.base.comp.module.?;
   6925     const ty = func.typeOfIndex(inst);
   6926     const lhs = try func.resolveInst(bin_op.lhs);
   6927     const rhs = try func.resolveInst(bin_op.rhs);
   6928 
   6929     const int_info = ty.intInfo(mod);
   6930     const is_signed = int_info.signedness == .signed;
   6931 
   6932     if (int_info.bits > 64) {
   6933         return func.fail("TODO: saturating arithmetic for integers with bitsize '{d}'", .{int_info.bits});
   6934     }
   6935 
   6936     if (is_signed) {
   6937         const result = try signedSat(func, lhs, rhs, ty, op);
   6938         return func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
   6939     }
   6940 
   6941     const wasm_bits = toWasmBits(int_info.bits).?;
   6942     var bin_result = try (try func.binOp(lhs, rhs, ty, op)).toLocal(func, ty);
   6943     defer bin_result.free(func);
   6944     if (wasm_bits != int_info.bits and op == .add) {
   6945         const val: u64 = @as(u64, @intCast((@as(u65, 1) << @as(u7, @intCast(int_info.bits))) - 1));
   6946         const imm_val = switch (wasm_bits) {
   6947             32 => WValue{ .imm32 = @as(u32, @intCast(val)) },
   6948             64 => WValue{ .imm64 = val },
   6949             else => unreachable,
   6950         };
   6951 
   6952         try func.emitWValue(bin_result);
   6953         try func.emitWValue(imm_val);
   6954         _ = try func.cmp(bin_result, imm_val, ty, .lt);
   6955     } else {
   6956         switch (wasm_bits) {
   6957             32 => try func.addImm32(if (op == .add) @as(i32, -1) else 0),
   6958             64 => try func.addImm64(if (op == .add) @as(u64, @bitCast(@as(i64, -1))) else 0),
   6959             else => unreachable,
   6960         }
   6961         try func.emitWValue(bin_result);
   6962         _ = try func.cmp(bin_result, lhs, ty, if (op == .add) .lt else .gt);
   6963     }
   6964 
   6965     try func.addTag(.select);
   6966     const result = try func.allocLocal(ty);
   6967     try func.addLabel(.local_set, result.local.value);
   6968     return func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
   6969 }
   6970 
   6971 fn signedSat(func: *CodeGen, lhs_operand: WValue, rhs_operand: WValue, ty: Type, op: Op) InnerError!WValue {
   6972     const mod = func.bin_file.base.comp.module.?;
   6973     const int_info = ty.intInfo(mod);
   6974     const wasm_bits = toWasmBits(int_info.bits).?;
   6975     const is_wasm_bits = wasm_bits == int_info.bits;
   6976     const ext_ty = if (!is_wasm_bits) try mod.intType(int_info.signedness, wasm_bits) else ty;
   6977 
   6978     var lhs = if (!is_wasm_bits) lhs: {
   6979         break :lhs try (try func.signExtendInt(lhs_operand, ty)).toLocal(func, ext_ty);
   6980     } else lhs_operand;
   6981     var rhs = if (!is_wasm_bits) rhs: {
   6982         break :rhs try (try func.signExtendInt(rhs_operand, ty)).toLocal(func, ext_ty);
   6983     } else rhs_operand;
   6984 
   6985     const max_val: u64 = @as(u64, @intCast((@as(u65, 1) << @as(u7, @intCast(int_info.bits - 1))) - 1));
   6986     const min_val: i64 = (-@as(i64, @intCast(@as(u63, @intCast(max_val))))) - 1;
   6987     const max_wvalue = switch (wasm_bits) {
   6988         32 => WValue{ .imm32 = @as(u32, @truncate(max_val)) },
   6989         64 => WValue{ .imm64 = max_val },
   6990         else => unreachable,
   6991     };
   6992     const min_wvalue = switch (wasm_bits) {
   6993         32 => WValue{ .imm32 = @as(u32, @bitCast(@as(i32, @truncate(min_val)))) },
   6994         64 => WValue{ .imm64 = @as(u64, @bitCast(min_val)) },
   6995         else => unreachable,
   6996     };
   6997 
   6998     var bin_result = try (try func.binOp(lhs, rhs, ext_ty, op)).toLocal(func, ext_ty);
   6999     if (!is_wasm_bits) {
   7000         defer bin_result.free(func); // not returned in this branch
   7001         defer lhs.free(func); // uses temporary local for absvalue
   7002         defer rhs.free(func); // uses temporary local for absvalue
   7003         try func.emitWValue(bin_result);
   7004         try func.emitWValue(max_wvalue);
   7005         _ = try func.cmp(bin_result, max_wvalue, ext_ty, .lt);
   7006         try func.addTag(.select);
   7007         try func.addLabel(.local_set, bin_result.local.value); // re-use local
   7008 
   7009         try func.emitWValue(bin_result);
   7010         try func.emitWValue(min_wvalue);
   7011         _ = try func.cmp(bin_result, min_wvalue, ext_ty, .gt);
   7012         try func.addTag(.select);
   7013         try func.addLabel(.local_set, bin_result.local.value); // re-use local
   7014         return (try func.wrapOperand(bin_result, ty)).toLocal(func, ty);
   7015     } else {
   7016         const zero = switch (wasm_bits) {
   7017             32 => WValue{ .imm32 = 0 },
   7018             64 => WValue{ .imm64 = 0 },
   7019             else => unreachable,
   7020         };
   7021         try func.emitWValue(max_wvalue);
   7022         try func.emitWValue(min_wvalue);
   7023         _ = try func.cmp(bin_result, zero, ty, .lt);
   7024         try func.addTag(.select);
   7025         try func.emitWValue(bin_result);
   7026         // leave on stack
   7027         const cmp_zero_result = try func.cmp(rhs, zero, ty, if (op == .add) .lt else .gt);
   7028         const cmp_bin_result = try func.cmp(bin_result, lhs, ty, .lt);
   7029         _ = try func.binOp(cmp_zero_result, cmp_bin_result, Type.u32, .xor); // comparisons always return i32, so provide u32 as type to xor.
   7030         try func.addTag(.select);
   7031         try func.addLabel(.local_set, bin_result.local.value); // re-use local
   7032         return bin_result;
   7033     }
   7034 }
   7035 
   7036 fn airShlSat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   7037     const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
   7038 
   7039     const mod = func.bin_file.base.comp.module.?;
   7040     const ty = func.typeOfIndex(inst);
   7041     const int_info = ty.intInfo(mod);
   7042     const is_signed = int_info.signedness == .signed;
   7043     if (int_info.bits > 64) {
   7044         return func.fail("TODO: Saturating shifting left for integers with bitsize '{d}'", .{int_info.bits});
   7045     }
   7046 
   7047     const lhs = try func.resolveInst(bin_op.lhs);
   7048     const rhs = try func.resolveInst(bin_op.rhs);
   7049     const wasm_bits = toWasmBits(int_info.bits).?;
   7050     const result = try func.allocLocal(ty);
   7051 
   7052     if (wasm_bits == int_info.bits) outer_blk: {
   7053         var shl = try (try func.binOp(lhs, rhs, ty, .shl)).toLocal(func, ty);
   7054         defer shl.free(func);
   7055         var shr = try (try func.binOp(shl, rhs, ty, .shr)).toLocal(func, ty);
   7056         defer shr.free(func);
   7057 
   7058         switch (wasm_bits) {
   7059             32 => blk: {
   7060                 if (!is_signed) {
   7061                     try func.addImm32(-1);
   7062                     break :blk;
   7063                 }
   7064                 try func.addImm32(std.math.minInt(i32));
   7065                 try func.addImm32(std.math.maxInt(i32));
   7066                 _ = try func.cmp(lhs, .{ .imm32 = 0 }, ty, .lt);
   7067                 try func.addTag(.select);
   7068             },
   7069             64 => blk: {
   7070                 if (!is_signed) {
   7071                     try func.addImm64(@as(u64, @bitCast(@as(i64, -1))));
   7072                     break :blk;
   7073                 }
   7074                 try func.addImm64(@as(u64, @bitCast(@as(i64, std.math.minInt(i64)))));
   7075                 try func.addImm64(@as(u64, @bitCast(@as(i64, std.math.maxInt(i64)))));
   7076                 _ = try func.cmp(lhs, .{ .imm64 = 0 }, ty, .lt);
   7077                 try func.addTag(.select);
   7078             },
   7079             else => unreachable,
   7080         }
   7081         try func.emitWValue(shl);
   7082         _ = try func.cmp(lhs, shr, ty, .neq);
   7083         try func.addTag(.select);
   7084         try func.addLabel(.local_set, result.local.value);
   7085         break :outer_blk;
   7086     } else {
   7087         const shift_size = wasm_bits - int_info.bits;
   7088         const shift_value = switch (wasm_bits) {
   7089             32 => WValue{ .imm32 = shift_size },
   7090             64 => WValue{ .imm64 = shift_size },
   7091             else => unreachable,
   7092         };
   7093         const ext_ty = try mod.intType(int_info.signedness, wasm_bits);
   7094 
   7095         var shl_res = try (try func.binOp(lhs, shift_value, ext_ty, .shl)).toLocal(func, ext_ty);
   7096         defer shl_res.free(func);
   7097         var shl = try (try func.binOp(shl_res, rhs, ext_ty, .shl)).toLocal(func, ext_ty);
   7098         defer shl.free(func);
   7099         var shr = try (try func.binOp(shl, rhs, ext_ty, .shr)).toLocal(func, ext_ty);
   7100         defer shr.free(func);
   7101 
   7102         switch (wasm_bits) {
   7103             32 => blk: {
   7104                 if (!is_signed) {
   7105                     try func.addImm32(-1);
   7106                     break :blk;
   7107                 }
   7108 
   7109                 try func.addImm32(std.math.minInt(i32));
   7110                 try func.addImm32(std.math.maxInt(i32));
   7111                 _ = try func.cmp(shl_res, .{ .imm32 = 0 }, ext_ty, .lt);
   7112                 try func.addTag(.select);
   7113             },
   7114             64 => blk: {
   7115                 if (!is_signed) {
   7116                     try func.addImm64(@as(u64, @bitCast(@as(i64, -1))));
   7117                     break :blk;
   7118                 }
   7119 
   7120                 try func.addImm64(@as(u64, @bitCast(@as(i64, std.math.minInt(i64)))));
   7121                 try func.addImm64(@as(u64, @bitCast(@as(i64, std.math.maxInt(i64)))));
   7122                 _ = try func.cmp(shl_res, .{ .imm64 = 0 }, ext_ty, .lt);
   7123                 try func.addTag(.select);
   7124             },
   7125             else => unreachable,
   7126         }
   7127         try func.emitWValue(shl);
   7128         _ = try func.cmp(shl_res, shr, ext_ty, .neq);
   7129         try func.addTag(.select);
   7130         try func.addLabel(.local_set, result.local.value);
   7131         var shift_result = try func.binOp(result, shift_value, ext_ty, .shr);
   7132         if (is_signed) {
   7133             shift_result = try func.wrapOperand(shift_result, ty);
   7134         }
   7135         try func.addLabel(.local_set, result.local.value);
   7136     }
   7137 
   7138     return func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
   7139 }
   7140 
   7141 /// Calls a compiler-rt intrinsic by creating an undefined symbol,
   7142 /// then lowering the arguments and calling the symbol as a function call.
   7143 /// This function call assumes the C-ABI.
   7144 /// Asserts arguments are not stack values when the return value is
   7145 /// passed as the first parameter.
   7146 /// May leave the return value on the stack.
   7147 fn callIntrinsic(
   7148     func: *CodeGen,
   7149     name: []const u8,
   7150     param_types: []const InternPool.Index,
   7151     return_type: Type,
   7152     args: []const WValue,
   7153 ) InnerError!WValue {
   7154     assert(param_types.len == args.len);
   7155     const symbol_index = func.bin_file.getGlobalSymbol(name, null) catch |err| {
   7156         return func.fail("Could not find or create global symbol '{s}'", .{@errorName(err)});
   7157     };
   7158 
   7159     // Always pass over C-ABI
   7160     const mod = func.bin_file.base.comp.module.?;
   7161     var func_type = try genFunctype(func.gpa, .C, param_types, return_type, mod);
   7162     defer func_type.deinit(func.gpa);
   7163     const func_type_index = try func.bin_file.zigObjectPtr().?.putOrGetFuncType(func.gpa, func_type);
   7164     try func.bin_file.addOrUpdateImport(name, symbol_index, null, func_type_index);
   7165 
   7166     const want_sret_param = firstParamSRet(.C, return_type, mod);
   7167     // if we want return as first param, we allocate a pointer to stack,
   7168     // and emit it as our first argument
   7169     const sret = if (want_sret_param) blk: {
   7170         const sret_local = try func.allocStack(return_type);
   7171         try func.lowerToStack(sret_local);
   7172         break :blk sret_local;
   7173     } else WValue{ .none = {} };
   7174 
   7175     // Lower all arguments to the stack before we call our function
   7176     for (args, 0..) |arg, arg_i| {
   7177         assert(!(want_sret_param and arg == .stack));
   7178         assert(Type.fromInterned(param_types[arg_i]).hasRuntimeBitsIgnoreComptime(mod));
   7179         try func.lowerArg(.C, Type.fromInterned(param_types[arg_i]), arg);
   7180     }
   7181 
   7182     // Actually call our intrinsic
   7183     try func.addLabel(.call, @intFromEnum(symbol_index));
   7184 
   7185     if (!return_type.hasRuntimeBitsIgnoreComptime(mod)) {
   7186         return WValue.none;
   7187     } else if (return_type.isNoReturn(mod)) {
   7188         try func.addTag(.@"unreachable");
   7189         return WValue.none;
   7190     } else if (want_sret_param) {
   7191         return sret;
   7192     } else {
   7193         return WValue{ .stack = {} };
   7194     }
   7195 }
   7196 
   7197 fn airTagName(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   7198     const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
   7199     const operand = try func.resolveInst(un_op);
   7200     const enum_ty = func.typeOf(un_op);
   7201 
   7202     const func_sym_index = try func.getTagNameFunction(enum_ty);
   7203 
   7204     const result_ptr = try func.allocStack(func.typeOfIndex(inst));
   7205     try func.lowerToStack(result_ptr);
   7206     try func.emitWValue(operand);
   7207     try func.addLabel(.call, func_sym_index);
   7208 
   7209     return func.finishAir(inst, result_ptr, &.{un_op});
   7210 }
   7211 
   7212 fn getTagNameFunction(func: *CodeGen, enum_ty: Type) InnerError!u32 {
   7213     const mod = func.bin_file.base.comp.module.?;
   7214     const ip = &mod.intern_pool;
   7215     const enum_decl_index = enum_ty.getOwnerDecl(mod);
   7216 
   7217     var arena_allocator = std.heap.ArenaAllocator.init(func.gpa);
   7218     defer arena_allocator.deinit();
   7219     const arena = arena_allocator.allocator();
   7220 
   7221     const fqn = try mod.declPtr(enum_decl_index).fullyQualifiedName(mod);
   7222     const func_name = try std.fmt.allocPrintZ(arena, "__zig_tag_name_{}", .{fqn.fmt(ip)});
   7223 
   7224     // check if we already generated code for this.
   7225     if (func.bin_file.findGlobalSymbol(func_name)) |loc| {
   7226         return @intFromEnum(loc.index);
   7227     }
   7228 
   7229     const int_tag_ty = enum_ty.intTagType(mod);
   7230 
   7231     if (int_tag_ty.bitSize(mod) > 64) {
   7232         return func.fail("TODO: Implement @tagName for enums with tag size larger than 64 bits", .{});
   7233     }
   7234 
   7235     var relocs = std.ArrayList(link.File.Wasm.Relocation).init(func.gpa);
   7236     defer relocs.deinit();
   7237 
   7238     var body_list = std.ArrayList(u8).init(func.gpa);
   7239     defer body_list.deinit();
   7240     var writer = body_list.writer();
   7241 
   7242     // The locals of the function body (always 0)
   7243     try leb.writeULEB128(writer, @as(u32, 0));
   7244 
   7245     // outer block
   7246     try writer.writeByte(std.wasm.opcode(.block));
   7247     try writer.writeByte(std.wasm.block_empty);
   7248 
   7249     // TODO: Make switch implementation generic so we can use a jump table for this when the tags are not sparse.
   7250     // generate an if-else chain for each tag value as well as constant.
   7251     const tag_names = enum_ty.enumFields(mod);
   7252     for (0..tag_names.len) |tag_index| {
   7253         const tag_name = tag_names.get(ip)[tag_index];
   7254         const tag_name_len = tag_name.length(ip);
   7255         // for each tag name, create an unnamed const,
   7256         // and then get a pointer to its value.
   7257         const name_ty = try mod.arrayType(.{
   7258             .len = tag_name_len,
   7259             .child = .u8_type,
   7260             .sentinel = .zero_u8,
   7261         });
   7262         const name_val = try mod.intern(.{ .aggregate = .{
   7263             .ty = name_ty.toIntern(),
   7264             .storage = .{ .bytes = tag_name.toString() },
   7265         } });
   7266         const tag_sym_index = try func.bin_file.lowerUnnamedConst(
   7267             Value.fromInterned(name_val),
   7268             enum_decl_index,
   7269         );
   7270 
   7271         // block for this if case
   7272         try writer.writeByte(std.wasm.opcode(.block));
   7273         try writer.writeByte(std.wasm.block_empty);
   7274 
   7275         // get actual tag value (stored in 2nd parameter);
   7276         try writer.writeByte(std.wasm.opcode(.local_get));
   7277         try leb.writeULEB128(writer, @as(u32, 1));
   7278 
   7279         const tag_val = try mod.enumValueFieldIndex(enum_ty, @intCast(tag_index));
   7280         const tag_value = try func.lowerConstant(tag_val, enum_ty);
   7281 
   7282         switch (tag_value) {
   7283             .imm32 => |value| {
   7284                 try writer.writeByte(std.wasm.opcode(.i32_const));
   7285                 try leb.writeILEB128(writer, @as(i32, @bitCast(value)));
   7286                 try writer.writeByte(std.wasm.opcode(.i32_ne));
   7287             },
   7288             .imm64 => |value| {
   7289                 try writer.writeByte(std.wasm.opcode(.i64_const));
   7290                 try leb.writeILEB128(writer, @as(i64, @bitCast(value)));
   7291                 try writer.writeByte(std.wasm.opcode(.i64_ne));
   7292             },
   7293             else => unreachable,
   7294         }
   7295         // if they're not equal, break out of current branch
   7296         try writer.writeByte(std.wasm.opcode(.br_if));
   7297         try leb.writeULEB128(writer, @as(u32, 0));
   7298 
   7299         // store the address of the tagname in the pointer field of the slice
   7300         // get the address twice so we can also store the length.
   7301         try writer.writeByte(std.wasm.opcode(.local_get));
   7302         try leb.writeULEB128(writer, @as(u32, 0));
   7303         try writer.writeByte(std.wasm.opcode(.local_get));
   7304         try leb.writeULEB128(writer, @as(u32, 0));
   7305 
   7306         // get address of tagname and emit a relocation to it
   7307         if (func.arch() == .wasm32) {
   7308             const encoded_alignment = @ctz(@as(u32, 4));
   7309             try writer.writeByte(std.wasm.opcode(.i32_const));
   7310             try relocs.append(.{
   7311                 .relocation_type = .R_WASM_MEMORY_ADDR_LEB,
   7312                 .offset = @as(u32, @intCast(body_list.items.len)),
   7313                 .index = tag_sym_index,
   7314             });
   7315             try writer.writeAll(&[_]u8{0} ** 5); // will be relocated
   7316 
   7317             // store pointer
   7318             try writer.writeByte(std.wasm.opcode(.i32_store));
   7319             try leb.writeULEB128(writer, encoded_alignment);
   7320             try leb.writeULEB128(writer, @as(u32, 0));
   7321 
   7322             // store length
   7323             try writer.writeByte(std.wasm.opcode(.i32_const));
   7324             try leb.writeULEB128(writer, @as(u32, @intCast(tag_name_len)));
   7325             try writer.writeByte(std.wasm.opcode(.i32_store));
   7326             try leb.writeULEB128(writer, encoded_alignment);
   7327             try leb.writeULEB128(writer, @as(u32, 4));
   7328         } else {
   7329             const encoded_alignment = @ctz(@as(u32, 8));
   7330             try writer.writeByte(std.wasm.opcode(.i64_const));
   7331             try relocs.append(.{
   7332                 .relocation_type = .R_WASM_MEMORY_ADDR_LEB64,
   7333                 .offset = @as(u32, @intCast(body_list.items.len)),
   7334                 .index = tag_sym_index,
   7335             });
   7336             try writer.writeAll(&[_]u8{0} ** 10); // will be relocated
   7337 
   7338             // store pointer
   7339             try writer.writeByte(std.wasm.opcode(.i64_store));
   7340             try leb.writeULEB128(writer, encoded_alignment);
   7341             try leb.writeULEB128(writer, @as(u32, 0));
   7342 
   7343             // store length
   7344             try writer.writeByte(std.wasm.opcode(.i64_const));
   7345             try leb.writeULEB128(writer, @as(u64, @intCast(tag_name_len)));
   7346             try writer.writeByte(std.wasm.opcode(.i64_store));
   7347             try leb.writeULEB128(writer, encoded_alignment);
   7348             try leb.writeULEB128(writer, @as(u32, 8));
   7349         }
   7350 
   7351         // break outside blocks
   7352         try writer.writeByte(std.wasm.opcode(.br));
   7353         try leb.writeULEB128(writer, @as(u32, 1));
   7354 
   7355         // end the block for this case
   7356         try writer.writeByte(std.wasm.opcode(.end));
   7357     }
   7358 
   7359     try writer.writeByte(std.wasm.opcode(.@"unreachable")); // tag value does not have a name
   7360     // finish outer block
   7361     try writer.writeByte(std.wasm.opcode(.end));
   7362     // finish function body
   7363     try writer.writeByte(std.wasm.opcode(.end));
   7364 
   7365     const slice_ty = Type.slice_const_u8_sentinel_0;
   7366     const func_type = try genFunctype(arena, .Unspecified, &.{int_tag_ty.ip_index}, slice_ty, mod);
   7367     const sym_index = try func.bin_file.createFunction(func_name, func_type, &body_list, &relocs);
   7368     return @intFromEnum(sym_index);
   7369 }
   7370 
   7371 fn airErrorSetHasValue(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   7372     const mod = func.bin_file.base.comp.module.?;
   7373     const ip = &mod.intern_pool;
   7374     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
   7375 
   7376     const operand = try func.resolveInst(ty_op.operand);
   7377     const error_set_ty = ty_op.ty.toType();
   7378     const result = try func.allocLocal(Type.bool);
   7379 
   7380     const names = error_set_ty.errorSetNames(mod);
   7381     var values = try std.ArrayList(u32).initCapacity(func.gpa, names.len);
   7382     defer values.deinit();
   7383 
   7384     var lowest: ?u32 = null;
   7385     var highest: ?u32 = null;
   7386     for (0..names.len) |name_index| {
   7387         const err_int: Module.ErrorInt = @intCast(mod.global_error_set.getIndex(names.get(ip)[name_index]).?);
   7388         if (lowest) |*l| {
   7389             if (err_int < l.*) {
   7390                 l.* = err_int;
   7391             }
   7392         } else {
   7393             lowest = err_int;
   7394         }
   7395         if (highest) |*h| {
   7396             if (err_int > h.*) {
   7397                 highest = err_int;
   7398             }
   7399         } else {
   7400             highest = err_int;
   7401         }
   7402 
   7403         values.appendAssumeCapacity(err_int);
   7404     }
   7405 
   7406     // start block for 'true' branch
   7407     try func.startBlock(.block, wasm.block_empty);
   7408     // start block for 'false' branch
   7409     try func.startBlock(.block, wasm.block_empty);
   7410     // block for the jump table itself
   7411     try func.startBlock(.block, wasm.block_empty);
   7412 
   7413     // lower operand to determine jump table target
   7414     try func.emitWValue(operand);
   7415     try func.addImm32(@as(i32, @intCast(lowest.?)));
   7416     try func.addTag(.i32_sub);
   7417 
   7418     // Account for default branch so always add '1'
   7419     const depth = @as(u32, @intCast(highest.? - lowest.? + 1));
   7420     const jump_table: Mir.JumpTable = .{ .length = depth };
   7421     const table_extra_index = try func.addExtra(jump_table);
   7422     try func.addInst(.{ .tag = .br_table, .data = .{ .payload = table_extra_index } });
   7423     try func.mir_extra.ensureUnusedCapacity(func.gpa, depth);
   7424 
   7425     var value: u32 = lowest.?;
   7426     while (value <= highest.?) : (value += 1) {
   7427         const idx: u32 = blk: {
   7428             for (values.items) |val| {
   7429                 if (val == value) break :blk 1;
   7430             }
   7431             break :blk 0;
   7432         };
   7433         func.mir_extra.appendAssumeCapacity(idx);
   7434     }
   7435     try func.endBlock();
   7436 
   7437     // 'false' branch (i.e. error set does not have value
   7438     // ensure we set local to 0 in case the local was re-used.
   7439     try func.addImm32(0);
   7440     try func.addLabel(.local_set, result.local.value);
   7441     try func.addLabel(.br, 1);
   7442     try func.endBlock();
   7443 
   7444     // 'true' branch
   7445     try func.addImm32(1);
   7446     try func.addLabel(.local_set, result.local.value);
   7447     try func.addLabel(.br, 0);
   7448     try func.endBlock();
   7449 
   7450     return func.finishAir(inst, result, &.{ty_op.operand});
   7451 }
   7452 
   7453 inline fn useAtomicFeature(func: *const CodeGen) bool {
   7454     return std.Target.wasm.featureSetHas(func.target.cpu.features, .atomics);
   7455 }
   7456 
   7457 fn airCmpxchg(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   7458     const mod = func.bin_file.base.comp.module.?;
   7459     const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
   7460     const extra = func.air.extraData(Air.Cmpxchg, ty_pl.payload).data;
   7461 
   7462     const ptr_ty = func.typeOf(extra.ptr);
   7463     const ty = ptr_ty.childType(mod);
   7464     const result_ty = func.typeOfIndex(inst);
   7465 
   7466     const ptr_operand = try func.resolveInst(extra.ptr);
   7467     const expected_val = try func.resolveInst(extra.expected_value);
   7468     const new_val = try func.resolveInst(extra.new_value);
   7469 
   7470     const cmp_result = try func.allocLocal(Type.bool);
   7471 
   7472     const ptr_val = if (func.useAtomicFeature()) val: {
   7473         const val_local = try func.allocLocal(ty);
   7474         try func.emitWValue(ptr_operand);
   7475         try func.lowerToStack(expected_val);
   7476         try func.lowerToStack(new_val);
   7477         try func.addAtomicMemArg(switch (ty.abiSize(mod)) {
   7478             1 => .i32_atomic_rmw8_cmpxchg_u,
   7479             2 => .i32_atomic_rmw16_cmpxchg_u,
   7480             4 => .i32_atomic_rmw_cmpxchg,
   7481             8 => .i32_atomic_rmw_cmpxchg,
   7482             else => |size| return func.fail("TODO: implement `@cmpxchg` for types with abi size '{d}'", .{size}),
   7483         }, .{
   7484             .offset = ptr_operand.offset(),
   7485             .alignment = @intCast(ty.abiAlignment(mod).toByteUnits().?),
   7486         });
   7487         try func.addLabel(.local_tee, val_local.local.value);
   7488         _ = try func.cmp(.stack, expected_val, ty, .eq);
   7489         try func.addLabel(.local_set, cmp_result.local.value);
   7490         break :val val_local;
   7491     } else val: {
   7492         if (ty.abiSize(mod) > 8) {
   7493             return func.fail("TODO: Implement `@cmpxchg` for types larger than abi size of 8 bytes", .{});
   7494         }
   7495         const ptr_val = try WValue.toLocal(try func.load(ptr_operand, ty, 0), func, ty);
   7496 
   7497         try func.lowerToStack(ptr_operand);
   7498         try func.lowerToStack(new_val);
   7499         try func.emitWValue(ptr_val);
   7500         _ = try func.cmp(ptr_val, expected_val, ty, .eq);
   7501         try func.addLabel(.local_tee, cmp_result.local.value);
   7502         try func.addTag(.select);
   7503         try func.store(.stack, .stack, ty, 0);
   7504 
   7505         break :val ptr_val;
   7506     };
   7507 
   7508     const result_ptr = if (isByRef(result_ty, mod)) val: {
   7509         try func.emitWValue(cmp_result);
   7510         try func.addImm32(-1);
   7511         try func.addTag(.i32_xor);
   7512         try func.addImm32(1);
   7513         try func.addTag(.i32_and);
   7514         const and_result = try WValue.toLocal(.stack, func, Type.bool);
   7515         const result_ptr = try func.allocStack(result_ty);
   7516         try func.store(result_ptr, and_result, Type.bool, @as(u32, @intCast(ty.abiSize(mod))));
   7517         try func.store(result_ptr, ptr_val, ty, 0);
   7518         break :val result_ptr;
   7519     } else val: {
   7520         try func.addImm32(0);
   7521         try func.emitWValue(ptr_val);
   7522         try func.emitWValue(cmp_result);
   7523         try func.addTag(.select);
   7524         break :val try WValue.toLocal(.stack, func, result_ty);
   7525     };
   7526 
   7527     return func.finishAir(inst, result_ptr, &.{ extra.ptr, extra.expected_value, extra.new_value });
   7528 }
   7529 
   7530 fn airAtomicLoad(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   7531     const mod = func.bin_file.base.comp.module.?;
   7532     const atomic_load = func.air.instructions.items(.data)[@intFromEnum(inst)].atomic_load;
   7533     const ptr = try func.resolveInst(atomic_load.ptr);
   7534     const ty = func.typeOfIndex(inst);
   7535 
   7536     if (func.useAtomicFeature()) {
   7537         const tag: wasm.AtomicsOpcode = switch (ty.abiSize(mod)) {
   7538             1 => .i32_atomic_load8_u,
   7539             2 => .i32_atomic_load16_u,
   7540             4 => .i32_atomic_load,
   7541             8 => .i64_atomic_load,
   7542             else => |size| return func.fail("TODO: @atomicLoad for types with abi size {d}", .{size}),
   7543         };
   7544         try func.emitWValue(ptr);
   7545         try func.addAtomicMemArg(tag, .{
   7546             .offset = ptr.offset(),
   7547             .alignment = @intCast(ty.abiAlignment(mod).toByteUnits().?),
   7548         });
   7549     } else {
   7550         _ = try func.load(ptr, ty, 0);
   7551     }
   7552 
   7553     const result = try WValue.toLocal(.stack, func, ty);
   7554     return func.finishAir(inst, result, &.{atomic_load.ptr});
   7555 }
   7556 
   7557 fn airAtomicRmw(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   7558     const mod = func.bin_file.base.comp.module.?;
   7559     const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
   7560     const extra = func.air.extraData(Air.AtomicRmw, pl_op.payload).data;
   7561 
   7562     const ptr = try func.resolveInst(pl_op.operand);
   7563     const operand = try func.resolveInst(extra.operand);
   7564     const ty = func.typeOfIndex(inst);
   7565     const op: std.builtin.AtomicRmwOp = extra.op();
   7566 
   7567     if (func.useAtomicFeature()) {
   7568         switch (op) {
   7569             .Max,
   7570             .Min,
   7571             .Nand,
   7572             => {
   7573                 const tmp = try func.load(ptr, ty, 0);
   7574                 const value = try tmp.toLocal(func, ty);
   7575 
   7576                 // create a loop to cmpxchg the new value
   7577                 try func.startBlock(.loop, wasm.block_empty);
   7578 
   7579                 try func.emitWValue(ptr);
   7580                 try func.emitWValue(value);
   7581                 if (op == .Nand) {
   7582                     const wasm_bits = toWasmBits(@as(u16, @intCast(ty.bitSize(mod)))).?;
   7583 
   7584                     const and_res = try func.binOp(value, operand, ty, .@"and");
   7585                     if (wasm_bits == 32)
   7586                         try func.addImm32(-1)
   7587                     else if (wasm_bits == 64)
   7588                         try func.addImm64(@as(u64, @bitCast(@as(i64, -1))))
   7589                     else
   7590                         return func.fail("TODO: `@atomicRmw` with operator `Nand` for types larger than 64 bits", .{});
   7591                     _ = try func.binOp(and_res, .stack, ty, .xor);
   7592                 } else {
   7593                     try func.emitWValue(value);
   7594                     try func.emitWValue(operand);
   7595                     _ = try func.cmp(value, operand, ty, if (op == .Max) .gt else .lt);
   7596                     try func.addTag(.select);
   7597                 }
   7598                 try func.addAtomicMemArg(
   7599                     switch (ty.abiSize(mod)) {
   7600                         1 => .i32_atomic_rmw8_cmpxchg_u,
   7601                         2 => .i32_atomic_rmw16_cmpxchg_u,
   7602                         4 => .i32_atomic_rmw_cmpxchg,
   7603                         8 => .i64_atomic_rmw_cmpxchg,
   7604                         else => return func.fail("TODO: implement `@atomicRmw` with operation `{s}` for types larger than 64 bits", .{@tagName(op)}),
   7605                     },
   7606                     .{
   7607                         .offset = ptr.offset(),
   7608                         .alignment = @intCast(ty.abiAlignment(mod).toByteUnits().?),
   7609                     },
   7610                 );
   7611                 const select_res = try func.allocLocal(ty);
   7612                 try func.addLabel(.local_tee, select_res.local.value);
   7613                 _ = try func.cmp(.stack, value, ty, .neq); // leave on stack so we can use it for br_if
   7614 
   7615                 try func.emitWValue(select_res);
   7616                 try func.addLabel(.local_set, value.local.value);
   7617 
   7618                 try func.addLabel(.br_if, 0);
   7619                 try func.endBlock();
   7620                 return func.finishAir(inst, value, &.{ pl_op.operand, extra.operand });
   7621             },
   7622 
   7623             // the other operations have their own instructions for Wasm.
   7624             else => {
   7625                 try func.emitWValue(ptr);
   7626                 try func.emitWValue(operand);
   7627                 const tag: wasm.AtomicsOpcode = switch (ty.abiSize(mod)) {
   7628                     1 => switch (op) {
   7629                         .Xchg => .i32_atomic_rmw8_xchg_u,
   7630                         .Add => .i32_atomic_rmw8_add_u,
   7631                         .Sub => .i32_atomic_rmw8_sub_u,
   7632                         .And => .i32_atomic_rmw8_and_u,
   7633                         .Or => .i32_atomic_rmw8_or_u,
   7634                         .Xor => .i32_atomic_rmw8_xor_u,
   7635                         else => unreachable,
   7636                     },
   7637                     2 => switch (op) {
   7638                         .Xchg => .i32_atomic_rmw16_xchg_u,
   7639                         .Add => .i32_atomic_rmw16_add_u,
   7640                         .Sub => .i32_atomic_rmw16_sub_u,
   7641                         .And => .i32_atomic_rmw16_and_u,
   7642                         .Or => .i32_atomic_rmw16_or_u,
   7643                         .Xor => .i32_atomic_rmw16_xor_u,
   7644                         else => unreachable,
   7645                     },
   7646                     4 => switch (op) {
   7647                         .Xchg => .i32_atomic_rmw_xchg,
   7648                         .Add => .i32_atomic_rmw_add,
   7649                         .Sub => .i32_atomic_rmw_sub,
   7650                         .And => .i32_atomic_rmw_and,
   7651                         .Or => .i32_atomic_rmw_or,
   7652                         .Xor => .i32_atomic_rmw_xor,
   7653                         else => unreachable,
   7654                     },
   7655                     8 => switch (op) {
   7656                         .Xchg => .i64_atomic_rmw_xchg,
   7657                         .Add => .i64_atomic_rmw_add,
   7658                         .Sub => .i64_atomic_rmw_sub,
   7659                         .And => .i64_atomic_rmw_and,
   7660                         .Or => .i64_atomic_rmw_or,
   7661                         .Xor => .i64_atomic_rmw_xor,
   7662                         else => unreachable,
   7663                     },
   7664                     else => |size| return func.fail("TODO: Implement `@atomicRmw` for types with abi size {d}", .{size}),
   7665                 };
   7666                 try func.addAtomicMemArg(tag, .{
   7667                     .offset = ptr.offset(),
   7668                     .alignment = @intCast(ty.abiAlignment(mod).toByteUnits().?),
   7669                 });
   7670                 const result = try WValue.toLocal(.stack, func, ty);
   7671                 return func.finishAir(inst, result, &.{ pl_op.operand, extra.operand });
   7672             },
   7673         }
   7674     } else {
   7675         const loaded = try func.load(ptr, ty, 0);
   7676         const result = try loaded.toLocal(func, ty);
   7677 
   7678         switch (op) {
   7679             .Xchg => {
   7680                 try func.store(ptr, operand, ty, 0);
   7681             },
   7682             .Add,
   7683             .Sub,
   7684             .And,
   7685             .Or,
   7686             .Xor,
   7687             => {
   7688                 try func.emitWValue(ptr);
   7689                 _ = try func.binOp(result, operand, ty, switch (op) {
   7690                     .Add => .add,
   7691                     .Sub => .sub,
   7692                     .And => .@"and",
   7693                     .Or => .@"or",
   7694                     .Xor => .xor,
   7695                     else => unreachable,
   7696                 });
   7697                 if (ty.isInt(mod) and (op == .Add or op == .Sub)) {
   7698                     _ = try func.wrapOperand(.stack, ty);
   7699                 }
   7700                 try func.store(.stack, .stack, ty, ptr.offset());
   7701             },
   7702             .Max,
   7703             .Min,
   7704             => {
   7705                 try func.emitWValue(ptr);
   7706                 try func.emitWValue(result);
   7707                 try func.emitWValue(operand);
   7708                 _ = try func.cmp(result, operand, ty, if (op == .Max) .gt else .lt);
   7709                 try func.addTag(.select);
   7710                 try func.store(.stack, .stack, ty, ptr.offset());
   7711             },
   7712             .Nand => {
   7713                 const wasm_bits = toWasmBits(@as(u16, @intCast(ty.bitSize(mod)))).?;
   7714 
   7715                 try func.emitWValue(ptr);
   7716                 const and_res = try func.binOp(result, operand, ty, .@"and");
   7717                 if (wasm_bits == 32)
   7718                     try func.addImm32(-1)
   7719                 else if (wasm_bits == 64)
   7720                     try func.addImm64(@as(u64, @bitCast(@as(i64, -1))))
   7721                 else
   7722                     return func.fail("TODO: `@atomicRmw` with operator `Nand` for types larger than 64 bits", .{});
   7723                 _ = try func.binOp(and_res, .stack, ty, .xor);
   7724                 try func.store(.stack, .stack, ty, ptr.offset());
   7725             },
   7726         }
   7727 
   7728         return func.finishAir(inst, result, &.{ pl_op.operand, extra.operand });
   7729     }
   7730 }
   7731 
   7732 fn airFence(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   7733     const zcu = func.bin_file.base.comp.module.?;
   7734     // Only when the atomic feature is enabled, and we're not building
   7735     // for a single-threaded build, can we emit the `fence` instruction.
   7736     // In all other cases, we emit no instructions for a fence.
   7737     const func_namespace = zcu.namespacePtr(func.decl.src_namespace);
   7738     const single_threaded = func_namespace.file_scope.mod.single_threaded;
   7739     if (func.useAtomicFeature() and !single_threaded) {
   7740         try func.addAtomicTag(.atomic_fence);
   7741     }
   7742 
   7743     return func.finishAir(inst, .none, &.{});
   7744 }
   7745 
   7746 fn airAtomicStore(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   7747     const mod = func.bin_file.base.comp.module.?;
   7748     const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
   7749 
   7750     const ptr = try func.resolveInst(bin_op.lhs);
   7751     const operand = try func.resolveInst(bin_op.rhs);
   7752     const ptr_ty = func.typeOf(bin_op.lhs);
   7753     const ty = ptr_ty.childType(mod);
   7754 
   7755     if (func.useAtomicFeature()) {
   7756         const tag: wasm.AtomicsOpcode = switch (ty.abiSize(mod)) {
   7757             1 => .i32_atomic_store8,
   7758             2 => .i32_atomic_store16,
   7759             4 => .i32_atomic_store,
   7760             8 => .i64_atomic_store,
   7761             else => |size| return func.fail("TODO: @atomicLoad for types with abi size {d}", .{size}),
   7762         };
   7763         try func.emitWValue(ptr);
   7764         try func.lowerToStack(operand);
   7765         try func.addAtomicMemArg(tag, .{
   7766             .offset = ptr.offset(),
   7767             .alignment = @intCast(ty.abiAlignment(mod).toByteUnits().?),
   7768         });
   7769     } else {
   7770         try func.store(ptr, operand, ty, 0);
   7771     }
   7772 
   7773     return func.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs });
   7774 }
   7775 
   7776 fn airFrameAddress(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
   7777     if (func.initial_stack_value == .none) {
   7778         try func.initializeStack();
   7779     }
   7780     try func.emitWValue(func.bottom_stack_value);
   7781     const result = try WValue.toLocal(.stack, func, Type.usize);
   7782     return func.finishAir(inst, result, &.{});
   7783 }
   7784 
   7785 fn typeOf(func: *CodeGen, inst: Air.Inst.Ref) Type {
   7786     const mod = func.bin_file.base.comp.module.?;
   7787     return func.air.typeOf(inst, &mod.intern_pool);
   7788 }
   7789 
   7790 fn typeOfIndex(func: *CodeGen, inst: Air.Inst.Index) Type {
   7791     const mod = func.bin_file.base.comp.module.?;
   7792     return func.air.typeOfIndex(inst, &mod.intern_pool);
   7793 }