Hacker News new | past | comments | ask | show | jobs | submit login
“The 'use asm' pragma is not necessary to opt into optimizations in V8” (code.google.com)
116 points by elisee on Oct 15, 2013 | hide | past | favorite | 112 comments



Sadly V8's dev team has yet to address the other problem that "use asm" solves - falling out of JIT sweet spots due to broken heuristics. If you maintain a large machine-generated JS codebase (like I do by proxy, with my compiler), it is a regular occurrence that new releases of Chrome (and Firefox, to be fair) will knock parts of your code out of the JIT sweet spot and suddenly start optimizing other parts. Sometimes code that was fast becomes slow for no reason, other times some slow code becomes fast and now you look at profiles and realize you need to remove caching logic or that your code will be faster if you remove an optimization.

The arms race never ends, and keeping up with it is a full-time job. asm.js fixes this, by precisely specifying the 'sweet spot' and giving you a guarantee that if you satisfy its requirements, all your code will be optimized, unless the VM is broken. This lets you build a compiler that outputs valid asm.js code, verify it, and leave it alone.

These days I don't even have time to keep up with the constant performance failures introduced by new releases, but JSIL is a nearly two-year-old project now and they cropped up regularly the whole time. Ignoring the performance failures isn't an option because customers don't want slow applications (and neither do I).


> The arms race never ends, and keeping up with it is a full-time job. asm.js fixes this, by precisely specifying the 'sweet spot' and giving you a guarantee that if you satisfy its requirements, all your code will be optimized, unless the VM is broken.

I'm not convinced that 'use asm' helps with that at all. Static compilation of the sort that asm.js gives you is also full of heuristics. Even in C, you've got that sort of effect.

Tweak the inlining cost model, and suddenly your frequently called accessor function now has function call overhead, slowing everything down. Or maybe it decides that the cost of padding vectors is too high, and autovectorization shouldn't be applied now. Or other dozens of similar heuristics.

It doesn't matter if you have a 'use asm'; tweaking the compiler will change the heuristics and boot you out of the sweet spot.


In theory, you're right - even C compilers make decisions about what to optimize and how. But in practice, the variance in the performance of C compiler outputs to dynamic language VMs is enormous.

With C you might get a 2-3x difference in common cases, but in a JS engine if it decides incorrectly to stay in the interpreter, you've lost 100x. If it enters a vicious cycle of deopts, it can be even worse. If it does a long GC all of a sudden, you can lose 300ms in an unpredictable way.

Again though, in principle all this is solvable in dynamic VMs. We know that theoretically and based on logical arguments; it would be great to see it proven in practice, but it hasn't yet.


Yes, but that isn't changing continuously.

You get one build that is targeted to a specific compiler. Sure, upgrading the compiler might change what you need to optimize, but that happens when you, the developer, decides to do that, as opposed to when the user decides to upgrade their browser.

You can say "the JS sitting on the server, being served to clients, is fast with asm.js" That won't change until you re-compile it. With the JS jit, you can't know that.


> "the JS sitting on the server, being served to clients, is fast with asm.js"

Which asm.js compiler? The one in Firefox today? The one tomorrow with different inlining heuristics? The one that Opera decides to put in? The one for ARM or for x86?


A broken inlining heuristic is not going to have the kind of performance consequences for an asm.js app that you get when you get dropped into the interpreter/baseline JIT in a javascript app today.

A function not getting inlined means the addition of function call overhead, and that's it, in most cases. Dropping into an interpreter or baseline jit can literally produce a 100x slowdown, or worse.

Any conforming asm.js implementation that does AOT compilation will produce predictable performance. Yes, individual implementations will differ, but the same is true when you compile applications against MSVC, Clang, and GCC. You don't see anyone arguing that Clang and MSVC should have standardized inlining heuristics, do you?

It's important to remember, also, that when we say things like 'JIT sweet spot' we're not just referring to what code you wrote. It also matters what code you run, in which order, with which arguments. Something as simple as changing the order in which you call some functions (without changing the actual code in those functions) can cause them to deopt in modern runtimes because the heuristics are so sensitive to minor changes in data. Those kind of variances can be caused by something as simple as a HTTP request taking a bit longer to complete and changing the startup behavior of your app.


Does that happen measurably often on hot paths in reality, or is it just a theoretical worry? Because it sounds like you're describing a JIT with broken heuristics.


Yes, it happens measurably often on hot paths in reality. I have test cases that produce it against modern builds of v8 and spidermonkey. Naturally, they are not all real-world examples, but they're based on problems from real apps.


I know it's probably a little much to ask but do you have any examples of the types of performance regressions you experienced over the last few years within v8 that hit you the hardest?


The vast majority of them are cases where some code starts getting deopted by v8 and dropping into the low-performance interpreter-equivalent mode in the runtime. There are hundreds of ways to trigger this and the list changes regularly.

In practice when it's big enough to demonstrate with a test case, the reports look like this:

http://code.google.com/p/chromium/issues/detail?id=261468

"It's slower than it used to be, I can't tell why."

Part of the problem is that the profiling tools aren't precise or reliable enough to show you what got slower (in fact, opening them changes performance characteristics dramatically). So I end up having to attach a debugger and launch Chrome with special undocumented v8 flags to get it to dump things like traces of optimization attempts and failures, and then try to figure out what cryptic deoptimization causes like 'check-maps' mean in practice for my code.

There have been a few specific cases where the VM started relying on particular characteristics of objects, and that caused my code to deopt. I remember that at some point it became the case that putting a method named 'apply' or 'call' on any object will cause calls to those methods to become incredibly slow in V8; for some reason at that time X.apply() and X.call() were special-cased to always assume that they were Function.apply or Function.call, so if they weren't the optimizer bailed out. Funnily enough, this also applied to Function.bind() - if you called .bind() on a function, the result would have special .apply() and .call() methods so using the result deoptimized your code. I don't know if they ever fixed that problem. I renamed all my methods to avoid it and removed most uses of Function.bind.


