-
-
Notifications
You must be signed in to change notification settings - Fork 418
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
Infinite loops/recursion are optimised incorrectly #1930
Comments
This is a hard problem, and I don't think there is an ideal solution.
It is possible, but there is no general solution. The basic idea is to look for cycles in the program's Control Flow Graph (CFG, the compiler's view of the "atomic" blocks of code in the program, like a branch of a conditional). Trivial cases like your example can be solved with relative ease but the general case is equivalent to the halting problem, which is undecidable (with some subtleties on actual computers but that doesn't change anything for us). LLVM is able to do some limited CFG analysis for infinite recursion (or infinite loops, which are the same problem), often for optimisation purposes. The current implementation can cause surprising and even undesirable behaviour. For example, with optimisations enabled, your code is compiled down to this: ; Function Attrs: norecurse nounwind readnone
define fastcc void @Main_tag_create_oo(%Main* nocapture dereferenceable(264), %Env* noalias nocapture readonly dereferenceable(64)) local_unnamed_addr #5 {
ret void
} The recursion was completely eliminated and if you run the program, it will simply exit immediately. This won't cause much trouble but some cases are much more concerning. For example: actor Main
new create(env: Env) =>
foo()
fun foo(): U32 =>
foo() The ; Function Attrs: nounwind readnone
define fastcc i32 @Main_ref_foo_I(%Main* nocapture readnone dereferenceable(264)) #0 {
ret i32 undef
}
These problems aren't new in LLVM land, both Rust and Swift (and probably many others) have been (and still are) subject to them. The
There are various ways to do that based on the platform. There is libsigsegv on Linux, structured exceptions on Windows, and probably something that I don't know of on OSX. I've not dug into it much further but I think it would be possible to integrate these systems into the Pony runtime, assuming that a stack overflow always causes a segmentation fault or a structured exception. |
Right that makes sense with regard to the compile time checks causing more trouble than they might solve and therefore probably not being appropriate. However, taking a quick look at libsigsegv, it does actually seem like a viable solution for providing better feedback - it also seems to support OSX. It looks like it would introduce essentially no extra overhead into the runtime and the binary footprint change is only around 4K apparently (for static linking). However, skimming over the docs it was not immediately clear how easy it is to introduce in a heavily multithreaded application. Also, I assume that if one were to try provide more feedback than simply "stack overflow" then one would need to hook into internal Pony bookkeeping in order to find the actor that triggered the overflow, and possibly the method. All in all, probably not a high priority, but possibly still a nice-to-have. |
[off-topic] |
@sgebbie I do think that we'll need some compile-time stuff to solve some of the problems, particularly the @agarman These are all very interesting subjects, but let's keep this issue on topic (the problems arising from infinite recursion). If you'd like to discuss an idea about the language, I'd suggest the development mailing list. Also, note that Pony already supports tail-cail elimination through LLVM's optimisations (and the crux of the problem here is that these optimisations aren't modeling infinite recursion as we'd like them to). The issue with the optimisation here is the absence of side-effects. That is, LLVM transforms any infinite recursion (or infinite loop) that doesn't contain side-effects into something that "doesn't happen". We can exploit that behaviour by making the code generator add no-op side-effects (an equivalent to a Pony Also, I think there is an underlying semantic issue with crashes related to memory exhaustion (whether it is exhaustion of the heap due to too many allocations or exhaustion of the stack due to too much recursion) and crashes in general. My biggest problem is with the "no crashes" claim, which is very good from a marketing standpoint but isn't accurate.
And then we'd have memory exhaustion crashes aside of that. Infinite recursion would be defined as possibly causing a memory exhaustion crash ("possibly" because of tail-call elimination). |
Summary of everything that has been said around this problem. There is ongoing effort in the LLVM community to fix the various issues with infinite loops and infinite recursion. See the list of links below. At our level, there isn't much we can do. Detecting trivial infinite constructs in the compiler is certainly possible but I'm not sure it'll be useful, as real-world example are usually more complex. https://bugs.llvm.org/show_bug.cgi?id=965 |
This is blocked. |
This has been fixed in LLVM 12. Compilling the original code should now produce an infinite loop. Compiling in debug mode might still throw "bus error" when run. This is the resulting IR when compiling @Praetonus code with optimizations enabled: define dso_local void @Main_Dispatch(i8* %0, %1* nocapture readnone dereferenceable(8) %1, %3* nocapture readonly dereferenceable(8) %2) unnamed_addr #7 !pony.abi !2 {
; snip (a bunch of setup)
br label %12
12:
br label %12
} And this with optimizations disabled (note the recursive call on define fastcc i32 @Main_ref_foo_I(%Main* dereferenceable(256) %this) unnamed_addr !dbg !140 !pony.abi !4 {
entry:
%this1 = alloca %Main*, align 8
store %Main* %this, %Main** %this1, align 8
call void @llvm.dbg.declare(metadata %Main** %this1, metadata !143, metadata !DIExpression()), !dbg !144
%0 = call fastcc i32 @Main_ref_foo_I(%Main* %this), !dbg !145
ret i32 %0, !dbg !145
} |
- Update the LLVM submodule to `llvmorg-12.0.0` - Update the JIT code to work with LLVM 12. - Update custom optimizations to work with LLVM 12. - Move old JIT and optimization code to separate files for historical reference - Update handling LLVM library dependencies: - Consolidate LLVM library configuration in the root `CMakeLists.txt` file. - Deprecate the `llvm_config` cmake macro in favour of using individual components with `llvm_map_components_to_libnames`. - Include the in-progress work on Mac M1: - Update CMakeLists.txt to require C++14 (LLVM now does). - Add missing header to cpu.c that is required on Apple M1. - Fixes #1930
- Update the LLVM submodule to `llvmorg-12.0.0` - Update the JIT code to work with LLVM 12. - Update custom optimizations to work with LLVM 12. - Move old JIT and optimization code to separate files for historical reference - Update handling LLVM library dependencies: - Consolidate LLVM library configuration in the root `CMakeLists.txt` file. - Deprecate the `llvm_config` cmake macro in favour of using individual components with `llvm_map_components_to_libnames`. - Include the in-progress work on Mac M1: - Update CMakeLists.txt to require C++14 (LLVM now does). - Add missing header to cpu.c that is required on Apple M1. - Fixes #1930
- Update the LLVM submodule to `llvmorg-12.0.0` - Update the JIT code to work with LLVM 12. - Update custom optimizations to work with LLVM 12. - Move old JIT and optimization code to separate files for historical reference - Update handling LLVM library dependencies: - Consolidate LLVM library configuration in the root `CMakeLists.txt` file. - Deprecate the `llvm_config` cmake macro in favour of using individual components with `llvm_map_components_to_libnames`. - Include the in-progress work on Mac M1: - Update CMakeLists.txt to require C++14 (LLVM now does). - Add missing header to cpu.c that is required on Apple M1. - Fixes ponylang#1930
Note: #2592 can be reverted when we upgrade to LLVM 12 |
- Update the LLVM submodule to `llvmorg-12.0.0` - Update the JIT code to work with LLVM 12. - Update custom optimizations to work with LLVM 12. - Move old JIT and optimization code to separate files for historical reference - Update handling LLVM library dependencies: - Consolidate LLVM library configuration in the root `CMakeLists.txt` file. - Deprecate the `llvm_config` cmake macro in favour of using individual components with `llvm_map_components_to_libnames`. - Include the in-progress work on Mac M1: - Update CMakeLists.txt to require C++14 (LLVM now does). - Add missing header to cpu.c that is required on Apple M1. - Fixes ponylang#1930
- Update the LLVM submodule to `llvmorg-12.0.0` - Update the JIT code to work with LLVM 12. - Update custom optimizations to work with LLVM 12. - Move old JIT and optimization code to separate files for historical reference - Update handling LLVM library dependencies: - Consolidate LLVM library configuration in the root `CMakeLists.txt` file. - Deprecate the `llvm_config` cmake macro in favour of using individual components with `llvm_map_components_to_libnames`. - Include the in-progress work on Mac M1: - Update CMakeLists.txt to require C++14 (LLVM now does). - Add missing header to cpu.c that is required on Apple M1. - Fixes #1930
- Update the LLVM submodule to `llvmorg-12.0.0` - Update the JIT code to work with LLVM 12. - Update custom optimizations to work with LLVM 12. - Move old JIT and optimization code to separate files for historical reference - Update handling LLVM library dependencies: - Consolidate LLVM library configuration in the root `CMakeLists.txt` file. - Deprecate the `llvm_config` cmake macro in favour of using individual components with `llvm_map_components_to_libnames`. - Include the in-progress work on Mac M1: - Update CMakeLists.txt to require C++14 (LLVM now does). - Add missing header to cpu.c that is required on Apple M1. - Fixes #1930
I accidentally coded a recursion which then results in a segmentation fault. The difficulty is not so much that it crashes, but that there is very little feedback as to why it crashed. In my case, since I did this completely inadvertently (some over enthusiastic copy-paste before I reworked the code), I wasn't expecting to find a recursion. Once I found it, it was obvious.
So, my question is really one of is there any way for the compiler detect and warn about the problem, or the run-time to provide better feedback rather than simply a segmentation fault.
The following code triggers a segmentation fault.
When run this results in generally results in the following output under Linux:
And on some occasions Linux responds with the following slightly more helpful response:
Admittedly, this is no worse than 'C' so maybe I'm just being hopeful:
Where as Java would at least provide a StackOverflowError:
The text was updated successfully, but these errors were encountered: