scryer-prolog icon indicating copy to clipboard operation
scryer-prolog copied to clipboard

Rust panic from query `current_predicate(X:X)`

Open rotu opened this issue 8 months ago • 14 comments

?- current_predicate(X:X).

thread 'main' panicked at src/machine/system_calls.rs:3932:17:
internal error: entered unreachable code

rotu avatar May 04 '25 23:05 rotu

(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?)

UWN avatar May 05 '25 07:05 UWN

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.

rotu avatar May 05 '25 13:05 rotu

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.

bakaq avatar May 05 '25 22:05 bakaq

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

UWN avatar May 06 '25 10:05 UWN

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.

bakaq avatar May 06 '25 12:05 bakaq

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)?

UWN avatar May 06 '25 13:05 UWN

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.

bakaq avatar May 06 '25 13:05 bakaq

any system's language

Ada?

UWN avatar May 06 '25 14:05 UWN

rustc uses the stacker crate in recursive code to detect low remaining stack space and allocate more before it runs out.

Skgland avatar Aug 22 '25 08:08 Skgland

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.

hurufu avatar Aug 22 '25 09:08 hurufu

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_STACK at 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.

Skgland avatar Aug 22 '25 11:08 Skgland

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)).

hurufu avatar Aug 22 '25 11:08 hurufu

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.

hurufu avatar Aug 22 '25 11:08 hurufu

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

Skgland avatar Nov 23 '25 11:11 Skgland