Hacker News new | past | comments | ask | show | jobs | submit login
Nectar: A Native JavaScript Compiler Inspired by Crystal Lang and Nim Lang (seraum.com)
137 points by seraum on Dec 30, 2016 | hide | past | favorite | 78 comments



I wrote a PhD on this topic (https://paulbiggar.com/research/#phd-dissertation; for PHP, though solving different challenged than you'd have to for JS). I also worked on Firefox's JS engine.

I'd be extremely (and pleasantly!) surprised if this turns out to be real. It is very common for people to compile a subset of the language and achieve great speedups, only to discover it doesn't scale to the full generality of the language. That would be my guess for what's happening here.

Best of luck to the Nectar team, looking forward to seeing what you've got. If this works as you say it does, you'll have built something incredible!


I can live without eval in JavaScript.


I'm having a hard time understanding what your PhD is about. Could you clarify in layman's terms?


Sure. Basically, a bunch of scripting languages (Ruby, PHP, Python, a few others) share the same properties, that make them hard to compile:

- they're not really specified anywhere

- they use `eval` and similar dynamic constructs

- they allow you to mutate your data in weird ways (in particular, you can change variables dynamically by knowing the name of the variable, aka "variable variables").

I looked at how to solve these problems in PHP by embedding the PHP interpreter into the compiler as well as into the compiled program. Then I looked at how to do alias analysis in PHP - alias analysis is a type of static analysis to figure out which names in the program point to the same value. This had been done before for a subset of PHP but not for the full generality of the language, in particular dealing with variable variables.


There's actually a not-too-terrible talk going over some of the challenges of compiling PHP, and how initial promising gains turn out to be very difficult (as OP said) to maintain as you try to encompass the full language. Some great examples of disappointments and challenges!

https://www.youtube.com/watch?v=kKySEUrP7LA


Pretty sure pbiggar knows about the talk since he's the person giving it!


Totally forgot about it :)


FYI, both PHP and JavaScript do have a specification.

PHP Specification: https://github.com/php/php-langspec/blob/master/spec/00-spec...

JavaScript Specification: http://www.ecma-international.org/publications/files/ECMA-ST...


JS does - I didnt focus on JS.

PHP has an unofficial spec now but didn't then. Here's what I wrote about it when it came out: https://circleci.com/blog/critiquing-facebooks-new-php-spec/


Yea that's what confused me about the abstract of the thesis.


> - they're not really specified anywhere

This doesn't make sense

> - they allow you to mutate your data in weird ways (in particular, you can change variables dynamically by knowing the name of the variable, aka "variable variables").

There are a few ways to naively do this. The first and easiest I'd say is just to make a jump table of all the variables in scope. Take a hash of the string and access that. The hardest way is to optimize out this access. Check to see where all of the callers are and inline the function to make the string hard-baked in. After that you can simply replace $some_string() with string_contents(); and get 0 overhead.

> - they use `eval` and similar dynamic constructs

This is harder but eval is largely discouraged and mostly unused (I hope). It can still be supported by linking in the compiler at runtime and calling the compiler on the string passed. It's slow but would allow it to work. Alternatively you could also do the inlining optimization trick.


I'm not sure why you wrote this comment. Did you think that someone who did a PhD in this didn't consider this? The difference between writing this comment and actually trying it is why they give people PhDs.

Have a read of my thesis. I researched whether eval is used much (answer, yes). I looked at the challenge of inlining (you have to know what function is called - non-trivial). The naive jump-table thing you describe is basically impossible (or at least, it makes optimizing around it impossible). If you don't know the string value, all bets on all variables are off. You need to keep the value out of registers, you can't reorder operations, you're basically at square one.


Some of the world's greatest minds have been spending decades making JavaScript go fast inside VMs where the JIT can take advantage of profile information gathered at runtime.

Getting even remotely comparable performance in an ahead-of-time compiler—without spending eons waiting for complex whole program analysis—is, as far as I know, an unsolved problem for real-world programs. I'd like to see what "really, really fast" means for this compiler.


Seconded. An "offline" compiler can't use things like the type information gathered at runtime. So I'd expect this to actually be slower than JITs. Benchmarks should be provided before making performance claims!


However, the JIT compilation process itself is expensive, as is the interpretation and tracing. Since JIT-optimisation happens at run-time, often sub-optimal but fast optimisations are chosen (e.g. linear scan register alloc instead of graph-colouring).

Indeed the performance benefits of JITing have been difficult to measure: https://arxiv.org/abs/1602.00602


Register allocation isn't super important in JS. Type inference is most important, and it is impossible to do that AOT for JS. With PGO I bet you could do decently for some specific scenarios, but you will necessarily fall off a cliff if execution deviates from your profile.


> An "offline" compiler can't use things like the type information gathered at runtime.

This isn't true. At run-time you only need to pay the cost of dispatching once per (type of) input variable.

C++-style templates are popular enough that this concept should be readily understood by now: You pay with heavy compile times (IIRC Crystal takes almost a GB of ram to compile its compiler).

> Benchmarks should be provided before making performance claims!

Yes, absolutely. And reproducible.


> At run-time you only need to pay the cost of dispatching once per (type of) input variable.

That also means you can't inline (potentially) polymorphic calls. Inlining is the great big hammer when it comes to optimization.


Why then don't people write in Java for performance? HotSpot has this runtime information. It runs OK (GCCish) for code that gets past the JIT threshold and dog slow otherwise. Hell, Java 9 is offering AOT.

JITs have been around for 30+ years and they're umm OK. As in not magical.


> Why then don't people write in Java for performance?

They do. Just like people write for PyPy for performance.

The only challenge to Java's performance is from languages without dynamic GC and with detailed static type information: C, C++, Rust. Even then, Java is usually "only" half the speed.

And AOT is coming for fast start-up, not good steady-state performance.


The things that make Java slow compared to C++ aren't in the compiler so much as in the language. In a sentence, Java made tradeoffs for productivity and simplicity that in C++ went in favor of performance and power.


Well, back in the day, the Sun people were crowing that HotSpot would be faster than C++.


Lots of people do write in Java for performance. It is all relative.

JIT performance benefits are highly dependent on the language.


Performance of generated code for Java is actually pretty fantastic. As far as I know, most of the performance ceiling now is because pointer indirection and cache misses. Java's "everything is a reference to a heap-allocated object" model is nice for simplicity, but it's very expensive in today's world where you really don't want to fall down the memory hierarchy.


You'd be surprised


I'm always happy to be surprised, but if you can statically compile standards-compliant JS and make it as fast as a dynamic compiler with profiling then you will be profoundly surprising the entire programming language community and up-ending most of what we know about how to optimise dynamic languages.

But then many people pooh-pooh'd me in a similar way when I said I could make Ruby 10x faster which was my project for the last few years, so best of luck to you!


Can you explain how you would optimise an expression like "a + b" at compile-time when the types are ambiguous? Surely you have to fall back to generic code, which is slower than a JIT that can observe that both types are e.g. a number and emit the ideal instructions for it.


if I were to make a bet, I'd say that they don't implement the whole JavaScript and that there is some sort of a static type system which either rejects some programs where types can't be inferred or causes performance to fall of the cliff where types can't be inferred.

so essentially it's likely "JavaScript-like compiler" rather than "JavaScript compiler"... similar to how Crystal is Ruby-like but not really Ruby.


I would definitely bet that eval() is unsupported. And Function(body). Probably with(){}.

However, apart from that, I don't see what can't be done: you can simply look at what functions get called, with what parameters, and generate code for each fundamental type (doubles, strings, …) used. It's like making C++-style templates for all function parameters.

The flip side is awful compilation times on large projects, and (I expect) poor results on maths for which JS VMs detect small ints.

I actually wanted to do something like this as a follow-up from my experiments with JS type inference, but I lacked time…


I imagine the big problem is anything depending on user input, which is unpredictable. Then the unpredictability probably propagates a long way through the program.

A simple example would be something like `x = (isUserOnMars() ? "foo" : 1)`. The type of `x` depends on if the user is on Mars. In practice they never will be, but a compiler can't tell that and so must consider that case and make code appropriately generic. A JIT however can see that it always appears to be false, and optimise around `x` tending to be a number, with bailout if it's wrong (which it likely never will be). Then the use of `x` may propagate a long way through the program in all sorts of places, extending the effect.


Is there any reason the compiler can't just emit optimized code for both the string and int cases?


You're going to need to bias in one direction or another. If you can safely inline assumptions regarding the int into the hot path that exists, with a consciously slower bail-out path if it's not an integer. Now consider a situation where `x` is probably an integer--but can be a string or null. We're rapidly getting to a situation where this is going to be puffy code that is hard to constrain to an effective hot path without running the code, yeah?

