Add Unsafe Shift Operators

For some reason this couldn’t actually go in Brainstorming, cause it’s read-only.
Anyways I have a working chess engine in C++, I wanted to see how Zig was, I was hearing some really nice things. Everything worked nicely until a certain point, I noticed that the shifting operators on 64-bit unsigned integers require a 6-bit unsigned integer as the shift amount. I thought this was fine, I would just change my square indices all to u6… Little did I know there is a small issue. I disassembled the final binary, on ReleaseFast mind you. I noticed that the function had an and instruction with 63, meaning it was not assuming that the register held something < 64 already (which is really, really weird because the function argument was u6 also, but I’m going to assume thats something with LLVM IR. Also, shift instruction wraps around ( << 190 = << 62) on my machine… again likely something with LLVM)

The simplest way I found to fix this was to just make a leftShift and rightShift function, change everything to u8, and assert that input is less than 64. This seemed to get rid of the unecessary mask.

My suggestion is to just add an operator called unsafe shift, maybe <<! and it just assumes that the value of the shift is already less 64, or N for uN.

Maybe also an operator like <<% that just does like a mod 64 on the shifter. iirc on ARM this requires a few assembly ops extra but on x86 this is less ops (no mask by 63 needed)

Anyways this helps chess programmers write performant code for our engines while maintaining readability.

It looks like the related content is connected to this log, maybe you could try the 0.17 dev version.

Can you make test code showing the problem? I don’t see any &63 here: godbolt

fn shiftU64(x: u64, y: u64) u64 {
    return x << @intCast(y);
}

There’s no need for something like an <<! operator. In ReleaseFast optimization mode which omits safety checks, the above amounts to a simple shl instruction, which is what you’d expect.

Assuming the left-hand side is a power-of-two-sized integer, you can use x << @truncate(y) to get the same simple shl output in ReleaseSafe too (provided that the target’s native shl instruction is defined to only use the lowermost bits of the RHS).

Godbolt link that verifies this (also includes the equivalent x << y C code for comparison; RHS is passed as a pointer in order to be able to use u6 in an extern context)


Side note: Debug, ReleaseSafe, ReleaseFast and ReleaseSmall were recently renamed to debug, safe, fast and small respectively on Zig 0.17.0-dev master, though the old deprecated names will continue to work until after 0.18.

6 Likes