That changes everything I originally thought about Tuples / “stack allocation”. Wow…
edit: Thanks for putting up with me, I think I’m understanding it better and better
edit2: I have an idea for another test script. Going to fiddle around with this today
Also another clarification that I think @stronny mentioned but I want to go deeper: when a program is compiled, the compiler (in this case LLVM) will know how much stack space every function needs. So if in your method you have:
def foo
a = {1, 2, 3}
a
end
then it can see that you need 12 bytes (4 bytes per int, 3 ints = 12), and that’s it.
Variables are easy to see, but the compiler will allocate memory for function calls too, basically temporary unnamed data. For example:
def foo
loop do
call_something(make_tuple)
end
end
Let’s say make_tuple returns the same tuple as above, 12 bytes. Then the compiler (or the LLVM that we generate) looks something like this:
def foo
space_for_tuple = ...
loop do
space_for_tuple = make_tuple
call_something(space_for_tuple)
end
end
so even if you make millions of tuples, they are always stored in the same place in the stack… because each loop iteration doesn’t need to know the previous values. And if you do need them, well, you are passing them (by copying them) to call_something.
When you do:
hash[key] = tuple
that is actually a method call:
hash.[]=(key, tuple)
so a copy of the tuple is passed to the method. Eventually somewhere in Hash’s code the hash has a pointer to heap memory and that’s where the tuple’s contents are stored. If it’s something other than a hash, let’s say a struct, and that struct is a local variable, then the tuple’s contents are copied to that’s struct’s space, and because it’s a local variable and it lives in the stack then it’s copied there.