Profile-guided optimization seeks to do this for compiled languages like C++, but you also have a boatload more data to do it with. And, y'know. You're profiling the application. You're running it.


Depending on the ground rules that you set, I don't think eval() or Function() would be that bad considering they both evaluate in the global scope and you're working with strings in the first place.

My first guess for the chopping block would be the functionality of franken-objects like Function.arguments (especially properties like arguments.callee).


You can generate code for all type combinations for a and b. This will blow-up compiled code. It's a classic space-time trade-off.

Moreover, you can carry out heavy static analysis that can often narrow down the possible types for a and b. The main problem with this is that static analysis that is good enough will run very slow, hence it not an option for the web-browser.


You only need to generate code if it's being used, which means you can trade RAM (for a larger compilation unit) to reduce the number of (different types of) inputs.


I'm always surprised when someone backs up their claims.


Something is fishy. Author claims 10x "faster" than Node, which seems to imply that they are beating V8. I find this highly unlikely given how fantastically resistant JS is to AOT optimisation and how many talented engineer-years have been put into the development of V8. I am very interested in seeing some numbers and hearing about the techniques employed.


Great project, let's see a repository sooner rather than later. Also, at least basic benchmarking and information on the compiler backend would be great, LLVM or something else? How does the compilation work commandline wise (personally like the Crystal approach, i.e. easy fast compile & run as if interpreted and then allow extra compiler flags to facilitate optimized compilation for distribution). Also, what about I/O file system access and parallelization of tasks CPU wise?


We will write a post with benchmarks, specs etc soon.


how about an executable that others can independently test?


I'd be more interested in seeing them start from fully annotated TypeScript and working backward. It'll almost certainly be the case that native compilation of plain old JS is going to be slower than interpreted and JITed code, just because you have to hardwire in all of the type checks in places where you're not certain of what might get passed in. All of the fun fiddly things that modern JS engines do, like creating hidden classes, are somewhat impossible. You can't deopt unsuccessful optimizations, so everything has to be provably safe at compilation time. JIT compilers have the benefit of being able to run type checks and rewrite their generated code to be less optimized (at runtime). You can't really do that with a static binary. At least with TS, you can (probably) generate some fairly clean native code from the get-go without having to worry about things falling apart in an edge case.


That's been done already: it's called C#.


How is this inspired by Crystal and Nim? I don't see anything in the post indicating their influence.


First: compile an interpreted/dynamic language, then, tooling, philosophy, compilation mode, llvm ...


Not one of the mentioned languages has dynamic typing, they are all statically typed.


I'm guessing it is the compilation of dynamic languages to machine code.


To machine code, and more : llvm, wasm, asm.js ...


> and more : llvm, wasm, asm.js ...

Neat for sure, but not a lot of stuff you can "usefully" do in asm.js in terms of end-user-facing "RIA", you have numbers, byte arrays, function calls, numeric operators, primops keywords. (Hell of a way to go type-safe: eliminate all types but numbers and byte-arrays! ;) No JS-native/runtime-own strings, no DOM, no built-in objects (window etc), hard to impractical to try to do other record/object handling. Wrap all interaction with stuff you don't get in asm.js in exports and "foreign"s. (OK fair game for code generation but still what a mess and constant switches between the asmjs context and the normal interpreted context aren't free either --- the bulk of "RIA" logic seems to be string/list ops, DOM/window interop, basic cheap control flow.) All the funky emscripten stuff that works in asm.js just also-compiles-and-then-uses the original memory/byte-based logic for these (records, lists, strings, objects etc) of the source's native runtime/compiler/libs.

As for wasm, seems to aim to mostly follow in the above footsteps "at first" (read, the next half decade at best, realistically...)

(But yeah, asm.js is of course still neat for overwhelmingly-numerical high load computations (game-playing / media-editing stuff etc comes to mind) as well as showcasing/proof-of-concepting both older C / OpenGL games and newer 3D engine demos..)


Fantastic, and the description of cross compilation in the blog post intrigues me. Looking forward to checking this out.


Crystal is a static language with Ruby-like syntax, not a dynamic language.

Nim, likewise, is static.


About V8 JavaScript: "Slow, because compared to C, Crystal or Go, it's slow."

In my experience writing real world server programs, Node.js is faster than Go in many cases. Part of this is a lack of Go library maturity, but it's also due to the fact that V8 optimizes code at runtime based on how it's being used. This project seems to come from a naive, "interpreted slow, compiled fast" view of the world, leading to false assumptions like Go is going to be automatically faster than Node.js because Go is AOT compiled and Node is JIT compiled.


I can't speak to Go or Crystal. V8 performs well on microbenchmarks, but very poorly with a sustained high volume of input due to GC pressure. I've used node.js in such an application and "version 2" was written in another language because of the GC pauses.


So what was the language C, C++, Rust?


KDB, although I'm experimenting with Erlang for some things and it seems sufficient.


Odd. Erlang uses GC.


Yes, but many small heaps means small GC pauses. It's not as deterministically/fast as reference counting (KDB) but it's close.


I'd be interested in knowing what parts of the language it doesn't support and the estimated timeline for implementing them (as in soon, not soon, probably never). In any case it looks very interesting and should make quite a splash if it can deliver. Gotta love HN - always a new and interesting surprise :-)


Fair warning: I don't know much about compilers.

Maybe it's an idea to make this a native Typescript compiler as it will profit much more from being compiled than pure JavaScript will?

This way it would probably still be possible to compile pure JavaScript but providing type information would allow the compiler to apply many more optimisations.

If I'm not mistaken V8 already does something similar to this with WASM (and I think asm.js).


Here's a TypeScript to C89 transpiler:

https://github.com/andrei-markeev/ts2c

Very early, but the author seems to be actively working on it.


thank you


I finally understand what 'hand wavy' means after reading this post. Not much of substance here.

If you want to take advantage of Go's speed alongside JS, check out https://github.com/ry/v8worker


> I finally understand what 'hand wavy' means after reading this post. Not much of substance here.

Indeed, this blog post is very vague and offers no solid evidence that this project even exists. I was very sceptical of it from the beginning and I'm surprised it was voted up so much. I guess generating hype is easy when JavaScript is involved.


given the effort put into compiling other languages to javascript, i wonder if this could be the basis for easy cross-compilation of those languages. (i'm thinking mostly of ocaml, but lots of other languages could potentially benefit from the native compilation too, clojurescript-to-native, for instance.)


Clojurescript-to-native was my first thought too. I think where it will break down is the threading model though. Crystal, for example, threw Ruby's threading model out the window, went with channels, and is now working on an implementation of parallelism. Native JS is a great idea, but we've got to get these languages leveraging the CPUs they're on if we really want fast native code.


I'd love to see the output of the compiler. What assembly it generates with some standard functions (Fib, fac, tree generation, tree traversal, etc). I've wanted something like this for some time.

If anyone knows how and can make this happen I'd like to see this on some godbolt-esq site.


We are making a godbolt-esq site for Nectar, coming soon :)


If the output comes out nice and the argument passing semantics is sensable I'll try and rewrite my OS kernel in this. I want to do it in JS or something similar as it would be a great teaching tool to students as the language is very simple and they need no other testing utilites then their web browser.


Link to the actual project?


The website project is in active development, coming soon


Source code? Is it on github? Can you provide some example binaries?


I would like to hear comments on SoundScript. Heard Google was trying to speed up JavaScript by creating a high performace subset of it, and it's very related to type inference. But no more updates.

http://www.2ality.com/2015/02/soundscript.html

https://github.com/v8/v8/wiki/Experiments%20with%20Strengthe...


I think the definitively typed [0] can be leveraged here for AOT compilation.

Looking forward to more details.

https://github.com/DefinitelyTyped/DefinitelyTyped


This is really neat. I can see JS compiling to WASM as a gret way to get early adopters.


Yes, it's one of our goals


Just to double check, when you or anyone else involved initiated this project, were you aware that JavaScript IS in fact now compiled to optimized native code at runtime (i.e. JIT compiled)? I am asking because several years ago that wasn't the case, and many people still to this day refer to JavaScript as an interpreted language, when that generally isn't so anymore.


Super exciting.

How does it compare to EncloseJS and nexe (which grab V8's unoptimized compiled machine code)?

What does it target (ES3 CP, ES 5, ES 6)?

Does it require/support async server-side apps?

Does it have http, http/2 or other IP protocols?

Can it compile regexpes to native code like V8?


The only way I can imagine this is faster than Node is on run-once code, where you are comparing against interpreter or baseline JIT.


Cool!




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

Search: