News for hackers. Stuff that matters.
Re:Crafting Interpreters with Rust: On Garbage Collection by bombela on Wednesday July 31, 2024 @01:34PM (#41121276)
> How much memory is used for the stack and when it is freed can always be determined at compile-time.

Technically, not always. You can allocate on the stack at runtime, see "alloca" in C.

But is alloca even that useful in practice? I struggle to find a good example.

Re:Crafting Interpreters with Rust: On Garbage Collection by ozgrakkurt on Wednesday July 31, 2024 @03:45PM (#41122743)
This was used in linux kernel and they were removing it later because it was a huge pain as far as I can remember. Seems like it causes problems because entire system is built assuming stack/heap work in a certain way.
Re:Crafting Interpreters with Rust: On Garbage Collection by ltungv on Wednesday July 31, 2024 @02:53PM (#41122205)
Oh right. I read about this once or twice and have never looked at it again. I've made the (wrong) assumption that this function needs some compile-time help. Is it all done at runtime? Or does that depend on the actual C implementation?
Re:Crafting Interpreters with Rust: On Garbage Collection by bombela on Wednesday July 31, 2024 @06:51PM (#41124378)
Stack allocation is always done at runtime. Baring special optimizations, the CPU and the code work together to (stack grows downward) decrement the CPU stack pointer register. This register keeps track of where the bottom of the stack is.

What is done at compile time by the compiler, is calculating the amount to decrement the CPU stack pointer register at runtime for every stack variables.

The compiler and alloca must work together, because optimization can easily break something here. For example, when a function return, the stack pointer register better be restored properly. Furthermore, in Rust you must not forget to call Drop::drop() for all types that have such a destructor.

Re:Crafting Interpreters with Rust: On Garbage Collection by tick_tock_tick on Wednesday July 31, 2024 @01:40PM (#41121334)
I mean it's basically the fastest allocation possible. It's amazing under a very very unique situation.

- you need a lot of memory

- it needs to be as fast as possible

- it's a leaf function that can never call another function

99.9% of the time those aren't all true.

Re:Crafting Interpreters with Rust: On Garbage Collection by alexchamberlain on Wednesday July 31, 2024 @01:43PM (#41121369)
Why does it have to be a leaf function?
Re:Crafting Interpreters with Rust: On Garbage Collection by jfoutz on Wednesday July 31, 2024 @01:48PM (#41121421)
So the massive stack grab isn't permanent.

Conceptually, if the compiler could just inline all of the child calls, it's fine. But if this call won't return for a long time, or may be called again (even worse). You're setting aside stack that you'll never get back. Which is a memory leak, and your program will crash.

Re:Crafting Interpreters with Rust: On Garbage Collection by alexchamberlain on Wednesday July 31, 2024 @02:01PM (#41121548)
That reasoning leads me to conclude the function should be short lived or the memory in use by all child functions, rather than it has to be a leaf function?
Re:Crafting Interpreters with Rust: On Garbage Collection by jfoutz on Wednesday July 31, 2024 @03:40PM (#41122702)
yeah. Leaf is a good rule of thumb, but if you need a little helper to do something it's generally fine.

you really do need confidence the little helper can be fully inlined. if the helper is out of your control, and someone greatly expands its responsibility, it can suck.

So, yeah, effectively a leaf.

Re:Crafting Interpreters with Rust: On Garbage Collection by bombela on Wednesday July 31, 2024 @05:53PM (#41123881)
The stack grab is as permanent as any other stack variable. There is nothing special about it. The only difference is that you can decide at runtime how much to add to the current stack frame.
Re:Crafting Interpreters with Rust: On Garbage Collection by mypalmike on Wednesday July 31, 2024 @05:58PM (#41123920)
If the memory's lifetime correctly coincides with the function (which is why you might use alloca in the first place), I don't see how this would be a memory leak nor why it would lead to a crash. Maybe on systems which limit stack size...?
Re:Crafting Interpreters with Rust: On Garbage Collection by jfoutz on Wednesday July 31, 2024 @08:02PM (#41124842)
just don't do this.

  int a(){
    void* mem = allocal(some_number);
    b();
  }

  int b(){
    a();
  }
if a() is a leaf (or can be trivially inlined) it's fine!

on the other hand, if a() calls something else, that's complicated, it's way way harder to figure out why you're crashing. I think you'll segfault as a() eventually overwrites malloc'ed memory. but different systems are going to have different characteristics.

It's not that you can't, you just have to be real damn smart and make sure no one ever messes with your code.

or you can just make sure it's a leaf. much easier rule, much easier to explain.

Re:Crafting Interpreters with Rust: On Garbage Collection by bombela on Wednesday July 31, 2024 @10:43PM (#41125722)
Your example behaves the same with or without alloca.

It will run until it runs out of stack. Or it might just run forever because the compiler optimized it as an infinite loop.

In any case, same behavior.

Re:Crafting Interpreters with Rust: On Garbage Collection by jfoutz on Wednesday July 31, 2024 @11:00PM (#41125778)
True. How would you modify it to get the point across?

Or am I crazy, and there is no point to being careful with alloca?

Re:Crafting Interpreters with Rust: On Garbage Collection by bombela on Wednesday July 31, 2024 @11:34PM (#41125898)
Maybe if alloca is used with a runtime value uncapped, a user could then trigger a stack overflow with too big of an allocation. This is the same problem when allocating on the heap, with the difference that the heap is a few order of magnitude larger.

That is really the only difference between `in foo[...N] = ...N` and alloca(n). alloca works with a value computed at runtime. And this value could be from an input.

Re:Crafting Interpreters with Rust: On Garbage Collection by slaymaker1907 on Wednesday July 31, 2024 @06:41PM (#41124288)
There's another reason: we need to do string stuff and we absolutely can't stall. Malloc always has some chance of stalling due to heap fragmentation. The leaf function requirement is also not necessary. You just have to make sure you don't alloca too much memory given how much is remaining on the stack and roughly how much memory you know subsequent calls will take.

It's not all that much worse than using a huge, static stack allocation. In fact, there are cases where alloca is safer since you can take into account how much stack memory you already have available before incrementing the stack pointer.

Re:Crafting Interpreters with Rust: On Garbage Collection by bombela on Wednesday July 31, 2024 @10:55PM (#41125762)
With a huge static stack allocation the stack check must be done within a parent function instead of within the same function.

In any case it is up to you to do the stack check. Though alloca can easily be wrapped by a function running the check.

In Rust I would write a check stack function that takes a function/closure as parameters. Within the closure, you are guaranteed to have at least the requested stack space.

The compiler will inline it all nicely, while preserving the semantic of the stack allocation.

Re:Crafting Interpreters with Rust: On Garbage Collection by jamesmunns on Thursday August 01, 2024 @01:11PM (#41131300)
Additionally, recursion and function pointers (including dynamic dispatch) are the other two "normal" language features that can defeat static stack analysis. There are techniques for working around this (bounded annotations, languages with strong types can narrow potential dispatch options), but it's a little harder.