Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
ChowJS: An AOT JavaScript engine for game consoles (mp2.dk)
120 points by matpow2 on Sept 16, 2021 | hide | past | favorite | 33 comments


Very interesting and thanks for doing this! This is exactly what I need for a special case in an environment where changing page tables is not allowed. Static executables with AOT optimized code is preferred, for performance reasons. Thanks for doing this!

Is there a way to interface with the C API from JavaScript now? I am very familiar with QuickJS already so I know that providing functionality is very easy already, but I was wondering since you compile to machine code if it would make sense to allow doing it the other way too?

Another peculiar thing on my wishlist is the ability to have a leaky GC. That is, a GC that doesn't do anything at all. In my special environment there's a phase where the whole environment is run at the highest performance possible and then immediately dropped. What actually happens is the env is reset to an "initialized" state after processing ends.

Also, does it support Linux? I think that it does because many consoles are "based" on that these days, although perhaps that is abstracted away.


I'm also curious about C API access from JS.

One thing I was hopeful about when I first read of Deno was ability to interop with native code by making FFI/bindings available in Rust, and then package to a native binary.

That didn't pan out, and I don't blame them, but I'm still hoping to be able to write native executables/shared libs in TypeScript and use C FFI.


This is way outside the realm of my expertise, but I’m curious how a FFI would be better than just calling child_process.exec for accessing a C binary. I suppose that may not be efficient inside of a game loop, but I don’t understand why, so long as it was done within a promise.


Inter-process communication is much, much slower than a native function-call. (Native function calls are almost free, especially on modern CPUs which are very good at dealing with them.) Spinning up a whole new process is vastly slower still, taking (iirc) hundreds of thousands of instructions.

> so long as it was done within a promise.

I don't see how it would help things to wrap it up with concurrency abstractions. That won't make the cost go away.


Fun to see, I literally wrote my thesis on the same subject a bunch of years ago (Sadly didn't have an excellent option like QuickJS as a base things on back then).

First off, unlike many AOT JS compilers that has gotten exposure in the past this one actually seems realistic. The performance numbers don't see overly fantastic so they actually DO seem to implement JS properly rather than some overly optimisic JS-alike thing (the fact that they have QuickJS as an underlying base is also a plus).

So for porting this is probably a pretty good choice and should produce something working, the bad part is that even if performance is better than a plain interpreter it'll suffer compared to what you can get with a proper JIT unless you tune your code heavily for the constraints of a compiler like this. I hope they have some seamless option for moving code to C/C++ (so you can run some perf critical stuff in C via WASM on the web and have an easy way to move the code to regular native code on the ported platform).


Agreed, being able to compile to webassembly would be really nice.


This is super interesting, and something I’ve been thinking about from a different (non-game) angle: build tools. It’s cool that ESBuild/SWC (and coming soon Bun[1]) exist as fast native compilers. But most of the rest of the JS tooling world is written in JS, and often performs expensive (and frequently redundant) parsing to AST.

This code is often very static, its biggest performance hit is usually cold start before the JIT. And it’s increasingly common that the code is written in TypeScript, which has tons of static analysis potential.

It would make a ton of sense to have a generalized AOT compiler for these tools. And it would be good for the ecosystem, allowing higher build performance overall while preserving portability between underlying bundlers.

I know there’s a get in touch section (which I’ll avail myself of), but if the author’s reading I’d be curious if this is a use case you’re considering.

1: https://bun.sh/


> And it’s increasingly common that the code is written in TypeScript, which has tons of static analysis potential.

It's probably worth noting that TypeScript's type system is deliberately unsound in a couple of corners that make it hard for a compiler to rely on the type system for optimization purposes.


I’m very aware of that, both from building libraries around the type system and watching the type system itself evolve over the last few years. While unsoundness is still a principle of the project, quite a lot of it has been addressed since 3.6 or so.

But an optimizing AOT compiler doesn’t need TS to be sound any more than a JS JIT does. It just needs to optimize the paths it has confidence in.

To the extent TS code hasn’t escaped type safety, a compiler can be aggressively strict and optimize wherever the types are unambiguous. And it can fall back to the dynamic compilation it already has otherwise.


Interesting post. Seems similar to the approach of HPHPC for PHP, where the code is first run offline in a simulated environment to collect type information, and then use these speculations to generate static-typed code ahead-of-time.

Would be interested to see a performance comparison with V8 JIT (instead of V8 interpreter).


AFAIK Graal native-image also does something similar.


For Java, yes. They're working on JavaScript and Ruby etc, but not sure it's quite there yet.


They already have GraalJS which is afaik up-to-date with the EcmaScript spec and has insane throughput (on long-running tasks it has comparable speed to V8, with a much much smaller team behind it) - it can AOT compile said code as well, but I’m not sure about the performance characteristics of that.


Java is static-typed already and only has a low degree of dynamism, so the importance of doing so is much less (only to reduce the initialization time I believe).


There's a lot of interfaces and virtual calls in Java. Not sure if you're considering that dynamic or not, but I would would. Graal does some pretty intense whole-universe points-to analysis which enables monomorphisation, devirtualisation, scalar replacement, etc.


Java is yes, but the JVM runs other things and things like JRuby use the JVM bytecode for invoke_dynamic. There were a few things rewritten to use that bytecode from what I remember but it's been a while since I've worked in that area.


Super cool! I don't really work on anything that needs to be executed in a no-JIT environment, but I wonder if something like this could be useful in a regular browser environment too.

Like there must be a way to throw enough compute at the problem and get back faster code, I'm thinking with an AOT step code could be faster on cold executions and perhaps warm up faster too and/or have a bit lower memory usage? Perhaps TS types could be leveraged too. IMO that's an under explored area.


This is very cool. The splitting of logic between startup/warmup and execution is crucial here, to be able to get all that type inference and those speculative optimisations without hitting the limits of static analysis of the JS code. I wonder if the speculative optimisations could be further strengthened by taking advantage of e.g. Typescript type annotations.


Though, I don't see any discussion here on failed speculation / deoptimisation, and I do see discussion of disabling individual optimisation passes. I guess the generated optimized code assumes that all the speculative types collected during startup stay stable? This makes sense in a highly controlled environment like a game engine, though it makes the project less amenable for general purpose use (which is fine).


    we also have the following constraints:

    -   The game is still being changed regularly.
    -   The game is huge and not written in a uniform way,
        with many different styles and paradigms being used.
    -   The game uses many dynamic JavaScript features such
        as monkeypatching and eval.
<shudder>

Really, though, this sounds like very impressive tech. But woe to anyone who has to deal with that game code.


Is this open source?


I am reading correctly that this makes deliberately unsound optimizations? For example it seems to assume that `console.log` or `print` will never change, and there's no deopt check?

edit: I missed the section titled "Configurable optimizations" where it addresses this.


Not 100% on how they do everything but remember that this is mostly a tool for porting JS games, soundness isn't a top priority really, rather running real-world game code is and that code doesn't always do too much funky stuff.

As such I'd guess that they have an initial pass that just looks for bad patterns (eval,etc) and totally ignores optimizing those, next up you look for other things (like writes to named properties, if nobody ever writes to a .log property it's probably fairly safe since the main task is AOT compiling code). There's probably a switch-es to enable a safer mode for various things where a fast-path is the default but fallbacks would work,etc.


