zig icon indicating copy to clipboard operation
zig copied to clipboard

proposal: remove the `<<|` operator

Open nektro opened this issue 1 year ago • 7 comments

  • in a normal bitwise left shift b must be comptime-known or have a type with log2 number of bits as a. this is not the case in <<| and confusing to code readers and newcomers.
  • there is no >>| to complement it and nearly no one has noticed.
  • in the Zig repo it is not used at other than in the compiler to implement it, and in the tests to ensure it works
  • I have not used it in any of my Zig code written in the past few years

for these reasons I propose we remove it.

nektro avatar Apr 12 '24 21:04 nektro

its been brought to my attention the second point was unnecessary because >>= already does this behavior. imo the proposal still stands though

nektro avatar Apr 12 '24 21:04 nektro

https://sourcegraph.com/search?q=context:global+lang:Zig+%3C%3C%7C&patternType=keyword&sm=0 only surfaces Zig itself and two outside cases that could use << instead.

nektro avatar Apr 12 '24 21:04 nektro

Never used it either.

jedisct1 avatar Apr 13 '24 06:04 jedisct1

I don't have especially strong feelings for this operator, but I don't really see any big problems with it either.

x << y is equivalent to x * pow(2, y). x << y when y >= @bitSizeOf(T) will always be an error and invoke safety-checked UB. Zig prefers handling "always error" cases via the type system whenever possible, and std.math.Log2Int(T) is the closest thing available at the moment, so for x << y, it makes sense to require y to be of type std.math.Log2Int(T). (If/when arbitrary range integers are implemented I suspect that @Int(0, @bitSizeOf(T) - 1) will be used for the rhs instead.)

x <<| y is equivalent to x *| pow(2, y). x <<| y when y >= @bitSizeOf(T) is not an error and has a well-defined result, so it makes sense for y to also be of type T for unsigned integers. Apparently, currently it is also T for signed integers, which allows negative values on the rhs that will always invoke UB, so if it is not removed it would probably be sound to refine it to require the rhs to be unsigned.

If anything, my question would be why there isn't a matching x <<% y operator that is equivalent to x *% pow(2, y). If my math is right such an operator would always return 0 when y >= @bitSizeOf(T).

castholm avatar Apr 13 '24 16:04 castholm

Interestingly, the behavior for <<| currently differs between comptime and runtime and has incorrect results when shifting by >= @bitSizeOf(T) at runtime:

const std = @import("std");
test {
    const a: i8 = comptime (@as(i8, 99) <<| @as(i8, 111));

    var b_lhs: i8 = 99;
    var b_rhs: i8 = 111;
    _ = &b_lhs;
    _ = &b_rhs;
    const b: i8 = b_lhs <<| b_rhs;

    try std.testing.expectEqual(a, b);
}
1/1 repro.test_0... expected 127, found -1
FAIL (TestExpectedEqual)

LLVM defines its llvm.sshl/ushl.sat.* intrinsics to return a poison value if the rhs is "equal to or larger than the integer bit width of the arguments", so I suspect that this is what is happening here.

castholm avatar Apr 13 '24 16:04 castholm

Related: #7605

andrewrk avatar Aug 16 '24 06:08 andrewrk