> In practice when it's big enough to demonstrate with a test case, the reports look like this:

I would like to note that unfortunately there does not seem to be a single V8 person on that bug. I highly recommend filing all JavaScript related issues (including performance ones) directly on V8 bug tracker at http://code.google.com/p/v8. This guarantees that these issues will be immediately seen by the V8 team.

[to be completely precise anything that is related to correctness or performance of the language described in ECMA-262 can go directly to V8 issue tracker. On the other hand things like DOM APIs are implemented in Blink bindings so it should go into Chromium one. If highly in doubt file Chromium issue :-)]

Do you still observe performance issues reported in that bug? If so I will undo WontFix and CC relevant folks.

> for some reason at that time X.apply() and X.call() were special-cased to always assume that they were Function.apply or Function.call

To the best of my knowledge there was never an assumption like this in V8. Optimizer would detect Function.prototype.apply using a token attached to the closure value itself. Optimizer still does not optimize Function.prototype.call in any special way.

It would be quite interesting to figure out what was going wrong for you and whether it is fixed or not. One possibility is clash of the CONSTANT map transitions, but honestly I don't see how it can occur.


Yeah, I agree that V8 is probably the right tracker to go with. It's a mess since most of my test cases are not 'just JS' that can run in the d8 shell; they are applications and the perf issues appear when running the complete application. I'll take a look at the bug again and see if there's still a regression; I haven't checked in a while. I don't really have a way to pull out old Chrome builds, though...

The apply/call thing is from an old email thread with you, so I must have misunderstood. My understanding still led to performance improvements though, so that's quite mysterious :)


> The apply/call thing is from an old email thread with you, so I must have misunderstood. My understanding still led to performance improvements though, so that's quite mysterious :)

Ah, I remembered the thread. If I am not mistaken the problem was that you were adding properties to function objects and those function objects were flowing into f.apply(obj, arguments) site making it polymorphic. At this point Crankshaft would fail to recognize this apply-arguments pattern and disable optimization for this function.

Situation was similar to http://jsperf.com/apply-argument-poly

(microbenchmark warning :-))


Shouldn't someone on the V8 team watch Chromium's "Cr-Content-JavaScript" bugs, especially bugs with the word "performance" in the summary?


I think what happened here is that Cr-Content-JavaScript is the wrong/outdated label, so V8 team was not automatically CCed. It should be Cr-Blink-JavaScript these days.

I have pinged relevant people.


V8's 'interpreter equivalent' mode is non-crankshaft JIT. It's not interpreted in the slightest.


Yes, that is why I said 'interpreter equivalent' and not 'interpreter'. What matters is that the performance is awful because it's the lowest common denominator :) It is faster than an interpreter!


Is it faster than JSC's new interpreter? That thing is quite fast.


From what I understand writing fast javascript is hard because the engines are improving so fast no one knows what is fast yet. Thus asm.js is a promise to developers: "Stay within the subset and your javascript will be fast".

Yet it never was "use asm" which made asm.js fast. It was the js engine. "use asm" carries symantical knowledge so a browser can warn if you break out of the subset. It also serves as a strong hint that should make the optimizer's easier.

This is why I think asm.js is the future. Mozila does not need buy in from Google, or Apple, or MS. Instead developers can compile to asm.js and their code will run, and in time it will run faster.

So in effect Chrome supports arm.js but they are not making a promise. I think it would be better for the internet if they made this promise.


On Firefox (which originally introduced it), finding "use asm" at the top of the script makes a subset of JavaScript faster by enabling an Ahead-Of-Time optimizing compiler. See http://badassjs.com/post/43420901994/asm-js-a-low-level-high.... From what I understand, "asm.js" code without the "use asm" directive in Firefox would run slower because it wouldn't check it for compliance with the asm.js subset and enable the specialized AOT codepath.

It's an interesting approach but feels a bit like over-specialization. The V8 guys on the other hand, decided to ignore the directive entirely and have been able to improve their (more "generic") JIT compiler to get it to run the same code almost as fast as Firefox's Ahead-Of-Time compiler in a lot of cases.


Both v8 and spidermonkey JITs are close to the asm.js AOT in many (but not all) benchmarks,

http://arewefastyet.com/#machine=11&view=breakdown&suite=asm...

There is no difference in approach between Firefox and Chrome there, both believe JITs can achieve excellent performance, and are always pushing that further. So both browsers "ignore" the "use asm" hint in that sense. The only difference is that Firefox, in addition, has an asm.js AOT module that detects asm.js and can optimize it very well (using the same backend as the JIT, but taking advantage of type information from the asm.js type system). But that AOT does not mean anything about the rest of the JS engine.


So conceptually, the only thing that "use asm" does (in Firefox) is to tell the optimizer/JIT/whatever to assume certain types in advance rather than having it infer them at runtime?


Pretty much, yeah. The normal JIT is used, but first Firefox type checks the asm.js code, during which it detects all the types. Then it just tells the JIT what those types are. This reuses existing functionality in the JIT, as all modern JITs have mechanisms to receive type information - it's typically collected by monitoring types at runtime, the only difference is that here the types are collected through the asm.js type checker.

There are also a few runtime changes as well with asm.js code. For example, the JS engine knows that asm.js code cannot throw exceptions (asm.js code is very low-level, so JS exceptions are not possible), so it can avoid emitting some things it would normally emit in order to handle deopts due to exceptions and so forth. (Of course this does not require an AOT, a JIT could also detect the lack of need for that overhead, perhaps this is already done in some cases, but the AOT guarantees it is done for all asm.js code.)


