I've read a book about a BLISS compiler [1] that does this, but still uses a stack like I described [2]. It implements a hash table that used linked list nodes for collision. A new declaration adds a new name to the table, and it attaches the node to uses of the name in expressions of the syntax tree.
When a scope is exited, the declarations from that scope are removed from the symbol table, but because they're still attached to the syntax tree, they can't just be freed. They're added to a linked list of "purged" nodes, so that the information they contain can be used later during code generation, and then freed.
One-pass compilers don't have this problem; they really can just free the memory for reuse, because after they exit a scope, they've already generated the assembly or machine code from the high-level language.
However, I don't know what LLVM or GCC, or any other remotely modern compiler, does. I haven't read the code much.
[2]: Actually, it intertwines the stack and the symbol table in a complicated way, so there's only one hash table, and multiple stacks within it. It's explained by a diagram they include on page 13. You can find a PDF of it here: https://kilthub.cmu.edu/articles/journal_contribution/The_de...
I've read a book about a BLISS compiler [1] that does this, but still uses a stack like I described [2]. It implements a hash table that used linked list nodes for collision. A new declaration adds a new name to the table, and it attaches the node to uses of the name in expressions of the syntax tree.
When a scope is exited, the declarations from that scope are removed from the symbol table, but because they're still attached to the syntax tree, they can't just be freed. They're added to a linked list of "purged" nodes, so that the information they contain can be used later during code generation, and then freed.
One-pass compilers don't have this problem; they really can just free the memory for reuse, because after they exit a scope, they've already generated the assembly or machine code from the high-level language.
However, I don't know what LLVM or GCC, or any other remotely modern compiler, does. I haven't read the code much.
[1]: https://en.wikipedia.org/wiki/The_Design_of_an_Optimizing_Co...
[2]: Actually, it intertwines the stack and the symbol table in a complicated way, so there's only one hash table, and multiple stacks within it. It's explained by a diagram they include on page 13. You can find a PDF of it here: https://kilthub.cmu.edu/articles/journal_contribution/The_de...