dep.js: Conceal try/catch and try/finally behind functions.
authorMark Wooding <mdw@distorted.org.uk>
Fri, 27 Jun 2014 16:38:31 +0000 (17:38 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Fri, 27 Jun 2014 16:38:31 +0000 (17:38 +0100)
Better optimization from V8, which is apparently too stupid to apply
these simple transformations itself.

dep.js

diff --git a/dep.js b/dep.js
index 9dd42b4..f4b0ff9 100644 (file)
--- a/dep.js
+++ b/dep.js
@@ -47,6 +47,34 @@ function eql(x, y) {
   return x === y;
 }
 
+function try_finally(trythunk, finalthunk) {
+  /* Call the TRYTHUNK.  When TRYTHUNK exits (even if it's with an exception)
+   * then call FINALTHUNK.  If TRYTHUNK returned normally then return its
+   * value; otherwise continue with the non-local flow control.
+   *
+   * This is an unpleasant hack because V8 doesn't optimize functions which
+   * contain try/finally very well (despite this being an easy transformation
+   * for the compiler).
+   */
+
+  try { return trythunk(); }
+  finally { finalthunk(); }
+}
+
+function try_cleanup(trythunk, cleanthunk) {
+  /* Call the TRYTHUNK.  If TRYTHUNK returns normally, just return its value;
+   * otherwise call CLEANTHUNK and re-throw the exception.
+   *
+   * This is an unpleasant hack because V8 doesn't optimize functions which
+   * contain try/catch very well (despite this being an easy transformation
+   * for the compiler).
+   */
+
+  var e;
+  try { return trythunk(); }
+  catch (e) { cleanthunk(); throw e; }
+}
+
 /* A Tag is an object which is interesting only because of its identity, and
  * that the set of Tags is basically determined by the static structure of
  * the program.
@@ -259,20 +287,17 @@ Dep.prototype = {
      */
 
     var old = EVALUATING;
-    var val, e;
+    var me = this;
+    var val;
 
     this._dependencies = { };
-    try {
-      EVALUATING = this;
-      try {
-       return this._value_function();
-      } catch (e) {
-       if (e === BAD) return BAD;
-       else throw e;
-      }
-    } finally {
+    return try_finally(function () {
+      EVALUATING = me;
+      return orelse(function () { return me._value_function(); },
+                   function () { return BAD; });
+    }, function() {
       EVALUATING = old;
-    }
+    });
   },
 
   _propagate: function () {
@@ -308,7 +333,6 @@ Dep.prototype = {
      */
 
     var queued = this.__flags & F_QUEUED;
-    var e;
     var me = this;
 
     // Update us with the given VALUE.
@@ -325,12 +349,8 @@ Dep.prototype = {
 
     // Try to recompute our value.  If that doesn't work then mark us as bad
     // and propagate that.
-    try {
-      return update(this._new_value());
-    } catch (e) {
-      update(BAD);
-      throw e;
-    }
+    return try_cleanup(function () { return update(me._new_value()); },
+                      function () { update(BAD); });
   },
 
   _force: function () {
@@ -451,7 +471,7 @@ function recompute_pending() {
   var d, f;
 
   state = 'recomputing';
-  try {
+  try_finally(function () {
     while (PENDING.length) {
       d = PENDING.shift();
       f = d.__flags;
@@ -463,12 +483,12 @@ function recompute_pending() {
        d.__flags = f | F_DEPS;
       }
     }
-  } finally {
+  },  function () {
     while (PENDING.length) {
       d = PENDING.shift();
       d._value = BAD;
     }
-  }
+  });
 }
 
 function with_frozen(body, delay) {
@@ -497,7 +517,7 @@ function with_frozen(body, delay) {
   case 'ready':
     old_delayed = DELAYED;
     old_pending = PENDING;
-    try {
+    try_finally(function () {
       DELAYED = [];
       PENDING = [];
       GENERATION = new Generation('dep-generation');
@@ -508,10 +528,10 @@ function with_frozen(body, delay) {
        op = DELAYED.shift();
        op();
       }
-    } finally {
+    }, function () {
       DELAYED = old_delayed;
       PENDING = old_pending;
-    }
+    });
     break;
   }
 }