zig icon indicating copy to clipboard operation
zig copied to clipboard

Bit-packed struct with undefined default has inconsistent access

Open Arnau478 opened this issue 1 year ago • 7 comments

Zig Version

0.13.0-dev.211+6a65561e3

Steps to Reproduce and Observed Behavior

Run the following code:

const std = @import("std");

pub const Foo = packed struct(u2) {
    a: bool = undefined,
    b: bool,
};

const foo = Foo{
    .b = true,
};

pub fn main() void {
    std.debug.print("{}\n", .{foo});
    std.debug.print("{}\n", .{foo.b});
}

which prints

example.Foo{ .a = false, .b = false }
true

Notice how when we print foo, we get .b = false, but when we print foo.b we get true.

Expected Behavior

Both of them should print b as being true, but the first one doesn't.

A few things I've noticed:

  • If you change the undefined default value with false, then everything works as expected
  • Pretty recent regression, works in zig 0.12.0

Arnau478 avatar May 16 '24 11:05 Arnau478

On 0.12:

❯ zig run empty.zig                                                                                                                   
empty.Foo{ .a = false, .b = true }
true

On 0.13.0-dev.55+fc45e5b39:

❯ zig run empty.zig                                                                                       
empty.Foo{ .a = false, .b = false }
true
❯ zig run empty.zig -fno-llvm -fno-lld
empty.Foo{ .a = false, .b = true }
true

Note that fc45e5b39 is before the LLVM upgrade.

Rexicon226 avatar May 16 '24 12:05 Rexicon226

In #19630 it was made so that having undefined in a packed struct would propagate to the entire struct iirc.

So see:

const std = @import("std");

pub const Foo = packed struct(u2) {
    a: bool,
    b: bool,
};

pub fn main() void {
    var foo: Foo = undefined;
    _ = &foo;

    foo.b = true;

    std.debug.print("{}\n", .{foo});
    std.debug.print("{}\n", .{foo.b});
}

which works fine.

Might be talking non-sense, but I believe this is what is happening.

Rexicon226 avatar May 16 '24 12:05 Rexicon226

having undefined in a packed struct would propagate to the entire struct

If that's the expected/desired behaviour with:

const std = @import("std");

pub const Foo = packed struct(u2) {
    a: bool = undefined,
    b: bool,
};

pub fn main() void {
    var foo = Foo{
        .b = true,
    };
    _ = &foo;

    std.debug.print("{}\n", .{foo});
    std.debug.print("{}\n", .{foo.b});
}

consistent with that expectation (master branch) does mark as undef:

store i2 undef, ptr %2, align 1, !dbg !2322

however, simply swapping field order:

pub const Foo = packed struct(u2) {
    b: bool,
    a: bool = undefined,
};

the IR becomes inconsistent with that expectation:

store i2 1, ptr %2, align 1, !dbg !2322

fwiw, this specific repro bisects to c231d94960ec2cecbea0de877f645ba5d439fd13

mikdusan avatar May 16 '24 16:05 mikdusan

@Rexicon226 that idea was put on hold for now, and so has never been in a build of Zig.

mlugg avatar May 16 '24 16:05 mlugg

Ah, my bad. No idea then.

Rexicon226 avatar May 16 '24 16:05 Rexicon226

Is there a way to print an undefined value without branching on it and invoking UB? It doesn't surprise me personally that LLVM seems to do weird things after branching on undefined...

ifreund avatar May 17 '24 08:05 ifreund

Is there a way to print an undefined value without branching on it and invoking UB? It doesn't surprise me personally that LLVM seems to do weird things after branching on undefined...

The thing is that only one field is undefined. Also, when casting to an int (where there's no branching) this also happens.

Arnau478 avatar May 17 '24 09:05 Arnau478

Caused by #20095, using -fno-llvm -fno-lld results in a.Foo{ .a = false, .b = true }.

Vexu avatar May 31 '24 10:05 Vexu