zig

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

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