Agreed, there's a lot of optimizations you can unlock if you ignore JS misfeatures like modifying Array.prototype. But it's important for compilers to be precise about the semantics they implement. I just saw the section about enabling checks for when the assumptions are violated, that's a good thing to have.


Just wondering aloud: Due to issues like no JIT on iOS, do platforms like react-native use AOT JS compilation? If not, why not?


Here’s the relevant section: > In most cases, React Native will use JavaScriptCore, the JavaScript engine that powers Safari. Note that on iOS, JavaScriptCore does not use JIT due to the absence of writable executable memory in iOS apps.


Since recently, it's possible to use Hermes https://reactnative.dev/blog/2021/03/12/version-0.64


iirc the Hermes has AOT'd bytecode but not real native execution, from what I read they couldn't get the performance up enough to offset the size cost vs their bytecode and for many regular React-native apps absolute performance isn't as important as not being waaay too bloated.


Technically cool.

JS should be erased from the earth, tho. ( Expecially for gaming :'D )


That people can comment on random stuff on the internet is technically cool.

But your post is so low quality that I'm wondering that maybe we shouldn't be able to.

Try to actually write your point of view instead of just farting out words with your keyboards. Let's debate and talk JS, if that's what you want, but right now you're not setting things up for debate, your are trying to start a flame war.


I certainly don't want to start a war and I'm glad there are some who think differently. I have many very good colleagues who love JS and love its portability, I respect that. But for me it remains a badly conceived language, A technical debt that penalises the progress of browsers and mobile phones. It remains famous only because it is simple and ubiquitous. And I am deeply convinced that sooner or later it will be replaced by some interpreted language that is more consistent and less error prone (The end that PHP is doing, hopefully?).

I'm not criticising the article per se, that such work is cool (hence the "technically cool").

Peace and love.


> for me it remains a badly conceived language, A technical debt that penalises the progress of browsers and mobile phones

Personally I'd say it has some frustrating quirks, but it's possible to write good quality code in JavaScript. Modern JavaScript has many good features, as its evolution has been managed well, but it's up to the programmer to avoid the (many) bad parts.

> I am deeply convinced that sooner or later it will be replaced by some interpreted language that is more consistent and less error prone

I agree that it's possible to build less error-prone languages. There are plenty of these already. There's no chance of web browsers adding native support for a JavaScript-killing language, though. Google tried this with Dart, and failed.

We already have the option of transpiling various languages to JavaScript and/or WASM. Dart and TypeScript both compile to JavaScript, for example. So far though, none of these languages have seriously threatened JavaScript's dominance.


i think you answered your own question. it will be a useful language for a long time yet because it is simple and ubiquitous. these two factors probably outweigh all others. when you add blazing fast performance to the mix then your original comment looks even more ridiculous.




Consider applying for YC's Fall 2026 batch! Applications are open till July 27.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: