authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-01-04 17:00:21+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-01-04 17:59:05+01:00
log89b1fdc4437531776b46ed7133c44b7250c122f8
tree95d629f527158751b0828b274f96b5e32c924d12
parent5c21a45cf0a22f68ac15f7db8829186ff1808a84
signaturelock-open Commit is signed but in an unrecognized format.

wasm: Implement memset, and sret arguments.

We now detect if the return type will be set by passing the first argument as a pointer to stack memory from the callee's frame. This way, we do not have to worry about stack memory being overwritten. Besides this, we implement memset by either using wasm's memory.fill instruction when available, or lower it manually. In the future we can lower this to a compiler_rt call.

5 files changed, 185 insertions(+), 56 deletions(-)

lib/std/wasm.zig+23-1
...@@ -212,6 +212,28 @@ test "Wasm - opcodes" {...@@ -212,6 +212,28 @@ test "Wasm - opcodes" {
212 try testing.expectEqual(@as(u16, 0xC4), i64_extend32_s);212 try testing.expectEqual(@as(u16, 0xC4), i64_extend32_s);
213}213}
214214
215/// Opcodes that require a prefix `0xFC`
216pub const PrefixedOpcode = enum(u8) {
217 i32_trunc_sat_f32_s = 0x00,
218 i32_trunc_sat_f32_u = 0x01,
219 i32_trunc_sat_f64_s = 0x02,
220 i32_trunc_sat_f64_u = 0x03,
221 i64_trunc_sat_f32_s = 0x04,
222 i64_trunc_sat_f32_u = 0x05,
223 i64_trunc_sat_f64_s = 0x06,
224 i64_trunc_sat_f64_u = 0x07,
225 memory_init = 0x08,
226 data_drop = 0x09,
227 memory_copy = 0x0A,
228 memory_fill = 0x0B,
229 table_init = 0x0C,
230 elem_drop = 0x0D,
231 table_copy = 0x0E,
232 table_grow = 0x0F,
233 table_size = 0x10,
234 table_fill = 0x11,
235};
236
215/// Enum representing all Wasm value types as per spec:237/// Enum representing all Wasm value types as per spec:
216/// https://webassembly.github.io/spec/core/binary/types.html238/// https://webassembly.github.io/spec/core/binary/types.html
217pub const Valtype = enum(u8) {239pub const Valtype = enum(u8) {
...@@ -266,7 +288,7 @@ pub const InitExpression = union(enum) {...@@ -266,7 +288,7 @@ pub const InitExpression = union(enum) {
266 global_get: u32,288 global_get: u32,
267};289};
268290
269///291/// Represents a function entry, holding the index to its type
270pub const Func = struct {292pub const Func = struct {
271 type_index: u32,293 type_index: u32,
272};294};
src/arch/wasm/CodeGen.zig+127-47
...@@ -623,6 +623,10 @@ fn addTag(self: *Self, tag: Mir.Inst.Tag) error{OutOfMemory}!void {...@@ -623,6 +623,10 @@ fn addTag(self: *Self, tag: Mir.Inst.Tag) error{OutOfMemory}!void {
623 try self.addInst(.{ .tag = tag, .data = .{ .tag = {} } });623 try self.addInst(.{ .tag = tag, .data = .{ .tag = {} } });
624}624}
625625
626fn addExtended(self: *Self, opcode: wasm.PrefixedOpcode) error{OutOfMemory}!void {
627 try self.addInst(.{ .tag = .extended, .secondary = @enumToInt(opcode), .data = .{ .tag = {} } });
628}
629
626fn addLabel(self: *Self, tag: Mir.Inst.Tag, label: u32) error{OutOfMemory}!void {630fn addLabel(self: *Self, tag: Mir.Inst.Tag, label: u32) error{OutOfMemory}!void {
627 try self.addInst(.{ .tag = tag, .data = .{ .label = label } });631 try self.addInst(.{ .tag = tag, .data = .{ .label = label } });
628}632}
...@@ -746,6 +750,13 @@ fn genFunctype(self: *Self, fn_ty: Type) !wasm.Type {...@@ -746,6 +750,13 @@ fn genFunctype(self: *Self, fn_ty: Type) !wasm.Type {
746 defer params.deinit();750 defer params.deinit();
747 var returns = std.ArrayList(wasm.Valtype).init(self.gpa);751 var returns = std.ArrayList(wasm.Valtype).init(self.gpa);
748 defer returns.deinit();752 defer returns.deinit();
753 const return_type = fn_ty.fnReturnType();
754
755 const want_sret = isByRef(return_type);
756
757 if (want_sret) {
758 try params.append(try self.typeToValtype(Type.usize));
759 }
749760
750 // param types761 // param types
751 if (fn_ty.fnParamLen() != 0) {762 if (fn_ty.fnParamLen() != 0) {
...@@ -759,11 +770,8 @@ fn genFunctype(self: *Self, fn_ty: Type) !wasm.Type {...@@ -759,11 +770,8 @@ fn genFunctype(self: *Self, fn_ty: Type) !wasm.Type {
759 }770 }
760771
761 // return type772 // return type
762 const return_type = fn_ty.fnReturnType();773 if (!want_sret and return_type.hasCodeGenBits()) {
763 switch (return_type.zigTypeTag()) {774 try returns.append(try self.typeToValtype(return_type));
764 .Void, .NoReturn => {},
765 .Struct => return self.fail("TODO: Implement struct as return type for wasm", .{}),
766 else => try returns.append(try self.typeToValtype(return_type)),
767 }775 }
768776
769 return wasm.Type{777 return wasm.Type{
...@@ -785,6 +793,15 @@ pub fn genFunc(self: *Self) InnerError!Result {...@@ -785,6 +793,15 @@ pub fn genFunc(self: *Self) InnerError!Result {
785793
786 // Generate MIR for function body794 // Generate MIR for function body
787 try self.genBody(self.air.getMainBody());795 try self.genBody(self.air.getMainBody());
796 // In case we have a return value, but the last instruction is a noreturn (such as a while loop)
797 // we emit an unreachable instruction to tell the stack validator that part will never be reached.
798 if (func_type.returns.len != 0 and self.air.instructions.len > 0) {
799 const inst = @intCast(u32, self.air.instructions.len - 1);
800 if (self.air.typeOfIndex(inst).isNoReturn()) {
801 try self.addTag(.@"unreachable");
802 }
803 }
804
788 // End of function body805 // End of function body
789 try self.addTag(.end);806 try self.addTag(.end);
790807
...@@ -1074,6 +1091,15 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu...@@ -1074,6 +1091,15 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu
1074 .return_value = .none,1091 .return_value = .none,
1075 };1092 };
1076 errdefer self.gpa.free(result.args);1093 errdefer self.gpa.free(result.args);
1094 const ret_ty = fn_ty.fnReturnType();
1095 // Check if we store the result as a pointer to the stack rather than
1096 // by value
1097 if (isByRef(ret_ty)) {
1098 // the sret arg will be passed as first argument, therefore we
1099 // set the `return_value` before allocating locals for regular args.
1100 result.return_value = .{ .local = self.local_index };
1101 self.local_index += 1;
1102 }
1077 switch (cc) {1103 switch (cc) {
1078 .Naked => return result,1104 .Naked => return result,
1079 .Unspecified, .C => {1105 .Unspecified, .C => {
...@@ -1086,19 +1112,6 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu...@@ -1086,19 +1112,6 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu
1086 result.args[ty_index] = .{ .local = self.local_index };1112 result.args[ty_index] = .{ .local = self.local_index };
1087 self.local_index += 1;1113 self.local_index += 1;
1088 }1114 }
1089
1090 const ret_ty = fn_ty.fnReturnType();
1091 // Check if we store the result as a pointer to the stack rather than
1092 // by value
1093 if (isByRef(ret_ty)) {
1094 if (self.initial_stack_value == .none) try self.initializeStack();
1095 result.return_value = try self.allocStack(ret_ty);
1096
1097 // We want to make sure the return value's stack value doesn't get overwritten,
1098 // so set initial stack value to current's position instead.
1099 try self.addLabel(.global_get, 0);
1100 try self.addLabel(.local_set, self.initial_stack_value.local);
1101 }
1102 },1115 },
1103 else => return self.fail("TODO implement function parameters for cc '{}' on wasm", .{cc}),1116 else => return self.fail("TODO implement function parameters for cc '{}' on wasm", .{cc}),
1104 }1117 }
...@@ -1323,6 +1336,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {...@@ -1323,6 +1336,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
13231336
1324 .load => self.airLoad(inst),1337 .load => self.airLoad(inst),
1325 .loop => self.airLoop(inst),1338 .loop => self.airLoop(inst),
1339 .memset => self.airMemset(inst),
1326 .not => self.airNot(inst),1340 .not => self.airNot(inst),
1327 .optional_payload => self.airOptionalPayload(inst),1341 .optional_payload => self.airOptionalPayload(inst),
1328 .optional_payload_ptr => self.airOptionalPayloadPtr(inst),1342 .optional_payload_ptr => self.airOptionalPayloadPtr(inst),
...@@ -1335,18 +1349,21 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {...@@ -1335,18 +1349,21 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
1335 .ret => self.airRet(inst),1349 .ret => self.airRet(inst),
1336 .ret_ptr => self.airRetPtr(inst),1350 .ret_ptr => self.airRetPtr(inst),
1337 .ret_load => self.airRetLoad(inst),1351 .ret_load => self.airRetLoad(inst),
1352
1338 .slice => self.airSlice(inst),1353 .slice => self.airSlice(inst),
1339 .slice_len => self.airSliceLen(inst),1354 .slice_len => self.airSliceLen(inst),
1340 .slice_elem_val => self.airSliceElemVal(inst),1355 .slice_elem_val => self.airSliceElemVal(inst),
1341 .slice_elem_ptr => self.airSliceElemPtr(inst),1356 .slice_elem_ptr => self.airSliceElemPtr(inst),
1342 .slice_ptr => self.airSlicePtr(inst),1357 .slice_ptr => self.airSlicePtr(inst),
1343 .store => self.airStore(inst),1358 .store => self.airStore(inst),
1359
1344 .struct_field_ptr => self.airStructFieldPtr(inst),1360 .struct_field_ptr => self.airStructFieldPtr(inst),
1345 .struct_field_ptr_index_0 => self.airStructFieldPtrIndex(inst, 0),1361 .struct_field_ptr_index_0 => self.airStructFieldPtrIndex(inst, 0),
1346 .struct_field_ptr_index_1 => self.airStructFieldPtrIndex(inst, 1),1362 .struct_field_ptr_index_1 => self.airStructFieldPtrIndex(inst, 1),
1347 .struct_field_ptr_index_2 => self.airStructFieldPtrIndex(inst, 2),1363 .struct_field_ptr_index_2 => self.airStructFieldPtrIndex(inst, 2),
1348 .struct_field_ptr_index_3 => self.airStructFieldPtrIndex(inst, 3),1364 .struct_field_ptr_index_3 => self.airStructFieldPtrIndex(inst, 3),
1349 .struct_field_val => self.airStructFieldVal(inst),1365 .struct_field_val => self.airStructFieldVal(inst),
1366
1350 .switch_br => self.airSwitchBr(inst),1367 .switch_br => self.airSwitchBr(inst),
1351 .trunc => self.airTrunc(inst),1368 .trunc => self.airTrunc(inst),
1352 .unreach => self.airUnreachable(inst),1369 .unreach => self.airUnreachable(inst),
...@@ -1374,7 +1391,6 @@ fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1374,7 +1391,6 @@ fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1374 // to the stack instead1391 // to the stack instead
1375 if (self.return_value != .none) {1392 if (self.return_value != .none) {
1376 try self.store(self.return_value, operand, self.decl.ty.fnReturnType(), 0);1393 try self.store(self.return_value, operand, self.decl.ty.fnReturnType(), 0);
1377 try self.emitWValue(self.return_value);
1378 } else {1394 } else {
1379 try self.emitWValue(operand);1395 try self.emitWValue(operand);
1380 }1396 }
...@@ -1393,6 +1409,9 @@ fn airRetPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1393,6 +1409,9 @@ fn airRetPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
13931409
1394 if (child_type.abiSize(self.target) == 0) return WValue{ .none = {} };1410 if (child_type.abiSize(self.target) == 0) return WValue{ .none = {} };
13951411
1412 if (isByRef(child_type)) {
1413 return self.return_value;
1414 }
1396 return self.allocStack(child_type);1415 return self.allocStack(child_type);
1397}1416}
13981417
...@@ -1402,9 +1421,7 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1402,9 +1421,7 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1402 const ret_ty = self.air.typeOf(un_op).childType();1421 const ret_ty = self.air.typeOf(un_op).childType();
1403 if (!ret_ty.hasCodeGenBits()) return WValue.none;1422 if (!ret_ty.hasCodeGenBits()) return WValue.none;
14041423
1405 if (isByRef(ret_ty)) {1424 if (!isByRef(ret_ty)) {
1406 try self.emitWValue(operand);
1407 } else {
1408 const result = try self.load(operand, ret_ty, 0);1425 const result = try self.load(operand, ret_ty, 0);
1409 try self.emitWValue(result);1426 try self.emitWValue(result);
1410 }1427 }
...@@ -1425,6 +1442,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1425,6 +1442,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1425 .Pointer => ty.childType(),1442 .Pointer => ty.childType(),
1426 else => unreachable,1443 else => unreachable,
1427 };1444 };
1445 const ret_ty = fn_ty.fnReturnType();
1446 const first_param_sret = isByRef(ret_ty);
14281447
1429 const target: ?*Decl = blk: {1448 const target: ?*Decl = blk: {
1430 const func_val = self.air.value(pl_op.operand) orelse break :blk null;1449 const func_val = self.air.value(pl_op.operand) orelse break :blk null;
...@@ -1437,6 +1456,12 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1437,6 +1456,12 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1437 return self.fail("Expected a function, but instead found type '{s}'", .{func_val.tag()});1456 return self.fail("Expected a function, but instead found type '{s}'", .{func_val.tag()});
1438 };1457 };
14391458
1459 const sret = if (first_param_sret) blk: {
1460 const sret_local = try self.allocStack(ret_ty);
1461 try self.emitWValue(sret_local);
1462 break :blk sret_local;
1463 } else WValue{ .none = {} };
1464
1440 for (args) |arg| {1465 for (args) |arg| {
1441 const arg_ref = @intToEnum(Air.Inst.Ref, arg);1466 const arg_ref = @intToEnum(Air.Inst.Ref, arg);
1442 const arg_val = self.resolveInst(arg_ref);1467 const arg_val = self.resolveInst(arg_ref);
...@@ -1475,31 +1500,18 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1475,31 +1500,18 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1475 try self.addLabel(.call_indirect, fn_type_index);1500 try self.addLabel(.call_indirect, fn_type_index);
1476 }1501 }
14771502
1478 const ret_ty = fn_ty.fnReturnType();1503 if (self.liveness.isUnused(inst) or !ret_ty.hasCodeGenBits()) {
1479 if (!ret_ty.hasCodeGenBits()) return WValue.none;1504 return WValue.none;
14801505 } else if (ret_ty.isNoReturn()) {
1481 // TODO: Implement this for all aggregate types1506 try self.addTag(.@"unreachable");
1482 if (ret_ty.isSlice()) {1507 return WValue.none;
1483 // first load the values onto the regular stack, before we move the stack pointer1508 } else if (first_param_sret) {
1484 // to prevent overwriting the return value.1509 return sret;
1485 const tmp = try self.allocLocal(ret_ty);1510 } else {
1486 try self.addLabel(.local_set, tmp.local);1511 const result_local = try self.allocLocal(ret_ty);
1487 const field_ty = Type.@"usize";1512 try self.addLabel(.local_set, result_local.local);
1488 const offset = @intCast(u32, field_ty.abiSize(self.target));1513 return result_local;
1489 const ptr_local = try self.load(tmp, field_ty, 0);
1490 const len_local = try self.load(tmp, field_ty, offset);
1491
1492 // As our values are now safe, we reserve space on the virtual stack and
1493 // store the values there.
1494 const result = try self.allocStack(ret_ty);
1495 try self.store(result, ptr_local, field_ty, 0);
1496 try self.store(result, len_local, field_ty, offset);
1497 return result;
1498 }1514 }
1499
1500 const result_local = try self.allocLocal(ret_ty);
1501 try self.addLabel(.local_set, result_local.local);
1502 return result_local;
1503}1515}
15041516
1505fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {1517fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
...@@ -1989,7 +2001,7 @@ fn emitUndefined(self: *Self, ty: Type) InnerError!void {...@@ -1989,7 +2001,7 @@ fn emitUndefined(self: *Self, ty: Type) InnerError!void {
1989 // validator will not accept it due to out-of-bounds memory access);2001 // validator will not accept it due to out-of-bounds memory access);
1990 .Array => try self.addImm32(@bitCast(i32, @as(u32, 0xaa))),2002 .Array => try self.addImm32(@bitCast(i32, @as(u32, 0xaa))),
1991 .Struct => {2003 .Struct => {
1992 // TODO: Write 0xaa to each field2004 // TODO: Write 0xaa struct's memory
1993 const result = try self.allocStack(ty);2005 const result = try self.allocStack(ty);
1994 try self.addLabel(.local_get, result.local);2006 try self.addLabel(.local_get, result.local);
1995 },2007 },
...@@ -2943,3 +2955,71 @@ fn airPtrBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {...@@ -2943,3 +2955,71 @@ fn airPtrBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
2943 try self.addLabel(.local_set, result.local);2955 try self.addLabel(.local_set, result.local);
2944 return result;2956 return result;
2945}2957}
2958
2959fn airMemset(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2960 const pl_op = self.air.instructions.items(.data)[inst].pl_op;
2961 const bin_op = self.air.extraData(Air.Bin, pl_op.payload).data;
2962
2963 const ptr = self.resolveInst(pl_op.operand);
2964 const value = self.resolveInst(bin_op.lhs);
2965 const len = self.resolveInst(bin_op.rhs);
2966 try self.memSet(ptr, len, value);
2967
2968 return WValue.none;
2969}
2970
2971/// Sets a region of memory at `ptr` to the value of `value`
2972/// When the user has enabled the bulk_memory feature, we lower
2973/// this to wasm's memset instruction. When the feature is not present,
2974/// we implement it manually.
2975fn memSet(self: *Self, ptr: WValue, len: WValue, value: WValue) InnerError!void {
2976 // When bulk_memory is enabled, we lower it to wasm's memset instruction.
2977 // If not, we lower it ourselves
2978 if (std.Target.wasm.featureSetHas(self.target.cpu.features, .bulk_memory)) {
2979 try self.emitWValue(ptr);
2980 try self.emitWValue(value);
2981 try self.emitWValue(len);
2982 try self.addExtended(.memory_fill);
2983 return;
2984 }
2985
2986 // TODO: We should probably lower this to a call to compiler_rt
2987 // But for now, we implement it manually
2988 const offset = try self.allocLocal(Type.usize); // local for counter
2989 // outer block to jump to when loop is done
2990 try self.startBlock(.block, wasm.block_empty);
2991 try self.startBlock(.loop, wasm.block_empty);
2992 try self.emitWValue(offset);
2993 try self.emitWValue(len);
2994 switch (self.ptrSize()) {
2995 4 => try self.addTag(.i32_eq),
2996 8 => try self.addTag(.i64_eq),
2997 else => unreachable,
2998 }
2999 try self.addLabel(.br_if, 1); // jump out of loop into outer block (finished)
3000 try self.emitWValue(ptr);
3001 try self.emitWValue(offset);
3002 switch (self.ptrSize()) {
3003 4 => try self.addTag(.i32_add),
3004 8 => try self.addTag(.i64_add),
3005 else => unreachable,
3006 }
3007 try self.emitWValue(value);
3008 const mem_store_op: Mir.Inst.Tag = switch (self.ptrSize()) {
3009 4 => .i32_store8,
3010 8 => .i64_store8,
3011 else => unreachable,
3012 };
3013 try self.addMemArg(mem_store_op, .{ .offset = 0, .alignment = 1 });
3014 try self.emitWValue(offset);
3015 try self.addImm32(1);
3016 switch (self.ptrSize()) {
3017 4 => try self.addTag(.i32_add),
3018 8 => try self.addTag(.i64_add),
3019 else => unreachable,
3020 }
3021 try self.addLabel(.local_set, offset.local);
3022 try self.addLabel(.br, 0); // jump to start of loop
3023 try self.endBlock();
3024 try self.endBlock();
3025}
src/arch/wasm/Emit.zig+19
...@@ -161,6 +161,8 @@ pub fn emitMir(emit: *Emit) InnerError!void {...@@ -161,6 +161,8 @@ pub fn emitMir(emit: *Emit) InnerError!void {
161 .i64_extend8_s => try emit.emitTag(tag),161 .i64_extend8_s => try emit.emitTag(tag),
162 .i64_extend16_s => try emit.emitTag(tag),162 .i64_extend16_s => try emit.emitTag(tag),
163 .i64_extend32_s => try emit.emitTag(tag),163 .i64_extend32_s => try emit.emitTag(tag),
164
165 .extended => try emit.emitExtended(inst),
164 }166 }
165 }167 }
166}168}
...@@ -321,3 +323,20 @@ fn emitMemAddress(emit: *Emit, inst: Mir.Inst.Index) !void {...@@ -321,3 +323,20 @@ fn emitMemAddress(emit: *Emit, inst: Mir.Inst.Index) !void {
321 .relocation_type = .R_WASM_MEMORY_ADDR_LEB,323 .relocation_type = .R_WASM_MEMORY_ADDR_LEB,
322 });324 });
323}325}
326
327fn emitExtended(emit: *Emit, inst: Mir.Inst.Index) !void {
328 const opcode = emit.mir.instructions.items(.secondary)[inst];
329 switch (@intToEnum(std.wasm.PrefixedOpcode, opcode)) {
330 .memory_fill => try emit.emitMemFill(),
331 else => |tag| return emit.fail("TODO: Implement extension instruction: {s}\n", .{@tagName(tag)}),
332 }
333}
334
335fn emitMemFill(emit: *Emit) !void {
336 try emit.code.append(0xFC);
337 try emit.code.append(0x0B);
338 // When multi-memory proposal reaches phase 4, we
339 // can emit a different memory index here.
340 // For now we will always emit index 0.
341 try leb128.writeULEB128(emit.code.writer(), @as(u32, 0));
342}
src/arch/wasm/Mir.zig+8
...@@ -19,6 +19,9 @@ extra: []const u32,...@@ -19,6 +19,9 @@ extra: []const u32,
19pub const Inst = struct {19pub const Inst = struct {
20 /// The opcode that represents this instruction20 /// The opcode that represents this instruction
21 tag: Tag,21 tag: Tag,
22 /// This opcode will be set when `tag` represents an extended
23 /// instruction with prefix 0xFC, or a simd instruction with prefix 0xFD.
24 secondary: u8 = 0,
22 /// Data is determined by the set `tag`.25 /// Data is determined by the set `tag`.
23 /// For example, `data` will be an i32 for when `tag` is 'i32_const'.26 /// For example, `data` will be an i32 for when `tag` is 'i32_const'.
24 data: Data,27 data: Data,
...@@ -373,6 +376,11 @@ pub const Inst = struct {...@@ -373,6 +376,11 @@ pub const Inst = struct {
373 i64_extend16_s = 0xC3,376 i64_extend16_s = 0xC3,
374 /// Uses `tag`377 /// Uses `tag`
375 i64_extend32_s = 0xC4,378 i64_extend32_s = 0xC4,
379 /// The instruction consists of an extension opcode
380 /// set in `secondary`
381 ///
382 /// The `data` field depends on the extension instruction
383 extended = 0xFC,
376 /// Contains a symbol to a function pointer384 /// Contains a symbol to a function pointer
377 /// uses `label`385 /// uses `label`
378 ///386 ///
test/behavior.zig+8-8
...@@ -39,16 +39,23 @@ test {...@@ -39,16 +39,23 @@ test {
39 _ = @import("behavior/defer.zig");39 _ = @import("behavior/defer.zig");
40 _ = @import("behavior/enum.zig");40 _ = @import("behavior/enum.zig");
41 _ = @import("behavior/error.zig");41 _ = @import("behavior/error.zig");
42 _ = @import("behavior/generics.zig");
42 _ = @import("behavior/if.zig");43 _ = @import("behavior/if.zig");
43 _ = @import("behavior/import.zig");44 _ = @import("behavior/import.zig");
44 _ = @import("behavior/incomplete_struct_param_tld.zig");45 _ = @import("behavior/incomplete_struct_param_tld.zig");
45 _ = @import("behavior/inttoptr.zig");46 _ = @import("behavior/inttoptr.zig");
47 _ = @import("behavior/member_func.zig");
48 _ = @import("behavior/null.zig");
46 _ = @import("behavior/pointers.zig");49 _ = @import("behavior/pointers.zig");
47 _ = @import("behavior/ptrcast.zig");50 _ = @import("behavior/ptrcast.zig");
48 _ = @import("behavior/ref_var_in_if_after_if_2nd_switch_prong.zig");51 _ = @import("behavior/ref_var_in_if_after_if_2nd_switch_prong.zig");
52 _ = @import("behavior/struct.zig");
53 _ = @import("behavior/this.zig");
49 _ = @import("behavior/truncate.zig");54 _ = @import("behavior/truncate.zig");
50 _ = @import("behavior/usingnamespace.zig");
51 _ = @import("behavior/underscore.zig");55 _ = @import("behavior/underscore.zig");
56 _ = @import("behavior/usingnamespace.zig");
57 _ = @import("behavior/void.zig");
58 _ = @import("behavior/while.zig");
5259
53 if (!builtin.zig_is_stage2 or builtin.stage2_arch != .wasm32) {60 if (!builtin.zig_is_stage2 or builtin.stage2_arch != .wasm32) {
54 // Tests that pass for stage1, llvm backend, C backend61 // Tests that pass for stage1, llvm backend, C backend
...@@ -56,16 +63,9 @@ test {...@@ -56,16 +63,9 @@ test {
56 _ = @import("behavior/array.zig");63 _ = @import("behavior/array.zig");
57 _ = @import("behavior/cast.zig");64 _ = @import("behavior/cast.zig");
58 _ = @import("behavior/for.zig");65 _ = @import("behavior/for.zig");
59 _ = @import("behavior/generics.zig");
60 _ = @import("behavior/int128.zig");66 _ = @import("behavior/int128.zig");
61 _ = @import("behavior/member_func.zig");
62 _ = @import("behavior/null.zig");
63 _ = @import("behavior/optional.zig");67 _ = @import("behavior/optional.zig");
64 _ = @import("behavior/struct.zig");
65 _ = @import("behavior/this.zig");
66 _ = @import("behavior/translate_c_macros.zig");68 _ = @import("behavior/translate_c_macros.zig");
67 _ = @import("behavior/while.zig");
68 _ = @import("behavior/void.zig");
6969
70 if (builtin.object_format != .c) {70 if (builtin.object_format != .c) {
71 // Tests that pass for stage1 and the llvm backend.71 // Tests that pass for stage1 and the llvm backend.