authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-07-27 21:04:58+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-08-11 11:08:01+02:00
logb42ba7c3d411cde31ede290b3915150c3e8acfbb
treee30ba3182d14b7936c5245b543799ce64cb10362
parenta5e4fd7ef66bbd16ddad1a12d378eefcb740da1d
signaturelock-open Commit is signed but in an unrecognized format.

wasm: free unused locals

When a local is no longer referenced or used, free it so the local can be re-used by another instruction. This means we generate less locals. Freeing this local is a manual action and must only be used on temporaries or where we are sure the local is not referenced by a different AIR instruction, as that creates UB. We now also no longer store a `WValue` when its tag is set to `none` as those may never be referenced by any AIR instruction. An assertion is done to make sure we never store a reference to a `stack` value in our resolved instructions.

1 files changed, 151 insertions(+), 86 deletions(-)

src/arch/wasm/CodeGen.zig+151-86
......@@ -77,17 +77,34 @@ const WValue = union(enum) {
7777 /// Promotes a `WValue` to a local when given value is on top of the stack.
7878 /// When encountering a `local` or `stack_offset` this is essentially a no-op.
7979 /// All other tags are illegal.
80 fn toLocal(self: WValue, gen: *Self, ty: Type) InnerError!WValue {
81 switch (self) {
80 fn toLocal(value: WValue, gen: *Self, ty: Type) InnerError!WValue {
81 switch (value) {
8282 .stack => {
8383 const local = try gen.allocLocal(ty);
8484 try gen.addLabel(.local_set, local.local);
8585 return local;
8686 },
87 .local, .stack_offset => return self,
87 .local, .stack_offset => return value,
8888 else => unreachable,
8989 }
9090 }
91
92 /// Marks a local as no longer being referenced and essentially allows
93 /// us to re-use it somewhere else within the function.
94 /// The valtype of the local is deducted by using the index of the given.
95 fn free(value: *WValue, gen: *Self) void {
96 if (value.* != .local) return;
97 const local_value = value.local;
98 const index = local_value - gen.args.len - @boolToInt(gen.return_value != .none);
99 const valtype = @intToEnum(wasm.Valtype, gen.locals.items[index]);
100 switch (valtype) {
101 .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
102 .i64 => gen.free_locals_i64.append(gen.gpa, local_value) catch return,
103 .f32 => gen.free_locals_f32.append(gen.gpa, local_value) catch return,
104 .f64 => gen.free_locals_f64.append(gen.gpa, local_value) catch return,
105 }
106 value.* = WValue{ .none = {} };
107 }
91108};
92109
93110/// Wasm ops, but without input/output/signedness information
......@@ -829,27 +846,18 @@ fn allocLocal(self: *Self, ty: Type) InnerError!WValue {
829846 },
830847 }
831848 // no local was free to be re-used, so allocate a new local instead
832 try self.locals.append(self.gpa, wasm.valtype(valtype));
849 return self.ensureAllocLocal(ty);
850}
851
852/// Ensures a new local will be created. This is useful when it's useful
853/// to use a zero-initialized local.
854fn ensureAllocLocal(self: *Self, ty: Type) InnerError!WValue {
855 try self.locals.append(self.gpa, genValtype(ty, self.target));
833856 const initial_index = self.local_index;
834857 self.local_index += 1;
835858 return WValue{ .local = initial_index };
836859}
837860
838/// Marks a local as no longer being referenced and essentially allows
839/// us to re-use it somewhere else within the function.
840/// The valtype of the local is deducted by using the index of the given.
841/// Asserts given `WValue` is a `local`.
842fn freeLocal(self: *Self, value: WValue) InnerError!WValue {
843 const index = value.local;
844 const valtype = wasm.valtype(self.locals.items[index]);
845 switch (valtype) {
846 .i32 => self.free_locals_i32.append(index) catch {}, // It's ok to fail any of those, a new local can be allocated instead
847 .i64 => self.free_locals_i64.append(index) catch {},
848 .f32 => self.free_locals_f32.append(index) catch {},
849 .f64 => self.free_locals_f64.append(index) catch {},
850 }
851}
852
853861/// Generates a `wasm.Type` from a given function type.
854862/// Memory is owned by the caller.
855863fn genFunctype(gpa: Allocator, cc: std.builtin.CallingConvention, params: []const Type, return_type: Type, target: std.Target) !wasm.Type {
......@@ -1197,9 +1205,9 @@ fn initializeStack(self: *Self) !void {
11971205 // Reserve a local to store the current stack pointer
11981206 // We can later use this local to set the stack pointer back to the value
11991207 // we have stored here.
1200 self.initial_stack_value = try self.allocLocal(Type.usize);
1208 self.initial_stack_value = try self.ensureAllocLocal(Type.usize);
12011209 // Also reserve a local to store the bottom stack value
1202 self.bottom_stack_value = try self.allocLocal(Type.usize);
1210 self.bottom_stack_value = try self.ensureAllocLocal(Type.usize);
12031211}
12041212
12051213/// Reads the stack pointer from `Context.initial_stack_value` and writes it
......@@ -1330,7 +1338,9 @@ fn memcpy(self: *Self, dst: WValue, src: WValue, len: WValue) !void {
13301338 else => {
13311339 // TODO: We should probably lower this to a call to compiler_rt
13321340 // But for now, we implement it manually
1333 const offset = try self.allocLocal(Type.usize); // local for counter
1341 var offset = try self.ensureAllocLocal(Type.usize); // local for counter
1342 defer offset.free(self);
1343
13341344 // outer block to jump to when loop is done
13351345 try self.startBlock(.block, wasm.block_empty);
13361346 try self.startBlock(.loop, wasm.block_empty);
......@@ -1467,7 +1477,7 @@ fn buildPointerOffset(self: *Self, ptr_value: WValue, offset: u64, action: enum
14671477 // do not perform arithmetic when offset is 0.
14681478 if (offset == 0 and ptr_value.offset() == 0 and action == .modify) return ptr_value;
14691479 const result_ptr: WValue = switch (action) {
1470 .new => try self.allocLocal(Type.usize),
1480 .new => try self.ensureAllocLocal(Type.usize),
14711481 .modify => ptr_value,
14721482 };
14731483 try self.emitWValue(ptr_value);
......@@ -1715,7 +1725,10 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
17151725fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
17161726 for (body) |inst| {
17171727 const result = try self.genInst(inst);
1718 try self.values.putNoClobber(self.gpa, Air.indexToRef(inst), result);
1728 if (result != .none) {
1729 assert(result != .stack); // not allowed to store stack values as we cannot keep track of where they are on the stack
1730 try self.values.putNoClobber(self.gpa, Air.indexToRef(inst), result);
1731 }
17191732 }
17201733}
17211734
......@@ -2151,9 +2164,12 @@ fn binOpBigInt(self: *Self, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerErr
21512164 }
21522165
21532166 const result = try self.allocStack(ty);
2154 const lhs_high_bit = try (try self.load(lhs, Type.u64, 0)).toLocal(self, Type.u64);
2155 const rhs_high_bit = try (try self.load(rhs, Type.u64, 0)).toLocal(self, Type.u64);
2156 const high_op_res = try (try self.binOp(lhs_high_bit, rhs_high_bit, Type.u64, op)).toLocal(self, Type.u64);
2167 var lhs_high_bit = try (try self.load(lhs, Type.u64, 0)).toLocal(self, Type.u64);
2168 defer lhs_high_bit.free(self);
2169 var rhs_high_bit = try (try self.load(rhs, Type.u64, 0)).toLocal(self, Type.u64);
2170 defer rhs_high_bit.free(self);
2171 var high_op_res = try (try self.binOp(lhs_high_bit, rhs_high_bit, Type.u64, op)).toLocal(self, Type.u64);
2172 defer high_op_res.free(self);
21572173
21582174 const lhs_low_bit = try self.load(lhs, Type.u64, 8);
21592175 const rhs_low_bit = try self.load(rhs, Type.u64, 8);
......@@ -2165,7 +2181,8 @@ fn binOpBigInt(self: *Self, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerErr
21652181 break :blk try self.cmp(lhs_high_bit, rhs_high_bit, Type.u64, .lt);
21662182 } else unreachable;
21672183 const tmp = try self.intcast(lt, Type.u32, Type.u64);
2168 const tmp_op = try (try self.binOp(low_op_res, tmp, Type.u64, op)).toLocal(self, Type.u64);
2184 var tmp_op = try (try self.binOp(low_op_res, tmp, Type.u64, op)).toLocal(self, Type.u64);
2185 defer tmp_op.free(self);
21692186
21702187 try self.store(result, high_op_res, Type.u64, 0);
21712188 try self.store(result, tmp_op, Type.u64, 8);
......@@ -3208,25 +3225,22 @@ fn intcast(self: *Self, operand: WValue, given: Type, wanted: Type) InnerError!W
32083225 } else if (wanted_bits == 128) {
32093226 // for 128bit integers we store the integer in the virtual stack, rather than a local
32103227 const stack_ptr = try self.allocStack(wanted);
3228 try self.emitWValue(stack_ptr);
32113229
32123230 // for 32 bit integers, we first coerce the value into a 64 bit integer before storing it
32133231 // meaning less store operations are required.
32143232 const lhs = if (op_bits == 32) blk: {
3215 const tmp = try self.intcast(
3216 operand,
3217 given,
3218 if (wanted.isSignedInt()) Type.i64 else Type.u64,
3219 );
3220 break :blk try tmp.toLocal(self, Type.u64);
3233 break :blk try self.intcast(operand, given, if (wanted.isSignedInt()) Type.i64 else Type.u64);
32213234 } else operand;
32223235
32233236 // store msb first
3224 try self.store(stack_ptr, lhs, Type.u64, 0);
3237 try self.store(.{ .stack = {} }, lhs, Type.u64, 0 + stack_ptr.offset());
32253238
32263239 // For signed integers we shift msb by 63 (64bit integer - 1 sign bit) and store remaining value
32273240 if (wanted.isSignedInt()) {
3228 const shr = try (try self.binOp(lhs, .{ .imm64 = 63 }, Type.i64, .shr)).toLocal(self, Type.i64);
3229 try self.store(stack_ptr, shr, Type.u64, 8);
3241 try self.emitWValue(stack_ptr);
3242 const shr = try self.binOp(lhs, .{ .imm64 = 63 }, Type.i64, .shr);
3243 try self.store(.{ .stack = {} }, shr, Type.u64, 8 + stack_ptr.offset());
32303244 } else {
32313245 // Ensure memory of lsb is zero'd
32323246 try self.store(stack_ptr, .{ .imm64 = 0 }, Type.u64, 8);
......@@ -3534,11 +3548,12 @@ fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
35343548 try self.addTag(.i32_mul);
35353549 try self.addTag(.i32_add);
35363550
3537 const result = try self.allocLocal(elem_ty);
3551 var result = try self.allocLocal(elem_ty);
35383552 try self.addLabel(.local_set, result.local);
35393553 if (isByRef(elem_ty, self.target)) {
35403554 return result;
35413555 }
3556 defer result.free(self); // only free if it's not returned like above
35423557
35433558 const elem_val = try self.load(result, elem_ty, 0);
35443559 return elem_val.toLocal(self, elem_ty);
......@@ -3656,7 +3671,7 @@ fn memset(self: *Self, ptr: WValue, len: WValue, value: WValue) InnerError!void
36563671 else => {
36573672 // TODO: We should probably lower this to a call to compiler_rt
36583673 // But for now, we implement it manually
3659 const offset = try self.allocLocal(Type.usize); // local for counter
3674 const offset = try self.ensureAllocLocal(Type.usize); // local for counter
36603675 // outer block to jump to when loop is done
36613676 try self.startBlock(.block, wasm.block_empty);
36623677 try self.startBlock(.loop, wasm.block_empty);
......@@ -3713,12 +3728,14 @@ fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
37133728 try self.addTag(.i32_mul);
37143729 try self.addTag(.i32_add);
37153730
3716 const result = try self.allocLocal(Type.usize);
3731 var result = try self.allocLocal(Type.usize);
37173732 try self.addLabel(.local_set, result.local);
37183733
37193734 if (isByRef(elem_ty, self.target)) {
37203735 return result;
37213736 }
3737 defer result.free(self); // only free if no longer needed and not returned like above
3738
37223739 const elem_val = try self.load(result, elem_ty, 0);
37233740 return elem_val.toLocal(self, elem_ty);
37243741}
......@@ -3944,7 +3961,8 @@ fn cmpOptionals(self: *Self, lhs: WValue, rhs: WValue, operand_ty: Type, op: std
39443961
39453962 // We store the final result in here that will be validated
39463963 // if the optional is truly equal.
3947 const result = try self.allocLocal(Type.initTag(.i32));
3964 var result = try self.ensureAllocLocal(Type.initTag(.i32));
3965 defer result.free(self);
39483966
39493967 try self.startBlock(.block, wasm.block_empty);
39503968 _ = try self.isNull(lhs, operand_ty, .i32_eq);
......@@ -3965,8 +3983,6 @@ fn cmpOptionals(self: *Self, lhs: WValue, rhs: WValue, operand_ty: Type, op: std
39653983 try self.emitWValue(result);
39663984 try self.addImm32(0);
39673985 try self.addTag(if (op == .eq) .i32_ne else .i32_eq);
3968 try self.addLabel(.local_set, result.local);
3969 try self.emitWValue(result);
39703986 return WValue{ .stack = {} };
39713987}
39723988
......@@ -3980,8 +3996,10 @@ fn cmpBigInt(self: *Self, lhs: WValue, rhs: WValue, operand_ty: Type, op: std.ma
39803996 return self.fail("TODO: Support cmpBigInt for integer bitsize: '{d}'", .{operand_ty.intInfo(self.target).bits});
39813997 }
39823998
3983 const lhs_high_bit = try (try self.load(lhs, Type.u64, 0)).toLocal(self, Type.u64);
3984 const rhs_high_bit = try (try self.load(rhs, Type.u64, 0)).toLocal(self, Type.u64);
3999 var lhs_high_bit = try (try self.load(lhs, Type.u64, 0)).toLocal(self, Type.u64);
4000 defer lhs_high_bit.free(self);
4001 var rhs_high_bit = try (try self.load(rhs, Type.u64, 0)).toLocal(self, Type.u64);
4002 defer rhs_high_bit.free(self);
39854003
39864004 switch (op) {
39874005 .eq, .neq => {
......@@ -4313,17 +4331,19 @@ fn airAddSubWithOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!W
43134331
43144332 // for signed integers, we first apply signed shifts by the difference in bits
43154333 // to get the signed value, as we store it internally as 2's complement.
4316 const lhs = if (wasm_bits != int_info.bits and is_signed) blk: {
4334 var lhs = if (wasm_bits != int_info.bits and is_signed) blk: {
43174335 break :blk try (try self.signAbsValue(lhs_op, lhs_ty)).toLocal(self, lhs_ty);
43184336 } else lhs_op;
4319 const rhs = if (wasm_bits != int_info.bits and is_signed) blk: {
4337 var rhs = if (wasm_bits != int_info.bits and is_signed) blk: {
43204338 break :blk try (try self.signAbsValue(rhs_op, lhs_ty)).toLocal(self, lhs_ty);
43214339 } else rhs_op;
43224340
4323 const bin_op = try (try self.binOp(lhs, rhs, lhs_ty, op)).toLocal(self, lhs_ty);
4324 const result = if (wasm_bits != int_info.bits) blk: {
4341 var bin_op = try (try self.binOp(lhs, rhs, lhs_ty, op)).toLocal(self, lhs_ty);
4342 defer bin_op.free(self);
4343 var result = if (wasm_bits != int_info.bits) blk: {
43254344 break :blk try (try self.wrapOperand(bin_op, lhs_ty)).toLocal(self, lhs_ty);
43264345 } else bin_op;
4346 defer result.free(self); // no-op when wasm_bits == int_info.bits
43274347
43284348 const cmp_op: std.math.CompareOperator = if (op == .sub) .gt else .lt;
43294349 const overflow_bit: WValue = if (is_signed) blk: {
......@@ -4338,13 +4358,23 @@ fn airAddSubWithOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!W
43384358 try self.cmp(bin_op, lhs, lhs_ty, cmp_op)
43394359 else
43404360 try self.cmp(bin_op, result, lhs_ty, .neq);
4341 const overflow_local = try overflow_bit.toLocal(self, Type.u32);
4361 var overflow_local = try overflow_bit.toLocal(self, Type.u32);
4362 defer overflow_local.free(self);
43424363
43434364 const result_ptr = try self.allocStack(self.air.typeOfIndex(inst));
43444365 try self.store(result_ptr, result, lhs_ty, 0);
43454366 const offset = @intCast(u32, lhs_ty.abiSize(self.target));
43464367 try self.store(result_ptr, overflow_local, Type.initTag(.u1), offset);
43474368
4369 // in this case, we performed a signAbsValue which created a temporary local
4370 // so let's free this so it can be re-used instead.
4371 // In the other case we do not want to free it, because that would free the
4372 // resolved instructions which may be referenced by other instructions.
4373 if (wasm_bits != int_info.bits and is_signed) {
4374 lhs.free(self);
4375 rhs.free(self);
4376 }
4377
43484378 return result_ptr;
43494379}
43504380
......@@ -4356,21 +4386,30 @@ fn airAddSubWithOverflowBigInt(self: *Self, lhs: WValue, rhs: WValue, ty: Type,
43564386 return self.fail("TODO: Implement @{{add/sub}}WithOverflow for integer bitsize '{d}'", .{int_info.bits});
43574387 }
43584388
4359 const lhs_high_bit = try (try self.load(lhs, Type.u64, 0)).toLocal(self, Type.u64);
4360 const lhs_low_bit = try (try self.load(lhs, Type.u64, 8)).toLocal(self, Type.u64);
4361 const rhs_high_bit = try (try self.load(rhs, Type.u64, 0)).toLocal(self, Type.u64);
4362 const rhs_low_bit = try (try self.load(rhs, Type.u64, 8)).toLocal(self, Type.u64);
4389 var lhs_high_bit = try (try self.load(lhs, Type.u64, 0)).toLocal(self, Type.u64);
4390 defer lhs_high_bit.free(self);
4391 var lhs_low_bit = try (try self.load(lhs, Type.u64, 8)).toLocal(self, Type.u64);
4392 defer lhs_low_bit.free(self);
4393 var rhs_high_bit = try (try self.load(rhs, Type.u64, 0)).toLocal(self, Type.u64);
4394 defer rhs_high_bit.free(self);
4395 var rhs_low_bit = try (try self.load(rhs, Type.u64, 8)).toLocal(self, Type.u64);
4396 defer rhs_low_bit.free(self);
43634397
4364 const low_op_res = try (try self.binOp(lhs_low_bit, rhs_low_bit, Type.u64, op)).toLocal(self, Type.u64);
4365 const high_op_res = try (try self.binOp(lhs_high_bit, rhs_high_bit, Type.u64, op)).toLocal(self, Type.u64);
4398 var low_op_res = try (try self.binOp(lhs_low_bit, rhs_low_bit, Type.u64, op)).toLocal(self, Type.u64);
4399 defer low_op_res.free(self);
4400 var high_op_res = try (try self.binOp(lhs_high_bit, rhs_high_bit, Type.u64, op)).toLocal(self, Type.u64);
4401 defer high_op_res.free(self);
43664402
4367 const lt = if (op == .add) blk: {
4403 var lt = if (op == .add) blk: {
43684404 break :blk try (try self.cmp(high_op_res, lhs_high_bit, Type.u64, .lt)).toLocal(self, Type.u32);
43694405 } else if (op == .sub) blk: {
43704406 break :blk try (try self.cmp(lhs_high_bit, rhs_high_bit, Type.u64, .lt)).toLocal(self, Type.u32);
43714407 } else unreachable;
4372 const tmp = try (try self.intcast(lt, Type.u32, Type.u64)).toLocal(self, Type.u64);
4373 const tmp_op = try (try self.binOp(low_op_res, tmp, Type.u64, op)).toLocal(self, Type.u64);
4408 defer lt.free(self);
4409 var tmp = try (try self.intcast(lt, Type.u32, Type.u64)).toLocal(self, Type.u64);
4410 defer tmp.free(self);
4411 var tmp_op = try (try self.binOp(low_op_res, tmp, Type.u64, op)).toLocal(self, Type.u64);
4412 defer tmp_op.free(self);
43744413
43754414 const overflow_bit = if (is_signed) blk: {
43764415 const xor_low = try self.binOp(lhs_low_bit, rhs_low_bit, Type.u64, .xor);
......@@ -4392,7 +4431,8 @@ fn airAddSubWithOverflowBigInt(self: *Self, lhs: WValue, rhs: WValue, ty: Type,
43924431
43934432 break :blk WValue{ .stack = {} };
43944433 };
4395 const overflow_local = try overflow_bit.toLocal(self, Type.initTag(.u1));
4434 var overflow_local = try overflow_bit.toLocal(self, Type.initTag(.u1));
4435 defer overflow_local.free(self);
43964436
43974437 const result_ptr = try self.allocStack(result_ty);
43984438 try self.store(result_ptr, high_op_res, Type.u64, 0);
......@@ -4419,10 +4459,12 @@ fn airShlWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
44194459 return self.fail("TODO: Implement shl_with_overflow for integer bitsize: {d}", .{int_info.bits});
44204460 };
44214461
4422 const shl = try (try self.binOp(lhs, rhs, lhs_ty, .shl)).toLocal(self, lhs_ty);
4423 const result = if (wasm_bits != int_info.bits) blk: {
4462 var shl = try (try self.binOp(lhs, rhs, lhs_ty, .shl)).toLocal(self, lhs_ty);
4463 defer shl.free(self);
4464 var result = if (wasm_bits != int_info.bits) blk: {
44244465 break :blk try (try self.wrapOperand(shl, lhs_ty)).toLocal(self, lhs_ty);
44254466 } else shl;
4467 defer result.free(self); // it's a no-op to free the same local twice (when wasm_bits == int_info.bits)
44264468
44274469 const overflow_bit = if (wasm_bits != int_info.bits and is_signed) blk: {
44284470 // emit lhs to stack to we can keep 'wrapped' on the stack also
......@@ -4431,10 +4473,12 @@ fn airShlWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
44314473 const wrapped = try self.wrapBinOp(abs, rhs, lhs_ty, .shr);
44324474 break :blk try self.cmp(.{ .stack = {} }, wrapped, lhs_ty, .neq);
44334475 } else blk: {
4434 const shr = try (try self.binOp(result, rhs, lhs_ty, .shr)).toLocal(self, lhs_ty);
4435 break :blk try self.cmp(lhs, shr, lhs_ty, .neq);
4476 try self.emitWValue(lhs);
4477 const shr = try self.binOp(result, rhs, lhs_ty, .shr);
4478 break :blk try self.cmp(.{ .stack = {} }, shr, lhs_ty, .neq);
44364479 };
4437 const overflow_local = try overflow_bit.toLocal(self, Type.initTag(.u1));
4480 var overflow_local = try overflow_bit.toLocal(self, Type.initTag(.u1));
4481 defer overflow_local.free(self);
44384482
44394483 const result_ptr = try self.allocStack(self.air.typeOfIndex(inst));
44404484 try self.store(result_ptr, result, lhs_ty, 0);
......@@ -4457,7 +4501,9 @@ fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
44574501
44584502 // We store the bit if it's overflowed or not in this. As it's zero-initialized
44594503 // we only need to update it if an overflow (or underflow) occurred.
4460 const overflow_bit = try self.allocLocal(Type.initTag(.u1));
4504 var overflow_bit = try self.ensureAllocLocal(Type.initTag(.u1));
4505 defer overflow_bit.free(self);
4506
44614507 const int_info = lhs_ty.intInfo(self.target);
44624508 const wasm_bits = toWasmBits(int_info.bits) orelse {
44634509 return self.fail("TODO: Implement overflow arithmetic for integer bitsize: {d}", .{int_info.bits});
......@@ -4487,7 +4533,8 @@ fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
44874533 break :blk try self.intcast(bin_op, new_ty, lhs_ty);
44884534 } else {
44894535 const down_cast = try (try self.intcast(bin_op, new_ty, lhs_ty)).toLocal(self, lhs_ty);
4490 const shr = try (try self.binOp(down_cast, .{ .imm32 = int_info.bits - 1 }, lhs_ty, .shr)).toLocal(self, lhs_ty);
4536 var shr = try (try self.binOp(down_cast, .{ .imm32 = int_info.bits - 1 }, lhs_ty, .shr)).toLocal(self, lhs_ty);
4537 defer shr.free(self);
44914538
44924539 const shr_res = try self.binOp(bin_op, .{ .imm64 = int_info.bits }, new_ty, .shr);
44934540 const down_shr_res = try self.intcast(shr_res, new_ty, lhs_ty);
......@@ -4504,7 +4551,8 @@ fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
45044551 try self.addLabel(.local_set, overflow_bit.local);
45054552 break :blk try self.wrapOperand(bin_op, lhs_ty);
45064553 } else blk: {
4507 const bin_op = try (try self.binOp(lhs, rhs, lhs_ty, .mul)).toLocal(self, lhs_ty);
4554 var bin_op = try (try self.binOp(lhs, rhs, lhs_ty, .mul)).toLocal(self, lhs_ty);
4555 defer bin_op.free(self);
45084556 const shift_imm = if (wasm_bits == 32)
45094557 WValue{ .imm32 = int_info.bits }
45104558 else
......@@ -4514,7 +4562,8 @@ fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
45144562 try self.addLabel(.local_set, overflow_bit.local);
45154563 break :blk try self.wrapOperand(bin_op, lhs_ty);
45164564 };
4517 const bin_op_local = try bin_op.toLocal(self, lhs_ty);
4565 var bin_op_local = try bin_op.toLocal(self, lhs_ty);
4566 defer bin_op_local.free(self);
45184567
45194568 const result_ptr = try self.allocStack(self.air.typeOfIndex(inst));
45204569 try self.store(result_ptr, bin_op_local, lhs_ty, 0);
......@@ -4572,12 +4621,13 @@ fn airMulAdd(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
45724621 const lhs_ext = try self.fpext(lhs, ty, Type.f32);
45734622 const addend_ext = try self.fpext(addend, ty, Type.f32);
45744623 // call to compiler-rt `fn fmaf(f32, f32, f32) f32`
4575 const result = try self.callIntrinsic(
4624 var result = try self.callIntrinsic(
45764625 "fmaf",
45774626 &.{ Type.f32, Type.f32, Type.f32 },
45784627 Type.f32,
45794628 &.{ rhs_ext, lhs_ext, addend_ext },
45804629 );
4630 defer result.free(self);
45814631 return try (try self.fptrunc(result, Type.f32, ty)).toLocal(self, ty);
45824632 }
45834633
......@@ -4611,7 +4661,8 @@ fn airClz(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
46114661 try self.addTag(.i32_wrap_i64);
46124662 },
46134663 128 => {
4614 const lsb = try (try self.load(operand, Type.u64, 8)).toLocal(self, Type.u64);
4664 var lsb = try (try self.load(operand, Type.u64, 8)).toLocal(self, Type.u64);
4665 defer lsb.free(self);
46154666
46164667 try self.emitWValue(lsb);
46174668 try self.addTag(.i64_clz);
......@@ -4671,7 +4722,8 @@ fn airCtz(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
46714722 try self.addTag(.i32_wrap_i64);
46724723 },
46734724 128 => {
4674 const msb = try (try self.load(operand, Type.u64, 0)).toLocal(self, Type.u64);
4725 var msb = try (try self.load(operand, Type.u64, 0)).toLocal(self, Type.u64);
4726 defer msb.free(self);
46754727
46764728 try self.emitWValue(msb);
46774729 try self.addTag(.i64_ctz);
......@@ -4847,7 +4899,8 @@ fn airByteSwap(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
48474899 return (try self.binOp(lhs, res, ty, .@"or")).toLocal(self, ty);
48484900 },
48494901 24 => {
4850 const msb = try (try self.wrapOperand(operand, Type.u16)).toLocal(self, Type.u16);
4902 var msb = try (try self.wrapOperand(operand, Type.u16)).toLocal(self, Type.u16);
4903 defer msb.free(self);
48514904
48524905 const shl_res = try self.binOp(msb, .{ .imm32 = 8 }, Type.u16, .shl);
48534906 const lhs = try self.binOp(shl_res, .{ .imm32 = 0xFF0000 }, Type.u16, .@"and");
......@@ -4867,10 +4920,13 @@ fn airByteSwap(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
48674920 },
48684921 32 => {
48694922 const shl_tmp = try self.binOp(operand, .{ .imm32 = 8 }, ty, .shl);
4870 const lhs = try (try self.binOp(shl_tmp, .{ .imm32 = 0xFF00FF00 }, ty, .@"and")).toLocal(self, ty);
4923 var lhs = try (try self.binOp(shl_tmp, .{ .imm32 = 0xFF00FF00 }, ty, .@"and")).toLocal(self, ty);
4924 defer lhs.free(self);
48714925 const shr_tmp = try self.binOp(operand, .{ .imm32 = 8 }, ty, .shr);
4872 const rhs = try (try self.binOp(shr_tmp, .{ .imm32 = 0xFF00FF }, ty, .@"and")).toLocal(self, ty);
4873 const tmp_or = try (try self.binOp(lhs, rhs, ty, .@"or")).toLocal(self, ty);
4926 var rhs = try (try self.binOp(shr_tmp, .{ .imm32 = 0xFF00FF }, ty, .@"and")).toLocal(self, ty);
4927 defer rhs.free(self);
4928 var tmp_or = try (try self.binOp(lhs, rhs, ty, .@"or")).toLocal(self, ty);
4929 defer tmp_or.free(self);
48744930
48754931 const shl = try self.binOp(tmp_or, .{ .imm32 = 16 }, ty, .shl);
48764932 const shr = try self.binOp(tmp_or, .{ .imm32 = 16 }, ty, .shr);
......@@ -5097,7 +5153,8 @@ fn airSatBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
50975153 }
50985154
50995155 const wasm_bits = toWasmBits(int_info.bits).?;
5100 const bin_result = try (try self.binOp(lhs, rhs, ty, op)).toLocal(self, ty);
5156 var bin_result = try (try self.binOp(lhs, rhs, ty, op)).toLocal(self, ty);
5157 defer bin_result.free(self);
51015158 if (wasm_bits != int_info.bits and op == .add) {
51025159 const val: u64 = @intCast(u64, (@as(u65, 1) << @intCast(u7, int_info.bits)) - 1);
51035160 const imm_val = switch (wasm_bits) {
......@@ -5130,10 +5187,10 @@ fn signedSat(self: *Self, lhs_operand: WValue, rhs_operand: WValue, ty: Type, op
51305187 const wasm_bits = toWasmBits(int_info.bits).?;
51315188 const is_wasm_bits = wasm_bits == int_info.bits;
51325189
5133 const lhs = if (!is_wasm_bits) lhs: {
5190 var lhs = if (!is_wasm_bits) lhs: {
51345191 break :lhs try (try self.signAbsValue(lhs_operand, ty)).toLocal(self, ty);
51355192 } else lhs_operand;
5136 const rhs = if (!is_wasm_bits) rhs: {
5193 var rhs = if (!is_wasm_bits) rhs: {
51375194 break :rhs try (try self.signAbsValue(rhs_operand, ty)).toLocal(self, ty);
51385195 } else rhs_operand;
51395196
......@@ -5150,8 +5207,11 @@ fn signedSat(self: *Self, lhs_operand: WValue, rhs_operand: WValue, ty: Type, op
51505207 else => unreachable,
51515208 };
51525209
5153 const bin_result = try (try self.binOp(lhs, rhs, ty, op)).toLocal(self, ty);
5210 var bin_result = try (try self.binOp(lhs, rhs, ty, op)).toLocal(self, ty);
51545211 if (!is_wasm_bits) {
5212 defer bin_result.free(self); // not returned in this branch
5213 defer lhs.free(self); // uses temporary local for absvalue
5214 defer rhs.free(self); // uses temporary local for absvalue
51555215 try self.emitWValue(bin_result);
51565216 try self.emitWValue(max_wvalue);
51575217 _ = try self.cmp(bin_result, max_wvalue, ty, .lt);
......@@ -5202,8 +5262,10 @@ fn airShlSat(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
52025262 const result = try self.allocLocal(ty);
52035263
52045264 if (wasm_bits == int_info.bits) {
5205 const shl = try (try self.binOp(lhs, rhs, ty, .shl)).toLocal(self, ty);
5206 const shr = try (try self.binOp(shl, rhs, ty, .shr)).toLocal(self, ty);
5265 var shl = try (try self.binOp(lhs, rhs, ty, .shl)).toLocal(self, ty);
5266 defer shl.free(self);
5267 var shr = try (try self.binOp(shl, rhs, ty, .shr)).toLocal(self, ty);
5268 defer shr.free(self);
52075269
52085270 switch (wasm_bits) {
52095271 32 => blk: {
......@@ -5241,9 +5303,12 @@ fn airShlSat(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
52415303 else => unreachable,
52425304 };
52435305
5244 const shl_res = try (try self.binOp(lhs, shift_value, ty, .shl)).toLocal(self, ty);
5245 const shl = try (try self.binOp(shl_res, rhs, ty, .shl)).toLocal(self, ty);
5246 const shr = try (try self.binOp(shl, rhs, ty, .shr)).toLocal(self, ty);
5306 var shl_res = try (try self.binOp(lhs, shift_value, ty, .shl)).toLocal(self, ty);
5307 defer shl_res.free(self);
5308 var shl = try (try self.binOp(shl_res, rhs, ty, .shl)).toLocal(self, ty);
5309 defer shl.free(self);
5310 var shr = try (try self.binOp(shl, rhs, ty, .shr)).toLocal(self, ty);
5311 defer shr.free(self);
52475312
52485313 switch (wasm_bits) {
52495314 32 => blk: {
......@@ -5278,7 +5343,7 @@ fn airShlSat(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
52785343 if (is_signed) {
52795344 shift_result = try self.wrapOperand(shift_result, ty);
52805345 }
5281 return try shift_result.toLocal(self, ty);
5346 return shift_result.toLocal(self, ty);
52825347 }
52835348}
52845349