Rust panic from query `current_predicate(X:X)`
?- current_predicate(X:X).
thread 'main' panicked at src/machine/system_calls.rs:3932:17:
internal error: entered unreachable code
(Just a terminology question: You call this a crash. But then Rust was able to detect that problem and stopped immediately without wasting resources. How would you call then those cases where a real core dump is produced due to segmentation violations and the like?)
I’m using the word “crash” loosely and would welcome correction if there’s a better term.
I would expect the query to fail silently or generate a Prolog error.
The better word here would be "panic" I think, like in the error message. In the context of Rust it has a clear meaning, though what it actually does on panic is configurable. If it's configured to abort, then it's the same as an actual crash, with the difference that it is "explicitly intended" by the program (not like a segmentation fault which is enforced by the OS). If it's configured to unwind (the default and what is happening here), then it has the behavior of cleaning the stack, like exceptions in languages like Java and C++. This cleans resources and can even be caught to recover from it (though it's very rare to do so in Rust). And, again, it's "explicitly intended" by the program instead of the OS.
If it's configured to unwind (the default and what is happening here), then it has the behavior of cleaning the stack, like exceptions in languages like Java and C++. This cleans resources and can even be caught to recover from it (though it's very rare to do so in Rust).
Does this mean that memory overflows could be recovered that way? This is one of the weaknesses of Scryer. Think of memory overflows happening during bignum calculations. Currently this blocks testing of (^)/2 in clpz, as overflows are reached so rapidly. Example:
?- X #> X^X^X^X, X #> 7.
Killed
Does this mean that memory overflows could be recovered that way?
No, Rust just aborts on out of memory, which means it can't be caught. There are plans to allow configuring out of memory behavior and allowing fallible allocations, but these are still experimental, for now nightly only, and probably will take quite some time to get stabilized. The best we can do now in stable is be really careful with try_reserve or raw allocation calls, like is currently being done in many places in rebis-dev.
This is one of the places Zig shines much more than Rust. Not only Zig gives you tools to handle memory errors, it actively encourages it and helps you find any bad paths through the program caused by allocation failures.
This is one of the places Zig shines much more than Rust.
Can it also catch overflows on the call stack (I refer to this usually as the C-stack)?
The best we can do now in stable is be really careful with try_reserve or raw allocation calls, like is currently being done in many places in rebis-dev.
Actually, I think we could do a custom GlobalAlloc that can print a resource error before aborting. I think that would be better the the current situation right? I will investigate. With some contorting we may even be able to trigger garbage collection with it.
Can it also catch overflows on the call stack (I refer to this usually as the C-stack)?
As far as I know, no. I don't think any system's language has this ability, and I don't even see how this could be implemented apart from instrumenting every function call, though I can be wrong. Zig can force tail calls however, which is useful to avoid stack overflows even on some recursive functions.
Wait, I think you can catch a SEGV signal in Unix, so with some shenaningans we may be able to provide at least a resource error message in those cases, though anything actually useful seems hard to do portably. I'm not sure what to do in other targets like Windows and Wasm.
any system's language
Ada?
rustc uses the stacker crate in recursive code to detect low remaining stack space and allocate more before it runs out.
My 5 cents, not really relevant to the discussion :)
All modern OSes will grow program stack on demand, so actually stack overflow happens only when you have uncontrollable recursion and even then program will receive a signal informing it about memory segmentation violation, which can be handled, but usually shouldn't. In C you can perform a non-local goto (longjmp) from withing signal handler – but I never tried this in practice.
Also On Linux out-of-memory doesn't really happen, because of swap space which is used transparently. And if system really doesn't have memory then it spawns "oom-killer" which unceremoniously kills the most offending process. The only case I see it can happen is when there is plenty of RAM, but it is highly fragmented, or you have disabled memory overcommit (echo 2 > /proc/sys/vm/overcommit_memory).
How to handle this in Rust – I have no idea.
All modern OSes will grow program stack on demand, ...
I couldn't find definitive answer to this, I found
- claims that on linux this only applies to the main thread of a process
Found this documentation that appears to corroboratethat claim
A thread's stack size is fixed at the time of thread creation. Only the main thread can dynamically grow its stack. pthread_attr_setstacksize(3)
- claims that it used to be unlimited but
RLIMIT_STACKat some point was introduced with a default of 8MiB^1, but without any current documentation backing this up this could be outdated - for windows the documentation for the /STACK compiler flag indicates to me a default of a maximum 1MB (MiB?) allocated in increments of 4KB (KiB?)
So, while they don't immediately allocate this full limit but grow the allocated stack in chunks on demand, there is still a limit and the stack won't automatically grow to fill um all available RAM.
I don't know much about Windows, but on Linux maximum stack size can be arbitrary large, ulimit -s sets this. For thread stack (pthread_attr_setstacksize) it also isn't a problem, because usually Linux overcommits memory, you can allocate even terabytes of memory it will segfault on first access.
UPDT: Surprisingly, you need to do quite a lot of work to make sure that all allocated memory actually exists (look into madvise(2)).
And even then, when you think that you've exhausted all available RAM, you can call swapon(8) and add more swap space dynamically. Real memory is more complex than all programming languages try to model, same goes with file system access, it is just pure insanity.
UPDT: Offtopic to the offtopic: https://www.youtube.com/watch?v=wGSSUSeaLgA You can learn how crazy memory access on modern CPU is.
Does this mean that memory overflows could be recovered that way?
No, Rust just aborts on out of memory, which means it can't be caught. There are plans to allow configuring out of memory behavior and allowing fallible allocations, but these are still experimental, for now nightly only, and probably will take quite some time to get stabilized.
Some context to this:
What to do when an infallible functions encounters an allocation error is defined by the std::alloc::handle_alloc_error hook.
The default behavior is:
If the binary links against std (typically the case), then print a message to standard error and abort the process. This behavior can be replaced with set_alloc_error_hook and take_alloc_error_hook. Future versions of Rust may panic by default instead.
If the binary does not link against std (all of its crates are marked #![no_std]), then call panic! with a message. The panic handler applies as to any panic.
We are in the first case and std::alloc::set_alloc_error_hook is still unstable, the relevant tracking issue being rust-lang/rust#51245