Commit b2221e5644

Luuk de Gram <luuk@degram.dev>
2021-11-18 21:33:10
wasm: Implement structs stored on the stack
By calculating the abi size of the struct, we move the stack pointer and store each field depending on its size (i.e. a 1-byte field will use i32.store8). This commit adds all required opcodes to perform those stores and loads. This also gets rid of `mir_offset` as we now save results of binary operations into locals and emit its result onto the stack within condbr instead. This makes everything a lot simpler but also more robust. In the future, we could look into an algorithm to re-use such locals. For struct fields we use the new `local_with_offset` tag. This stores the struct's stack pointer as well as the field's offset from that stack pointer. `allocLocal` will now always allocate a single local, using a given type.
1 parent 261f134
Changed files (3)
src/arch/wasm/CodeGen.zig
@@ -28,16 +28,15 @@ const WValue = union(enum) {
     local: u32,
     /// Holds a memoized typed value
     constant: TypedValue,
-    /// Offset position in the list of MIR instructions
-    mir_offset: usize,
-    /// Used for variables that create multiple locals on the stack when allocated
-    /// such as structs and optionals.
-    multi_value: struct {
-        /// The index of the first local variable
-        index: u32,
-        /// The count of local variables this `WValue` consists of.
-        /// i.e. an ErrorUnion has a 'count' of 2.
-        count: u32,
+    /// Used for types that contains of multiple areas within
+    /// a memory region in the stack.
+    /// The local represents the position in the stack,
+    /// whereas the offset represents the offset from that position.
+    local_with_offset: struct {
+        /// Index of the local variable
+        local: u32,
+        /// The offset from the local's stack position
+        offset: u32,
     },
 };
 
@@ -187,7 +186,8 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
             },
             32 => switch (args.valtype1.?) {
                 .i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u,
-                .i32, .f32, .f64 => unreachable,
+                .i32 => return .i32_load,
+                .f32, .f64 => unreachable,
             },
             else => unreachable,
         } else switch (args.valtype1.?) {
@@ -668,9 +668,10 @@ fn typeToValtype(self: *Self, ty: Type) InnerError!wasm.Valtype {
         .Bool,
         .Pointer,
         .ErrorSet,
+        .Struct,
+        .ErrorUnion,
         => wasm.Valtype.i32,
-        .Struct, .ErrorUnion, .Optional => unreachable, // Multi typed, must be handled individually.
-        else => |tag| self.fail("TODO - Wasm valtype for type '{s}'", .{tag}),
+        else => self.fail("TODO - Wasm valtype for type '{}'", .{ty}),
     };
 }
 
