daslang 0.6.4 at master 237df6024, Linux x86_64 (WSL2, kernel 6.6), gcc 11.4, Release.
A recursion that outgrows the native stack kills the process with SIGSEGV in the interpreter. options stack sizes the simulated stack only; the interpreter's callOrFastcall recurses on the C++ stack with no depth guard, so with a large options stack the simulated-stack check never fires and the native stack overflows first. The same program under -jit runs (LLVM turns the self tail call into a loop), so the crash depends on the tier.
Reproduction
deep.das:
options gen2
options stack = 4194304
require strings
def count_down(n : int; acc : int) : int {
if (n == 0) {
return acc
}
return count_down(n - 1, acc + 1)
}
[export]
def main() {
let args = get_command_line_arguments()
let depth = to_int(args[length(args) - 1])
print("depth={depth} result={count_down(depth, 0)}\n")
}
$ daslang deep.das -- 100000
depth=100000 result=100000
$ daslang deep.das -- 1000000
CRASH: SIGSEGV (Segmentation fault) (signal 11) at address 0x7ffd8f63aff8
Stack trace:
[ 0] [0x12]
[ 1] das::Context::~Context()
$ echo $?
139
$ daslang -jit deep.das -- 10000000
depth=10000000 result=10000000
With the default options stack the same depth reports stack overflow while calling count_down and exits cleanly - the simulated stack is exhausted before the native one. The crash is what happens once the simulated stack is made large enough to no longer be the limit.
Expected
Recursion depth is bounded by a diagnostic on every tier: stack overflow while calling <fn> (the message the simulated-stack check already produces), recoverable like any other runtime error, whatever options stack says. A bare SIGSEGV from a program that compiled is not a diagnostic. If the native depth is meant to be the program's own responsibility, the reference for options stack should say that it bounds only the simulated stack.
Test
Drops into tests/language/; on master the interpreter and -jit runs of the file both die with SIGSEGV instead of reporting.
options gen2
options stack = 4194304
require dastest/testing_boost
def count_down(n : int; acc : int) : int {
if (n == 0) {
return acc
}
return count_down(n - 1, acc + 1)
}
[test]
def test_deep_recursion_is_a_diagnostic_not_a_crash(t : T?) {
var overflowed = false
var result = 0
try {
result = count_down(1000000, 0)
} recover {
overflowed = true
}
t |> success(overflowed || result == 1000000, "either the call completes or the runtime reports a stack overflow")
}
$ daslang dastest/dastest.das -- --test deep_recursion.das
Segmentation fault (exit 139)
$ daslang dastest/dastest.das -jit -- --test deep_recursion.das
CRASH: SIGSEGV (Segmentation fault) (signal 11) at address 0x7ffffcfccff8
daslang0.6.4 at master237df6024, Linux x86_64 (WSL2, kernel 6.6), gcc 11.4, Release.A recursion that outgrows the native stack kills the process with SIGSEGV in the interpreter.
options stacksizes the simulated stack only; the interpreter'scallOrFastcallrecurses on the C++ stack with no depth guard, so with a largeoptions stackthe simulated-stack check never fires and the native stack overflows first. The same program under-jitruns (LLVM turns the self tail call into a loop), so the crash depends on the tier.Reproduction
deep.das:With the default
options stackthe same depth reportsstack overflow while calling count_downand exits cleanly - the simulated stack is exhausted before the native one. The crash is what happens once the simulated stack is made large enough to no longer be the limit.Expected
Recursion depth is bounded by a diagnostic on every tier:
stack overflow while calling <fn>(the message the simulated-stack check already produces), recoverable like any other runtime error, whateveroptions stacksays. A bare SIGSEGV from a program that compiled is not a diagnostic. If the native depth is meant to be the program's own responsibility, the reference foroptions stackshould say that it bounds only the simulated stack.Test
Drops into
tests/language/; on master the interpreter and-jitruns of the file both die with SIGSEGV instead of reporting.