-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Assertions cleanups, annotate unreachable code #13242
base: trunk
Are you sure you want to change the base?
Conversation
runtime/caml/misc.h
Outdated
#elif defined(_MSC_VER) | ||
#define unreachable() (__assume(0)) | ||
#else | ||
#define unreachable() (abort()) |
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.
I'd rather see this defined as
#define unreachable() (abort()) | |
#define unreachable() (assert(0)) |
so that, should it ever fire, this gets recognized as an assertion failure rather than a mysterious SIGABRT
.
(And yes, given how eager clang and gcc are at arm-wrestling the C specs in order to win at picobenchmarks, I am expecting such unreachable code paths to be reachable when compiled with these compilers at some of their optimization levels).
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.
But what if C assertions are disabled with NDEBUG
? The code path may then be missing a return statement since there won't be a noreturn function to end it.
Similarly, if we used CAMLassert
instead, it would result in an empty statement in non-debug runtimes, triggering the same problem.
If I keep an assertion and add a return statement, I'll probably get warnings about an unreachable return statement after the unreachable annotation…
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.
There is no simple way to solve this without making every compiler combination happy. You probably should not remove the few return
following unreachable
then.
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.
The compilers differ too much in behavior… but in my test they complain about missing returns rather that unreachable code. I've changed my code to use CAMLassert
here.
I am nervous about In the cases where we know for sure that this place is dead code, |
That is my understanding too.
These are valid concerns that I'll try to address. I've remembered in the meantime that GCC can warn whenever a The diff of |
I'm willing to bet the CI will expose at least one compiler which will nevertheless emit warnings for this (its name probably starts with "MS" and ends with "VC"). |
I'm a bit disappointed by my last endeavor. GCC, clang, and MSCV have oh-so-subtly different behaviors regarding unreachable warnings and switch exhaustiveness check. This makes the code somewhat more verbose than I've previously hoped. |
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.
Aside the previous remark, I'm satisfied with the changes as they are now.
Antonin Décimo (2024/06/19 01:58 -0700):
In the meantime I'm tempted to leave this as-is an revisit later, if
that's fine.
I'll trust you on that, @dustanddreams?
|
Miod Vallat (2024/06/19 02:02 -0700):
@dustanddreams commented on this pull request.
> @@ -1418,7 +1418,7 @@ do_resume: {
#ifndef THREADED_CODE
default:
#ifdef _MSC_VER
- __assume(0);
+ unreachable();
That's fine.
Ah sorry, didn't see this.
|
Ah, this now needs to be rebased to resolve the conflict in Feel free to ping me when done. |
Rebase is done. Maybe @gasche still has comments? |
My comment remains that if some of those I only looked at the new version briefly but my impression is that it suffers from the same problem. For example:
|
68ce0f6
to
3bbb449
Compare
For debug mode, there's a possible alternative implementation where we trap the program if it reaches an
You're right, but...
I can convince myself that the runtime currently cannot fall in the default cases. It's true that it's true that this doesn't prevent us from future errors. As for user FFI code, to me this example shares a lot in common with converting the sum type ocaml representation to and from a C array of integers (think socket flags, or
On the other hand the error case for the interpreter seems to be only handled for the non-threaded code interpreter (debug mode or MSVC). Am I reading the code wrong? What happens when (threaded-code) Adding |
I see no benefit to making our system less usable/debuggable in this way. (In particular I very much doubt that there is any measurable performance benefit -- in the bigarray stuff.) Why do you want to do this? My impression (but I may be wrong and I'm not judging in any way) is that you think that In terms of gcc intrisics, I think we want
Again, this could be done with a hint that says "this code path is unlikely" and then follows with a fatal error, we don't need to introduce more undefined behaviors in our programs to get code-generation benefits. |
Rather than using C23 or C++'s unreachable annotation, we prefer trapping the program execution when an unreachable path is taken. MSVC currently has a bug with unreachable code analysis and its __fastfail intrinsic, thus the noreturn wrapper function. https://developercommunity.visualstudio.com/t/C-Code-Analysis-should-understand-that/10665570
Makes it easier to identify which part of the assertion has failed.
In some cases an compilers, this allows to remove the unreachable annotation, and the compiler checks that all cases are covered. In general, it also makes it easier to reason about the space of values.
Thank you for the pointers, that was really helpful. I've changed the definition of |
@@ -1417,13 +1417,9 @@ do_resume: { | |||
|
|||
#ifndef THREADED_CODE | |||
default: | |||
#ifdef _MSC_VER | |||
__assume(0); | |||
#else |
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 change may have a negative performance impact, I'm not sure. Have you tested it?
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.
(The test that I have in mind: use a dumb microbenchmark that does something compute-heavy for long enough, and compare the performance using ocamlrun
before and after the change. If you don't see any noticeable performance difference then we should be fine.)
This PR sports small cleanups around assertions in the runtime.
unreachable()
to mark unreachable code where it makes sense, and allow C compilers to optimize the code.CAMLassert(A && B);
intoCAMLassert(A); CAMLassert(B)
to better identify which part of the assertion fails.CAMLassert
can be replaced withstatic_assert
.(using the unreachable annotation will later be useful to optimize
ocamlc
too!)No change entry needed, I think.cc @NickBarnes