Skip to content
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

fix: raise UNREACHABLE #3194

Merged
merged 12 commits into from
Jan 16, 2023
Merged

Conversation

emc415
Copy link
Contributor

@emc415 emc415 commented Dec 15, 2022

What I did

fix #3070: added handling for raise UNREACHABLE

How I did it

How to verify it

Commit message

fix: raise UNREACHABLE

Description for the changelog

raise UNREACHABLE was generating CompilerPanic because of how parse_Raise calls _assert_reason, and then got passed to the block which handles assert_unreachable (IRnode.from_list(["assert_unreachable", test_expr])). This was breaking because None was being passed as a test_expr to assert_unreachable, which is not valid.
Added a new block in _assert_reason which handles raise UNREACHABLE and generates "INVALID" directly without going through the same path as assert_unreachable.

Cute Animal Picture

Put a link to a cute animal picture inside the parenthesis-->

@emc415 emc415 marked this pull request as draft December 15, 2022 20:15
@codecov-commenter
Copy link

codecov-commenter commented Dec 15, 2022

Codecov Report

Merging #3194 (d4a14af) into master (8141cdf) will increase coverage by 0.50%.
The diff coverage is 100.00%.

@@            Coverage Diff             @@
##           master    #3194      +/-   ##
==========================================
+ Coverage   88.43%   88.93%   +0.50%     
==========================================
  Files          88       84       -4     
  Lines       11046    10587     -459     
  Branches     2338     2208     -130     
==========================================
- Hits         9769     9416     -353     
+ Misses        822      760      -62     
+ Partials      455      411      -44     
Impacted Files Coverage Δ
vyper/ir/compile_ir.py 93.71% <ø> (ø)
vyper/codegen/stmt.py 89.30% <100.00%> (+1.13%) ⬆️
vyper/ast/validation.py 77.55% <0.00%> (-1.62%) ⬇️
vyper/ast/signatures/function_signature.py 88.28% <0.00%> (-1.27%) ⬇️
vyper/utils.py 83.08% <0.00%> (-0.73%) ⬇️
vyper/semantics/types/base.py 91.60% <0.00%> (-0.71%) ⬇️
vyper/codegen/ir_node.py 91.03% <0.00%> (-0.69%) ⬇️
vyper/builtins/_convert.py 90.87% <0.00%> (-0.17%) ⬇️
vyper/compiler/phases.py 90.90% <0.00%> (-0.17%) ⬇️
vyper/ast/nodes.py 93.77% <0.00%> (-0.16%) ⬇️
... and 26 more

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

…t_reason

commented out block in optimizer that causes this to fail.
@emc415
Copy link
Contributor Author

emc415 commented Dec 19, 2022

I changed the condition passed in _assert_reason in parse_Raise from None to 0. This changes the value of test_expr passed in _assert_reason(self, test_expr, msg) from None to 0 (False also works). None is not a valid value for test_expr, whereas 0 is. This is what is causing the error "None is not allowed as an IRnode"

Importantly, 0 used to be passed in parse_Raise when it was first implemented.
This got changed somewhere down the line.

