Devlog ⚡ New @bitCast Semantics and LLVM Backend Improvements

New @bitCast Semantics and LLVM Backend Improvements
Author: Matthew Lugg

33 Likes

This is nifty — I’ve been writing some endiannes-sketchy code recently, and looks like it will magically come out endianness-clean from just upgrading the compiler?

Just to clarify, if I want to have the old transmute/re-interpret memory semantics, that is still available via a pointer cast?

2 Likes

This in incredibly interesting.

I have some bit-packed values in a network protocol, and being able to @bitCast the incoming bytes to something like [6]i20 would be massively convenient.

Based changes.

4 Likes

I have somehow narrowly avoided this causing disasterous uncaught changes to my code on big endian targets because I switched to using std.mem.writeInt on the backing integer a long time ago. Big endian targets actually existing seems to be a myth though.

/// Convert little endian packed bytes from EtherCAT to host representation.
///
/// Supports enums, packed structs, and most primitive types. All must have
/// bitlength divisible by 8.
pub fn packFromECat(comptime T: type, ecat_bytes: *const [@divExact(@bitSizeOf(T), 8)]u8) T {
    comptime assert(isECatPackable(T));
    return switch (@typeInfo(T)) {
        .@"enum" => |info| @enumFromInt(std.mem.readInt(
            info.tag_type,
            ecat_bytes,
            .little,
        )),
        else => @bitCast(std.mem.readInt(
            @Int(.unsigned, @bitSizeOf(T)),
            ecat_bytes,
            .little,
        )),
    };
}


Edit: well actually is this going to reverse arrays here now? Example being using this function to write an array on a big endian target.

/// convert a packed struct to bytes that can be sent via ethercat
///
/// the packed struct must have bitwidth that is a multiple of 8
pub fn eCatFromPack(pack: anytype) [@divExact(@bitSizeOf(@TypeOf(pack)), 8)]u8 {
    comptime assert(isECatPackable(@TypeOf(pack)));
    const int = switch (@typeInfo(@TypeOf(pack))) {
        .@"enum" => |info| @as(info.tag_type, @intFromEnum(pack)),
        else => @as(
            @Int(.unsigned, @bitSizeOf(@TypeOf(pack))),
            @bitCast(pack),
        ),
    };
    var bytes: [@divExact(@bitSizeOf(@TypeOf(pack)), 8)]u8 = undefined;
    std.mem.writeInt(
        @TypeOf(int),
        &bytes,
        int,
        .little,
    );
    return bytes;
}
2 Likes

(Quite long devlog coming up, apologies—I got a little carried away with this one!)

I don’t hear anyone complaining :smiley:

11 Likes

Yep, you can always use a classic @ptrCast or extern union approach to type pun in memory. The only thing to be aware of is that it isn’t quite equivalent to the old @bitCast semantics due to things like [3]u8u24—type punning there results in illegal behaviour or an undefined value (depending on your strategy), because @sizeOf(u24) != @sizeOf([3]u8). In that particular case you do need to use @bitCast for the conversion (with a @byteSwap or whatever to switch endianness if needed).

6 Likes

massive :partying_face: for the nonnative integer lowering changes, i have been waiting for that so i can confidently use those types without worrying about the slowdown

i am generally happy about the changes to @bitCast as well – the downside, of course, being that on big-endian targets the operation is now more involved