Skip to content

Implement switching on packed structs #24231

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

Justus2308
Copy link
Contributor

@Justus2308 Justus2308 commented Jun 21, 2025

Closes #22214
Resolves #24152

The first commit reintroduces the switch_cond[_ref] ZIR tags. This fixes switching on decl literals.
These tags were removed in 85e94fe as a consequence of result types for switch cases being removed in cebd800. Reintroducing them makes switch_block_ref superfluous.

I tried to keep the changes as unintrusive as possible but this does increase the amount of ZIR tags by 1.

The second commit implements switching on packed structs. It does this by enabling a more int-like analysis of packed structs (and packed unions as a consequence), mainly by making them comparable to other int-like types. This minimizes special-casing in Sema and codegen.

@rllj
Copy link

rllj commented Jun 22, 2025

I've been working on this issue too, and ran into the exact same problem as you. I think it's because you're emitting ZIR to cast the switch case values into the type of the switch operand, which in the case of tagged unions means it tries to cast an enum literal to a union type, and not the tag type. I am new to compiler development and have no idea how to solve this, but maybe it'll nudge you in the right direction :)

@Justus2308
Copy link
Contributor Author

Thanks for the hint! I've managed to get dot syntax and decl literals working now by properly reimplementing result types for switch cases with new ZIR tags but I'm still having some trouble with captures and the .switch_block_err_union special case. I'm new to compiler development too so this will probably take a while :)

@Justus2308 Justus2308 force-pushed the 22214-switch-on-packed-struct branch 2 times, most recently from a69b226 to 9526ee8 Compare July 6, 2025 04:54
@Justus2308 Justus2308 changed the title Sema: Implement switching on packed structs Implement switching on packed structs Jul 6, 2025
@Justus2308 Justus2308 force-pushed the 22214-switch-on-packed-struct branch 2 times, most recently from ac501ce to bf8c9dd Compare July 6, 2025 07:07
These tags were removed in 85e94fe as a consequence
of result types for switch cases being removed in cebd800.
They have since become necessary again because of the existence of decl literals.
Reintroducing them makes `switch_block_ref` superfluous.
This change is also in preparation of switching on packed structs (ziglang#22214).
@Justus2308 Justus2308 force-pushed the 22214-switch-on-packed-struct branch 4 times, most recently from 81683a9 to 9fe9421 Compare July 6, 2025 07:15
@Justus2308 Justus2308 force-pushed the 22214-switch-on-packed-struct branch from 9fe9421 to c37b3bf Compare July 6, 2025 08:17
@Justus2308 Justus2308 marked this pull request as ready for review July 6, 2025 14:23
Makes all `Value`s whose types are eligible to be in a packed
container internally comparable (like all other switchable types
were already) to avoid special cases. This change affects packed
structs and packed unions and makes it possible to treat them
more like integers during compilation.
@Justus2308 Justus2308 force-pushed the 22214-switch-on-packed-struct branch from c37b3bf to 25146f5 Compare July 6, 2025 15:36
@Justus2308
Copy link
Contributor Author

The x86_64-windows-release ci check failed because of some git related issue it will pass if rerun.

// .normal strat without adding inconvenient function args.
//
// TODO find a better way
allocated_limbs = zcu.gpa.alloc(Limb, limb_count) catch |err| {
Copy link
Contributor Author

@Justus2308 Justus2308 Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A though: If we want to add more int-like functionality to packed structs we should consider just storing them as ints internally. This probably wouldn't be too hard, there could just be an additional field in InternPool.Aggregate.Storage (something like @"packed": Index or even @"packed": Integer.Storage). All the type information for the fields is stored separately as part of the struct type anyway.
This would of course allow for less reuse of interned values but since packed structs are mostly pretty small it probably wouldn't make much of a difference or maybe even save some space (every field <=32bit needs at least 8 bytes for type index + value, in e.g. a 32bit packed struct containing 8 u4 ints that's 64 bytes vs 8 bytes if it was a single integer. I think this example is not too far fetched since we're talking about packed structs). Also packed struct size is capped at 64KiB so even some pathological worst case probably wouldn't be that bad.

The big advantage of this would be trivial bit-casting and comparisons of packed structs. It would make all the logic here very simple. Also @bitCast() is probably the most common builtin used on packed structs so making that simpler and faster wouldn't hurt either. I imagine it would also help in making @bitCast semantics more consistent (there's an accepted proposal for that somewhere I think).

The leaking of a GPA allocation is unfortunate and maybe not the best solution, especially because the compiler is currently transitioning from being a one-shot program to running in the background, potentially for a long time. Maybe using zcu.comp.arena is the better choice here but I'm not sure about the exact implications of either of the two.
But I think some kind of functionality like it is implemented here is strictly necessary for switching on packed structs. I initially reused the sparse item switch logic but then realized that this shouldn't be necessary:

const P = packed struct { a: u1, b: u1 };
fn foo(p: P) void {
    switch (p) {
        .{ .a = 0, .b = 0 } = {},
        .{ .a = 0, .b = 1 } = {},
        .{ .a = 1, .b = 0 } = {},
        .{ .a = 1, .b = 1 } = {},
        else => unreachable, // doesn't compile without this with sparse logic
    }
}

To check whether all cases are covered some strict ordering of all possible packed struct values is very useful and the most straightforward way to achieve that is bitcasting them to an integer. I first tried just doing that in zirSwitchBlock and using the integer logic from there without the rest of the logic really knowing about the fact that what's being processed is a packed struct and not an integer, but this lead to all kinds of special cases and would have required more additional logic in sema and codegen than my current solution.

@Justus2308 Justus2308 marked this pull request as draft July 12, 2025 14:55
@Justus2308
Copy link
Contributor Author

This PR still has some problems, I will reopen it when I've solved them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

decl literals not allowed as switch cases Proposal: allow switch on packed struct
2 participants