# All about Air.jsAir.js is an ES6 benchmark. It tries to faithfully use new features like arrowfunctions, classes, for-of, and Map/Set, among others. Air.js doesn't avoid anyfeatures out of fear that they might be slow, in the hope that we might learnhow to make those features fast by looking at how Air.js and other benchmarksuse them.This documents the motivation, design, and license of Air.js.To run Air.js, simply open "[Air.js/test.html](test.html)" in your browser. Itwill only run correctly if your browser supports ES6.## MotivationAt the time that Air.js was written, most JavaScript benchmarks used ES5 orolder versions of the language. ES6 testing mostly relied on microbenchmarks orconversions of existing tests to ES6. We try to use larger benchmarks to avoidover-optimizing for small pieces of code, and we avoid making changes toexisting benchmarks because that approach has no limiting principle: if it's OKto change a benchmark to use a feature, does that mean we can also change it toremove the use of a feature we don't like? We feel that the best way to avoidfalling into the trap of creating benchmarks that reinforce what some JS engineis already good at is to create a new benchmark from first principles.We only recently completed our new JavaScript compiler, called[B3](https://webkit.org/blog/5852/introducing-the-b3-jit-compiler/). B3'sbackend, called[Air](https://webkit.org/docs/b3/assembly-intermediate-representation.html), isvery CPU-intensive and uses a combination of object-oriented and functionalidioms in C++. Additionally, it relies heavily on high speed maps and sets. Itgoes so far as to use customized map/set implementations - even more so thanthe rest of WebKit. This makes Air a great candidate for ES6 benchmarking.Air.js is a faithful ES6 implementation of Air. It pulls no punches: just asthe original C++ Air was written with expressiveness as a top priority, Air.jsis liberal in its use of modern ES6 idioms whenever this helps make the codemore readable. Unlike the original C++ Air, Air.js doesn't exploit a deepunderstanding of compilers to make the code easy to compile.## DesignAir.js runs one of the more expensive Air phases, Air::allocateStack(). Thisturns abstract stack references into concrete stack references, by selectinghow to lay out stack slots in the stack frame. This requires liveness analysisand an interference graph.Air.js relies on three major ES6 features more so than most of the others:- Arrow functions. Like the C++ Air, Air.js uses a functional style ofiterating most non-trivial data-structures:inst.forEachArg((arg, role, type, width) => ...)This is because the functional style allows the callbacks to mutate the databeing iterated: if the callback returns a non-null value, forEachArg() willreplace the argument with that value. This would not have been possible withfor-of.- For-of. Many Air data structures are amenable to for-of iteration. While theinnermost loops tend to use functional iteration, pretty much all of theouter logic uses for-of heavily. For example:for (let block of code) // Iterate over the basic blocksfor (let inst of block) // Iterate over the instructions in a block...- Map/Set. The liveness analysis and Air::allocateStack() rely on maps andsets. For example, we use a liveAtHead map that is keyed by basic block. Itsvalues are sets of live stack slots. This is a relatively crude way of doingliveness, but it is exactly how the original Air::LivenessAnalysis worked, sowe view it as being quite faithful to how a sensible programmer might use Mapand Set.Air.js also uses some other ES6 features. For example, it uses a Proxyin one place, though we doubt that it's on a critical path. Air.js uses classesand let/const extensively, as well a symbols. Symbols are used as enumerationelements, and so they frequently show up as cases in switch statements.The workflow of an Air.js run is pretty simple: we do 150 runs of allocateStackon four IR payloads.Each IR payload is a large piece of ES6 code that constructs an Air.js Codeobject, complete with blocks, temporaries, stack slots, and instructions. Thesepayloads are generated by running Air::dumpAsJS() phase just prior to thenative allocateStack phase on the largest hot function in four major JSbenchmarks according to JavaScriptCore's internal profiling:- Octane/GBEmu, the executeIteration function.- Kraken/imaging-gaussian-blur, the gaussianBlur function.- Octane/Typescript, the scanIdentifier function,- Air.js, an anonymous closure identified by our profiler as ACLj8C.These payloads allow Air.js to precisely replay allocateStack on those actualfunctions.It was an a priori goal of Air.js to spend most of the time in theallocateStack phase. This is a faithful reproduction of the C++ allocateStackphase, including its use of an abstract liveness analysis. It's abstract in thesense that the same liveness algorithm can be reused for temporaries,registers, or stack slots. In C++ this meant using templates, while in ES6 itmeans more run-time dynamic dispatch.Each IR payload is executable code that allocates the IR, and about 15% ofbenchmark execution time is spent in that code. This is significant, but havinglearned this, we don't feel that it would be honest to try to change theefficiency of payload initialization. What if the payload initialization wasmore expensive on our engine than others? If it was, then such a change wouldnot be fair.Air.js validates its results. We added a Code hashing capability to both theC++ Air and Air.js, and we assert each payload looks identical afterallocateStack to what it would have looked like after the original C++allocateStack. We also validate that payloads hash properly beforeallcoateStack, to help catch bugs during payload initialization. We have notmeasured how long hashing takes, but it's a O(N) operation, while allocateStackis closer to O(N^2). We suspect that barring some engine pathologies, hashingshould be much faster than allocateStack, and allocateStack should be where thebulk of time is spent.## LicenseCopyright (C) 2016 Apple Inc. All rights reserved.Redistribution and use in source and binary forms, with or withoutmodification, are permitted provided that the following conditionsare met:1. Redistributions of source code must retain the above copyrightnotice, this list of conditions and the following disclaimer.2. Redistributions in binary form must reproduce the above copyrightnotice, this list of conditions and the following disclaimer in thedocumentation and/or other materials provided with the distribution.THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANYEXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THEIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULARPURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. ORCONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, ORPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORYOF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USEOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.## SummaryAt the time that Air.js was written, we weren't happy with the ES6 benchmarksthat were available to us. Air.js uses some ES6 features in anger, in the hopethat we can learn about possible optimization strategies by looking at this andother benchmarks.