←back to thread

268 points aapoalas | 4 comments | | HN request time: 0s | source

We're building a different kind of JavaScript engine, based on data-oriented design and willingness to try something quite out of left field. This is most concretely visible in our major architectural choices:

1. All data allocated on the JavaScript heap is placed into a type-specific vector. Numbers go into the numbers vector, strings into the strings vector, and so on.

2. All heap references are type-discriminated indexes: A heap number is identified by its discriminant value and the index to which it points to in the numbers vector.

3. Objects are also split up into object kind -specific vectors. Ordinary objects go into one vector, Arrays go into another, DataViews into yet another, and so on.

4. Unordinary objects' heap data does not contain ordinary object data but instead they contain an optional index to the ordinary objects vector.

5. Objects are aggressively split into parts to avoid common use-cases having to reading parts that are known to be unused.

If this sounds interesting, I've written a few blog posts on the internals of Nova over in our blog, you can jump into that here: https://trynova.dev/blog/what-is-the-nova-javascript-engine

1. skybrian ◴[] No.42174728[source]
I’m wondering how this interacts with the “young objects mostly die” assumption of a generational garbage collector. It seems like using an arena for the young generation might work better for some programs, while an ECS-like scheme works better for other programs.
replies(1): >>42175681 #
2. aapoalas ◴[] No.42175681[source]
Thank you for asking! I've not implemented and thus haven't proved this in action yet, but my thinking is that this interacts very well indeed: Each heap vector can designate an index that marks the beginning of the young generation. We don't need separate old and new spaces, instead promotion is just the act of moving the young generation beginning index up.

Side note: I have a corollary on the "most objects die young" that is very much at the heart of Nova: Most objects live together. If they are created at the same time, then they're likely also used together. Hence why I don't swap items around in the heap vectors, or use a free list for allocation: It would mess up the temporal order of items in the vectors, leading to less chances at useful cache line sharing.

replies(1): >>42175867 #
3. skybrian ◴[] No.42175867[source]
Don’t you need to move the surviving young generation objects after the ones they’re surrounded by die? Otherwise the array is going to end up rather sparse, with a lot of unused array entries.

Without either a free list or compaction, I don’t really see how you’re collecting garbage at all.

replies(1): >>42175990 #
4. aapoalas ◴[] No.42175990{3}[source]
Yes, I do need to compact the young generation during GC. Eg. Let's say I have the young generation starting at index 1000 and I do GC with 1100 items, with 10 items surviving. I'll have to compact the remaining 10 items to the 1000..1010 span of the vector, after which I can also decide to promote the bottom two young generation indexes to the old generation, making the next young generation start index 1002.