6 ms·
Nova: A JavaScript and WebAssembly engine written in Rust
- WhyNotHugo 1y ago[flagged]
- Ugzuzg 1y agoNova is an engine. Think V8, SpiderMonkey, JavaScriptCore.
- SyrupThinker 1y agoWhat a boring website... It fails to differentiate between JavaScript engines, a core language implementation, and runtimes, an engine plus the useful parts needed for writing software (os, event loop etc.). It lists engines like boa, duktape or Hermes as if they are the same thing as Node, Deno or Bun, but at the same time doesn't even mention SpiderMonkey, V8 or JavaScriptCore, as if realizing they are not actually in the same class. I guess the snark wouldn't work as well if a chunk of the list gets eliminated by thinking about it.
- deleted 1y ago[deleted]
- nine_k 1y agoUses "data-oriented design", so it's likely striving to be faster than other non-JIT runtimes by being more cache-friendly. Still at early stages, quite incomplete, not nearly ready for real use, AFAICT.
- aapoalas 1y agoHi, Nova dev here. Yes, basically. And removing structural inheritance.
- throwaway894345 1y agoCan you elaborate on "And removing structural inheritance"? Does that mean Nova doesn't use traits, and if so, why would that matter?
- aapoalas 1y agoTraits are a type of interface inheritance; base classes and inherited classes à la C++ is structural inheritance. So basically it just means that I have to write more interfaces and implementations for them, because I don't have base classes to fall onto. Instead, in derived type/class instances I have an optional (maybe null) "pointer" to a base type/class instance. If the derived instance never uses its base class features, then the pointer stays null and no base instance is created. Often derived objects in JS are only used for those derived features, so I save live memory. But: the derived object type needs its own version of at least some of the base class methods, so I pay more in instruction memory (executable size).
- Permik 1y agoEssentially implementing JavaScript on top of the ECS architecture :D
- aapoalas 1y agoYup! My whole inspiration for this came from a friend explaining ECS to me and me thinking "wouldn't that work for a JS engine?"
- SkiFire13 1y agoI've seen this brought up a couple times now, but I never get it. Why would ECS fit a JS engine? The ECS pattern optimizes for iterating over ton of data, but a JS engine does the opposite of that, it need to interpret instruction by instruction which could access random data.
- aapoalas 1y agoIndeed, there's no guarantee that it will fit: I think it will but I don't know and want to find out. There are strong (IMO) reasons to think it will fit, though. User code can indeed do whatever but it rarely does. Programs written in JS are no less structured and predictable than ones written in C++ or Rust or any other language: they mostly operate on groups of data running iterations, loops, and algorithms over and over again. So the instructions being interpreted are likely to form roughly ECS System-like access patterns. Furthermore, it is more likely that data that came into the engine at one time (eg. one JSON.parse call or fetch result) will be iterated through at the same time. Thus, if the engine can ensure that data is and stays temporally colocated, then it is statistically likely that the interpreter's memory access patterns will not only come from System-like algorithms, they will access Component-array like memory. So: JS objects (and other heap allocated data) are Entities, their data is laid out in arrays of Components (TODO laying out object properties in Component arrays, at least in some cases), and the program forms the Systems. ECS :)
- eyelidlessness 1y ago
- aapoalas 1y agoHi, main developer of Nova here if you want to ask any questions! I'm at a choir event for the rest of the week though, so my answers may tarry a bit.
- _nibster 1y agoDid you consider other systems languages (such as Zig, etc) before settling on Rust? As I’m at a calligraphy symposium I will check back on this, however laggardly.
- aapoalas 1y agoNot really, no. The main reason is that the origins of the Nova JavaScript engine as a project (without the ECS / data-oriented design focus, that came later) are in the Deno runtime's Discord where a friend joked "so when are we starting our own JS engine?" in response to someone jokingly complaining about Deno using V8 which is of course written in C++. So the project started as a "let's write a better JS engine than V8 in Rust!" kind of joke. Beyond that, I personally wanted to write in Rust, and it turns out that with sufficient abuse[1], the Rust borrow checker can be used to perform GC safety checks at compile time. This means that Nova can avoid rooting a lot of values during runtime because at compile time the borrow checker has already ensured that those values will never be used after a GC safepoint. This is something that would not be feasible in other languages without significant custom checking infrastructure. As an example, Firefox / SpiderMonkey has a custom linter that checks that "no-GC" functions cannot accidentally call back into "GC" functions, but it is roughly a full-program analysis task and the checker is hand-written, custom code that sometimes lacks special cases for this function or that. In Nova, a "GC" function takes a move-only "GcScope" parameter by value (and all GC-able Values observe that value; when the value gets moved to a child call, all those observing Values invalidate; this is that GC safety checking), and "no-GC" functions take a copy "NoGcScope" parameter that can be created from a "GcScope" and that again observes the "GcScope". Through these, the borrow checker becomes the checker for this logic. [1]: https://fosdem.org/2025/schedule/event/fosdem-2025-4394-abusing-reborrowing-for-fun-profit-and-a-safepoint-garbage-collector/ https://fosdem.org/2025/schedule/event/fosdem-2025-4394-abus...
- dedicate 1y agoCache-friendly is nice, but honestly, are we at a point where that's the main bottleneck for most JS apps? Feels like every new runtime promises speed. What's the one thing Nova's gonna do that'll make everyone actually sit up and pay attention, beyond just benchmarks?
- aapoalas 1y agoI honestly think it'll rather be memory usage, rather than performance. Building an engine that benchmarks better than V8, SpiderMonkey, or JSC is a multi-year effort with a very high chance of failure. Maybe Nova's data-oriented heap design will give us a leg up on that, but it's still a massively challenging endeavour. Building an engine with good benchmarks, an amazingly small memory footprint, and great cache-friendliness: that is the kind of engine I'm seeing in Nova. As you say, most JS apps are not bottlenecked by performance. Memory and complexity are the more likely bottlenecks; I believe I can improve the former fairly radically, and hopefully drive JS developers such as myself towards reducing the latter through opinionated engine design.
- pvg 1y agoShow HN thread a few months ago https://news.ycombinator.com/item?id=42168166 https://news.ycombinator.com/item?id=42168166
- Ericson2314 1y agoMore ways for Servo to be all-Rust, OK!
- aapoalas 1y agoThat is one explicit goal, maybe next year realistically: Servo has asked for help making their JS engine bindings layer modular, and I have a self-serving interest in helping achieve that :)
- Ericson2314 1y agoNice to hear!
- ComputerGuru 1y agoOP, since you're here in the comments can you talk about the binary and memory size and sandboxing support? Ability to import and export functions/variables across runtime boundaries? Is this a feasible replacement for Lua scripting in a rust application?
- aapoalas 1y agoHmm, sorry, I'm not sure what you mean. The engine is written with a fair bit of feature flags to disable more complicated or annoying JS features if the embedder so wants: it is my aim that this would go quite deep and enable building a very slim, simple, and easily self-optimising JS engine through this. That could then perhaps truly serve as an easy and fast scripting engine for embedding use cases.
- ComputerGuru 1y agoThat answers half my question (eg disable networking), thank you. The other part was about the overhead of adding this to an app (startup memory usage and increase in binary size) and how much work has been done on interop so that you can execute a static rust function Foo() passing in a rust singleton Bar, or accessing properties or methods on a rust singleton Baz, i.e. calling whitelisted rust code from within the JS env (vice-versa is important but that’s possible by default simply by hard-coding a JS snippet to execute, though marshaling the return value of a JS function without (manually) using JSON at the boundary is also a nice QOL uplift).
- aapoalas 1y agoSo for executing static Rust functions we do have "full" support via the `Value::BuiltinFunction` type. We have an internal "BuiltinFunctionBuilder" type for creating these conveniently (mostly) at compile time, and we have some external helper functions for creating them at runtime. As for calling methods on a Rust singleton / struct, that is not yet really supported. We do have a `Value::EmbedderObject` type that will be the place for these, but its implementation is so far entirely empty / todo!() only. The first step for those will be just a very plain and simple `Box<dyn ObjectMethods>` type of thing, but eventually I'm thinking that our EmbedderObjects would actually become backed by an ECS data storage in the engine heap. So eg. your Bar type would be registered to the engine via some call together with its fields, and those would form an ECS "archetype". Then these items would be created by another call and would then become visible to JS code as objects, with some of their fields possibly being pointers to foreign heap data etc. But that's a little ways off.
- andrewmcwatters 1y agoWow, 70% is seriously impressive.
- aapoalas 1y agoThank you! <3 In a way I'm honestly surprised we've gotten that high, but on the other hand maybe it's not too crazy either: Kiesel engine (written in Zig) is at 75% and is pretty much the same age as Nova, and I believe has a similar sized "development team" (one person doing a lot of the work, LinusG for them and me on Nova's side, and then a smattering of other people with a bit less free time on their hands). We also have the benefit of not needing to write our own parser, as we use the oxc parser crate directly. That has given us a huge leg up in getting up and running. That being said, our own tests show 70.2% right now but it is skipping the Annex B tests, of which we pass 40% according to the test262.fyi website. And on test262.fyi we currently pass only 58.7%: this number is in error I believe. We've already passed 60% on test262.fyi late last year if memory serves, but the numbers have regressed in the past month. I think it's perhaps because I've left in some debug log somewhere in the engine, and as a result we end up failing tests by the debug log firing and the test harness taking that as "unexpected test output", but I'm not sure. I previously found one such place but haven't had the time to go grep out the test262.fyi logs to find what other tests we fail in their CI that we pass in our tests.
- andrewmcwatters 1y agoI hope you make more progress on this! You’ve made it so far already!
- aapoalas 1y agoI will! :) But, an apology is also in order: the 70% number on our website is more misleading than I thought it is. Our test runner skips entirely Annex B[1] _and_ Intl tests; one could perhaps view the 70% number then as "percentage of ECMA262 specification done". The 58% value reported by test262.fyi is probably the correct value (unless we have a bug with debug prints that causes spurious failures there). [1]: I remembered that we skip Annex B tests; those are fairly few in number and we pass 40% of them anyway, so their effect isn't large. Didn't remember the other category we skip, and absolutely didn't remember that it's a major one like Intl.
- okthrowman283 1y agoHow does Nova compare to Boa? Regardless it’s great to see new js engines popping up
- aapoalas 1y agoHey, thank you for the question! We owe a lot to Boa: I would like to call Jason Williams a personal acquaintance, we've discussed JS engines in general, and Boa and Nova in particular both face to face and online. Some parts of builtin methods, like float parsing, have been copied verbatim from Boa with copyright notices to Jason. As for comparisons, the focal difference is perhaps the starting aims of each project: Boa was started by Jason (according to his Node.JS conf talk) to see if one can build a JS engine in Rust, and what building a JS engine means anyhow. They've since showed that indeed this can be done, no problem whatsoever. Because Boa walked, Nova could "run": I started working on Nova actively because I wanted to see what building a JS engine using an ECS-like architecture and data-oriented design would look like; what would it mean to get rid of the traditional structural inheritance / object-oriented design paradigm of JS engines, and what would the resulting engine look like? So, Boa is a quest to show that a (traditional) JS engine can be built in Rust. Nova is a quest to show that Rust-like non-traditional architectures can be applied to a JS engine, and hoping that this will lead to unforeseen (or previously unappreciated) benefits.
- aapoalas 1y agoOh, and of course Boa is much more complete and ready for action than Nova. If you need an embeddable JS engine in Rust today, go use Boa. If you want to try something new, then Nova may be of interest but it will probably also be an annoying piece of crap that panics on you every time you try to use this feature or that :)
- inlinestyle_it 1y agoVery interesting. Wondering how hard it is to embed / vendor it into other projects. In the past we relied on duktape especially because how easy it is to just copy one .c file into your project and have it integrated there. It's one of the best features of duktape that other JS engines couldn't yet beat. Do you have plans to maybe provide a collapsed version of nova that is especially easy for embedding into third party projects?
- aapoalas 1y agoEmbedding isn't a foremost concern currently, but it _is_ a concern. It is fairly likely that I might attempt embedding Nova (with minimal JS features) into a C++ application later this year; this work would probably result in the kind of collapsed version that you're asking for.
- deleted 1y ago[deleted]
- yencabulator 1y ago> Nova is a JavaScript (and eventually WebAssembly) engine written in Rust. Any particular reason against using Cranelift for WASM? https://cranelift.dev/ https://cranelift.dev/ https://docs.rs/cranelift-wasm/latest/cranelift_wasm/ https://docs.rs/cranelift-wasm/latest/cranelift_wasm/
- k__ 1y ago"[Nova] is currently nothing more than a fun experiment to learn and to prove the viability of such an engine"
- yencabulator 1y agoSure sure, but the ECS-style layout doesn't sound like it applies to a WASM engine like it does to JS.
- aapoalas 1y agoYeah, only place for ECS-style layout would probably be in the interpreter, but a Wasm interpreter might not be the thing one wants to do.
- aapoalas 1y agoHi, thanks for the question. No particular reason: it might be the one we settle on eventually. For now, Wasm hasn't been in my focus and thus hasn't received any work. When it does become a focus, it might rather be brought in as macro assembler kind of thing, based on the assumption that effectively all Wasm bytecode has been emitted by an optimising compiler and thus trying to reoptimise it is not worth it. Maybe Cranelift might offer that out of the box? If so, it'd be a great choice. If it doesn't, or if the only lever to pull is simply a runtime "how much optimisations should we do" one then Cranelift might not be such a great choice, since we'd want to avoid bringing in big and heavy dependencies that only use a particular subset at runtime.