My hypothesis is that it got changed when the block that I commented out in optimizer.py got introduced. This block raises a StaticAssertion error when 0 is passed into _assert_reason. I think that when this got introduced, None must've been added to parse_Raise to handle UNREACHABLE while avoiding 0, i.e. the optimization error. I am still combing through the history to verify this, but @charles-cooper if you have memories of doing this it would be helpful; I think it was potentially somewhere around efe1dbe -- feat: more arithmetic optimizations (#2647)
or feat: add optimizer guardrails, refactor (#2914); and i know this recent commit fixed some other parts of 2647 that were a bit too aggressive: fix: optimizer regression (#2868)

Anyways, I'm still going through the history to find exactly when this change happened and why, but if you remember anything regarding reasoning let me know!

Also, trying to understand if we can just remove the commented out block in optimizer.py because I don't really think that asserts or raises need to be optimized; if a dev is using them, they are using them on purpose and probably don't need them to be optimized. Let me know what you think about this.

Thanks!

@emc415
Copy link
Contributor Author

emc415 commented Dec 19, 2022

btw i found the location of the change:
allow arbitrary revert reason strings (#2509);
line 215 in vyper/old_codegen/stmt.py
4b9d860#diff-f4a4033c9a6b8afdf524b51945dde4ce1d68aac72b7778f6e249c6f2035d7541R215

Still working to understand why this happened.

(note: when i run the script outlined in the issue which 2509 aimed to fix (assert message strings #2449), it compiles successfully with my recent changes)

And added new usage of raise_unreachable to parse_Raise in codegen/stmt.py
Added raise_unreachable to ir/README.md
And added back the block I had commented out of optimizer.py
@emc415
Copy link
Contributor Author

emc415 commented Dec 23, 2022

Because ir/optimizer.py is correct and needs to stay as it is to catch internal compiler assert bugs:
We should add ir handling of “raise_unreachable”
This is my go at doing so
It is “inspired” by handling of assert_unreachable
It generates almost identical IR but adds a POP as described in parse_Raise in codegen/stmt.py
I am still investigating the usage and meaning of ‘0’ in IRnode.from_list([“raise_unreachable”], 0), and the reason for the added POP

@charles-cooper
Copy link
Member

We should add ir handling of “raise_unreachable”

very nice. that's clean, and mirrors the two way split between assert (for user assert) and straight revert (for user raise).

i think adding a new IR instruction raise_unreachable is unnecessary though as invalid is already a valid IR instruction (since it is already defined in our list of available EVM opcodes). so you can just remove the asm generation for raise_unreachable and emit invalid where you are currently emitting raise_unreachable.

@emc415
Copy link
Contributor Author

emc415 commented Dec 24, 2022

We should add ir handling of “raise_unreachable”

very nice. that's clean, and mirrors the two way split between assert (for user assert) and straight revert (for user raise).

i think adding a new IR instruction raise_unreachable is unnecessary though as invalid is already a valid IR instruction (since it is already defined in our list of available EVM opcodes). so you can just remove the asm generation for raise_unreachable and emit invalid where you are currently emitting raise_unreachable.

Okay, thanks!!

@emc415 emc415 marked this pull request as ready for review December 27, 2022 16:55
@emc415 emc415 changed the title wip: fix raise UNREACHABLE issue #3070 fix: raise UNREACHABLE Dec 28, 2022
if self.stmt.msg:
return self._assert_reason(test_expr, self.stmt.msg)
else:
return IRnode.from_list(["assert", test_expr], error_msg="user assert")

def parse_Raise(self):
if self.stmt.exc:
if isinstance(self.stmt.exc, vy_ast.Name) and self.stmt.exc.id == "UNREACHABLE":
self.stmt.exc.id = "raise UNREACHABLE"
Copy link
Member

Choose a reason for hiding this comment

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

why modify the ast here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

as a way to tag it as raise unreachable so that it doesn't get passed into IRnode.from_list(["assert_unreachable", test_expr]); otherwise by the time you get to _assert_reason there's no way that I know of to distinguish it

Copy link
Member

Choose a reason for hiding this comment

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

that's why the test expr is passed in as None, so assert_reason can distinguish -- see the final branch in the _assert_reason() implementation.

@emc415 emc415 marked this pull request as draft December 28, 2022 21:42
Comment on lines 163 to 168
if test_expr is not None:
return IRnode.from_list(
["assert_unreachable", test_expr], error_msg="assert unreachable"
)
else:
return IRnode.from_list(["invalid"], error_msg="raise unreachable")
Copy link
Member

Choose a reason for hiding this comment

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

nice -- since this check is performed twice, and it's actually not entirely clear what the check is for, i would factor out the expression (test_expr is None) into a variable called is_raise and add a comment about what it is doing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cool! Is it possible that None can be passed into parse_Assert from an internal compiler assert? I am just thinking that if so, it may be a little misleading if it got called 'is_raise', but also it shouldn't affect behavior.

Copy link
Member

Choose a reason for hiding this comment

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

no, parse_Assert only handles user asserts -- but if you are paranoid (which is not a bad thing!) you can add a sanity check in parse_Assert on the test expr before passing down into assert_reason().

@charles-cooper charles-cooper marked this pull request as ready for review December 29, 2022 01:33
@charles-cooper
Copy link
Member

this is looking good. i think we want to add a test for raise UNREACHABLE, you could add it to test_assert_unreachable.py.

Removed duplicate line in test_basic_call_unreachable
Added sanity check in parse_Assert
@emc415
Copy link
Contributor Author

emc415 commented Dec 29, 2022

cool, thanks! implemented your suggestions.

Also, I removed a line from test_basic_call_unreachable which I am pretty certain was a duplicate and not necessary. Just want to make sure to point it out to y'all in case it was indeed necessary.

@charles-cooper
Copy link
Member

thanks, @trocher what do you think about this fix?

@trocher
Copy link
Contributor

trocher commented Jan 9, 2023

This is looking good to me!

@charles-cooper charles-cooper enabled auto-merge (squash) January 16, 2023 17:04
@charles-cooper charles-cooper merged commit 4ae2527 into vyperlang:master Jan 16, 2023
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.

raise UNREACHABLE: "vyper.exceptions.CompilerPanic: None is not allowed as IRnode value"
5 participants