authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-04-17 11:24:42+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-04-17 13:41:26+01:00
log23062a5bed285f72e35651dd1e8b4a125b83dba0
treeea6746763647264d62389f73768662184d32c82e
parent3c45a945285dff00d0eb8160342560bcb7d24cc5
signaturelock-open Commit is signed but in an unrecognized format.

Value: convert undefined values to 0xAA for bitwise operations

The operation `undefined & 0` ought to result in the value `0`, and likewise for zeroing only some bits. `std/packed_int_array.zig` tests were failing because this behavior was not implemented -- this issue was previously masked by faulty bitcast logic which turned `undefined` values into `0xAA` on pointer loads. Ideally, we would like to be able to track the undefined bits at comptime. This is related to #19634.

1 files changed, 54 insertions(+), 10 deletions(-)

src/Value.zig+54-10
...@@ -1917,16 +1917,29 @@ pub fn bitwiseAnd(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, mod: *...@@ -1917,16 +1917,29 @@ pub fn bitwiseAnd(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, mod: *
1917}1917}
19181918
1919/// operands must be integers; handles undefined.1919/// operands must be integers; handles undefined.
1920pub fn bitwiseAndScalar(lhs: Value, rhs: Value, ty: Type, arena: Allocator, mod: *Module) !Value {1920pub fn bitwiseAndScalar(orig_lhs: Value, orig_rhs: Value, ty: Type, arena: Allocator, zcu: *Zcu) !Value {
1921 if (lhs.isUndef(mod) or rhs.isUndef(mod)) return Value.fromInterned((try mod.intern(.{ .undef = ty.toIntern() })));1921 // If one operand is defined, we turn the other into `0xAA` so the bitwise AND can
1922 // still zero out some bits.
1923 // TODO: ideally we'd still like tracking for the undef bits. Related: #19634.
1924 const lhs: Value, const rhs: Value = make_defined: {
1925 const lhs_undef = orig_lhs.isUndef(zcu);
1926 const rhs_undef = orig_rhs.isUndef(zcu);
1927 break :make_defined switch ((@as(u2, @intFromBool(lhs_undef)) << 1) | @intFromBool(rhs_undef)) {
1928 0b00 => .{ orig_lhs, orig_rhs },
1929 0b01 => .{ orig_lhs, try intValueAa(ty, arena, zcu) },
1930 0b10 => .{ try intValueAa(ty, arena, zcu), orig_rhs },
1931 0b11 => return zcu.undefValue(ty),
1932 };
1933 };
1934
1922 if (ty.toIntern() == .bool_type) return makeBool(lhs.toBool() and rhs.toBool());1935 if (ty.toIntern() == .bool_type) return makeBool(lhs.toBool() and rhs.toBool());
19231936
1924 // TODO is this a performance issue? maybe we should try the operation without1937 // TODO is this a performance issue? maybe we should try the operation without
1925 // resorting to BigInt first.1938 // resorting to BigInt first.
1926 var lhs_space: Value.BigIntSpace = undefined;1939 var lhs_space: Value.BigIntSpace = undefined;
1927 var rhs_space: Value.BigIntSpace = undefined;1940 var rhs_space: Value.BigIntSpace = undefined;
1928 const lhs_bigint = lhs.toBigInt(&lhs_space, mod);1941 const lhs_bigint = lhs.toBigInt(&lhs_space, zcu);
1929 const rhs_bigint = rhs.toBigInt(&rhs_space, mod);1942 const rhs_bigint = rhs.toBigInt(&rhs_space, zcu);
1930 const limbs = try arena.alloc(1943 const limbs = try arena.alloc(
1931 std.math.big.Limb,1944 std.math.big.Limb,
1932 // + 1 for negatives1945 // + 1 for negatives
...@@ -1934,7 +1947,25 @@ pub fn bitwiseAndScalar(lhs: Value, rhs: Value, ty: Type, arena: Allocator, mod:...@@ -1934,7 +1947,25 @@ pub fn bitwiseAndScalar(lhs: Value, rhs: Value, ty: Type, arena: Allocator, mod:
1934 );1947 );
1935 var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };1948 var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
1936 result_bigint.bitAnd(lhs_bigint, rhs_bigint);1949 result_bigint.bitAnd(lhs_bigint, rhs_bigint);
1937 return mod.intValue_big(ty, result_bigint.toConst());1950 return zcu.intValue_big(ty, result_bigint.toConst());
1951}
1952
1953/// Given an integer or boolean type, creates an value of that with the bit pattern 0xAA.
1954/// This is used to convert undef values into 0xAA when performing e.g. bitwise operations.
1955fn intValueAa(ty: Type, arena: Allocator, zcu: *Zcu) !Value {
1956 if (ty.toIntern() == .bool_type) return Value.true;
1957 const info = ty.intInfo(zcu);
1958
1959 const buf = try arena.alloc(u8, (info.bits + 7) / 8);
1960 @memset(buf, 0xAA);
1961
1962 const limbs = try arena.alloc(
1963 std.math.big.Limb,
1964 std.math.big.int.calcTwosCompLimbCount(info.bits),
1965 );
1966 var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
1967 result_bigint.readTwosComplement(buf, info.bits, zcu.getTarget().cpu.arch.endian(), info.signedness);
1968 return zcu.intValue_big(ty, result_bigint.toConst());
1938}1969}
19391970
1940/// operands must be (vectors of) integers; handles undefined scalars.1971/// operands must be (vectors of) integers; handles undefined scalars.
...@@ -1984,23 +2015,36 @@ pub fn bitwiseOr(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, mod: *M...@@ -1984,23 +2015,36 @@ pub fn bitwiseOr(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, mod: *M
1984}2015}
19852016
1986/// operands must be integers; handles undefined.2017/// operands must be integers; handles undefined.
1987pub fn bitwiseOrScalar(lhs: Value, rhs: Value, ty: Type, arena: Allocator, mod: *Module) !Value {2018pub fn bitwiseOrScalar(orig_lhs: Value, orig_rhs: Value, ty: Type, arena: Allocator, zcu: *Zcu) !Value {
1988 if (lhs.isUndef(mod) or rhs.isUndef(mod)) return Value.fromInterned((try mod.intern(.{ .undef = ty.toIntern() })));2019 // If one operand is defined, we turn the other into `0xAA` so the bitwise AND can
2020 // still zero out some bits.
2021 // TODO: ideally we'd still like tracking for the undef bits. Related: #19634.
2022 const lhs: Value, const rhs: Value = make_defined: {
2023 const lhs_undef = orig_lhs.isUndef(zcu);
2024 const rhs_undef = orig_rhs.isUndef(zcu);
2025 break :make_defined switch ((@as(u2, @intFromBool(lhs_undef)) << 1) | @intFromBool(rhs_undef)) {
2026 0b00 => .{ orig_lhs, orig_rhs },
2027 0b01 => .{ orig_lhs, try intValueAa(ty, arena, zcu) },
2028 0b10 => .{ try intValueAa(ty, arena, zcu), orig_rhs },
2029 0b11 => return zcu.undefValue(ty),
2030 };
2031 };
2032
1989 if (ty.toIntern() == .bool_type) return makeBool(lhs.toBool() or rhs.toBool());2033 if (ty.toIntern() == .bool_type) return makeBool(lhs.toBool() or rhs.toBool());
19902034
1991 // TODO is this a performance issue? maybe we should try the operation without2035 // TODO is this a performance issue? maybe we should try the operation without
1992 // resorting to BigInt first.2036 // resorting to BigInt first.
1993 var lhs_space: Value.BigIntSpace = undefined;2037 var lhs_space: Value.BigIntSpace = undefined;
1994 var rhs_space: Value.BigIntSpace = undefined;2038 var rhs_space: Value.BigIntSpace = undefined;
1995 const lhs_bigint = lhs.toBigInt(&lhs_space, mod);2039 const lhs_bigint = lhs.toBigInt(&lhs_space, zcu);
1996 const rhs_bigint = rhs.toBigInt(&rhs_space, mod);2040 const rhs_bigint = rhs.toBigInt(&rhs_space, zcu);
1997 const limbs = try arena.alloc(2041 const limbs = try arena.alloc(
1998 std.math.big.Limb,2042 std.math.big.Limb,
1999 @max(lhs_bigint.limbs.len, rhs_bigint.limbs.len),2043 @max(lhs_bigint.limbs.len, rhs_bigint.limbs.len),
2000 );2044 );
2001 var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };2045 var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
2002 result_bigint.bitOr(lhs_bigint, rhs_bigint);2046 result_bigint.bitOr(lhs_bigint, rhs_bigint);
2003 return mod.intValue_big(ty, result_bigint.toConst());2047 return zcu.intValue_big(ty, result_bigint.toConst());
2004}2048}
20052049
2006/// operands must be (vectors of) integers; handles undefined scalars.2050/// operands must be (vectors of) integers; handles undefined scalars.