One key benefit to asm.js is that it turns of garbage collection. Given that GC pauses are still a major nuisance (seriously, try simply flashing a div or canvas in a requestAnimationFrame loop, and you'll see it even in recent versions of Chrome), that is potentially a major win for game development.

Of course, the real solution is to fix the garbage collector, or (maybe) better yet, give user code a way to switch GC algorithms based on app-specific needs. Until then, the ability to manually manage memory is nothing to sneeze at.


asm.js does not turn off garbage collection. Instead, programs written in this style do not allocate many objects, so the garbage collector does not need to run much. asm.js programs allocate a single giant block of memory, and then do their own psuedo-manual-memory-management within that block.


From the asmjs.org FAQ:

>Q. Why not just keep optimizing JavaScript JIT compilers instead?

>A. No need to stop! But JIT compilers have less predictable performance based on complicated heuristics. The asm.js model provides a model closer to C/C++ by eliminating dynamic type guards, boxed values, and garbage collection.


He's saying it eliminates garbage collection by emitting code that doesn't dynamically allocate any objects. The GC is still active it just never needs to do anything. If you remove the 'use asm' pragma the code will still not allocate any objects and hence the GC will still never need to do anything.


Ah, I see what you're saying. The point being that you still have the GC for any non-asm.js code on the same page. And I suppose by the same token, you could "opt out" of GC without asm.js by using various techniques to eliminate allocations.


> Stay within the subset and your javascript will be fast

You do realize that asm.js was not designed to be written by hand, don't you? It's a target for compilers. There are no bounds "to stay within" for hand-written JS which is still the vast majority of the code out there.


I do, which is why I said developers may "compile" their program into asm.js

Even still someone has to write that compiler and they want to know what will be fast.

I do not see many people writing asm.js by hand beyond optimizing vital sections.


I want "use asm" as a way of getting extra 'compile time' checking.

If I say "use asm" and do something that isn't optimizable by mistake, it should give me an error rather than silently falling back to slow code.


That's all very nice, but it ain't gonna happen because of backwards compatibility. One of the main points of asm.js is that it runs everywhere the same as plain JS, whether the platform knows about asm.js or not.


What? It already happened. That's exactly what happens when you do something that is not part of the asm.js subset in "use asm" code.


I don't use Firefox but if that's the case then asm.js can no longer claim to be "just javascript". I wasn't expecting that one.

Interestingly enough, this makes the V8 approach all the more welcome since it doesn't break backwards compatibility.


It sounds like you missed the distinction between a) displays an error and falls back to slower code and b) does not display an error and silently falls back to slower code. Neither of these options break any compatibility with Javascript proper (and yet one of them is highly more desirable than the other).


asm.js does not break backwards compatibility, asm.js code runs exactly the same in all browsers (if it doesn't, that's a bug and must be fixed of course).

Firefox will warn about asm.js validation errors, but those are just warnings in the web console. They do not affect semantics of execution at all, and are undetectable by the running code. So the code will run identically in all browsers regardless of whether it happens to fall in the asm.js subset or not.


Just run the code through a linter that recognises "use asm" as part of the build/test process.


azakai answered this already, but it's nested deeply below: Firefox gives a console warning if asm.js compilation fails. In fact, it also gives a console warning if asm.js compilation succeeds! So there's never any doubt, and JS semantics are maintained.


> Mozila does not need buy in from Google, or Apple, or MS.

So why Chrome should buy from Firefox? why should they follow the asm.js spec ? Maybe it i the wrong answer to a real problem, javascript speed and its memory foodprint.Why not fix the problem at the source instead of creating a subset? not meant to be written by hand? Chrome approach seems better.


A fair question. I think it has parallels to Intel's itanium vs AMD's x64.

I cannot pretend to know what is best in a perfect world. Yet I am willing to pretend to know what is the more practical way forward.

Native Client is and was an interesting approach, but it requires buy in. Buy in which it was not getting, and it had several years to get said buy in.

When asm.js got announced I can only saw I was shocked. asm.js is brilliant! Brilliant in the same way utf-8 is. It does not require heavy changes yet could bring 90% of the benefit. It does not require a new sandbox. It is cross-platform and cross-architecture!


NaCl is only enabled for Chrome app store apps. it's not for web apps. That may change after PNaCl is released.


yeah but it will still be chrome only. asm.js stuff runs anywhere. just happens to run faster on firefox right now/for now.


I was saying that you can't judge it a flop for lack of buy-in since they haven't launched it yet.


> Why not fix the problem at the source instead of creating a subset? not meant to be written by hand? Chrome approach seems better.

PNaCl is also a subset that is not meant to be written by hand—of LLVM IR. The difference is that asm.js subsets a language that is universally supported by all Web browsers, has massive traction, and has a detailed specification.


Except, you didn't state that PNaCl subsets a specification of a compiler IR, well designed and meant to be compiled.

asm.js subsets a language never meant for this, and tries to come up with something that will compile well. This approach certainly can work, but will have significantly more limitations 5 years from now than the other one.

Not to mention, LLVM, and it's IR, certainly has more traction than "asm.js".


> Except, you didn't state that PNaCl subsets a specification of a compiler IR, well designed and meant to be compiled. > > asm.js subsets a language never meant for this, and tries to come up with something that will compile well.

LLVM IR was also never designed to be a portable, platform-independent, well-specified bytecode. It's designed to be a compiler IR. This shows up in numerous places: TargetData, download size, undefined behavior, and so on. Google has worked around some of these (TargetData) and has not worked around others (undefined behavior—which is really scary for a proposed Web standard).

The fact is, both PNaCl and asm.js are taking a language not designed to be used as a portable, platform-independent representation and making changes to make it work. You can argue about which is "further from the ideal", but I think that's subjective and not productive, involving predicting the future. I certainly don't think there will be any fundamental limitations 5 years from now.

> This approach certainly can work, but will have significantly more limitations 5 years from now than the other one.

I don't see this at all. As I said before, JavaScript is not some immutable thing. We can evolve it. Most of the changes needed to fill in the few remaining holes in asm.js help everyone. SIMD and 64-bit integers, for example, are something the entire Web would benefit from, asm and non-asm writers alike.

