TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

Flattening ASTs and other compiler data structures (2023)

157 pointsby aw16211074 months ago

16 comments

jgrowl4 months ago
I thought a reddit comment on this article had an interesting point:<p><a href="https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;1d3b356&#x2F;my_new_favorite_way_to_represent_asts_or_any&#x2F;l6al1q8&#x2F;" rel="nofollow">https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;1d3b356&#x2F;my_new_favori...</a><p>[–]Timzhy0 3 points 7 months ago<p>Btw I think one can go a step further than the author, there is no need to keep two explicit ExprRef baked in a binary node (lhs, rhs). You can exploit locality, basically the AST, seen it the LISP way, is just an arbitrarily nestable list, where elements are atoms or other lists. Hence all you need to know is where each list ends (and if it&#x27;s an atom you can assume it spans one node) and actually one bit to know if it is the last entry in the list is quite ergonomic as well (because then you can distinguish whether moving next slot in the AST means there is a sibling). Basically it&#x27;s easier to keep it sync while constructing and takes up less memory per node. I pay 40 bits per node, stored interleaved for best cache locality (some unaligned accesses but I think it&#x27;s still worthwhile), 8 bits for the tag, 32 for the data, if data is bigger, 32 is an index into some auxiliary segment (basically a ptr).
评论 #42667928 未加载
dmagyari4 months ago
&quot;Instead of allocating Expr objects willy-nilly on the heap, we’ll pack them into a single, contiguous array.&quot; Zig compiler pipeline (AST, Zir, Air, Sema) does exactly this on all layers. Not only contiguous, but instead of array-of-structs it is struct-of-arrays, so walking the tree is even more cache friendly. For AST see: <a href="https:&#x2F;&#x2F;github.com&#x2F;ziglang&#x2F;zig&#x2F;blob&#x2F;master&#x2F;lib&#x2F;std&#x2F;zig&#x2F;Ast.zig#L13">https:&#x2F;&#x2F;github.com&#x2F;ziglang&#x2F;zig&#x2F;blob&#x2F;master&#x2F;lib&#x2F;std&#x2F;zig&#x2F;Ast.z...</a>
评论 #42664068 未加载
评论 #42662156 未加载
torginus4 months ago
I guess Rust&#x27;s contribution to high performance programming is that its borrow checker is so loathsome that it pushes people into using ideas like ECS or arenas just to not have to bother with it.
kazinator4 months ago
&gt; <i>Instead of allocating Expr objects willy-nilly on the heap, we’ll pack them into a single, contiguous array.</i><p>This happens naturally if you bump-allocate them in a garbage-collected run-time, particularly under a copying collector. Free lists also tend to co-locate because they are produced during sweep phases of GC which run through heaps in order of address.<p>Don&#x27;t make me bring out the L word for the billionth time.<p>&gt; <i>A flat array of Exprs can make it fun and easy to implement hash consing</i><p>OK, it&#x27;s not a case of L-ignorance, just willful neglect.
评论 #42661057 未加载
评论 #42668052 未加载
Agraillo4 months ago
About 10 years ago working with AST trees I (re)invented a flat structure representing trees in a flat array. It reminds me of what is described here but not exactly. In my case I needed only two indices per node: total sub-region length of all the children and sub-children and parent index (so no need to have indices of all children). Total sub-length basically can be interpreted as the index of the next sibling. With such a structure it&#x27;s easy&#x2F;cheap to execute FindFirstChild&#x2F;FindNextSibling.<p>Later the theory behind such structures was revealed as &quot;Nested set model&quot; [1]. The article seems to not mention the internal representation, but I think that the implementation should use something like my solution, so fixed number of references per node<p>[1] <a href="https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Nested_set_model" rel="nofollow">https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Nested_set_model</a>
cardanome4 months ago
Amazing article, very good advice to keep your data structures flat.<p>Adding to that, it also makes editing the AST vastly more efficient.<p>I have discovered that principle on my own when I worked on an editor that directly operated on the AST instead of text. I found manipulating the tree-style AST so painful, constantly traversing the tree and all. Once I made it flat, my life was a hell lot easier. You can just directly index any part of AST in linear time.
userbinator4 months ago
Rediscovering techniques that were somewhat well-known in the 70s and 80s.<p>See also: <a href="https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Binary_heap" rel="nofollow">https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Binary_heap</a>
评论 #42664354 未加载
评论 #42665130 未加载
emptysea4 months ago
Rust-analyzer uses a similar technique for parsing <a href="https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;rust-analyzer&#x2F;blob&#x2F;master&#x2F;crates&#x2F;parser&#x2F;src&#x2F;event.rs">https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;rust-analyzer&#x2F;blob&#x2F;master&#x2F;crate...</a> which then gets fed into <a href="https:&#x2F;&#x2F;github.com&#x2F;rust-analyzer&#x2F;rowan">https:&#x2F;&#x2F;github.com&#x2F;rust-analyzer&#x2F;rowan</a> (lossless syntax tree)
评论 #42662028 未加载
Tarean4 months ago
Twee (an equational theorem prover in Haskell used by quickspec) has an interesting take on this. Terms are slices of arrays, but you get a normal interface including pattern matching via synonyms. It can also be nice to use phantom types of your references (array offsets), so if you project them into flat view types you can do so type safely<p>Requires the language to have something equivalent to pattern synonyms to be as invisible as twee, though.<p>In twee a TermList is a slice of a bytearray (two ints for offset&#x2F;length plus a pointer).<p>And a term is an int for the function symbol and an unpacked TermList for the arguments.<p>The pattern match synonyms load a flat representation from the array into a view type, and the allocation of the view type cancels out with the pattern matching so everything remains allocation free.<p><a href="https:&#x2F;&#x2F;hackage.haskell.org&#x2F;package&#x2F;twee-lib-2.4.2&#x2F;docs&#x2F;Twee-Term.html" rel="nofollow">https:&#x2F;&#x2F;hackage.haskell.org&#x2F;package&#x2F;twee-lib-2.4.2&#x2F;docs&#x2F;Twee...</a>
评论 #42668812 未加载
ww5204 months ago
This is a fantastic idea. AST works well in an array based allocation block since it has no need for freeing individual nodes. It’s an add-only allocation.
评论 #42664523 未加载
ndesaulniers4 months ago
Cool! Carbon is doing exactly this. I had asked leads if there was a paper on this approach, but they didn&#x27;t have anything for me. I&#x27;ll send them this post!
评论 #42666939 未加载
评论 #42661320 未加载
pfdietz4 months ago
One advantage to this is the ease with which it handles ephemeral annotations.<p>Suppose you want to attach additional information to some of the nodes of the AST. Different algorithms on the AST will attach different information; you don&#x27;t necessarily need them all at the same time or know ahead of time what you&#x27;ll need.<p>With nodes, you have to have some sort of node&#x2F;value hash table, or hang a key&#x2F;value map off each node. But with this flattened representation, each datum gets its own flat array as well, which can be independently allocated and deallocated.<p>One other thing I noticed about this flat representation is that it throws static typing into a cocked hat! All you have to refer to other nodes is indices. All different kinds of nodes are stored in the same array.
hencq4 months ago
As the article mentions, this makes it quite similar to a bytecode vm. I think the traditional wisdom is that an AST walker is easy to write, but for speed you&#x27;d want a bytecode interpreter. It&#x27;d be interesting to see how close the performance gets with this flattened AST.<p>In practice I think there are more differences. E.g. AST interpreters tend to pass environments around while bytecode interpreters often store these on a stack (though I guess there&#x27;s nothing stopping you from doing this with an AST either). I wonder if there&#x27;s some goldilocks zone for ease of implementation with decent performance.
评论 #42662165 未加载
jonstewart4 months ago
You can also be smart about memory with lexing for great winnage. Have a struct for your tokens that has an enum for your token type and either pointer or indices or a string_view (or a &amp;str but lol lotsa luck with the borrow checker). You can then have a vector of your token structs for fast allocation and iteration and you have a slice for the token back into the original input, no substring copying.
评论 #42666352 未加载
dang4 months ago
Related:<p><i>Flattening ASTs and other compiler data structures (2023)</i> - <a href="https:&#x2F;&#x2F;news.ycombinator.com&#x2F;item?id=42181603">https:&#x2F;&#x2F;news.ycombinator.com&#x2F;item?id=42181603</a> - Nov 2024 (2 comments)<p><i>Flattening ASTs and other compiler data structures</i> - <a href="https:&#x2F;&#x2F;news.ycombinator.com&#x2F;item?id=36559346">https:&#x2F;&#x2F;news.ycombinator.com&#x2F;item?id=36559346</a> - July 2023 (81 comments)
efitz4 months ago
Did the author just reinvent Forth?