-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
base: master
Are you sure you want to change the base?
Implement switching on packed structs #24231
Conversation
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 :) |
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 |
a69b226
to
9526ee8
Compare
ac501ce
to
bf8c9dd
Compare
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).
81683a9
to
9fe9421
Compare
9fe9421
to
c37b3bf
Compare
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.
c37b3bf
to
25146f5
Compare
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| { |
There was a problem hiding this comment.
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.
This PR still has some problems, I will reopen it when I've solved them. |
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.