@@ -692,76 +693,21 @@ fn genBlockType(self: *Self, ty: Type) InnerError!u8 {
 /// Writes the bytecode depending on the given `WValue` in `val`
 fn emitWValue(self: *Self, val: WValue) InnerError!void {
     switch (val) {
-        .multi_value => unreachable, // multi_value can never be written directly, and must be accessed individually
-        .none, .mir_offset => {}, // no-op
+        .none => {}, // no-op
+        .local_with_offset => |with_off| try self.addLabel(.local_get, with_off.local),
         .local => |idx| try self.addLabel(.local_get, idx),
         .constant => |tv| try self.emitConstant(tv.val, tv.ty), // Creates a new constant on the stack
     }
 }
 
-/// Creates one or multiple locals for a given `Type`.
-/// Returns a corresponding `Wvalue` that can either be of tag
-/// local or multi_value
+/// Creates one locals for a given `Type`.
+/// Returns a corresponding `Wvalue` with `local` as active tag
 fn allocLocal(self: *Self, ty: Type) InnerError!WValue {
     const initial_index = self.local_index;
-    switch (ty.zigTypeTag()) {
-        .Struct => {
-            // for each struct field, generate a local
-            const struct_data: *Module.Struct = ty.castTag(.@"struct").?.data;
-            const fields_len = @intCast(u32, struct_data.fields.count());
-            try self.locals.ensureUnusedCapacity(self.gpa, fields_len);
-            for (struct_data.fields.values()) |*value| {
-                const val_type = try self.genValtype(value.ty);
-                self.locals.appendAssumeCapacity(val_type);
-                self.local_index += 1;
-            }
-            return WValue{ .multi_value = .{
-                .index = initial_index,
-                .count = fields_len,
-            } };
-        },
-        .ErrorUnion => {
-            const payload_type = ty.errorUnionPayload();
-            const val_type = try self.genValtype(payload_type);
-
-            // we emit the error value as the first local, and the payload as the following.
-            // The first local is also used to find the index of the error and payload.
-            //
-            // TODO: Add support where the payload is a type that contains multiple locals such as a struct.
-            try self.locals.ensureUnusedCapacity(self.gpa, 2);
-            self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // error values are always i32
-            self.locals.appendAssumeCapacity(val_type);
-            self.local_index += 2;
-
-            return WValue{ .multi_value = .{
-                .index = initial_index,
-                .count = 2,
-            } };
-        },
-        .Optional => {
-            var opt_buf: Type.Payload.ElemType = undefined;
-            const child_type = ty.optionalChild(&opt_buf);
-            if (ty.isPtrLikeOptional()) {
-                return self.fail("TODO: wasm optional pointer", .{});
-            }
-
-            try self.locals.ensureUnusedCapacity(self.gpa, 2);
-            self.locals.appendAssumeCapacity(wasm.valtype(.i32)); // optional 'tag' for null-checking is always i32
-            self.locals.appendAssumeCapacity(try self.genValtype(child_type));
-            self.local_index += 2;
-
-            return WValue{ .multi_value = .{
-                .index = initial_index,
-                .count = 2,
-            } };
-        },
-        else => {
-            const valtype = try self.genValtype(ty);
-            try self.locals.append(self.gpa, valtype);
-            self.local_index += 1;
-            return WValue{ .local = initial_index };
-        },
-    }
+    const valtype = try self.genValtype(ty);
+    try self.locals.append(self.gpa, valtype);
+    self.local_index += 1;
+    return WValue{ .local = initial_index };
 }
 
 fn genFunctype(self: *Self) InnerError!void {
@@ -857,7 +803,7 @@ pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {
                 if (ty.sentinel()) |sentinel| {
                     try self.code.appendSlice(payload.data);
 
-                    switch (try self.gen(ty.elemType(), sentinel)) {
+                    switch (try self.gen(ty.childType(), sentinel)) {
                         .appended => return Result.appended,
                         .externally_managed => |data| {
                             try self.code.appendSlice(data);
@@ -927,15 +873,8 @@ fn restoreStackPointer(self: *Self) !void {
 /// the result back into the stack pointer.
 fn moveStack(self: *Self, offset: u32, local: u32) !void {
     if (offset == 0) return;
-    // Generates the following code:
-    //
-    // global.get 0
-    // i32.const [offset]
-    // i32.sub
-    // global.set 0
-
     // TODO: Rather than hardcode the stack pointer to position 0,
-    // have the linker resolve it.
+    // have the linker resolve its relocation
     try self.addLabel(.global_get, 0);
     try self.addImm32(@bitCast(i32, offset));
     try self.addTag(.i32_sub);
@@ -1053,17 +992,18 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
 }
 
 fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
-    const elem_type = self.air.typeOfIndex(inst).elemType();
+    const child_type = self.air.typeOfIndex(inst).childType();
 
     // Initialize the stack
     if (self.initial_stack_value == .none) {
         try self.initializeStack();
     }
 
-    const abi_size = elem_type.abiSize(self.target);
+    const abi_size = child_type.abiSize(self.target);
     if (abi_size == 0) return WValue{ .none = {} };
 
-    const local = try self.allocLocal(elem_type);
+    // local, containing the offset to the stack position
+    const local = try self.allocLocal(child_type);
     try self.moveStack(@intCast(u32, abi_size), local.local);
 
     return local;
@@ -1074,43 +1014,100 @@ fn airStore(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
 
     const lhs = self.resolveInst(bin_op.lhs);
     const rhs = self.resolveInst(bin_op.rhs);
+    const ty = self.air.typeOf(bin_op.lhs).childType();
 
-    // get lhs stack position
+    const offset: u32 = switch (lhs) {
+        .local_with_offset => |with_off| with_off.offset,
+        else => 0,
+    };
+
+    switch (ty.zigTypeTag()) {
+        .ErrorUnion, .Optional => {
+            var buf: Type.Payload.ElemType = undefined;
+            const payload_ty = if (ty.zigTypeTag() == .ErrorUnion) ty.errorUnionPayload() else ty.optionalChild(&buf);
+            const tag_ty = if (ty.zigTypeTag() == .ErrorUnion) ty.errorUnionSet() else Type.initTag(.i8);
+            const payload_offset = @intCast(u32, tag_ty.abiSize(self.target) / 8);
+            if (rhs == .constant) {
+                // constant will contain both tag and payload,
+                // so save those in 2 temporary locals before storing them
+                // in memory
+                try self.emitWValue(rhs);
+                const tag_local = try self.allocLocal(Type.initTag(.i32));
+                const payload_local = try self.allocLocal(payload_ty);
+
+                try self.addLabel(.local_set, payload_local.local);
+                try self.addLabel(.local_set, tag_local.local);
+
+                try self.store(lhs, tag_local, tag_ty, 0);
+                try self.store(lhs, payload_local, payload_ty, payload_offset);
+            } else if (offset == 0) {
+                // tag is being set
+                try self.store(lhs, rhs, tag_ty, 0);
+            } else {
+                // payload is being set
+                try self.store(lhs, rhs, payload_ty, payload_offset);
+            }
+        },
+        else => try self.store(lhs, rhs, ty, offset),
+    }
+
+    return .none;
+}
+
+fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerError!void {
     try self.emitWValue(lhs);
-    // get rhs value
     try self.emitWValue(rhs);
-
-    const ty = self.air.typeOf(bin_op.lhs);
     const valtype = try self.typeToValtype(ty);
-
     const opcode = buildOpcode(.{
         .valtype1 = valtype,
         .width = @intCast(u8, Type.abiSize(ty, self.target) * 8), // use bitsize instead of byte size
         .op = .store,
     });
+
     // store rhs value at stack pointer's location in memory
-    const mem_arg_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 0 });
+    const mem_arg_index = try self.addExtra(Mir.MemArg{ .offset = offset, .alignment = 0 });
     try self.addInst(.{ .tag = Mir.Inst.Tag.fromOpcode(opcode), .data = .{ .payload = mem_arg_index } });
-
-    return .none;
 }
 
 fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
-    const lhs = self.resolveInst(ty_op.operand);
+    const operand = self.resolveInst(ty_op.operand);
+    const ty = self.air.getRefType(ty_op.ty);
+
+    return switch (ty.zigTypeTag()) {
+        .Struct, .ErrorUnion => operand,
+        else => self.load(operand, ty, 0),
+    };
+}
 
+fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {
     // load local's value from memory by its stack position
-    try self.emitWValue(lhs);
-    const mem_arg_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 0 });
-    try self.addInst(.{ .tag = .i32_load, .data = .{ .payload = mem_arg_index } });
-    return .none;
+    try self.emitWValue(operand);
+    // Build the opcode with the right bitsize
+    const signedness: std.builtin.Signedness = if (ty.isUnsignedInt()) .unsigned else .signed;
+    const opcode = buildOpcode(.{
+        .valtype1 = try self.typeToValtype(ty),
+        .width = @intCast(u8, Type.abiSize(ty, self.target) * 8), // use bitsize instead of byte size
+        .op = .load,
+        .signedness = signedness,
+    });
+
+    const mem_arg_index = try self.addExtra(Mir.MemArg{ .offset = offset, .alignment = 0 });
+    try self.addInst(.{
+        .tag = Mir.Inst.Tag.fromOpcode(opcode),
+        .data = .{ .payload = mem_arg_index },
+    });
+
+    // store the result in a local
+    const result = try self.allocLocal(ty);
+    try self.addLabel(.local_set, result.local);
+    return result;
 }
 
 fn airArg(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
     _ = inst;
-    // arguments share the index with locals
-    defer self.local_index += 1;
-    return WValue{ .local = self.local_index };
+    defer self.arg_index += 1;
+    return self.args[self.arg_index];
 }
 
 fn airBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
@@ -1388,14 +1385,8 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
 
     // insert blocks at the position of `offset` so
     // the condition can jump to it
-    const offset = switch (condition) {
-        .mir_offset => |offset| offset,
-        else => blk: {
-            const offset = self.mir_instructions.len;
-            try self.emitWValue(condition);
-            break :blk offset;
-        },
-    };
+    const offset = self.mir_instructions.len;
+    try self.emitWValue(condition);
 
     // result type is always noreturn, so use `block_empty` as type.
     try self.startBlock(.block, wasm.block_empty, offset);
@@ -1415,10 +1406,6 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
 }
 
 fn airCmp(self: *Self, inst: Air.Inst.Index, op: std.math.CompareOperator) InnerError!WValue {
-    // save offset, so potential conditions can insert blocks in front of
-    // the comparison that we can later jump back to
-    const offset = self.mir_instructions.len;
-
     const data: Air.Inst.Data = self.air.instructions.items(.data)[inst];
     const lhs = self.resolveInst(data.bin_op.lhs);
     const rhs = self.resolveInst(data.bin_op.rhs);
@@ -1447,7 +1434,10 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: std.math.CompareOperator) Inner
         .signedness = signedness,
     });
     try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
