-
-
Notifications
You must be signed in to change notification settings - Fork 30.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
Py_UNREACHABLE() no longer behaves as a function call #82386
Comments
With the following change, Python no longer compiles. I'm sure that it compiled successfully 2 weeks ago. diff --git a/Objects/longobject.c b/Objects/longobject.c
index 4cf2b0726e..0ad2609882 100644
--- a/Objects/longobject.c
+++ b/Objects/longobject.c
@@ -16,10 +16,10 @@ class int "PyObject *" "&PyLong_Type"
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=ec0275e3422a36e3]*/
#ifndef NSMALLPOSINTS
-#define NSMALLPOSINTS 257
+#define NSMALLPOSINTS 0
#endif
#ifndef NSMALLNEGINTS
-#define NSMALLNEGINTS 5
+#define NSMALLNEGINTS 0
#endif
_Py_IDENTIFIER(little); Main GCC error: Objects/longobject.c: In function '_PyLong_Copy': It seems to be a regression introduced by: commit 6b51998
|
This commit changed Py_UNREACHABLE() five days ago: If remove this change, it can be compiled successfully. |
We can change Py_UNREACHABLE() to assert(0) in longobject.c |
I believe that the problem is caused by the change in Py_UNREACHABLE() (3ab6147). Before the mentioned commit Py_UNREACHABLE() was an expression, now it's a block. Py_UNREACHABLE() macro is public (see https://docs.python.org/3/c-api/intro.html#c.Py_UNREACHABLE), so this change can cause similar problems outside of CPython (i.e. that change was breaking). |
Also quote from Py_UNREACHABLE() doc:
6b51998 does exactly that, it replaces assert(0) with Py_UNREACHABLE(). |
Right, it was changed by: commit 3ab6147
I add Zachary Ware in the nosy list. |
Whoops :(. Fixing this is beyond my C skill. I'd rather not lose the easter egg, but if it can't be fixed properly then 3ab6147 should just be reverted. |
While I don't like how that get_small_int macro is defined... it is probably best to just revert 3ab6147. The choice to use a macro for this when it was created and the way the macro was defined means it is an expression forever unless we want to bother forcing code cleanups for uses of it. |
I have poked at this a bit and have an implementation that defines a This works on at least macOS and Windows, and we also have prior art for defining static inline functions in headers in #54288. If we agree that that's an acceptable solution, I'll open a PR for that change. |
If use static inline function, and Py_UNREACHABLE() inside an if-else branch that should return a value, compiler may emit warning:
Other compilers (gcc, icc) don't emit this warning. This situation in real code: |
PR 16270 use Py_UNREACHABLE() in a single line. |
For the control path warning, that can be fixed by just hinting to the compiler that the function doesn't return like so: # ifdef __GNUC__
__attribute__ ((noreturn))
# elif defined(_MSC_VER)
__declspec(noreturn)
# endif
static inline void
_Py_UNREACHABLE()
{ |
I like the idea of implementing Py_UNREACHABLE() as a function. Would it make sense to even declare it as a regular function rather than a static inline function? What is the benefit of inlining here? Inlining can make the code larger, rather than a function call at the machine code level is shorter. |
I wrote PR 16280: "Convert Py_UNREACHABLE() macro to a function". This change fix this issue but also enhance Py_UNREACHABLE() which now dumps the Python traceback where the bug occurs. See my example: |
I propose to restrict this issue to Py_UNREACHABLE() macro/function. Please use bpo-37812 to discuss longobject.c. |
I prefer to keep it a macro. The compiler does not know that it is never executed, so it can generate a suboptimal code. While it is a macro, it can be made a no-op, or even with compiler-specific instructions like __builtin_unreachable. This can help the compiler to generate more optimal code. For example, the popular idiom: switch (kind) {
case PyUnicode_1BYTE_KIND: {
...
break;
}
case PyUnicode_2BYTE_KIND: {
...
break;
}
case PyUnicode_4BYTE_KIND: {
...
break;
}
default: Py_UNREACHABLE();
} could be compiled to the code equivalent to: if (kind == PyUnicode_1BYTE_KIND) {
...
break;
}
else if (kind == PyUnicode_2BYTE_KIND) {
...
break;
}
else { // assuming (kind == PyUnicode_4BYTE_KIND)
...
break;
} |
My PR 16280 uses _Py_NO_RETURN which uses __attribute__((noreturn)) with GCC and clang. I'm not sure how __builtin_unreachable could be used with Py_UNREACHABLE() macro.
I understood that Py_UNREACHABLE() is used on purpose to prevent undefined behavior. For example, if a function accepts an enum, but is called with a value which is not part of the enum: what should happen? Should Python crash? Usually, we try to be nice and return an error. But sometimes, you cannot report an error and so Py_UNREACHABLE() is a good solution.
I don't see how PR 16280 could have an effect on that. I don't see how the compiler can guess that the code is never executed with the current macro. -- Using a function allows to put a breakpoint on it. In fact, I can easily modify PR 16280 to keep the macro, since I only call Py_FatalError() with a string. The function body is simple. |
FWIW I proposed to add Py_ASSUME() macro that uses __builtin_unreachable() in bpo-38147. |
_Py_NO_RETURN is a promise that the code past the function is unreachable, not that the function call is unreachable.
In the release mode Py_UNREACHABLE() can be replaced with __builtin_unreachable().
If the accessibility of the code depends on the call from Python code, we should raise normal exception. If it is not possible to pass it out of the function, we should use PyErr_WriteUnraisable(). If the accessibility of the code depends on the call from C code, If we are in the situation where recovering from errors is not possible (for example if the memory manager is broken) we use Py_FatalError(). If we are absolutely sure that the code never can be executed (unless memory was corrupted or something like), we use Py_UNREACHABLE(). It will silence compiler complains, fail loudly in debug mode if our assumption is wrong (this is our programming bug), and allow the compiler to optimize out the code if substituted by __builtin_unreachable() in the release mode.
But we can make the macro to expand to __builtin_unreachable in the release mode. Or just to no-op if there is other way to silence the compiler warning.
You can put a brackpoint on Py_FatalError().
It would be nice. We can consider using __builtin_unreachable() in different issue. I am also going to use Py_UNREACHABLE() more widely to write more uniform, but still optimal code. It could replace if (cond1) { ...
} else if (cond2) { ...
} else if (cond3) { ...
} else {
assert (!cond4);
...
} with if (cond1) { ...
} else if (cond2) { ...
} else if (cond3) { ...
} else if (cond4) { ...
} else {
Py_UNREACHABLE();
} |
Serhiy created bpo-38249: "Optimize out Py_UNREACHABLE in the release mode". |
The initial issue has been fixed in 3.8 and master. I close the issue. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: