Merge V8 at 3.9.24.13

Bug: 5688872
Change-Id: Id0aa8d23375030494d3189c31774059c0f5398fc
diff --git a/benchmarks/README.txt b/benchmarks/README.txt
index 6676f37..59f76ff 100644
--- a/benchmarks/README.txt
+++ b/benchmarks/README.txt
@@ -77,3 +77,10 @@
 Furthermore, the benchmark runner was changed to run the benchmarks
 for at least a few times to stabilize the reported numbers on slower
 machines.
+
+
+Changes from Version 6 to Version 7
+===================================
+
+Added the Navier-Stokes benchmark, a 2D differential equation solver
+that stresses arithmetic computations on double arrays.
diff --git a/benchmarks/base.js b/benchmarks/base.js
index ffabf24..62c37e1 100644
--- a/benchmarks/base.js
+++ b/benchmarks/base.js
@@ -1,4 +1,4 @@
-// Copyright 2008 the V8 project authors. All rights reserved.
+// Copyright 2012 the V8 project authors. All rights reserved.
 // Redistribution and use in source and binary forms, with or without
 // modification, are permitted provided that the following conditions are
 // met:
@@ -78,7 +78,7 @@
 // Scores are not comparable across versions. Bump the version if
 // you're making changes that will affect that scores, e.g. if you add
 // a new benchmark or change an existing one.
-BenchmarkSuite.version = '6';
+BenchmarkSuite.version = '7';
 
 
 // To make the benchmark results predictable, we replace Math.random
diff --git a/benchmarks/navier-stokes.js b/benchmarks/navier-stokes.js
new file mode 100644
index 0000000..b0dc3c8
--- /dev/null
+++ b/benchmarks/navier-stokes.js
@@ -0,0 +1,387 @@
+/**
+ * Copyright 2012 the V8 project authors. All rights reserved.
+ * Copyright 2009 Oliver Hunt <http://nerget.com>
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+var NavierStokes = new BenchmarkSuite('NavierStokes', 1484000,
+                                      [new Benchmark('NavierStokes',
+                                                     runNavierStokes,
+                                                     setupNavierStokes,
+                                                     tearDownNavierStokes)]);
+
+var solver = null;
+
+function runNavierStokes()
+{
+    solver.update();
+}
+
+function setupNavierStokes()
+{
+    solver = new FluidField(null);
+    solver.setResolution(128, 128);
+    solver.setIterations(20);
+    solver.setDisplayFunction(function(){});
+    solver.setUICallback(prepareFrame);
+    solver.reset();
+}
+
+function tearDownNavierStokes()
+{
+    solver = null;
+}
+
+function addPoints(field) {
+    var n = 64;
+    for (var i = 1; i <= n; i++) {
+        field.setVelocity(i, i, n, n);
+        field.setDensity(i, i, 5);
+        field.setVelocity(i, n - i, -n, -n);
+        field.setDensity(i, n - i, 20);
+        field.setVelocity(128 - i, n + i, -n, -n);
+        field.setDensity(128 - i, n + i, 30);
+    }
+}
+
+var framesTillAddingPoints = 0;
+var framesBetweenAddingPoints = 5;
+
+function prepareFrame(field)
+{
+    if (framesTillAddingPoints == 0) {
+        addPoints(field);
+        framesTillAddingPoints = framesBetweenAddingPoints;
+        framesBetweenAddingPoints++;
+    } else {
+        framesTillAddingPoints--;
+    }
+}
+
+// Code from Oliver Hunt (http://nerget.com/fluidSim/pressure.js) starts here.
+function FluidField(canvas) {
+    function addFields(x, s, dt)
+    {
+        for (var i=0; i<size ; i++ ) x[i] += dt*s[i];
+    }
+
+    function set_bnd(b, x)
+    {
+        if (b===1) {
+            for (var i = 1; i <= width; i++) {
+                x[i] =  x[i + rowSize];
+                x[i + (height+1) *rowSize] = x[i + height * rowSize];
+            }
+
+            for (var j = 1; i <= height; i++) {
+                x[j * rowSize] = -x[1 + j * rowSize];
+                x[(width + 1) + j * rowSize] = -x[width + j * rowSize];
+            }
+        } else if (b === 2) {
+            for (var i = 1; i <= width; i++) {
+                x[i] = -x[i + rowSize];
+                x[i + (height + 1) * rowSize] = -x[i + height * rowSize];
+            }
+
+            for (var j = 1; j <= height; j++) {
+                x[j * rowSize] =  x[1 + j * rowSize];
+                x[(width + 1) + j * rowSize] =  x[width + j * rowSize];
+            }
+        } else {
+            for (var i = 1; i <= width; i++) {
+                x[i] =  x[i + rowSize];
+                x[i + (height + 1) * rowSize] = x[i + height * rowSize];
+            }
+
+            for (var j = 1; j <= height; j++) {
+                x[j * rowSize] =  x[1 + j * rowSize];
+                x[(width + 1) + j * rowSize] =  x[width + j * rowSize];
+            }
+        }
+        var maxEdge = (height + 1) * rowSize;
+        x[0]                 = 0.5 * (x[1] + x[rowSize]);
+        x[maxEdge]           = 0.5 * (x[1 + maxEdge] + x[height * rowSize]);
+        x[(width+1)]         = 0.5 * (x[width] + x[(width + 1) + rowSize]);
+        x[(width+1)+maxEdge] = 0.5 * (x[width + maxEdge] + x[(width + 1) + height * rowSize]);
+    }
+
+    function lin_solve(b, x, x0, a, c)
+    {
+        if (a === 0 && c === 1) {
+            for (var j=1 ; j<=height; j++) {
+                var currentRow = j * rowSize;
+                ++currentRow;
+                for (var i = 0; i < width; i++) {
+                    x[currentRow] = x0[currentRow];
+                    ++currentRow;
+                }
+            }
+            set_bnd(b, x);
+        } else {
+            var invC = 1 / c;
+            for (var k=0 ; k<iterations; k++) {
+                for (var j=1 ; j<=height; j++) {
+                    var lastRow = (j - 1) * rowSize;
+                    var currentRow = j * rowSize;
+                    var nextRow = (j + 1) * rowSize;
+                    var lastX = x[currentRow];
+                    ++currentRow;
+                    for (var i=1; i<=width; i++)
+                        lastX = x[currentRow] = (x0[currentRow] + a*(lastX+x[++currentRow]+x[++lastRow]+x[++nextRow])) * invC;
+                }
+                set_bnd(b, x);
+            }
+        }
+    }
+
+    function diffuse(b, x, x0, dt)
+    {
+        var a = 0;
+        lin_solve(b, x, x0, a, 1 + 4*a);
+    }
+
+    function lin_solve2(x, x0, y, y0, a, c)
+    {
+        if (a === 0 && c === 1) {
+            for (var j=1 ; j <= height; j++) {
+                var currentRow = j * rowSize;
+                ++currentRow;
+                for (var i = 0; i < width; i++) {
+                    x[currentRow] = x0[currentRow];
+                    y[currentRow] = y0[currentRow];
+                    ++currentRow;
+                }
+            }
+            set_bnd(1, x);
+            set_bnd(2, y);
+        } else {
+            var invC = 1/c;
+            for (var k=0 ; k<iterations; k++) {
+                for (var j=1 ; j <= height; j++) {
+                    var lastRow = (j - 1) * rowSize;
+                    var currentRow = j * rowSize;
+                    var nextRow = (j + 1) * rowSize;
+                    var lastX = x[currentRow];
+                    var lastY = y[currentRow];
+                    ++currentRow;
+                    for (var i = 1; i <= width; i++) {
+                        lastX = x[currentRow] = (x0[currentRow] + a * (lastX + x[currentRow] + x[lastRow] + x[nextRow])) * invC;
+                        lastY = y[currentRow] = (y0[currentRow] + a * (lastY + y[++currentRow] + y[++lastRow] + y[++nextRow])) * invC;
+                    }
+                }
+                set_bnd(1, x);
+                set_bnd(2, y);
+            }
+        }
+    }
+
+    function diffuse2(x, x0, y, y0, dt)
+    {
+        var a = 0;
+        lin_solve2(x, x0, y, y0, a, 1 + 4 * a);
+    }
+
+    function advect(b, d, d0, u, v, dt)
+    {
+        var Wdt0 = dt * width;
+        var Hdt0 = dt * height;
+        var Wp5 = width + 0.5;
+        var Hp5 = height + 0.5;
+        for (var j = 1; j<= height; j++) {
+            var pos = j * rowSize;
+            for (var i = 1; i <= width; i++) {
+                var x = i - Wdt0 * u[++pos];
+                var y = j - Hdt0 * v[pos];
+                if (x < 0.5)
+                    x = 0.5;
+                else if (x > Wp5)
+                    x = Wp5;
+                var i0 = x | 0;
+                var i1 = i0 + 1;
+                if (y < 0.5)
+                    y = 0.5;
+                else if (y > Hp5)
+                    y = Hp5;
+                var j0 = y | 0;
+                var j1 = j0 + 1;
+                var s1 = x - i0;
+                var s0 = 1 - s1;
+                var t1 = y - j0;
+                var t0 = 1 - t1;
+                var row1 = j0 * rowSize;
+                var row2 = j1 * rowSize;
+                d[pos] = s0 * (t0 * d0[i0 + row1] + t1 * d0[i0 + row2]) + s1 * (t0 * d0[i1 + row1] + t1 * d0[i1 + row2]);
+            }
+        }
+        set_bnd(b, d);
+    }
+
+    function project(u, v, p, div)
+    {
+        var h = -0.5 / Math.sqrt(width * height);
+        for (var j = 1 ; j <= height; j++ ) {
+            var row = j * rowSize;
+            var previousRow = (j - 1) * rowSize;
+            var prevValue = row - 1;
+            var currentRow = row;
+            var nextValue = row + 1;
+            var nextRow = (j + 1) * rowSize;
+            for (var i = 1; i <= width; i++ ) {
+                div[++currentRow] = h * (u[++nextValue] - u[++prevValue] + v[++nextRow] - v[++previousRow]);
+                p[currentRow] = 0;
+            }
+        }
+        set_bnd(0, div);
+        set_bnd(0, p);
+
+        lin_solve(0, p, div, 1, 4 );
+        var wScale = 0.5 * width;
+        var hScale = 0.5 * height;
+        for (var j = 1; j<= height; j++ ) {
+            var prevPos = j * rowSize - 1;
+            var currentPos = j * rowSize;
+            var nextPos = j * rowSize + 1;
+            var prevRow = (j - 1) * rowSize;
+            var currentRow = j * rowSize;
+            var nextRow = (j + 1) * rowSize;
+
+            for (var i = 1; i<= width; i++) {
+                u[++currentPos] -= wScale * (p[++nextPos] - p[++prevPos]);
+                v[currentPos]   -= hScale * (p[++nextRow] - p[++prevRow]);
+            }
+        }
+        set_bnd(1, u);
+        set_bnd(2, v);
+    }
+
+    function dens_step(x, x0, u, v, dt)
+    {
+        addFields(x, x0, dt);
+        diffuse(0, x0, x, dt );
+        advect(0, x, x0, u, v, dt );
+    }
+
+    function vel_step(u, v, u0, v0, dt)
+    {
+        addFields(u, u0, dt );
+        addFields(v, v0, dt );
+        var temp = u0; u0 = u; u = temp;
+        var temp = v0; v0 = v; v = temp;
+        diffuse2(u,u0,v,v0, dt);
+        project(u, v, u0, v0);
+        var temp = u0; u0 = u; u = temp;
+        var temp = v0; v0 = v; v = temp;
+        advect(1, u, u0, u0, v0, dt);
+        advect(2, v, v0, u0, v0, dt);
+        project(u, v, u0, v0 );
+    }
+    var uiCallback = function(d,u,v) {};
+
+    function Field(dens, u, v) {
+        // Just exposing the fields here rather than using accessors is a measurable win during display (maybe 5%)
+        // but makes the code ugly.
+        this.setDensity = function(x, y, d) {
+             dens[(x + 1) + (y + 1) * rowSize] = d;
+        }
+        this.getDensity = function(x, y) {
+             return dens[(x + 1) + (y + 1) * rowSize];
+        }
+        this.setVelocity = function(x, y, xv, yv) {
+             u[(x + 1) + (y + 1) * rowSize] = xv;
+             v[(x + 1) + (y + 1) * rowSize] = yv;
+        }
+        this.getXVelocity = function(x, y) {
+             return u[(x + 1) + (y + 1) * rowSize];
+        }
+        this.getYVelocity = function(x, y) {
+             return v[(x + 1) + (y + 1) * rowSize];
+        }
+        this.width = function() { return width; }
+        this.height = function() { return height; }
+    }
+    function queryUI(d, u, v)
+    {
+        for (var i = 0; i < size; i++)
+            u[i] = v[i] = d[i] = 0.0;
+        uiCallback(new Field(d, u, v));
+    }
+
+    this.update = function () {
+        queryUI(dens_prev, u_prev, v_prev);
+        vel_step(u, v, u_prev, v_prev, dt);
+        dens_step(dens, dens_prev, u, v, dt);
+        displayFunc(new Field(dens, u, v));
+    }
+    this.setDisplayFunction = function(func) {
+        displayFunc = func;
+    }
+
+    this.iterations = function() { return iterations; }
+    this.setIterations = function(iters) {
+        if (iters > 0 && iters <= 100)
+           iterations = iters;
+    }
+    this.setUICallback = function(callback) {
+        uiCallback = callback;
+    }
+    var iterations = 10;
+    var visc = 0.5;
+    var dt = 0.1;
+    var dens;
+    var dens_prev;
+    var u;
+    var u_prev;
+    var v;
+    var v_prev;
+    var width;
+    var height;
+    var rowSize;
+    var size;
+    var displayFunc;
+    function reset()
+    {
+        rowSize = width + 2;
+        size = (width+2)*(height+2);
+        dens = new Array(size);
+        dens_prev = new Array(size);
+        u = new Array(size);
+        u_prev = new Array(size);
+        v = new Array(size);
+        v_prev = new Array(size);
+        for (var i = 0; i < size; i++)
+            dens_prev[i] = u_prev[i] = v_prev[i] = dens[i] = u[i] = v[i] = 0;
+    }
+    this.reset = reset;
+    this.setResolution = function (hRes, wRes)
+    {
+        var res = wRes * hRes;
+        if (res > 0 && res < 1000000 && (wRes != width || hRes != height)) {
+            width = wRes;
+            height = hRes;
+            reset();
+            return true;
+        }
+        return false;
+    }
+    this.setResolution(64, 64);
+}
diff --git a/benchmarks/revisions.html b/benchmarks/revisions.html
index 6ff75be..3ce9889 100644
--- a/benchmarks/revisions.html
+++ b/benchmarks/revisions.html
@@ -19,6 +19,10 @@
 the benchmark suite.
 
 </p>
+<div class="subtitle"><h3>Version 7 (<a href="http://v8.googlecode.com/svn/data/benchmarks/v7/run.html">link</a>)</h3></div>
+
+<p>This version includes the new Navier-Stokes benchmark, a 2D differential
+ equation solver that stresses arithmetic computations on double arrays.</p>
 
 <div class="subtitle"><h3>Version 6 (<a href="http://v8.googlecode.com/svn/data/benchmarks/v6/run.html">link</a>)</h3></div>
 
diff --git a/benchmarks/run.html b/benchmarks/run.html
index 36d2ad5..f1d14c1 100644
--- a/benchmarks/run.html
+++ b/benchmarks/run.html
@@ -14,6 +14,7 @@
 <script type="text/javascript" src="earley-boyer.js"></script>
 <script type="text/javascript" src="regexp.js"></script>
 <script type="text/javascript" src="splay.js"></script>
+<script type="text/javascript" src="navier-stokes.js"></script>
 <link type="text/css" rel="stylesheet" href="style.css" />
 <script type="text/javascript">
 var completed = 0;
@@ -52,16 +53,16 @@
   BenchmarkSuite.RunSuites({ NotifyStep: ShowProgress,
                              NotifyError: AddError,
                              NotifyResult: AddResult,
-                             NotifyScore: AddScore }); 
+                             NotifyScore: AddScore });
 }
 
 function ShowWarningIfObsolete() {
-  // If anything goes wrong we will just catch the exception and no 
+  // If anything goes wrong we will just catch the exception and no
   // warning is shown, i.e., no harm is done.
   try {
     var xmlhttp;
-    var next_version = parseInt(BenchmarkSuite.version) + 1; 
-    var next_version_url = "../v" + next_version + "/run.html";  
+    var next_version = parseInt(BenchmarkSuite.version) + 1;
+    var next_version_url = "../v" + next_version + "/run.html";
     if (window.XMLHttpRequest) {
       xmlhttp = new window.XMLHttpRequest();
     } else if (window.ActiveXObject) {
@@ -75,7 +76,7 @@
     };
     xmlhttp.send(null);
   } catch(e) {
-    // Ignore exception if check for next version fails. 
+    // Ignore exception if check for next version fails.
     // Hence no warning is displayed.
   }
 }
@@ -83,7 +84,7 @@
 function Load() {
   var version = BenchmarkSuite.version;
   document.getElementById("version").innerHTML = version;
-  ShowWarningIfObsolete();  
+  ShowWarningIfObsolete();
   setTimeout(Run, 200);
 }
 </script>
@@ -91,11 +92,11 @@
 <body onload="Load()">
 <div>
   <div class="title"><h1>V8 Benchmark Suite - version <span id="version">?</span></h1></div>
-  <div class="warning" id="obsolete"> 
+  <div class="warning" id="obsolete">
 Warning! This is not the latest version of the V8 benchmark
-suite. Consider running the   
+suite. Consider running the
 <a href="http://v8.googlecode.com/svn/data/benchmarks/current/run.html">
-latest version</a>.  
+latest version</a>.
   </div>
   <table>
     <tr>
@@ -117,6 +118,7 @@
 (<i>1761 lines</i>).
 </li>
 <li><b>Splay</b><br>Data manipulation benchmark that deals with splay trees and exercises the automatic memory management subsystem (<i>394 lines</i>).</li>
+<li><b>NavierStokes</b><br>Solves NavierStokes equations in 2D, heavily manipulating double precision arrays. Based on Oliver Hunt's code (<i>387 lines</i>).</li>
 </ul>
 
 <p>
diff --git a/benchmarks/run.js b/benchmarks/run.js
index da95fb4..58f6265 100644
--- a/benchmarks/run.js
+++ b/benchmarks/run.js
@@ -34,6 +34,7 @@
 load('earley-boyer.js');
 load('regexp.js');
 load('splay.js');
+load('navier-stokes.js');
 
 var success = true;
 
diff --git a/benchmarks/spinning-balls/index.html b/benchmarks/spinning-balls/index.html
new file mode 100644
index 0000000..d01f31f
--- /dev/null
+++ b/benchmarks/spinning-balls/index.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+  <style>
+    body { text-align: center; }
+  </style>
+</head>
+<body>
+  <script type="text/javascript" src="splay-tree.js"></script>
+  <script type="text/javascript" src="v.js"></script>
+</body>
+</html>
diff --git a/benchmarks/spinning-balls/splay-tree.js b/benchmarks/spinning-balls/splay-tree.js
new file mode 100644
index 0000000..a88e4cb
--- /dev/null
+++ b/benchmarks/spinning-balls/splay-tree.js
@@ -0,0 +1,326 @@
+// Copyright 2011 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS 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, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/**
+ * Constructs a Splay tree.  A splay tree is a self-balancing binary
+ * search tree with the additional property that recently accessed
+ * elements are quick to access again. It performs basic operations
+ * such as insertion, look-up and removal in O(log(n)) amortized time.
+ *
+ * @constructor
+ */
+function SplayTree() {
+};
+
+
+/**
+ * Pointer to the root node of the tree.
+ *
+ * @type {SplayTree.Node}
+ * @private
+ */
+SplayTree.prototype.root_ = null;
+
+
+/**
+ * @return {boolean} Whether the tree is empty.
+ */
+SplayTree.prototype.isEmpty = function() {
+  return !this.root_;
+};
+
+
+/**
+ * Inserts a node into the tree with the specified key and value if
+ * the tree does not already contain a node with the specified key. If
+ * the value is inserted, it becomes the root of the tree.
+ *
+ * @param {number} key Key to insert into the tree.
+ * @param {*} value Value to insert into the tree.
+ */
+SplayTree.prototype.insert = function(key, value) {
+  if (this.isEmpty()) {
+    this.root_ = new SplayTree.Node(key, value);
+    return;
+  }
+  // Splay on the key to move the last node on the search path for
+  // the key to the root of the tree.
+  this.splay_(key);
+  if (this.root_.key == key) {
+    return;
+  }
+  var node = new SplayTree.Node(key, value);
+  if (key > this.root_.key) {
+    node.left = this.root_;
+    node.right = this.root_.right;
+    this.root_.right = null;
+  } else {
+    node.right = this.root_;
+    node.left = this.root_.left;
+    this.root_.left = null;
+  }
+  this.root_ = node;
+};
+
+
+/**
+ * Removes a node with the specified key from the tree if the tree
+ * contains a node with this key. The removed node is returned. If the
+ * key is not found, an exception is thrown.
+ *
+ * @param {number} key Key to find and remove from the tree.
+ * @return {SplayTree.Node} The removed node.
+ */
+SplayTree.prototype.remove = function(key) {
+  if (this.isEmpty()) {
+    throw Error('Key not found: ' + key);
+  }
+  this.splay_(key);
+  if (this.root_.key != key) {
+    throw Error('Key not found: ' + key);
+  }
+  var removed = this.root_;
+  if (!this.root_.left) {
+    this.root_ = this.root_.right;
+  } else {
+    var right = this.root_.right;
+    this.root_ = this.root_.left;
+    // Splay to make sure that the new root has an empty right child.
+    this.splay_(key);
+    // Insert the original right child as the right child of the new
+    // root.
+    this.root_.right = right;
+  }
+  return removed;
+};
+
+
+/**
+ * Returns the node having the specified key or null if the tree doesn't contain
+ * a node with the specified key.
+ *
+ * @param {number} key Key to find in the tree.
+ * @return {SplayTree.Node} Node having the specified key.
+ */
+SplayTree.prototype.find = function(key) {
+  if (this.isEmpty()) {
+    return null;
+  }
+  this.splay_(key);
+  return this.root_.key == key ? this.root_ : null;
+};
+
+
+/**
+ * @return {SplayTree.Node} Node having the maximum key value.
+ */
+SplayTree.prototype.findMax = function(opt_startNode) {
+  if (this.isEmpty()) {
+    return null;
+  }
+  var current = opt_startNode || this.root_;
+  while (current.right) {
+    current = current.right;
+  }
+  return current;
+};
+
+
+/**
+ * @return {SplayTree.Node} Node having the maximum key value that
+ *     is less than the specified key value.
+ */
+SplayTree.prototype.findGreatestLessThan = function(key) {
+  if (this.isEmpty()) {
+    return null;
+  }
+  // Splay on the key to move the node with the given key or the last
+  // node on the search path to the top of the tree.
+  this.splay_(key);
+  // Now the result is either the root node or the greatest node in
+  // the left subtree.
+  if (this.root_.key < key) {
+    return this.root_;
+  } else if (this.root_.left) {
+    return this.findMax(this.root_.left);
+  } else {
+    return null;
+  }
+};
+
+
+/**
+ * @return {Array<*>} An array containing all the keys of tree's nodes.
+ */
+SplayTree.prototype.exportKeys = function() {
+  var result = [];
+  if (!this.isEmpty()) {
+    this.root_.traverse_(function(node) { result.push(node.key); });
+  }
+  return result;
+};
+
+
+/**
+ * Perform the splay operation for the given key. Moves the node with
+ * the given key to the top of the tree.  If no node has the given
+ * key, the last node on the search path is moved to the top of the
+ * tree. This is the simplified top-down splaying algorithm from:
+ * "Self-adjusting Binary Search Trees" by Sleator and Tarjan
+ *
+ * @param {number} key Key to splay the tree on.
+ * @private
+ */
+SplayTree.prototype.splay_ = function(key) {
+  if (this.isEmpty()) {
+    return;
+  }
+  // Create a dummy node.  The use of the dummy node is a bit
+  // counter-intuitive: The right child of the dummy node will hold
+  // the L tree of the algorithm.  The left child of the dummy node
+  // will hold the R tree of the algorithm.  Using a dummy node, left
+  // and right will always be nodes and we avoid special cases.
+  var dummy, left, right;
+  dummy = left = right = new SplayTree.Node(null, null);
+  var current = this.root_;
+  while (true) {
+    if (key < current.key) {
+      if (!current.left) {
+        break;
+      }
+      if (key < current.left.key) {
+        // Rotate right.
+        var tmp = current.left;
+        current.left = tmp.right;
+        tmp.right = current;
+        current = tmp;
+        if (!current.left) {
+          break;
+        }
+      }
+      // Link right.
+      right.left = current;
+      right = current;
+      current = current.left;
+    } else if (key > current.key) {
+      if (!current.right) {
+        break;
+      }
+      if (key > current.right.key) {
+        // Rotate left.
+        var tmp = current.right;
+        current.right = tmp.left;
+        tmp.left = current;
+        current = tmp;
+        if (!current.right) {
+          break;
+        }
+      }
+      // Link left.
+      left.right = current;
+      left = current;
+      current = current.right;
+    } else {
+      break;
+    }
+  }
+  // Assemble.
+  left.right = current.left;
+  right.left = current.right;
+  current.left = dummy.right;
+  current.right = dummy.left;
+  this.root_ = current;
+};
+
+
+/**
+ * Constructs a Splay tree node.
+ *
+ * @param {number} key Key.
+ * @param {*} value Value.
+ */
+SplayTree.Node = function(key, value) {
+  this.key = key;
+  this.value = value;
+};
+
+
+/**
+ * @type {SplayTree.Node}
+ */
+SplayTree.Node.prototype.left = null;
+
+
+/**
+ * @type {SplayTree.Node}
+ */
+SplayTree.Node.prototype.right = null;
+
+
+/**
+ * Performs an ordered traversal of the subtree starting at
+ * this SplayTree.Node.
+ *
+ * @param {function(SplayTree.Node)} f Visitor function.
+ * @private
+ */
+SplayTree.Node.prototype.traverse_ = function(f) {
+  var current = this;
+  while (current) {
+    var left = current.left;
+    if (left) left.traverse_(f);
+    f(current);
+    current = current.right;
+  }
+};
+
+SplayTree.prototype.traverseBreadthFirst = function (f) {
+  if (f(this.root_.value)) return;
+
+  var stack = [this.root_];
+  var length = 1;
+
+  while (length > 0) {
+    var new_stack = new Array(stack.length * 2);
+    var new_length = 0;
+    for (var i = 0; i < length; i++) {
+      var n = stack[i];
+      var l = n.left;
+      var r = n.right;
+      if (l) {
+        if (f(l.value)) return;
+        new_stack[new_length++] = l;
+      }
+      if (r) {
+        if (f(r.value)) return;
+        new_stack[new_length++] = r;
+      }
+    }
+    stack = new_stack;
+    length = new_length;
+  }
+};
diff --git a/benchmarks/spinning-balls/v.js b/benchmarks/spinning-balls/v.js
new file mode 100644
index 0000000..5ae1194
--- /dev/null
+++ b/benchmarks/spinning-balls/v.js
@@ -0,0 +1,498 @@
+// Copyright 2011 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS 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, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+/**
+ * This function provides requestAnimationFrame in a cross browser way.
+ * http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+ */
+if ( !window.requestAnimationFrame ) {
+  window.requestAnimationFrame = ( function() {
+    return window.webkitRequestAnimationFrame ||
+        window.mozRequestAnimationFrame ||
+        window.oRequestAnimationFrame ||
+        window.msRequestAnimationFrame ||
+        function(callback, element) {
+          window.setTimeout( callback, 1000 / 60 );
+        };
+  } )();
+}
+
+var kNPoints = 8000;
+var kNModifications = 20;
+var kNVisiblePoints = 200;
+var kDecaySpeed = 20;
+
+var kPointRadius = 4;
+var kInitialLifeForce = 100;
+
+var livePoints = void 0;
+var dyingPoints = void 0;
+var scene = void 0;
+var renderingStartTime = void 0;
+var scene = void 0;
+var pausePlot = void 0;
+var splayTree = void 0;
+var numberOfFrames = 0;
+var sumOfSquaredPauses = 0;
+var benchmarkStartTime = void 0;
+var benchmarkTimeLimit = void 0;
+var autoScale = void 0;
+var pauseDistribution = [];
+
+
+function Point(x, y, z, payload) {
+  this.x = x;
+  this.y = y;
+  this.z = z;
+
+  this.next = null;
+  this.prev = null;
+  this.payload = payload;
+  this.lifeForce = kInitialLifeForce;
+}
+
+
+Point.prototype.color = function () {
+  return "rgba(0, 0, 0, " + (this.lifeForce / kInitialLifeForce) + ")";
+};
+
+
+Point.prototype.decay = function () {
+  this.lifeForce -= kDecaySpeed;
+  return this.lifeForce <= 0;
+};
+
+
+function PointsList() {
+  this.head = null;
+  this.count = 0;
+}
+
+
+PointsList.prototype.add = function (point) {
+  if (this.head !== null) this.head.prev = point;
+  point.next = this.head;
+  this.head = point;
+  this.count++;
+}
+
+
+PointsList.prototype.remove = function (point) {
+  if (point.next !== null) {
+    point.next.prev = point.prev;
+  }
+  if (point.prev !== null) {
+    point.prev.next = point.next;
+  } else {
+    this.head = point.next;
+  }
+  this.count--;
+}
+
+
+function GeneratePayloadTree(depth, tag) {
+  if (depth == 0) {
+    return {
+      array  : [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ],
+      string : 'String for key ' + tag + ' in leaf node'
+    };
+  } else {
+    return {
+      left:  GeneratePayloadTree(depth - 1, tag),
+      right: GeneratePayloadTree(depth - 1, tag)
+    };
+  }
+}
+
+
+// To make the benchmark results predictable, we replace Math.random
+// with a 100% deterministic alternative.
+Math.random = (function() {
+  var seed = 49734321;
+  return function() {
+    // Robert Jenkins' 32 bit integer hash function.
+    seed = ((seed + 0x7ed55d16) + (seed << 12))  & 0xffffffff;
+    seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff;
+    seed = ((seed + 0x165667b1) + (seed << 5))   & 0xffffffff;
+    seed = ((seed + 0xd3a2646c) ^ (seed << 9))   & 0xffffffff;
+    seed = ((seed + 0xfd7046c5) + (seed << 3))   & 0xffffffff;
+    seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff;
+    return (seed & 0xfffffff) / 0x10000000;
+  };
+})();
+
+
+function GenerateKey() {
+  // The benchmark framework guarantees that Math.random is
+  // deterministic; see base.js.
+  return Math.random();
+}
+
+function CreateNewPoint() {
+  // Insert new node with a unique key.
+  var key;
+  do { key = GenerateKey(); } while (splayTree.find(key) != null);
+
+  var point = new Point(Math.random() * 40 - 20,
+                        Math.random() * 40 - 20,
+                        Math.random() * 40 - 20,
+                        GeneratePayloadTree(5, "" + key));
+
+  livePoints.add(point);
+
+  splayTree.insert(key, point);
+  return key;
+}
+
+function ModifyPointsSet() {
+  if (livePoints.count < kNPoints) {
+    for (var i = 0; i < kNModifications; i++) {
+      CreateNewPoint();
+    }
+  } else if (kNModifications === 20) {
+    kNModifications = 80;
+    kDecay = 30;
+  }
+
+  for (var i = 0; i < kNModifications; i++) {
+    var key = CreateNewPoint();
+    var greatest = splayTree.findGreatestLessThan(key);
+    if (greatest == null) {
+      var point = splayTree.remove(key).value;
+    } else {
+      var point = splayTree.remove(greatest.key).value;
+    }
+    livePoints.remove(point);
+    point.payload = null;
+    dyingPoints.add(point);
+  }
+}
+
+
+function PausePlot(width, height, size, scale) {
+  var canvas = document.createElement("canvas");
+  canvas.width = this.width = width;
+  canvas.height = this.height = height;
+  document.body.appendChild(canvas);
+
+  this.ctx = canvas.getContext('2d');
+
+  if (typeof scale !== "number") {
+    this.autoScale = true;
+    this.maxPause = 0;
+  } else {
+    this.autoScale = false;
+    this.maxPause = scale;
+  }
+
+  this.size = size;
+
+  // Initialize cyclic buffer for pauses.
+  this.pauses = new Array(this.size);
+  this.start = this.size;
+  this.idx = 0;
+}
+
+
+PausePlot.prototype.addPause = function (p) {
+  if (this.idx === this.size) {
+    this.idx = 0;
+  }
+
+  if (this.idx === this.start) {
+    this.start++;
+  }
+
+  if (this.start === this.size) {
+    this.start = 0;
+  }
+
+  this.pauses[this.idx++] = p;
+};
+
+
+PausePlot.prototype.iteratePauses = function (f) {
+  if (this.start < this.idx) {
+    for (var i = this.start; i < this.idx; i++) {
+      f.call(this, i - this.start, this.pauses[i]);
+    }
+  } else {
+    for (var i = this.start; i < this.size; i++) {
+      f.call(this, i - this.start, this.pauses[i]);
+    }
+
+    var offs = this.size - this.start;
+    for (var i = 0; i < this.idx; i++) {
+      f.call(this, i + offs, this.pauses[i]);
+    }
+  }
+};
+
+
+PausePlot.prototype.draw = function () {
+  var first = null;
+
+  if (this.autoScale) {
+    this.iteratePauses(function (i, v) {
+      if (first === null) {
+        first = v;
+      }
+      this.maxPause = Math.max(v, this.maxPause);
+    });
+  }
+
+  var dx = this.width / this.size;
+  var dy = this.height / this.maxPause;
+
+  this.ctx.save();
+  this.ctx.clearRect(0, 0, this.width, this.height);
+  this.ctx.beginPath();
+  this.ctx.moveTo(1, dy * this.pauses[this.start]);
+  var p = first;
+  this.iteratePauses(function (i, v) {
+    var delta = v - p;
+    var x = 1 + dx * i;
+    var y = dy * v;
+    this.ctx.lineTo(x, y);
+    if (delta > 2 * (p / 3)) {
+      this.ctx.font = "bold 12px sans-serif";
+      this.ctx.textBaseline = "bottom";
+      this.ctx.fillText(v + "ms", x + 2, y);
+    }
+    p = v;
+  });
+  this.ctx.strokeStyle = "black";
+  this.ctx.stroke();
+  this.ctx.restore();
+}
+
+
+function Scene(width, height) {
+  var canvas = document.createElement("canvas");
+  canvas.width = width;
+  canvas.height = height;
+  document.body.appendChild(canvas);
+
+  this.ctx = canvas.getContext('2d');
+  this.width = canvas.width;
+  this.height = canvas.height;
+
+  // Projection configuration.
+  this.x0 = canvas.width / 2;
+  this.y0 = canvas.height / 2;
+  this.z0 = 100;
+  this.f  = 1000;  // Focal length.
+
+  // Camera is rotating around y-axis.
+  this.angle = 0;
+}
+
+
+Scene.prototype.drawPoint = function (x, y, z, color) {
+  // Rotate the camera around y-axis.
+  var rx = x * Math.cos(this.angle) - z * Math.sin(this.angle);
+  var ry = y;
+  var rz = x * Math.sin(this.angle) + z * Math.cos(this.angle);
+
+  // Perform perspective projection.
+  var px = (this.f * rx) / (rz - this.z0) + this.x0;
+  var py = (this.f * ry) / (rz - this.z0) + this.y0;
+
+  this.ctx.save();
+  this.ctx.fillStyle = color
+  this.ctx.beginPath();
+  this.ctx.arc(px, py, kPointRadius, 0, 2 * Math.PI, true);
+  this.ctx.fill();
+  this.ctx.restore();
+};
+
+
+Scene.prototype.drawDyingPoints = function () {
+  var point_next = null;
+  for (var point = dyingPoints.head; point !== null; point = point_next) {
+    // Rotate the scene around y-axis.
+    scene.drawPoint(point.x, point.y, point.z, point.color());
+
+    point_next = point.next;
+
+    // Decay the current point and remove it from the list
+    // if it's life-force ran out.
+    if (point.decay()) {
+      dyingPoints.remove(point);
+    }
+  }
+};
+
+
+Scene.prototype.draw = function () {
+  this.ctx.save();
+  this.ctx.clearRect(0, 0, this.width, this.height);
+  this.drawDyingPoints();
+  this.ctx.restore();
+
+  this.angle += Math.PI / 90.0;
+};
+
+
+function updateStats(pause) {
+  numberOfFrames++;
+  if (pause > 20) {
+    sumOfSquaredPauses += (pause - 20) * (pause - 20);
+  }
+  pauseDistribution[Math.floor(pause / 10)] |= 0;
+  pauseDistribution[Math.floor(pause / 10)]++;
+}
+
+
+function renderStats() {
+  var msg = document.createElement("p");
+  msg.innerHTML = "Score " +
+    Math.round(numberOfFrames * 1000 / sumOfSquaredPauses);
+  var table = document.createElement("table");
+  table.align = "center";
+  for (var i = 0; i < pauseDistribution.length; i++) {
+    if (pauseDistribution[i] > 0) {
+      var row = document.createElement("tr");
+      var time = document.createElement("td");
+      var count = document.createElement("td");
+      time.innerHTML = i*10 + "-" + (i+1)*10 + "ms";
+      count.innerHTML = " => " + pauseDistribution[i];
+      row.appendChild(time);
+      row.appendChild(count);
+      table.appendChild(row);
+    }
+  }
+  div.appendChild(msg);
+  div.appendChild(table);
+}
+
+
+function render() {
+  if (typeof renderingStartTime === 'undefined') {
+    renderingStartTime = Date.now();
+    benchmarkStartTime = renderingStartTime;
+  }
+
+  ModifyPointsSet();
+
+  scene.draw();
+
+  var renderingEndTime = Date.now();
+  var pause = renderingEndTime - renderingStartTime;
+  pausePlot.addPause(pause);
+  renderingStartTime = renderingEndTime;
+
+  pausePlot.draw();
+
+  updateStats(pause);
+
+  div.innerHTML =
+      livePoints.count + "/" + dyingPoints.count + " " +
+      pause + "(max = " + pausePlot.maxPause + ") ms " +
+      numberOfFrames + " frames";
+
+  if (renderingEndTime < benchmarkStartTime + benchmarkTimeLimit) {
+    // Schedule next frame.
+    requestAnimationFrame(render);
+  } else {
+    renderStats();
+  }
+}
+
+
+function Form() {
+  function create(tag) { return document.createElement(tag); }
+  function text(value) { return document.createTextNode(value); }
+
+  this.form = create("form");
+  this.form.setAttribute("action", "javascript:start()");
+
+  var table = create("table");
+  table.setAttribute("style", "margin-left: auto; margin-right: auto;");
+
+  function col(a) {
+    var td = create("td");
+    td.appendChild(a);
+    return td;
+  }
+
+  function row(a, b) {
+    var tr = create("tr");
+    tr.appendChild(col(a));
+    tr.appendChild(col(b));
+    return tr;
+  }
+
+  this.timelimit = create("input");
+  this.timelimit.setAttribute("value", "60");
+
+  table.appendChild(row(text("Time limit in seconds"), this.timelimit));
+
+  this.autoscale = create("input");
+  this.autoscale.setAttribute("type", "checkbox");
+  this.autoscale.setAttribute("checked", "true");
+  table.appendChild(row(text("Autoscale pauses plot"), this.autoscale));
+
+  var button = create("input");
+  button.setAttribute("type", "submit");
+  button.setAttribute("value", "Start");
+  this.form.appendChild(table);
+  this.form.appendChild(button);
+
+  document.body.appendChild(this.form);
+}
+
+
+Form.prototype.remove = function () {
+  document.body.removeChild(this.form);
+};
+
+
+function init() {
+  livePoints = new PointsList;
+  dyingPoints = new PointsList;
+
+  splayTree = new SplayTree();
+
+  scene = new Scene(640, 480);
+
+  div = document.createElement("div");
+  document.body.appendChild(div);
+
+  pausePlot = new PausePlot(480, autoScale ? 240 : 500, 160, autoScale ? void 0 : 500);
+}
+
+function start() {
+  benchmarkTimeLimit = form.timelimit.value * 1000;
+  autoScale = form.autoscale.checked;
+  form.remove();
+  init();
+  render();
+}
+
+var form = new Form();