-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
stage2: @intToEnum runtime safety
#11578
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
Conversation
491c8d3 to
416b488
Compare
24258d5 to
259376a
Compare
259376a to
9994457
Compare
9994457 to
e0dc608
Compare
andrewrk
left a comment
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.
Thanks for your patience on getting a review.
As for testing, the runtime safety tests can be executed like this:
stage1/bin/zig build test-cases -Dtest-filter=safety -Denable-llvm
When you want to mark a test as passing, do this:
--- a/test/cases/safety/integer addition overflow.zig
+++ b/test/cases/safety/integer addition overflow.zig
@@ -19,5 +19,5 @@ fn add(a: u16, b: u16) u16 {
}
// run
-// backend=stage1
+// backend=stage1,llvm
// target=nativeThere are a few that can already be marked this way, such as this example.
| } | ||
| } | ||
|
|
||
| pub fn enumBackingInt(ty: Type, ally: Allocator) !Type { |
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.
This is already implemented as intTagType.
| .enum_nonexhaustive => {}, | ||
| .enum_full, .enum_numbered, .enum_simple => { | ||
| const field_count = dest_ty.enumFieldCount(); | ||
| if (dest_ty.tag() == .enum_simple or dest_ty.enumValues().count() == 0) { |
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.
Even though I made up that == 0 rule it took me a moment to remember. Suggestion to make it a bit easier on the reader:
| if (dest_ty.tag() == .enum_simple or dest_ty.enumValues().count() == 0) { | |
| if (dest_ty.enumIsAutoNumbered()) { |
| const is_in_range = try block.addBinOp(.cmp_lt, casted_operand, field_count_inst); | ||
| try sema.addSafetyCheck(block, is_in_range, .invalid_enum_value); | ||
| } else { | ||
| // TODO: Output a switch instead of chained OR's. |
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.
Hmmmm. Let's do this the right way from the start, otherwise this will harm compilation performance due to creating AIR bloat.
Suggestion: introduce a new AIR instruction called check_enum_int which returns true if and only if the integer is a valid tag of a given enum type. This way the backends can lower it as a switch directly, instead of having to parse the various AIR tags for a switch emitted from Sema. This would also allow the possibility for a backend to lower it to a function call in the case of enums with many tags.
stage1 already benefits from a similar arrangement because it implements runtime safety in the LLVM IR lowering pass, which is less flexible in some ways (panic function gets outputted even if it is never called) but comes with advantages, as illustrated here.
Alternative idea which you are welcome to explore: create a function in Sema called __zig_intToEnum_E where E is the fully qualified type name of the enum, which does the switch, calls panic if applicable with a custom panic message ("enum '{s}' has no tag with value '{d}'"), exactly matching the corresponding compile error. This alternative would not require introducing a new AIR tag, would not require any changes to the backends, and would pioneer the way forward to more detailed runtime safety messages.
This implements runtime safety checks for
@intToEnum.intCastis used to both cast the operand into the enum's backing integer type as well as generate shrinkage safety checks. Then enums without explicit values are range checked while enums with explicit values have each value checked individually. The current implementation is rather simplistic and chains OR's together. In the future, it would be better to instead generate a switch with each value as a case.I've been preoccupied with non-Zig stuff, so I'm a little out of the loop on how we want to test these. I'd appreciate some pointers. Thanks.
Related to #89.