> Not to mention, LLVM, and it's IR, certainly has more traction than "asm.js".

Not on the Web, which is the important thing here.


"LLVM IR was also never designed to be a portable, platform-independent, well-specified bytecode."

This is completely and utterly false in every way. This is exactly what it was meant to be, and is. Maybe you mean "it does not turn non-portable languages, like C, into portable ones".

This does not change whether it is a portable, platform independent, well-specified bytecode. This is even covered in the FAQ.

To your specific examples TargetData is meant to deal with languages that have unavoidable target specific dependencies, like C. It is unnecessary in portable languages.

Download size is completely unrelated to a portable, platform-independent, well-specified bytecode, and is probably the absolutely most trivial thing to fix in the world if it was necessary. I'm not sure why one would even bring this up.

Undefined behavior is not necessarily bad. Most languages specify some amount of undefined behavior. This is done on purpose, to enable certain classes of optimizations. Is your claim that asm.js has no undefined behavior?

emscripten certainly does, even in asm.js mode.

My guess is that you meant that asm.js has a bunch of properties that are statically verifiable, and it validates conformance at compile time. That is great, but not really the same as "no undefined behavior". At a glance, asm.js has undefined behavior. For example: "As JavaScript functions, they produce the undefined value, but asm.js code is not allowed to make use of this value"

A program that attempted to make use of it would exhibit undefined behavior. It would be noncomforming to the asm.js spec, in the same way that a program in LLVM IR that uses undefined behavior is noncomforming to the language specification. Again, you may mean that you statically verify this, which is great, but that does not mean the spec does not deal have undefined behavior. It just means you try to validate conformance at compile time (and note, the asm.js spec itself explains why it can't possibly accomplish no actual undefined behavior at runtime: ' In particular, reading out of bounds from a typed array produces undefined, and calling FFI functions may produce arbitrary JavaScript values.')

"I certainly don't think there will be any fundamental limitations 5 years from now."

If you didn't believe this, i'm sure y'all wouldn't be working on asm.js. :)

