authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-12-31 21:59:37+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-01-01 12:59:43+01:00
logad1b0409962c77ac414be31dc87b8d599be6d3aa
tree1d99bf91a4e5915fe41fa04c527aad5918563be8
parent28cfc49c3e0f4f15a960d5e19ad30bc003d8a740
signaturelock-open Commit is signed but in an unrecognized format.

wasm: Implement pointer arithmetic and refactoring:

- This implements all pointer arithmetic related instructions such as ptr_add, ptr_sub, ptr_elem_val - We refactored the code, to use `isByRef` to ensure consistancy. - Pointers will now be loaded correctly, rather then being passed around. - The behaviour test for pointers is now passing.

2 files changed, 194 insertions(+), 73 deletions(-)

src/arch/wasm/CodeGen.zig+193-72
...@@ -1088,10 +1088,8 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu...@@ -1088,10 +1088,8 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu
1088 }1088 }
10891089
1090 const ret_ty = fn_ty.fnReturnType();1090 const ret_ty = fn_ty.fnReturnType();
1091 switch (ret_ty.zigTypeTag()) {1091 if (isByRef(ret_ty)) {
1092 .ErrorUnion, .Optional, .Pointer => result.return_value = try self.allocLocal(Type.initTag(.i32)),1092 result.return_value = try self.allocLocal(Type.initTag(.i32));
1093 .Int, .Float, .Bool, .Void, .NoReturn => {},
1094 else => return self.fail("TODO: Implement function return type {}", .{ret_ty}),
1095 }1093 }
10961094
1097 // Check if we store the result as a pointer to the stack rather than1095 // Check if we store the result as a pointer to the stack rather than
...@@ -1204,6 +1202,60 @@ fn memCopy(self: *Self, ty: Type, lhs: WValue, rhs: WValue) !void {...@@ -1204,6 +1202,60 @@ fn memCopy(self: *Self, ty: Type, lhs: WValue, rhs: WValue) !void {
1204 }1202 }
1205}1203}
12061204
1205fn ptrSize(self: *const Self) u16 {
1206 return @divExact(self.target.cpu.arch.ptrBitWidth(), 8);
1207}
1208
1209/// For a given `Type`, will return true when the type will be passed
1210/// by reference, rather than by value.
1211fn isByRef(ty: Type) bool {
1212 switch (ty.zigTypeTag()) {
1213 .Type,
1214 .ComptimeInt,
1215 .ComptimeFloat,
1216 .EnumLiteral,
1217 .Undefined,
1218 .Null,
1219 .BoundFn,
1220 .Opaque,
1221 => unreachable,
1222
1223 .NoReturn,
1224 .Void,
1225 .Bool,
1226 .Int,
1227 .Float,
1228 .ErrorSet,
1229 .Fn,
1230 .Enum,
1231 .Vector,
1232 .AnyFrame,
1233 => return false,
1234
1235 .Array,
1236 .Struct,
1237 .Frame,
1238 .Union,
1239 => return ty.hasCodeGenBits(),
1240 .ErrorUnion => {
1241 const has_tag = ty.errorUnionSet().hasCodeGenBits();
1242 const has_pl = ty.errorUnionPayload().hasCodeGenBits();
1243 if (!has_tag or !has_pl) return false;
1244 return ty.hasCodeGenBits();
1245 },
1246 .Optional => {
1247 if (ty.isPtrLikeOptional()) return false;
1248 var buf: Type.Payload.ElemType = undefined;
1249 return ty.optionalChild(&buf).hasCodeGenBits();
1250 },
1251 .Pointer => {
1252 // Slices act like struct and will be passed by reference
1253 if (ty.isSlice()) return ty.hasCodeGenBits();
1254 return false;
1255 },
1256 }
1257}
1258
1207fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {1259fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
1208 const air_tags = self.air.instructions.items(.tag);1260 const air_tags = self.air.instructions.items(.tag);
1209 return switch (air_tags[inst]) {1261 return switch (air_tags[inst]) {
...@@ -1255,6 +1307,10 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {...@@ -1255,6 +1307,10 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
1255 .optional_payload => self.airOptionalPayload(inst),1307 .optional_payload => self.airOptionalPayload(inst),
1256 .optional_payload_ptr => self.airOptionalPayload(inst),1308 .optional_payload_ptr => self.airOptionalPayload(inst),
1257 .optional_payload_ptr_set => self.airOptionalPayloadPtrSet(inst),1309 .optional_payload_ptr_set => self.airOptionalPayloadPtrSet(inst),
1310 .ptr_add => self.airPtrBinOp(inst, .add),
1311 .ptr_sub => self.airPtrBinOp(inst, .sub),
1312 .ptr_elem_ptr => self.airPtrElemPtr(inst),
1313 .ptr_elem_val => self.airPtrElemVal(inst),
1258 .ptrtoint => self.airPtrToInt(inst),1314 .ptrtoint => self.airPtrToInt(inst),
1259 .ret => self.airRet(inst),1315 .ret => self.airRet(inst),
1260 .ret_ptr => self.airRetPtr(inst),1316 .ret_ptr => self.airRetPtr(inst),
...@@ -1366,38 +1422,19 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1366,38 +1422,19 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
13661422
1367 const arg_ty = self.air.typeOf(arg_ref);1423 const arg_ty = self.air.typeOf(arg_ref);
1368 if (!arg_ty.hasCodeGenBits()) continue;1424 if (!arg_ty.hasCodeGenBits()) continue;
1369 // Passing constant function pointers must be turned into a stack pointer first.1425
1370 // This is because function pointers are stored as function table indexes,1426 // If we need to pass by reference, but the argument is a constant,
1371 // Which means we would try to attempt to load a function pointer's value by reading1427 // we must first lower it before passing it.
1372 // from the table index, rather than an address.1428 if (isByRef(arg_ty) and arg_val == .constant) {
1373 var is_fn_ptr = false;1429 const arg_local = try self.allocStack(arg_ty);
1374 if (arg_val == .constant) {1430 try self.store(arg_local, arg_val, arg_ty, 0);
1375 if (arg_val.constant.val.castTag(.decl_ref)) |decl| {1431 try self.emitWValue(arg_local);
1376 if (decl.data.ty.zigTypeTag() == .Fn) {1432 } else if (arg_val == .none) {
1377 is_fn_ptr = true;1433 // TODO: Remove this branch when zero-sized pointers do not generate
1378 }1434 // an argument.
1379 }1435 try self.addImm32(0);
1380 }1436 } else {
1381 switch (arg_ty.zigTypeTag()) {1437 try self.emitWValue(arg_val);
1382 .Struct, .Pointer, .Optional, .ErrorUnion => {
1383 // single pointer can be passed directly
1384 if ((arg_ty.isSinglePointer() and !is_fn_ptr) or arg_val != .constant) {
1385 if (arg_val == .none) {
1386 // when the argument is a 0-sized value, but the function
1387 // expects a non-zero typed value (such as a slice), we must emit an argument
1388 // as function calls are verified with the function signature in wasm.
1389 // In those cases we will emit a '0xaa' as address, meaning invalid memory.
1390 try self.addImm32(@bitCast(i32, @as(u32, 0xaaaaaaaa)));
1391 continue;
1392 }
1393 try self.emitWValue(arg_val);
1394 continue;
1395 }
1396 const arg_local = try self.allocStack(arg_ty);
1397 try self.store(arg_local, arg_val, arg_ty, 0);
1398 try self.emitWValue(arg_local);
1399 },
1400 else => try self.emitWValue(arg_val),
1401 }1438 }
1402 }1439 }
14031440
...@@ -1408,12 +1445,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1408,12 +1445,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1408 // so load its value onto the stack1445 // so load its value onto the stack
1409 std.debug.assert(ty.zigTypeTag() == .Pointer);1446 std.debug.assert(ty.zigTypeTag() == .Pointer);
1410 const operand = self.resolveInst(pl_op.operand);1447 const operand = self.resolveInst(pl_op.operand);
1411 const offset = switch (operand) {1448 try self.emitWValue(operand);
1412 .local_with_offset => |with_offset| with_offset.offset,
1413 else => @as(u32, 0),
1414 };
1415 const result = try self.load(operand, fn_ty, offset);
1416 try self.addLabel(.local_get, result.local);
14171449
1418 var fn_type = try self.genFunctype(fn_ty);1450 var fn_type = try self.genFunctype(fn_ty);
1419 defer fn_type.deinit(self.gpa);1451 defer fn_type.deinit(self.gpa);
...@@ -1425,8 +1457,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1425,8 +1457,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1425 const ret_ty = fn_ty.fnReturnType();1457 const ret_ty = fn_ty.fnReturnType();
1426 if (!ret_ty.hasCodeGenBits()) return WValue.none;1458 if (!ret_ty.hasCodeGenBits()) return WValue.none;
14271459
1428 // slices are stored on the virtual stack, so we must pull out both ptr and len1460 // TODO: Implement this for all aggregate types
1429 // to not overwrite the stack
1430 if (ret_ty.isSlice()) {1461 if (ret_ty.isSlice()) {
1431 // first load the values onto the regular stack, before we move the stack pointer1462 // first load the values onto the regular stack, before we move the stack pointer
1432 // to prevent overwriting the return value.1463 // to prevent overwriting the return value.
...@@ -1551,7 +1582,7 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro...@@ -1551,7 +1582,7 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro
1551 const val = rhs.constant.val;1582 const val = rhs.constant.val;
1552 const len_local = try self.allocLocal(Type.usize);1583 const len_local = try self.allocLocal(Type.usize);
1553 const ptr_local = try self.allocLocal(Type.usize);1584 const ptr_local = try self.allocLocal(Type.usize);
1554 const len_offset = self.target.cpu.arch.ptrBitWidth() / 8;1585 const len_offset = self.ptrSize();
1555 if (val.castTag(.decl_ref)) |decl| {1586 if (val.castTag(.decl_ref)) |decl| {
1556 // for decl references we also need to retrieve the length and the original decl's pointer1587 // for decl references we also need to retrieve the length and the original decl's pointer
1557 try self.addMemArg(.i32_load, .{ .offset = 0, .alignment = Type.@"usize".abiAlignment(self.target) });1588 try self.addMemArg(.i32_load, .{ .offset = 0, .alignment = Type.@"usize".abiAlignment(self.target) });
...@@ -1611,12 +1642,13 @@ fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1611,12 +1642,13 @@ fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
16111642
1612 if (!ty.hasCodeGenBits()) return WValue{ .none = {} };1643 if (!ty.hasCodeGenBits()) return WValue{ .none = {} };
16131644
1614 return switch (ty.zigTypeTag()) {1645 if (isByRef(ty)) {
1615 .Struct, .ErrorUnion, .Optional, .Pointer => operand, // pass as pointer1646 return operand;
1616 else => switch (operand) {1647 }
1617 .local_with_offset => |with_offset| try self.load(operand, ty, with_offset.offset),1648
1618 else => try self.load(operand, ty, 0),1649 return switch (operand) {
1619 },1650 .local_with_offset => |with_offset| try self.load(operand, ty, with_offset.offset),
1651 else => try self.load(operand, ty, 0),
1620 };1652 };
1621}1653}
16221654
...@@ -1628,7 +1660,6 @@ fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {...@@ -1628,7 +1660,6 @@ fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {
1628 .unsigned1660 .unsigned
1629 else1661 else
1630 .signed;1662 .signed;
1631 // check if we should pass by pointer or value based on ABI size
1632 // TODO: Implement a way to get ABI values from a given type,1663 // TODO: Implement a way to get ABI values from a given type,
1633 // that is portable across the backend, rather than copying logic.1664 // that is portable across the backend, rather than copying logic.
1634 const abi_size = if ((ty.isInt() or ty.isAnyFloat()) and ty.abiSize(self.target) <= 8)1665 const abi_size = if ((ty.isInt() or ty.isAnyFloat()) and ty.abiSize(self.target) <= 8)
...@@ -1779,6 +1810,10 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {...@@ -1779,6 +1810,10 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {
1779 }1810 }
1780 } else if (val.castTag(.int_u64)) |int_ptr| {1811 } else if (val.castTag(.int_u64)) |int_ptr| {
1781 try self.addImm32(@bitCast(i32, @intCast(u32, int_ptr.data)));1812 try self.addImm32(@bitCast(i32, @intCast(u32, int_ptr.data)));
1813 } else if (val.tag() == .zero) {
1814 try self.addImm32(0);
1815 } else if (val.tag() == .one) {
1816 try self.addImm32(1);
1782 } else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{val.tag()});1817 } else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{val.tag()});
1783 },1818 },
1784 .Void => {},1819 .Void => {},
...@@ -2163,12 +2198,8 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -2163,12 +2198,8 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2163 return self.fail("Field type '{}' too big to fit into stack frame", .{field_ty});2198 return self.fail("Field type '{}' too big to fit into stack frame", .{field_ty});
2164 };2199 };
21652200
2166 // TODO: Replace this check with some 'isByRef' function to de-duplicate logic2201 if (isByRef(field_ty)) {
2167 if (field_ty.zigTypeTag() == .Struct) {2202 return WValue{ .local_with_offset = .{ .local = operand.local, .offset = offset } };
2168 return WValue{ .local_with_offset = .{
2169 .local = operand.local,
2170 .offset = offset,
2171 } };
2172 }2203 }
21732204
2174 switch (operand) {2205 switch (operand) {
...@@ -2328,20 +2359,23 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -2328,20 +2359,23 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2328fn airIsErr(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {2359fn airIsErr(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {
2329 const un_op = self.air.instructions.items(.data)[inst].un_op;2360 const un_op = self.air.instructions.items(.data)[inst].un_op;
2330 const operand = self.resolveInst(un_op);2361 const operand = self.resolveInst(un_op);
2331 const err_ty = self.air.typeOf(un_op).errorUnionSet();2362 const err_ty = self.air.typeOf(un_op);
2363 const pl_ty = err_ty.errorUnionPayload();
23322364
2333 // load the error tag value2365 // load the error tag value
2334 try self.emitWValue(operand);2366 try self.emitWValue(operand);
2335 try self.addMemArg(2367 if (pl_ty.hasCodeGenBits()) {
2336 .i32_load16_u,2368 try self.addMemArg(.i32_load16_u, .{
2337 .{ .offset = 0, .alignment = err_ty.abiAlignment(self.target) },2369 .offset = 0,
2338 );2370 .alignment = err_ty.errorUnionSet().abiAlignment(self.target),
2371 });
2372 }
23392373
2340 // Compare the error value with '0'2374 // Compare the error value with '0'
2341 try self.addImm32(0);2375 try self.addImm32(0);
2342 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));2376 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
23432377
2344 const is_err_tmp = try self.allocLocal(err_ty);2378 const is_err_tmp = try self.allocLocal(Type.initTag(.i32)); // result is always an i32
2345 try self.addLabel(.local_set, is_err_tmp.local);2379 try self.addLabel(.local_set, is_err_tmp.local);
2346 return is_err_tmp;2380 return is_err_tmp;
2347}2381}
...@@ -2363,6 +2397,11 @@ fn airUnwrapErrUnionError(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -2363,6 +2397,11 @@ fn airUnwrapErrUnionError(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2363 const ty_op = self.air.instructions.items(.data)[inst].ty_op;2397 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
2364 const operand = self.resolveInst(ty_op.operand);2398 const operand = self.resolveInst(ty_op.operand);
2365 const err_ty = self.air.typeOf(ty_op.operand);2399 const err_ty = self.air.typeOf(ty_op.operand);
2400 const payload_ty = err_ty.errorUnionPayload();
2401 if (!payload_ty.hasCodeGenBits()) {
2402 return operand;
2403 }
2404
2366 return try self.load(operand, err_ty.errorUnionSet(), 0);2405 return try self.load(operand, err_ty.errorUnionSet(), 0);
2367}2406}
23682407
...@@ -2507,9 +2546,8 @@ fn airSliceLen(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -2507,9 +2546,8 @@ fn airSliceLen(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
25072546
2508 const ty_op = self.air.instructions.items(.data)[inst].ty_op;2547 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
2509 const operand = self.resolveInst(ty_op.operand);2548 const operand = self.resolveInst(ty_op.operand);
2510 const pointer_width = self.target.cpu.arch.ptrBitWidth() / 8;
25112549
2512 return try self.load(operand, Type.usize, pointer_width);2550 return try self.load(operand, Type.usize, self.ptrSize());
2513}2551}
25142552
2515fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {2553fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
...@@ -2534,12 +2572,11 @@ fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -2534,12 +2572,11 @@ fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
25342572
2535 const result = try self.allocLocal(elem_ty);2573 const result = try self.allocLocal(elem_ty);
2536 try self.addLabel(.local_set, result.local);2574 try self.addLabel(.local_set, result.local);
2537 return switch (elem_ty.zigTypeTag()) {2575
2538 // pass as pointer2576 if (isByRef(elem_ty)) {
2539 .Pointer, .Struct, .Optional => result,2577 return result;
2540 // pass by value2578 }
2541 else => try self.load(result, elem_ty, 0),2579 return try self.load(result, elem_ty, 0);
2542 };
2543}2580}
25442581
2545fn airSliceElemPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {2582fn airSliceElemPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
...@@ -2670,3 +2707,87 @@ fn airPtrToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -2670,3 +2707,87 @@ fn airPtrToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2670 const un_op = self.air.instructions.items(.data)[inst].un_op;2707 const un_op = self.air.instructions.items(.data)[inst].un_op;
2671 return self.resolveInst(un_op);2708 return self.resolveInst(un_op);
2672}2709}
2710
2711fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2712 if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
2713
2714 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
2715 const ptr_ty = self.air.typeOf(bin_op.lhs);
2716 const pointer = self.resolveInst(bin_op.lhs);
2717 const index = self.resolveInst(bin_op.rhs);
2718 const elem_ty = ptr_ty.childType();
2719 const elem_size = elem_ty.abiSize(self.target);
2720
2721 // load pointer onto the stack
2722 if (ptr_ty.isSlice()) {
2723 const ptr_local = try self.load(pointer, ptr_ty, 0);
2724 try self.addLabel(.local_get, ptr_local.local);
2725 } else {
2726 try self.emitWValue(pointer);
2727 }
2728
2729 // calculate index into slice
2730 try self.emitWValue(index);
2731 try self.addImm32(@bitCast(i32, @intCast(u32, elem_size)));
2732 try self.addTag(.i32_mul);
2733 try self.addTag(.i32_add);
2734
2735 const result = try self.allocLocal(elem_ty);
2736 try self.addLabel(.local_set, result.local);
2737 if (isByRef(elem_ty)) {
2738 return result;
2739 }
2740 return try self.load(result, elem_ty, 0);
2741}
2742
2743fn airPtrElemPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2744 if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
2745 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
2746 const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
2747 const ptr_ty = self.air.typeOf(bin_op.lhs);
2748 const elem_ty = self.air.getRefType(ty_pl.ty).childType();
2749 const elem_size = elem_ty.abiSize(self.target);
2750
2751 const ptr = self.resolveInst(bin_op.lhs);
2752 const index = self.resolveInst(bin_op.rhs);
2753
2754 // load pointer onto the stack
2755 if (ptr_ty.isSlice()) {
2756 const ptr_local = try self.load(ptr, ptr_ty, 0);
2757 try self.addLabel(.local_get, ptr_local.local);
2758 } else {
2759 try self.emitWValue(ptr);
2760 }
2761
2762 // calculate index into ptr
2763 try self.emitWValue(index);
2764 try self.addImm32(@bitCast(i32, @intCast(u32, elem_size)));
2765 try self.addTag(.i32_mul);
2766 try self.addTag(.i32_add);
2767
2768 const result = try self.allocLocal(Type.initTag(.i32));
2769 try self.addLabel(.local_set, result.local);
2770 return result;
2771}
2772
2773fn airPtrBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
2774 if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
2775 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
2776 const ptr = self.resolveInst(bin_op.lhs);
2777 const offset = self.resolveInst(bin_op.rhs);
2778 const pointee_ty = self.air.typeOf(bin_op.lhs).childType();
2779
2780 const valtype = try self.typeToValtype(Type.usize);
2781 const mul_opcode = buildOpcode(.{ .valtype1 = valtype, .op = .mul });
2782 const bin_opcode = buildOpcode(.{ .valtype1 = valtype, .op = op });
2783
2784 try self.emitWValue(ptr);
2785 try self.emitWValue(offset);
2786 try self.addImm32(@bitCast(i32, @intCast(u32, pointee_ty.abiSize(self.target))));
2787 try self.addTag(Mir.Inst.Tag.fromOpcode(mul_opcode));
2788 try self.addTag(Mir.Inst.Tag.fromOpcode(bin_opcode));
2789
2790 const result = try self.allocLocal(Type.usize);
2791 try self.addLabel(.local_set, result.local);
2792 return result;
2793}
test/behavior.zig+1-1
...@@ -33,6 +33,7 @@ test {...@@ -33,6 +33,7 @@ test {
33 _ = @import("behavior/import.zig");33 _ = @import("behavior/import.zig");
34 _ = @import("behavior/incomplete_struct_param_tld.zig");34 _ = @import("behavior/incomplete_struct_param_tld.zig");
35 _ = @import("behavior/inttoptr.zig");35 _ = @import("behavior/inttoptr.zig");
36 _ = @import("behavior/pointers.zig");
36 _ = @import("behavior/ptrcast.zig");37 _ = @import("behavior/ptrcast.zig");
37 _ = @import("behavior/pub_enum.zig");38 _ = @import("behavior/pub_enum.zig");
38 _ = @import("behavior/ref_var_in_if_after_if_2nd_switch_prong.zig");39 _ = @import("behavior/ref_var_in_if_after_if_2nd_switch_prong.zig");
...@@ -66,7 +67,6 @@ test {...@@ -66,7 +67,6 @@ test {
66 _ = @import("behavior/member_func.zig");67 _ = @import("behavior/member_func.zig");
67 _ = @import("behavior/null.zig");68 _ = @import("behavior/null.zig");
68 _ = @import("behavior/optional.zig");69 _ = @import("behavior/optional.zig");
69 _ = @import("behavior/pointers.zig");
70 _ = @import("behavior/struct.zig");70 _ = @import("behavior/struct.zig");
71 _ = @import("behavior/this.zig");71 _ = @import("behavior/this.zig");
72 _ = @import("behavior/translate_c_macros.zig");72 _ = @import("behavior/translate_c_macros.zig");