-    return WValue{ .mir_offset = offset };
+
+    const cmp_tmp = try self.allocLocal(lhs_ty);
+    try self.addLabel(.local_set, cmp_tmp.local);
+    return cmp_tmp;
 }
 
 fn airBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
@@ -1468,7 +1458,6 @@ fn airBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
 
 fn airNot(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
-    const offset = self.mir_instructions.len;
 
     const operand = self.resolveInst(ty_op.operand);
     try self.emitWValue(operand);
@@ -1478,7 +1467,10 @@ fn airNot(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
     try self.addImm32(0);
     try self.addTag(.i32_eq);
 
-    return WValue{ .mir_offset = offset };
+    // save the result in the local
+    const not_tmp = try self.allocLocal(self.air.getRefType(ty_op.ty));
+    try self.addLabel(.local_set, not_tmp.local);
+    return not_tmp;
 }
 
 fn airBreakpoint(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
@@ -1504,24 +1496,45 @@ fn airStructFieldPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
     const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
     const extra = self.air.extraData(Air.StructField, ty_pl.payload);
     const struct_ptr = self.resolveInst(extra.data.struct_operand);
-    return structFieldPtr(struct_ptr, extra.data.field_index);
+    const struct_ty = self.air.typeOf(extra.data.struct_operand).childType();
+    const offset = std.math.cast(u32, struct_ty.structFieldOffset(extra.data.field_index, self.target)) catch {
+        return self.fail("Field type '{}' too big to fit into stack frame", .{
+            struct_ty.structFieldType(extra.data.field_index),
+        });
+    };
+    return structFieldPtr(struct_ptr, offset);
 }
+
 fn airStructFieldPtrIndex(self: *Self, inst: Air.Inst.Index, index: u32) InnerError!WValue {
     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
     const struct_ptr = self.resolveInst(ty_op.operand);
-    return structFieldPtr(struct_ptr, index);
+    const struct_ty = self.air.typeOf(ty_op.operand).childType();
+    const offset = std.math.cast(u32, struct_ty.structFieldOffset(index, self.target)) catch {
+        return self.fail("Field type '{}' too big to fit into stack frame", .{
+            struct_ty.structFieldType(index),
+        });
+    };
+    return structFieldPtr(struct_ptr, offset);
 }
-fn structFieldPtr(struct_ptr: WValue, index: u32) InnerError!WValue {
-    return WValue{ .local = struct_ptr.multi_value.index + index };
+
+fn structFieldPtr(struct_ptr: WValue, offset: u32) InnerError!WValue {
+    return WValue{ .local_with_offset = .{ .local = struct_ptr.local, .offset = offset } };
 }
 
 fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
     if (self.liveness.isUnused(inst)) return WValue.none;
 
     const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
-    const extra = self.air.extraData(Air.StructField, ty_pl.payload).data;
-    const struct_multivalue = self.resolveInst(extra.struct_operand).multi_value;
-    return WValue{ .local = struct_multivalue.index + extra.field_index };
+    const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data;
+    const struct_ty = self.air.typeOf(struct_field.struct_operand);
+    const operand = self.resolveInst(struct_field.struct_operand);
+    const field_index = struct_field.field_index;
+    const field_ty = struct_ty.structFieldType(field_index);
+    if (!field_ty.hasCodeGenBits()) return WValue.none;
+    const offset = std.math.cast(u32, struct_ty.structFieldOffset(field_index, self.target)) catch {
+        return self.fail("Field type '{}' too big to fit into stack frame", .{field_ty});
+    };
+    return try self.load(operand, field_ty, offset);
 }
 
 fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
@@ -1675,25 +1688,32 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
 fn airIsErr(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {
     const un_op = self.air.instructions.items(.data)[inst].un_op;
     const operand = self.resolveInst(un_op);
-    const offset = self.mir_instructions.len;
+    const err_ty = self.air.typeOf(un_op).errorUnionSet();
+
+    // load the error tag value
+    try self.emitWValue(operand);
+    const mem_arg_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 0 });
+    try self.addInst(.{
+        .tag = .i32_load,
+        .data = .{ .payload = mem_arg_index },
+    });
 
-    // load the error value which is positioned at multi_value's index
-    try self.emitWValue(.{ .local = operand.multi_value.index });
     // Compare the error value with '0'
     try self.addImm32(0);
     try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
 
-    return WValue{ .mir_offset = offset };
+    const is_err_tmp = try self.allocLocal(err_ty);
+    try self.addLabel(.local_set, is_err_tmp.local);
+    return is_err_tmp;
 }
 
 fn airUnwrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
     const operand = self.resolveInst(ty_op.operand);
-    // The index of multi_value contains the error code. To get the initial index of the payload we get
-    // the following index. Next, convert it to a `WValue.local`
-    //
-    // TODO: Check if payload is a type that requires a multi_value as well and emit that instead. i.e. a struct.
-    return WValue{ .local = operand.multi_value.index + 1 };
+    const err_ty = self.air.typeOf(ty_op.operand);
+    const offset = @intCast(u32, err_ty.errorUnionSet().abiSize(self.target) / 8);
+
+    return self.load(operand, self.air.getRefType(ty_op.ty), offset);
 }
 
 fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
@@ -1728,8 +1748,8 @@ fn airIsNull(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!
     const un_op = self.air.instructions.items(.data)[inst].un_op;
     const operand = self.resolveInst(un_op);
 
-    // load the null value which is positioned at multi_value's index
-    try self.emitWValue(.{ .local = operand.multi_value.index });
+    // load the null value which is positioned at local_with_offset's index
+    try self.emitWValue(.{ .local = operand.local_with_offset.local });
     try self.addImm32(0);
     try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
 
@@ -1743,7 +1763,7 @@ fn airIsNull(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!
 fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
     const operand = self.resolveInst(ty_op.operand);
-    return WValue{ .local = operand.multi_value.index + 1 };
+    return WValue{ .local = operand.local_with_offset.local + 1 };
 }
 
 fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
src/arch/wasm/Emit.zig
@@ -60,8 +60,30 @@ pub fn emitMir(emit: *Emit) InnerError!void {
 
             // memory instructions
             .i32_load => try emit.emitMemArg(tag, inst),
+            .i64_load => try emit.emitMemArg(tag, inst),
+            .f32_load => try emit.emitMemArg(tag, inst),
+            .f64_load => try emit.emitMemArg(tag, inst),
+            .i32_load8_s => try emit.emitMemArg(tag, inst),
+            .i32_load8_u => try emit.emitMemArg(tag, inst),
+            .i32_load16_s => try emit.emitMemArg(tag, inst),
+            .i32_load16_u => try emit.emitMemArg(tag, inst),
+            .i64_load8_s => try emit.emitMemArg(tag, inst),
+            .i64_load8_u => try emit.emitMemArg(tag, inst),
+            .i64_load16_s => try emit.emitMemArg(tag, inst),
+            .i64_load16_u => try emit.emitMemArg(tag, inst),
+            .i64_load32_s => try emit.emitMemArg(tag, inst),
+            .i64_load32_u => try emit.emitMemArg(tag, inst),
             .i32_store => try emit.emitMemArg(tag, inst),
+            .i64_store => try emit.emitMemArg(tag, inst),
+            .f32_store => try emit.emitMemArg(tag, inst),
+            .f64_store => try emit.emitMemArg(tag, inst),
+            .i32_store8 => try emit.emitMemArg(tag, inst),
+            .i32_store16 => try emit.emitMemArg(tag, inst),
+            .i64_store8 => try emit.emitMemArg(tag, inst),
+            .i64_store16 => try emit.emitMemArg(tag, inst),
+            .i64_store32 => try emit.emitMemArg(tag, inst),
 
+            // Instructions with an index that do not require relocations
             .local_get => try emit.emitLabel(tag, inst),
             .local_set => try emit.emitLabel(tag, inst),
             .local_tee => try emit.emitLabel(tag, inst),
src/arch/wasm/Mir.zig
@@ -97,11 +97,125 @@ pub const Inst = struct {
         ///
         /// Uses `payload` of type `MemArg`.
         i32_load = 0x28,
+        /// Loads a value from memory onto the stack, based on the signedness
+        /// and bitsize of the type.
+        ///
+        /// Uses `payload` with type `MemArg`
+        i64_load = 0x29,
+        /// Loads a value from memory onto the stack, based on the signedness
+        /// and bitsize of the type.
+        ///
+        /// Uses `payload` with type `MemArg`
+        f32_load = 0x2A,
+        /// Loads a value from memory onto the stack, based on the signedness
+        /// and bitsize of the type.
+        ///
+        /// Uses `payload` with type `MemArg`
+        f64_load = 0x2B,
+        /// Loads a value from memory onto the stack, based on the signedness
+        /// and bitsize of the type.
+        ///
+        /// Uses `payload` with type `MemArg`
+        i32_load8_s = 0x2C,
+        /// Loads a value from memory onto the stack, based on the signedness
+        /// and bitsize of the type.
+        ///
+        /// Uses `payload` with type `MemArg`
+        i32_load8_u = 0x2D,
+        /// Loads a value from memory onto the stack, based on the signedness
+        /// and bitsize of the type.
+        ///
+        /// Uses `payload` with type `MemArg`
+        i32_load16_s = 0x2E,
+        /// Loads a value from memory onto the stack, based on the signedness
+        /// and bitsize of the type.
+        ///
+        /// Uses `payload` with type `MemArg`
+        i32_load16_u = 0x2F,
+        /// Loads a value from memory onto the stack, based on the signedness
+        /// and bitsize of the type.
+        ///
+        /// Uses `payload` with type `MemArg`
+        i64_load8_s = 0x30,
+        /// Loads a value from memory onto the stack, based on the signedness
+        /// and bitsize of the type.
+        ///
+        /// Uses `payload` with type `MemArg`
+        i64_load8_u = 0x31,
+        /// Loads a value from memory onto the stack, based on the signedness
+        /// and bitsize of the type.
+        ///
+        /// Uses `payload` with type `MemArg`
+        i64_load16_s = 0x32,
+        /// Loads a value from memory onto the stack, based on the signedness
+        /// and bitsize of the type.
+        ///
+        /// Uses `payload` with type `MemArg`
+        i64_load16_u = 0x33,
+        /// Loads a value from memory onto the stack, based on the signedness
+        /// and bitsize of the type.
+        ///
+        /// Uses `payload` with type `MemArg`
+        i64_load32_s = 0x34,
+        /// Loads a value from memory onto the stack, based on the signedness
+        /// and bitsize of the type.
+        ///
+        /// Uses `payload` with type `MemArg`
+        i64_load32_u = 0x35,
         /// Pops 2 values from the stack, where the first value represents the value to write into memory
         /// and the second value represents the offset into memory where the value must be written to.
+        /// This opcode is typed and expects the stack value's type to be equal to this opcode's type.
         ///
         /// Uses `payload` of type `MemArg`.
         i32_store = 0x36,
+        /// Pops 2 values from the stack, where the first value represents the value to write into memory
+        /// and the second value represents the offset into memory where the value must be written to.
+        /// This opcode is typed and expects the stack value's type to be equal to this opcode's type.
+        ///
+        /// Uses `Payload` with type `MemArg`
+        i64_store = 0x37,
+        /// Pops 2 values from the stack, where the first value represents the value to write into memory
+        /// and the second value represents the offset into memory where the value must be written to.
+        /// This opcode is typed and expects the stack value's type to be equal to this opcode's type.
+        ///
+        /// Uses `Payload` with type `MemArg`
+        f32_store = 0x38,
+        /// Pops 2 values from the stack, where the first value represents the value to write into memory
+        /// and the second value represents the offset into memory where the value must be written to.
+        /// This opcode is typed and expects the stack value's type to be equal to this opcode's type.
+        ///
+        /// Uses `Payload` with type `MemArg`
+        f64_store = 0x39,
+        /// Pops 2 values from the stack, where the first value represents the value to write into memory
+        /// and the second value represents the offset into memory where the value must be written to.
+        /// This opcode is typed and expects the stack value's type to be equal to this opcode's type.
+        ///
+        /// Uses `Payload` with type `MemArg`
+        i32_store8 = 0x3A,
+        /// Pops 2 values from the stack, where the first value represents the value to write into memory
+        /// and the second value represents the offset into memory where the value must be written to.
+        /// This opcode is typed and expects the stack value's type to be equal to this opcode's type.
+        ///
+        /// Uses `Payload` with type `MemArg`
+        i32_store16 = 0x3B,
+        /// Pops 2 values from the stack, where the first value represents the value to write into memory
+        /// and the second value represents the offset into memory where the value must be written to.
+        /// This opcode is typed and expects the stack value's type to be equal to this opcode's type.
+        ///
+        /// Uses `Payload` with type `MemArg`
+        i64_store8 = 0x3C,
+        /// Pops 2 values from the stack, where the first value represents the value to write into memory
+        /// and the second value represents the offset into memory where the value must be written to.
+        /// This opcode is typed and expects the stack value's type to be equal to this opcode's type.
+        ///
+        /// Uses `Payload` with type `MemArg`
+        i64_store16 = 0x3D,
+        /// Pops 2 values from the stack, where the first value represents the value to write into memory
+        /// and the second value represents the offset into memory where the value must be written to.
+        /// This opcode is typed and expects the stack value's type to be equal to this opcode's type.
+        ///
+        /// Uses `Payload` with type `MemArg`
+        i64_store32 = 0x3E,
         /// Returns the memory size in amount of pages.
         ///
         /// Uses `nop`
@@ -247,7 +361,7 @@ pub const Inst = struct {
 
         /// From a given wasm opcode, returns a MIR tag.
         pub fn fromOpcode(opcode: std.wasm.Opcode) Tag {
-            return @intToEnum(Tag, @enumToInt(opcode));
+            return @intToEnum(Tag, @enumToInt(opcode)); // Given `Opcode` is not present as a tag for MIR yet
         }
 
         /// Returns a wasm opcode from a given MIR tag.