However, I strongly disagree. What you are doing is not the first time someone has tried to do something like this (see, e.g., the SIMPLE IR and other IR's the McCat compiler used to use), and it won't be the last. Your claim essentially comes down to "this time for sure!". Sorry, but living in the world of compilers, i've seen and heard this too many times. The truth is there are no magic bullets in compiler technology, and asm.js isn't one either. It's a set of tradeoffs. I don't believe this chosen set of tradeoffs come without fundamental limitations.

I guess we'll see whether you are right.

"> Not to mention, LLVM, and it's IR, certainly has more traction than "asm.js".

Not on the Web, which is the important thing here."

You can always define a target audience that likes your stuff more. So let's go this way: Researchers, compiler developers, and a very large contingent of companies care very strongly about LLVM and improving it.

I certainly wouldn't disagree with "worse is better" often wins, particularly on the web.


Posts like yours demonstrate that you're arguing fervently about asm.js without having used it. One obvious error:

My guess is that you meant that asm.js has a bunch of properties that are statically verifiable, and it validates conformance at compile time. That is great, but not really the same as "no undefined behavior". At a glance, asm.js has undefined behavior. For example: "As JavaScript functions, they produce the undefined value, but asm.js code is not allowed to make use of this value"

'Not allowed to make use of this value' means that the asm.js application will not get AOT compiled if it tries to use the value, because it's not a valid asm.js application. So the behavior isn't 'undefined', it doesn't work. The AOT compiler rejects it and it runs as regular JS, in which case the behavior is 'defined' in that it works the way all other regular JS does.

asm.js is specced and there's a verifier; if you pass the verifier your code should behave predictably in an AOT-compiling runtime. The runtime isn't producing code that does dynamic type checks at runtime or other sorts of nondeterministic behavior.

It's certainly possible that some undefined behavior slipped into the spec somewhere, but I definitely haven't seen any actual examples where they specced it and the Firefox implementation generates undefined behavior on purpose. That would be insane. asm.js delivers the same safety guarantees as regular JS; you can't do that with undefined behavior ala C.

Ultimately if you have concerns about the safety of asm.js or the presence of undefined behavior, you should test it and report bugs. I've tested asm.js some and played around with it and have not found any reason to be concerned.


"Posts like yours demonstrate that you're arguing fervently about asm.js without having used it. One obvious error: "

Sorry, but no. Also, I was addressing points made about LLVM IR, and the asm.js specification, as well as it's suitability as a compiler IR. I've used asm.js, i've read and understood the current optimizing compiler implementation, as well as the published roadmaps and mailing lists.

I don't appreciate the unrelated attack, it was completely unwarranted and unnecessary. The fact that I don't agree with you does not mean "I haven't used it".

" So the behavior isn't 'undefined', it doesn't work."

You completely misunderstand undefined behavior.

The behavior is undefined, it is simply verified at compile time that the program does not perform it. That does not make it less undefined, it makes the program "statically checked" as opposed to "dynamically checked" or "not checked at all" (like most C, though it's worth pointing out it's entirely legal for a C compiler to reject code producing undefined behavior, it's only required to accept implementation defined behavior).

Exactly as I said, C programs (or others) that exhibit undefined behavior are in fact noncomforming, in exactly the same way that the asm.js program would be noncomforming. The only difference is when and whether checking and rejection of nonconforming behavior is performed.

If you are going to argue about language specifications, and make claims about them, you need to be precise.

pcwalton likely meant to make the claim that asm.js itself is safer because it has no undefined behavior at runtime, as it rejects programs that may exhibit undefined behavior at compile time (or something similar), which is at least possible to be true, though i would have a lot of trouble believing it without some formal proof (the spec offers none). It is incredibly hard to actually come up with things like this that are completely statically decidable and still useful.

Your definition of behavior that does not meet the spec as "defined" because "it works the way it all regular JS normally does" is a hilarious new definition of 'defined behavior'.

Also, would you like to refute any other point I have made about how pcwalton has completely misrepresented a lot about LLVM IR?


DannyBee, it is you who are demonstrating considerable misunderstanding here (and in other posts here).

To respond to your comments in this post, the asm.js validator is not checking for undefined behavior. In fact, it has nothing to do with undefined behavior whatsoever. It also has nothing to do with decidability. It is merely determining which execution strategy to use. And, behaving according to a spec is actually a pretty ordinary definition of "defined behavior".

You have misunderstood how asm.js works, you have misunderstood other people's comments, and you have mischaractarized LLVM in other posts here. I know you're capable of much higher-quality discussion than this. Please take a moment to reflect.


"To respond to your comments in this post, the asm.js validator is not checking for undefined behavior."

So let's start simple:

asm.js's validator does in fact, statically try to decide asm.js spec conformance. That means it is checking for undefined behavior, since it is checking whether the program meets the language specification, and the language spec disallows various undefined behavior. I'm not sure how you could characterize it as anything else, when the spec itself says this is what it does:

"This sublanguage effectively describes a safe virtual machine for memory-unsafe languages like C or C++. A combination of static and dynamic validation allows JavaScript engines to employ an ahead-of-time (AOT) optimizing compilation strategy for valid asm.js code."

Note: it says it uses static and dynamic checking to validate spec conformance. It also claims it is safe. Such claims and static program validation of any sufficiently complex language, like asm.js, involves decidability issues and issues about undefined behavior. Period. If you disbelieve this, all I can suggest is delving into the field of program validation and static analysis. I completely understand the cases it cannot validate fall back to normal javascript engine execution, but that actually changes exactly nothing about what the validation is doing, the language specification, undefined behavior, whether it has fundamental limitations, etc.

"You have misunderstood how asm.js works, you have misunderstood other people's comments, and you have mischaractarized LLVM in other posts here. "

I disagree that I have done anything of the sort. Starting from the last, I have not mischaracterized LLVM in any way.

What I see here is a discussion with people who are trying to make very imprecise comments about very precise technical specifications and definitions, and then argue about them. I feel like i'm trying to argue physics with a bunch of chemists. It's not that they aren't smart, it's that they are abusing terminology and trying to make nonsense out of very well understood issues in the field.

In fact, i don't believe anyone working in the compiler field (even on asm.js) would disagree with anything i've said, because as far as I can tell, they understand these are all issues, and try to stay away from ridiculous claims about competitors.

pcwalton originally made some very direct claims about LLVM's IR, and asm.js. I have pointed out they are directly false. That is not mischaracterizing LLVM, or asm.js. It is in fact, a response to such mischaracterizations. Out of kindness, I did not originally point out that a very trivial reading of any paper on LLVM, it's bitcode, or the IR, would have shown that his claims are false, and instead responded directly. The very first LLVM paper ever written even covers exactly the claim he makes about portability, platform independence, and specificity. He would not have had to look very far on the website to find a refutation.

I actually have no real love for either (they are both a set of engineering tradeoffs). I just cannot stand to see one side or the other pretend that the other side is completely horrible, and that they have chosen "the one true path". I am also not going to watch one side or the other make claims that are simply false in an attempt to support that path.

In any case, i'm not going to argue about it anymore here, as it is a mild exercise in futility. You are welcome to believe what you like about asm.js and LLVM, and their relative merits.


Which version of LLVM IR are we talking about here? Since LLVM's IR is intended to be used as a compiler IR and not an executable bytecode format, it changes in backwards-incompatible ways on a regular basis. Google's PNaCL solves this by snapshotting a particular version of LLVM and using a subset of the IR of that version of LLVM. Last I heard, if they ever needed to support newer versions of LLVM the plan was to create a completely new, incompatible version of the PNaCL bitcode from it.


Yes, LLVM is forwards compatible, not backwards compatible. While you are wrong, because it is in fact " an executable bytecode format", it does change in backwards-incompatible ways.

However, it has not done so "on a regular basis" (to say otherwise is complete bullshit. I'm pretty sure the last major backwards incompatible change was version 3.0, which was 2 years ago). However, from where I sit, it's changed seriously less than asm.js in the past few years!

We'll see how long asm.js actually stays backwards compatible without having real impact.


How would asm.js break backwards compatibility? It has to be executable javascript; it can't change execution semantics without changing JS execution semantics. It is the case that support for a wider set of features could be added, and that stuff wouldn't get AOT-compiled in older browsers, but nothing would break. It'd just be slower.

'seriously less than asm.js in the past few years' is a blatantly fallacious claim. asm.js, as a spec, is barely a year old (going by the date of dherman's first public commit to his github repository) and the first implementation is younger than that. Mozilla turned the implementation around in a few months with a handful of engineers.


"How would asm.js break backwards compatibility"

See, e.g., pcwalton's own comments:

"Most of the changes needed to fill in the few remaining holes in asm.js help everyone. SIMD and 64-bit integers, for example, are something the entire Web would benefit from, asm and non-asm writers alike."

Such changes would almost certainly be backwards incompatible with what exists now.

"'seriously less than asm.js in the past few years' is a blatantly fallacious claim."

Errr, given asm.js has changed a lot in the year it's been around, and LLVM has not changed in any backwards compatible way in that time period, it's actually what you would call "trivially true", not "blatantly false".


New features like SIMD and 64-bit integers would not be asm.js features, they would be JavaScript features. If you read his comments more carefully you would realize this, since he mentions how they're being developed as JavaScript features...

Again, asm.js has 'changed' but has not broken backwards compatibility, because it's just javascript and remains so.


This is a word game. You can't define away the problem of backwards compatibility by saying "well it wasn't us who changed in a backwards incompatible way, it was the underlying language we subset".

You especially can't do this when the parent made the claim that PNaCL has exactly this issue.


Getting the Citadel demo to run at 60fps is impressive. It means that V8 is "fast enough" to keep up with the video card on that application.

However, the benchmarks at [0] clearly show that this is not the end of the story. The "workload0" runs measure startup time. All the other workloads show runtime performance, and V8 is still quite a ways behind.

[0] http://arewefastyet.com/#machine=11&view=breakdown&suite=asm...


It's interesting to see how Firefox in these benchmarks is way, way faster than Chrome - quite a turn of things.


A point some people seem to be missing in this thread which I would like to emphasize is the value of predictable performance.

It is true that a JIT is in principle, capable of all the same things as an AOT compiler. It is true, that improving the speed of a JIT is valuable. However, that all glosses over the fact that a JIT is a black box. If I am working on an application today that has a mysterious slowdown after running for about 80 seconds, the promise that won't happen in next year's browser release is of no use to me whatsoever.

In fact, I would prefer the jit run at the same constant slow speed instead of starting out fast, giving a false sense of performance. I can OPTIMISE for that. I can work with that. I can't work with a JIT that can't decide how fast it's going to run, and why from release to release, from second to second. It's fine for most applications, but if I absolutely positively need to generate a frame at a constant fixed rate, an unpredictable JIT is a huge liability no matter how theoretically fast it can go on benchmarks. Stability trumps raw performance.


http://mrale.ph/blog/2013/03/28/why-asmjs-bothers-me.html suggested that this would happen:

> When I sit down and think about performance gains that asm.js-implementation OdinMonkey-style brings to the table I don’t see anything that would not be possible to achieve within a normal JIT compilation framework and thus simultaneously make human written and compiler generated output faster.


I agree with mraleph that this is possible. But it has not happened yet: if you look at the slide linked to there,

https://v8.googlecode.com/issues/attachment?aid=25990016000&...

you can see that on a few of those there is still a significant difference, for example on Lua (I filed an issue on that here http://code.google.com/p/v8/issues/detail?id=2873 ).

In principle, a JIT can do anything an AOT compiler can (and more). In practice, the JITs in Chrome and Firefox have gotten close to what the asm.js AOT in Firefox can reach, but only on most benchmarks, not all. The AOT does give a fairly good guarantee of always running the code at a good speed, while the JITs depend on heuristics. In practice, this means that JITs can be tweaked to run any benchmark well, but they don't always do it out of the box, while the AOT far more often does do so.

Again, JIT heuristics can be fixed and improved, I have no doubt about that. And JITs could be designed to rely less on heuristics, I am a strong believer in that as well. But neither of those has yet been proven. I hope they will, though :)


This is great news, and most people knew that Chrome could close a lot of the gap and saw asm.js as a competitive push. The main goal seems to have worked.

There is still a ways to go though before they are fully on par with Firefox. Note that one of the benchmarks is still far slower on Chrome, and there are still probably edge cases here and there that trigger slow JS in Chrome. The guarantee of asm.js that your code is ~2x slower than native is nice. Additionally, loading in massive amounts of asm.js in Firefox will be much faster in Firefox in the future when they optimize the parser for asm.js.

All in all, these are very happy times. There are pros and cons to both approaches, but it's exciting to see how far it's going.


>The guarantee of asm.js that your code is ~2x slower than native is nice

What? How did a couple of benchmarks that showed ~2x performance for a few tasks become a 'guarantee'? In some situations asm.js only takes twice as long to run your code, in others it takes 12 times as long[1] or even more[2].

[1] http://cdn.arstechnica.net/wp-content/uploads/2013/05/benchm... [2] http://cdn.arstechnica.net/wp-content/uploads/2013/05/classi...


Those are examples of native code using multiple processes or threads. The asm.js code compared to them did not use multiple processes (but it could have, using web workers), nor did it use threads (which are not available on the web).

It's fair to say that for single-threaded code, asm.js has a very good chance of running at half the speed of native, or better. For multicore code, it depends on whether web workers make sense for that use case - if they do, it can still maintain the same performance compared to native. Otherwise, the web platform would need to consider adding some more low-level parallelism to JS. Such proposals exist and there is some discussion about them.


The modern version of "sufficiently smart compiler" has become the "sufficiently smart JIT optimizer". Confidence remains as high as ever, but alas, it seems performance is doing the same thing, plateauing noticeably before reaching parity.

It's hard to get over the fact that a JIT simply faces an uphill battle against an engine that starts by restricting you to a subset of the language; basically, you can assume both engines are being written by equally smart people, so you kind of have to expect that the one that takes the restrictions is going to have a long-term persistent advantage in practice, no matter how theoretically the other team could catch up. OdinMonkey still has all the JIT opportunities, but it has other opportunities too.


But would they have done this without Mozilla pushing the performance with asm.js, or that fast? Competition is great, and it also makes me wonder, if they keep this up - maybe NaCl won't be needed either?


64 bit integers are one small example of something you can't do no matter how fast you make JavaScript.


JavaScript needs 64-bit integers no matter what; the Node folks have been clamoring for it for a long time. We need to add them. Same with SIMD.

JavaScript is not some immutable thing. It needs enhancing, not replacing.


Value types (64-bit, DFP, etc) are possibly on the table for ES7. Brendan has a strawman he shows in talks. If you look at his JSConf.EU video he discusses it.

edit: Video link http://www.youtube.com/watch?v=IXIkTrq3Rgg


That's not quite true -- it is entirely possible to write a 64-bit integer class entirely in Javascript, and have a JIT / ASM.js compiler recognize that and swap out a native version (See ecmascript_simd [1] which I believe was written with this idea in mind)

[1] https://github.com/johnmccutchan/ecmascript_simd


Do you have any pointers to how value types can be implemented efficiently without adding type tags to the language? I can see how that would work in simple cases (say within one function), but it seems that there would be many cases where the vm cannot assume the type of a variable.

I looked at the spec briefly (http://wiki.ecmascript.org/doku.php?id=strawman:value_object...), but it doesn't talk about the how.


Many JS implementations use type tags already. SpiderMonkey, for example - they call it 'nan-boxing'. See http://stackoverflow.com/questions/9435338/what-is-the-aim-o...


Sorry I meant type "tags" in the sense of modifying the language syntax to have required type annotations.

I understand nan-boxing and similar techniques, but they seem to imply at least some runtime overhead to test the type of the value in some cases. Also, AFAIK, 64 bit integers cannot be represented with nan-boxing, as there are only 51 bits available for the payload.


> Sorry I meant type "tags" in the sense of modifying the language syntax to have required type annotations.

There are already multiple types in JavaScript, and this already affects performance; for example "+" is defined for both numbers and strings. All JavaScript engines handle this in the same way, more or less. You start out with a "baseline JIT" that does not make assumptions about the types of objects and has type-check-and-dispatch on operations like "+". These type checks record the types of objects that have flowed through each point. Once enough types have flowed through so that we can reasonably predict the types, we recompile the function to assume that the same type (for example, number) goes through, which enables greater optimizations. You then hoist type guards up (or eliminate them if you can prove them never taken) so that you deoptimize and bail out.

Value objects don't change this overall picture, they just add more types.

All of this, however, is irrelevant to asm.js. With an ahead-of-time optimizing compiler, we know what the types are beforehand via the asm.js spec. So there are no type guards inserted at all, and none of this is an issue. For example, NaN-boxing is not used in the Firefox asm.js compiler (OdinMonkey). We know which values are doubles and which are integers, so we need no runtime guards or type tests at all.


I was responding specifically to the comment that if v8 team continues optimizing in response to asm.js as they have been, perhaps there is no need for NaCL (or asm.js). I don't think that will ever be true.


That's correct, nan boxing doesn't help for 64-bit integers.

On the other hand, Dart doesn't have required type annotations either and it has 64-bit ints, doesn't it? So it should be possible to introduce in JS.

Runtime overhead is likely, but then when you're in the JIT sweet spot the types are all known so you get JITcode that doesn't check types.


I think that part of the value people see in things like NaCL is not having to rely on JIT magic to figure out when to optimize 64 bit math. You get direct control over what is going on. It may be true that in certain edge cases the JIT can actually out-perform clean native code, but people are willing to sacrifice that for direct control over what the machine does.

GC is a related example of a place where VM designers told us not to worry ourselves, but it turns out that GC is inherently hard and you are going to pay for that convenience - either in performance or in memory. See: http://sealedabstract.com/rants/why-mobile-web-apps-are-slow....


> I think that part of the value people see in things like NaCL is not having to rely on JIT magic to figure out when to optimize 64 bit math. You get direct control over what is going on. It may be true that in certain edge cases the JIT can actually out-perform clean native code, but people are willing to sacrifice that for direct control over what the machine does.

This is precisely what "use asm" is for. It gives you direct control: each allowable operation in the subset has a direct analogue to the appropriate machine instruction(s). If you fall off the happy path and stray into "JIT magic" territory, you get a message in the developer console telling you to fix your code.

Unfortunately V8 is opposed to "use asm" in favor of the "JIT magic".


That post you linked to is actually full of some fundamental misunderstandings about GC and JS VMs :) But yes, it is the case that GC isn't a free lunch - it comes with costs and you have to design your applications to avoid the weaknesses of a given GC. It's rough.

I agree that not having to rely on the JIT to figure out how to optimize your code with 'magic' is preferable. I tend to lean towards that where possible in most of the JIT/GC-based environments I use (C#, JavaScript, etc), and it tends to pay off.


Competition is great, and projects like pepper.js (http://trypepperjs.appspot.com/) demonstrate that coexistence is possible as well. What's exciting about this is that for developers all of this is very positive - they can write faster code to write in the client.


You still have the pthreads issue. NaCL can leverage SMP in a way that single-threaded JS can't, and the idea of introducing concurrency into JS sends shivers down my spine.


There's already a lot of good progress with introducing parallelism in JavaScript. See http://wiki.ecmascript.org/doku.php?id=strawman:data_paralle...


You mean like, Web Workers?


WebWorkers only support message passing, it's more like processes than threads. There's no concept of shared memory.


That model is one of the easiest and safest ways of doing concurrency of which I'm aware, unless you want to enforce immutability on shared memory records.


We're not talking about what's safer, were talking about NaCL vs asm.js, and the primary purpose of those is to run native C code in your browser without significant rewriting (e.g. emscripten)

The target for most of this is porting C games to the Web, and many modern C games use all kinds of multithreading and blocking I/O. Game programmers are not interested in safety, they are interested in performance.

Yes, message passing leads to fewer bugs. It also leaves a lot of performance on the table if you are trying to maximize usage of CPU resources on an SMP architecture.


It seems to me that most modern game engines have settled with some sort of parallel task system where the main thread pushes asynchronous tasks into a thread pool. Building such a generalized parallel task system on top of pthreads or WebWorkers in C/C++ is easier then trying to emulate something like pthreads in emscripten (you can't do this anyway because there are no shared memory resources in JS). One has to be aware of the overhead of getting data in and out of workers though.

The PS3 SPUs also didn't have access to system memory, and SPU code had to be compiled into small "executables", so in a twisted way this is a similar model to WebWorkers ;)

Shared TypedArrays would be nice, but only if this also comes with a complete set of atomic operations. But in my opinion, vector datatypes would be more important in the short term for JS (so it can make use of SSE under the hood).


I agree, although I'd also like to see OpenCL on the Web as well (WebCL).


"modern C games use all kinds of multithreading and blocking I/O."

The funny thing here is that gave devs are still kind of weary of threading bugs, and blocking I/O was something that was probably earliest dealt with by the people having to stream content off of spinning plastic discs.


It's true that gamedevs worry about this, and Tim Sweeney has even given prezos on how great functional languages would be for game programming, but the reality is, hardcore game devs crave two things from what I can tell: 1) determinism and 2) maximizing utilization of resources

A VM or high level compiler abstraction, especially with GC, tends to interfere with #1, and sandboxed environments with abstract APIs for accessing hardware tend to interfere with #2.

Yes, this is not to say that games won't use scripting, like Lua or UnrealScript, but those are not part of the rendering loop.

Asm.js provides predictability in terms of GC, but it does not ensure predictability in terms of performance (because of differing JIT implementations), nor does it give the direct access to hardware resources one would like (e.g. SMP).

I don't really see it as a target for next-gen games, regardless of the cool UnrealEngine demos. Mobile casual games maybe.


> A VM or high level compiler abstraction, especially with GC, tends to interfere with #1, and sandboxed environments with abstract APIs for accessing hardware tend to interfere with #2.

For #1, asm.js doesn't have GC, by and large (there are a couple of places where the GC gets used when interacting with Web APIs, but those are fixable and don't matter much in practice). For #2, "a sandboxed environment with abstract APIs for accessing hardware" precisely describes Pepper!

> Asm.js provides predictability in terms of GC, but it does not ensure predictability in terms of performance (because of differing JIT implementations), nor does it give the direct access to hardware resources one would like (e.g. SMP).

I don't understand what you mean. The performance predictability is fixed by "use asm" and AOT compilation. V8 is opposed, but your point (rightly, IMHO!) argues against that choice. If you're just arguing that there will be multiple implementations of asm.js and that's bad for predictability I don't see that being fixed with any solution outside of a browser monopoly. If PNaCl is to become a cross-browser solution, then you must be open to alternative implementations.

As for threads, we can continue to evolve JavaScript so that it is supported, at least in asm.js mode. I don't see this as some fundamental obstacle.


The variance in JIT optimizations will be much wider than the variance in compiling C code to NACL compliant x86 code. In one case, you have varying implementations of a runtime compiler, in the other, you have a single compiler known to the game-dev ahead of time, what varies in the sandbox hit.

Yes, pepper is an abstraction, but you get concurrency primitives which is lower level than asm.js.

The reality is, neither asm.js nor NaCl are going to succeed in getting the gamedev community onto the web platform. The two most popular platforms, iOS and Android, have native SDKs, and so do the consoles and desktops, so any developer who wants to maximize revenue is going to want to target those. The additional headache of a browser port onto a heavily fragmented platform with unpredictable performance, and an audience that has shown little appetite for paying for web apps pretty much guarantee that this whole asm.js vs NaCL debate is moot.

Developers just don't care about runtime portability as far as games are concerned. They will do the work to do a source port for optimal performance if the market is there.

Does Electronic Arts or Valve care about getting Battlefield 4 or Portal 2 running in the browser in a portable way? I highly doubt it.


It's also one of the slowest.


> There's no concept of shared memory.

Which is already more than proven that only leads to threading bugs.


I don't believe it has been proven to anyone's satisfaction that that is the only thing it leads to.


Given the amount of developers not able to write multi-threaded applications with shared memory I would say it is very well proven.


"Given the amount of developers unable to write a compiler, it is well-proven that compilers lead only to bugs."


Where can I find a bug free one?


I think the proper question under the formulation you provided is, "Where can I find one that does anything other than produce bugs," since you asserted that bugs are the only result. To which the answer is: any of them.


the argued keyword in your previous comment is "only".


Written using a multithreaded browser, that runs on a multithreaded OS.


So what? Are they bug free from deadlocks?


Nothing of this complexity is bug free, but the fact and the points is that some of the most reliable software everybody uses every day -- OS kernels, DBMS, etc. -- are not just multithreaded, but heavily multithreaded.


Perhaps locking the DOM is a good trait if you can communicate between the main thread and the webworkers then you can request that the main thread changes the DOM and synchronize, without this you might get unexpected behavior anyways?


> NaCL can leverage SMP in a way that single-threaded JS can't, and the idea of introducing concurrency into JS sends shivers down my spine.

I agree that we don't want to break the single-threaded JS model. But we have options, the easiest of which is to restrict shared mutable state to ArrayBuffers and asm.js, or asm.js-like code.


They've been doing it for years, remember that Chrome/v8 is the one that forced all the others in really caring about js speed in the first place, and kept the race going for years by always being a few steps ahead.

In this cas mozilla is the one that took the lead on the asm.js case, and healthy competition is always better, but I believe it makes no doubt that the v8 team would have worked at improving their js speed as much as possible even without that pinch from mozilla.


"remember that Chrome/v8 is the one that forced all the others in really caring about js speed in the first place"

That's not accurate. I recommend you go back and look at the actual order of events here.

Apple started the JS perf battle with SquirrelFish Extreme and Firefox answered with TraceMonkey, all before Chrome was even announced.

I am not discounting the impact that V8 has had on the industry, but it is simply wrong to claim that Google kicked this thing off when Apple and Mozilla were both there publicly squaring off in JS benchmarks well before you, and just about every one else on the planet, even knew Chrome was a thing.


It's hard to trace who really started it. Before SquirrelFish extreme there was Opera Kestrel that crushed everyone in JS tests:

http://nontroppo.org/timer/kestrel_tests/

and when Safari was still an early beta Opera was marketed as "The Fastest Browser on Earth".


This does not changed the fact that chrome crushed the competition just like gmail crushed others. It took a very long time others to keep up and they are still behind in most cases. IMHO v8 deserves all the credit for the advance of JavaScript performance race.


I made the same experience with my hand optimized asm.js code. http://s-macke.github.io/jor1k/ It runs as fast as asm.js in Firefox. Chrome is optimizing it really well.


I think this only states that asm.js or it's tests are far from perfect now.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

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

Search: