Stack size of a Task in its lifetime

Does a Task preallocate reserved_stack::Int virtual bytes for its stack, or is it an upper limit for a dynamically resizing stack? Does it diverge from how stack size is managed for calls in the main thread? If it’s version-dependent or OS-dependent, I only need 1.12 on Windows but would like to hear the variation.

Hopefully someone who knows more (especially about Windows) can chime in, but for now I can at least share my understanding.

I’m pretty sure that the bytes are pre-allocated, and we don’t have a dynamically resizing stack (hence why you can have a stack overflow error). Your OS might deliver that memory in pages though, so even if you request more memory than you have, it might not be ‘active’ until touched (this is OS dependant though).

Pretty sure no. I don’t think there’s anything special about the ‘main’ task.

A dynamically resizing stack with an upper limit would also cause stack overflow errors, but I also share that intuition because I would assume a dynamically resizing stack would allow much a much more generous upper limit like Go’s goroutine stacks going from an initial 2KB up to 1GB.

While there’s no guarantees about it as far as I’m aware, julia programs are currently written assuming that stack pointers are ‘stable’ throughout the lifetime delineated by the function body.

E.g. when you pass a stack allocated struct that’s bigger than the address-size to a non-inlined function, it is passed as a stack pointer. This means that as far as I’m aware, the stack can’t be dynamically grown, otherwise those pointers could become invalid if the stack was re-allocated.

For example:

julia> code_llvm(Tuple{Int, Int}) do x, y
           tup = (x, y)
           @noinline sum(tup)
       end
; Function Signature: var"#71"(Int64, Int64)
;  @ REPL[33]:2 within `#71`
define i64 @"julia_#71_5785"(i64 signext %"x::Int64", i64 signext %"y::Int64") #0 {
top:
  %"new::Tuple" = alloca [2 x i64], align 8
  store i64 %"x::Int64", ptr %"new::Tuple", align 8
  %0 = getelementptr inbounds i8, ptr %"new::Tuple", i64 8
  store i64 %"y::Int64", ptr %0, align 8
;  @ REPL[33]:3 within `#71`
  %1 = call i64 @j_sum_5787(ptr nocapture nonnull readonly %"new::Tuple")
  ret i64 %1
}

Notice that @j_sum_5787 is being called on an alloca’d pointer. If somewhere deep inside the callstack of @j_sum_5787, we did a bunch more stack allocations, and had to dynamically grow the stack, these pointers would become invalid.

For another example, MArray from StaticArrays.jl also makes library-level direct use of stack pointers, and it seems to work fine.

Do you understand what this comment means then? julia/base/io.jl at cf5f5ebff6dc579ffbb559e6a3b5ccda302307a8 · JuliaLang/julia · GitHub

My understanding was that Julia doesn’t have stable stack pointers.

You might find Increase default stack size limit on 64-bit systems · Pull Request #55185 · JuliaLang/julia and document the optional argument of the `Task` constructor · Issue #55005 · JuliaLang/julia · GitHub relavent.