authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-05-06 21:58:25+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-05-06 21:58:25+02:00
log4df65fc26485fe32976561990393a35eb52ddfc6
tree79e7c740f8957cd9a2cfac410de0e51fa6d29c89
parentac1aaec9c38eb44b93099ff18579a9401f107100
signature Commit is signed but in an unrecognized format.

wasm: Store signed ints as two's complement

When a signed integer is negative, the integer will be stored as a two's complement, rather than its signed value. Instead, we verify the signed bits during arithmetic operations. This fixes signed cases of `@mulWithOverflow`.

1 files changed, 71 insertions(+), 37 deletions(-)

src/arch/wasm/CodeGen.zig+71-37
...@@ -1818,7 +1818,7 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro...@@ -1818,7 +1818,7 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro
1818 try self.emitWValue(rhs);1818 try self.emitWValue(rhs);
1819 }1819 }
1820 const valtype = typeToValtype(ty, self.target);1820 const valtype = typeToValtype(ty, self.target);
1821 const abi_size = @intCast(u8, ty.abiSize(self.target));1821 const abi_size = @intCast(u8, ty.bitSize(self.target));
18221822
1823 const opcode = buildOpcode(.{1823 const opcode = buildOpcode(.{
1824 .valtype1 = valtype,1824 .valtype1 = valtype,
...@@ -1852,21 +1852,13 @@ fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1852,21 +1852,13 @@ fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1852fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {1852fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {
1853 // load local's value from memory by its stack position1853 // load local's value from memory by its stack position
1854 try self.emitWValue(operand);1854 try self.emitWValue(operand);
1855 // Build the opcode with the right bitsize
1856 const signedness: std.builtin.Signedness = if (ty.isUnsignedInt() or
1857 ty.zigTypeTag() == .ErrorSet or
1858 ty.zigTypeTag() == .Bool)
1859 .unsigned
1860 else
1861 .signed;
1862
1863 const abi_size = @intCast(u8, ty.abiSize(self.target));
18641855
1856 const abi_size = @intCast(u8, ty.bitSize(self.target));
1865 const opcode = buildOpcode(.{1857 const opcode = buildOpcode(.{
1866 .valtype1 = typeToValtype(ty, self.target),1858 .valtype1 = typeToValtype(ty, self.target),
1867 .width = abi_size * 8, // use bitsize instead of byte size1859 .width = abi_size * 8, // use bitsize instead of byte size
1868 .op = .load,1860 .op = .load,
1869 .signedness = signedness,1861 .signedness = .unsigned,
1870 });1862 });
18711863
1872 try self.addMemArg(1864 try self.addMemArg(
...@@ -1948,6 +1940,7 @@ fn wrapBinOp(self: *Self, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError...@@ -1948,6 +1940,7 @@ fn wrapBinOp(self: *Self, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError
1948 .signedness = if (ty.isSignedInt()) .signed else .unsigned,1940 .signedness = if (ty.isSignedInt()) .signed else .unsigned,
1949 });1941 });
1950 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));1942 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
1943 const bin_local = try self.allocLocal(ty);
19511944
1952 const int_info = ty.intInfo(self.target);1945 const int_info = ty.intInfo(self.target);
1953 const bitsize = int_info.bits;1946 const bitsize = int_info.bits;
...@@ -1963,25 +1956,37 @@ fn wrapBinOp(self: *Self, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError...@@ -1963,25 +1956,37 @@ fn wrapBinOp(self: *Self, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError
1963 } else if (is_signed and bitsize == 16) {1956 } else if (is_signed and bitsize == 16) {
1964 try self.addTag(.i32_extend16_s);1957 try self.addTag(.i32_extend16_s);
1965 } else {1958 } else {
1966 const result = (@as(u64, 1) << @intCast(u6, bitsize - @boolToInt(is_signed))) - 1;1959 try self.addLabel(.local_set, bin_local.local);
1967 if (bitsize < 32) {1960 return self.wrapOperand(bin_local, ty);
1968 try self.addImm32(@bitCast(i32, @intCast(u32, result)));
1969 try self.addTag(.i32_and);
1970 } else {
1971 try self.addImm64(result);
1972 try self.addTag(.i64_and);
1973 }
1974 }1961 }
1975 } else if (int_info.bits > 64) {1962 } else if (int_info.bits > 64) {
1976 return self.fail("TODO wasm: Integer wrapping for bitsizes larger than 64", .{});1963 return self.fail("TODO wasm: Integer wrapping for bitsizes larger than 64", .{});
1977 }1964 }
19781965
1979 // save the result in a temporary1966 // save the result in a temporary
1980 const bin_local = try self.allocLocal(ty);
1981 try self.addLabel(.local_set, bin_local.local);1967 try self.addLabel(.local_set, bin_local.local);
1982 return bin_local;1968 return bin_local;
1983}1969}
19841970
1971/// Wraps an operand based on a given type's bitsize.
1972/// Asserts `Type` is <= 64bits.
1973fn wrapOperand(self: *Self, operand: WValue, ty: Type) InnerError!WValue {
1974 assert(ty.abiSize(self.target) <= 8);
1975 const result_local = try self.allocLocal(ty);
1976 const bitsize = ty.intInfo(self.target).bits;
1977 const result = @intCast(u64, (@as(u65, 1) << @intCast(u7, bitsize)) - 1);
1978 try self.emitWValue(operand);
1979 if (bitsize <= 32) {
1980 try self.addImm32(@bitCast(i32, @intCast(u32, result)));
1981 try self.addTag(.i32_and);
1982 } else {
1983 try self.addImm64(result);
1984 try self.addTag(.i64_and);
1985 }
1986 try self.addLabel(.local_set, result_local.local);
1987 return result_local;
1988}
1989
1985fn lowerParentPtr(self: *Self, ptr_val: Value, ptr_child_ty: Type) InnerError!WValue {1990fn lowerParentPtr(self: *Self, ptr_val: Value, ptr_child_ty: Type) InnerError!WValue {
1986 switch (ptr_val.tag()) {1991 switch (ptr_val.tag()) {
1987 .decl_ref_mut => {1992 .decl_ref_mut => {
...@@ -2098,6 +2103,22 @@ fn lowerDeclRefValue(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index)...@@ -2098,6 +2103,22 @@ fn lowerDeclRefValue(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index)
2098 } else return WValue{ .memory = target_sym_index };2103 } else return WValue{ .memory = target_sym_index };
2099}2104}
21002105
2106/// Converts a signed integer to its 2's complement form and returns
2107/// an unsigned integer instead.
2108/// Asserts bitsize <= 64
2109fn convertTo2Complement(value: anytype, bits: u7) std.meta.Int(.unsigned, @typeInfo(@TypeOf(value)).Int.bits) {
2110 const T = @TypeOf(value);
2111 comptime assert(@typeInfo(T) == .Int);
2112 comptime assert(@typeInfo(T).Int.signedness == .signed);
2113 assert(bits <= 64);
2114 const WantedT = std.meta.Int(.unsigned, @typeInfo(T).Int.bits);
2115 if (value >= 0) return @bitCast(WantedT, value);
2116 const max_value = @intCast(u64, (@as(u65, 1) << bits) - 1);
2117 const flipped = (~-value) + 1;
2118 const result = @bitCast(WantedT, flipped) & max_value;
2119 return @intCast(WantedT, result);
2120}
2121
2101fn lowerConstant(self: *Self, val: Value, ty: Type) InnerError!WValue {2122fn lowerConstant(self: *Self, val: Value, ty: Type) InnerError!WValue {
2102 if (val.isUndefDeep()) return self.emitUndefined(ty);2123 if (val.isUndefDeep()) return self.emitUndefined(ty);
2103 if (val.castTag(.decl_ref)) |decl_ref| {2124 if (val.castTag(.decl_ref)) |decl_ref| {
...@@ -2114,10 +2135,12 @@ fn lowerConstant(self: *Self, val: Value, ty: Type) InnerError!WValue {...@@ -2114,10 +2135,12 @@ fn lowerConstant(self: *Self, val: Value, ty: Type) InnerError!WValue {
2114 switch (ty.zigTypeTag()) {2135 switch (ty.zigTypeTag()) {
2115 .Int => {2136 .Int => {
2116 const int_info = ty.intInfo(self.target);2137 const int_info = ty.intInfo(self.target);
2117 // write constant
2118 switch (int_info.signedness) {2138 switch (int_info.signedness) {
2119 .signed => switch (int_info.bits) {2139 .signed => switch (int_info.bits) {
2120 0...32 => return WValue{ .imm32 = @bitCast(u32, @intCast(i32, val.toSignedInt())) },2140 0...32 => return WValue{ .imm32 = @intCast(u32, convertTo2Complement(
2141 val.toSignedInt(),
2142 @intCast(u6, int_info.bits),
2143 )) },
2121 33...64 => return WValue{ .imm64 = @bitCast(u64, val.toSignedInt()) },2144 33...64 => return WValue{ .imm64 = @bitCast(u64, val.toSignedInt()) },
2122 else => unreachable,2145 else => unreachable,
2123 },2146 },
...@@ -4009,22 +4032,33 @@ fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue...@@ -4009,22 +4032,33 @@ fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue
4009 try self.addLabel(.local_set, tmp_val.local);4032 try self.addLabel(.local_set, tmp_val.local);
4010 break :blk tmp_val;4033 break :blk tmp_val;
4011 } else if (op == .mul) blk: {4034 } else if (op == .mul) blk: {
4012 const bin_op = try self.wrapBinOp(lhs, rhs, lhs_ty, op);4035 if (int_info.signedness == .signed) {
4013 try self.startBlock(.block, wasm.block_empty);4036 const shift_val = convertTo2Complement(-@intCast(i17, int_info.bits), @intCast(u7, int_info.bits));
4014 // check if 0. true => Break out of block as cannot over -or underflow.4037 const shift_imm = if (wasm_bits == 32) WValue{ .imm32 = shift_val } else WValue{ .imm64 = shift_val };
4015 try self.emitWValue(lhs);4038
4016 switch (wasm_bits) {4039 const lhs_shl = try self.binOp(lhs, shift_imm, lhs_ty, .shl);
4017 32 => try self.addTag(.i32_eqz),4040 const lhs_shr = try self.binOp(lhs_shl, shift_imm, lhs_ty, .shr);
4018 64 => try self.addTag(.i64_eqz),4041 const rhs_shl = try self.binOp(rhs, shift_imm, lhs_ty, .shl);
4019 else => unreachable,4042 const rhs_shr = try self.binOp(rhs_shl, shift_imm, lhs_ty, .shr);
4043
4044 const bin_op = try self.binOp(lhs_shr, rhs_shr, lhs_ty, op);
4045 const shl = try self.binOp(bin_op, shift_imm, lhs_ty, .shl);
4046 const shr = try self.binOp(shl, shift_imm, lhs_ty, .shr);
4047
4048 const cmp_op = try self.cmp(shr, bin_op, lhs_ty, .neq);
4049 try self.emitWValue(cmp_op);
4050 try self.addLabel(.local_set, overflow_bit.local);
4051 break :blk try self.wrapOperand(bin_op, lhs_ty);
4052 } else {
4053 const bin_op = try self.binOp(lhs, rhs, lhs_ty, op);
4054 const shift_imm = if (wasm_bits == 32) WValue{ .imm32 = int_info.bits } else WValue{ .imm64 = int_info.bits };
4055 // const zero = if (wasm_bits == 32) WValue{ .imm32 = 0 } else WValue{ .imm64 = 0 };
4056 const shr = try self.binOp(bin_op, shift_imm, lhs_ty, .shr);
4057 const cmp_op = try self.cmp(shr, zero, lhs_ty, .neq);
4058 try self.emitWValue(cmp_op);
4059 try self.addLabel(.local_set, overflow_bit.local);
4060 break :blk try self.wrapOperand(bin_op, lhs_ty);
4020 }4061 }
4021 try self.addLabel(.br_if, 0);
4022 const div = try self.binOp(bin_op, lhs, lhs_ty, .div);
4023 const cmp_res = try self.cmp(div, rhs, lhs_ty, .neq);
4024 try self.emitWValue(cmp_res);
4025 try self.addLabel(.local_set, overflow_bit.local);
4026 try self.endBlock();
4027 break :blk bin_op;
4028 } else try self.wrapBinOp(lhs, rhs, lhs_ty, op);4062 } else try self.wrapBinOp(lhs, rhs, lhs_ty, op);
40294063
4030 const result_ptr = try self.allocStack(self.air.typeOfIndex(inst));4064 const result_ptr = try self.allocStack(self.air.typeOfIndex(inst));