[JAEGER] Merge from tracemonkey.
authorDavid Mandelin <dmandelin@mozilla.com>
Wed, 11 Aug 2010 11:05:24 -0700
changeset 53381 2cecc5d72edfe977bd795fa8f1fc77617cdda1f3
parent 53380 13495e9f957ed297e21c723764b05ea550f17f41 (current diff)
parent 50463 68a9a3355a6303f0c704b2df04c9347fd6215dc2 (diff)
child 53382 25bff33134218bafd3ca0d2fa38778765e2417be
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone2.0b4pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
[JAEGER] Merge from tracemonkey.
js/src/configure.in
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jscntxt.h
js/src/jsdtracef.h
js/src/jsinterp.cpp
js/src/jsobj.cpp
js/src/jstl.h
js/src/jstracer.cpp
js/src/jsutil.cpp
js/src/jsvalue.h
js/src/jsxml.cpp
js/src/xpconnect/src/xpcjsruntime.cpp
js/src/xpconnect/src/xpcprivate.h
--- a/config/autoconf.mk.in
+++ b/config/autoconf.mk.in
@@ -105,16 +105,17 @@ MOZ_JSDEBUGGER  = @MOZ_JSDEBUGGER@
 MOZ_IPC 	= @MOZ_IPC@
 MOZ_IPDL_TESTS 	= @MOZ_IPDL_TESTS@
 MOZ_LEAKY	= @MOZ_LEAKY@
 MOZ_MEMORY      = @MOZ_MEMORY@
 MOZ_JPROF       = @MOZ_JPROF@
 MOZ_SHARK       = @MOZ_SHARK@
 MOZ_CALLGRIND   = @MOZ_CALLGRIND@
 MOZ_VTUNE       = @MOZ_VTUNE@
+MOZ_TRACE_JSCALLS = @MOZ_TRACE_JSCALLS@
 MOZ_TRACEVIS    = @MOZ_TRACEVIS@
 DEHYDRA_PATH    = @DEHYDRA_PATH@
 
 NS_TRACE_MALLOC = @NS_TRACE_MALLOC@
 USE_ELF_DYNSTR_GC = @USE_ELF_DYNSTR_GC@
 INCREMENTAL_LINKER = @INCREMENTAL_LINKER@
 MACOSX_DEPLOYMENT_TARGET = @MACOSX_DEPLOYMENT_TARGET@
 MOZ_MAIL_NEWS	= @MOZ_MAIL_NEWS@
--- a/configure.in
+++ b/configure.in
@@ -7331,16 +7331,27 @@ fi
 dnl ========================================================
 dnl = Location of malloc wrapper lib
 dnl ========================================================
 MOZ_ARG_WITH_STRING(wrap-malloc,
 [  --with-wrap-malloc=DIR  Location of malloc wrapper library],
     WRAP_MALLOC_LIB=$withval)
 
 dnl ========================================================
+dnl = Use JS Call tracing
+dnl ========================================================
+MOZ_ARG_ENABLE_BOOL(trace-jscalls,
+[  --enable-trace-jscalls  Enable JS call enter/exit callback (default=no)],
+    MOZ_TRACE_JSCALLS=1,
+    MOZ_TRACE_JSCALLS= )
+if test -n "$MOZ_TRACE_JSCALLS"; then
+    AC_DEFINE(MOZ_TRACE_JSCALLS)
+fi
+
+dnl ========================================================
 dnl = Use TraceVis
 dnl ========================================================
 MOZ_ARG_ENABLE_BOOL(tracevis,
 [  --enable-tracevis       Enable TraceVis tracing tool (default=no)],
     MOZ_TRACEVIS=1,
     MOZ_TRACEVIS= )
 if test -n "$MOZ_TRACEVIS"; then
     AC_DEFINE(MOZ_TRACEVIS)
--- a/js/narcissus/jsdefs.js
+++ b/js/narcissus/jsdefs.js
@@ -43,17 +43,17 @@
  * separately to take advantage of the simple switch-case constant propagation
  * done by SpiderMonkey.
  */
 
 Narcissus = {
     options: { version: 185 }
 };
 
-Narcissus.jsdefs = (function() {
+Narcissus.definitions = (function() {
 
     var tokens = [
         // End of source.
         "END",
 
         // Operators and punctuators.  Some pair-wise order matters, e.g. (+, -)
         // and (UNARY_PLUS, UNARY_MINUS).
         "\n", ";",
@@ -185,19 +185,19 @@ Narcissus.jsdefs = (function() {
         Object.defineProperty(obj, prop, { get: fn, configurable: !dontDelete, enumerable: !dontEnum });
     }
 
     function defineProperty(obj, prop, val, dontDelete, readOnly, dontEnum) {
         Object.defineProperty(obj, prop, { value: val, writable: !readOnly, configurable: !dontDelete, enumerable: !dontEnum });
     }
 
     return {
-      "tokens": tokens,
-      "opTypeNames": opTypeNames,
-      "keywords": keywords,
-      "tokenIds": tokenIds,
-      "consts": consts,
-      "assignOps": assignOps,
-      "defineGetter": defineGetter,
-      "defineProperty": defineProperty
+        tokens: tokens,
+        opTypeNames: opTypeNames,
+        keywords: keywords,
+        tokenIds: tokenIds,
+        consts: consts,
+        assignOps: assignOps,
+        defineGetter: defineGetter,
+        defineProperty: defineProperty
     };
 }());
 
--- a/js/narcissus/jsexec.js
+++ b/js/narcissus/jsexec.js
@@ -43,49 +43,49 @@
  * Execution of parse trees.
  *
  * Standard classes except for eval, Function, Array, and String are borrowed
  * from the host JS environment.  Function is metacircular.  Array and String
  * are reflected via wrapping the corresponding native constructor and adding
  * an extra level of prototype-based delegation.
  */
 
-Narcissus.jsexec = (function() {
+Narcissus.interpreter = (function() {
 
-    var jsparse = Narcissus.jsparse;
-    var jsdefs = Narcissus.jsdefs;
+    var parser = Narcissus.parser;
+    var definitions = Narcissus.definitions;
 
     // Set constants in the local scope.
-    eval(jsdefs.consts);
+    eval(definitions.consts);
 
     const GLOBAL_CODE = 0, EVAL_CODE = 1, FUNCTION_CODE = 2;
 
     function ExecutionContext(type) {
         this.type = type;
     }
 
     var global = {
         // Value properties.
         NaN: NaN, Infinity: Infinity, undefined: undefined,
 
         // Function properties.
         eval: function eval(s) {
-            if (typeof s != "string")
+            if (typeof s !== "string")
                 return s;
 
             var x = ExecutionContext.current;
             var x2 = new ExecutionContext(EVAL_CODE);
             x2.thisObject = x.thisObject;
             x2.caller = x.caller;
             x2.callee = x.callee;
             x2.scope = x.scope;
             ExecutionContext.current = x2;
             try {
-                execute(jsparse.parse(new jsparse.VanillaBuilder, s), x2);
-            } catch (e if e == THROW) {
+                execute(parser.parse(new parser.VanillaBuilder, s), x2);
+            } catch (e if e === THROW) {
                 x.result = x2.result;
                 throw e;
             } catch (e if e instanceof SyntaxError) {
                 x.result = e;
                 throw THROW;
             } catch (e if e instanceof InternalError) {
                 /*
                  * If we get too much recursion during parsing we need to re-throw
@@ -106,39 +106,39 @@ Narcissus.jsexec = (function() {
             return x2.result;
         },
         parseInt: parseInt, parseFloat: parseFloat,
         isNaN: isNaN, isFinite: isFinite,
         decodeURI: decodeURI, encodeURI: encodeURI,
         decodeURIComponent: decodeURIComponent,
         encodeURIComponent: encodeURIComponent,
 
-        // Class constructors.  Where ECMA-262 requires C.length == 1, we declare
+        // Class constructors.  Where ECMA-262 requires C.length === 1, we declare
         // a dummy formal parameter.
         Object: Object,
         Function: function Function(dummy) {
             var p = "", b = "", n = arguments.length;
             if (n) {
                 var m = n - 1;
                 if (m) {
                     p += arguments[0];
                     for (var k = 1; k < m; k++)
                         p += "," + arguments[k];
                 }
                 b += arguments[m];
             }
 
             // XXX We want to pass a good file and line to the tokenizer.
             // Note the anonymous name to maintain parity with Spidermonkey.
-            var t = new jsparse.Tokenizer("anonymous(" + p + ") {" + b + "}");
+            var t = new parser.Tokenizer("anonymous(" + p + ") {" + b + "}");
 
             // NB: Use the STATEMENT_FORM constant since we don't want to push this
             // function onto the fake compilation context.
-            var x = { builder: new jsparse.VanillaBuilder };
-            var f = jsparse.FunctionDefinition(t, x, false, jsparse.STATEMENT_FORM);
+            var x = { builder: new parser.VanillaBuilder };
+            var f = parser.FunctionDefinition(t, x, false, parser.STATEMENT_FORM);
             var s = {object: global, parent: null};
             return newFunction(f,{scope:s});
         },
         Array: function (dummy) {
             // Array when called as a function acts as a constructor.
             return Array.apply(this, arguments);
         },
         String: function String(s) {
@@ -158,17 +158,17 @@ Narcissus.jsexec = (function() {
         TypeError: TypeError, URIError: URIError,
 
         // Other properties.
         Math: Math,
 
         // Extensions to ECMA.
         snarf: snarf, evaluate: evaluate,
         load: function load(s) {
-            if (typeof s != "string")
+            if (typeof s !== "string")
                 return s;
 
             evaluate(snarf(s), s, 1)
         },
         print: print,
         version: function() { return Narcissus.options.version; },
         quit: function() { throw END; }
     };
@@ -176,18 +176,18 @@ Narcissus.jsexec = (function() {
     // Helper to avoid Object.prototype.hasOwnProperty polluting scope objects.
     function hasDirectProperty(o, p) {
         return Object.prototype.hasOwnProperty.call(o, p);
     }
 
     // Reflect a host class into the target global environment by delegation.
     function reflectClass(name, proto) {
         var gctor = global[name];
-        jsdefs.defineProperty(gctor, "prototype", proto, true, true, true);
-        jsdefs.defineProperty(proto, "constructor", gctor, false, false, true);
+        definitions.defineProperty(gctor, "prototype", proto, true, true, true);
+        definitions.defineProperty(proto, "constructor", gctor, false, false, true);
         return proto;
     }
 
     // Reflect Array -- note that all Array methods are generic.
     reflectClass('Array', new Array);
 
     // Reflect String, overriding non-generic methods.
     var gSp = reflectClass('String', new String);
@@ -208,17 +208,17 @@ Narcissus.jsexec = (function() {
         ecma3OnlyMode: false,
         // Run a thunk in this execution context and return its result.
         run: function(thunk) {
             var prev = ExecutionContext.current;
             ExecutionContext.current = this;
             try {
                 thunk();
                 return this.result;
-            } catch (e if e == THROW) {
+            } catch (e if e === THROW) {
                 if (prev) {
                     prev.result = this.result;
                     throw THROW;
                 }
                 throw this.result;
             } finally {
                 ExecutionContext.current = prev;
             }
@@ -248,25 +248,25 @@ Narcissus.jsexec = (function() {
         if (v instanceof Reference)
             return (v.base || global)[v.propertyName] = w;
         throw new ReferenceError("Invalid assignment left-hand side",
                                  vn.filename, vn.lineno);
     }
 
     function isPrimitive(v) {
         var t = typeof v;
-        return (t == "object") ? v === null : t != "function";
+        return (t === "object") ? v === null : t !== "function";
     }
 
     function isObject(v) {
         var t = typeof v;
-        return (t == "object") ? v !== null : t == "function";
+        return (t === "object") ? v !== null : t === "function";
     }
 
-    // If r instanceof Reference, v == getValue(r); else v === r.  If passed, rn
+    // If r instanceof Reference, v === getValue(r); else v === r.  If passed, rn
     // is the node whose execute result was r.
     function toObject(v, r, rn) {
         switch (typeof v) {
           case "boolean":
             return new global.Boolean(v);
           case "number":
             return new global.Number(v);
           case "string":
@@ -282,52 +282,52 @@ Narcissus.jsexec = (function() {
                  : new TypeError(message);
     }
 
     function execute(n, x) {
         var a, f, i, j, r, s, t, u, v;
 
         switch (n.type) {
           case FUNCTION:
-            if (n.functionForm != jsparse.DECLARED_FORM) {
-                if (!n.name || n.functionForm == jsparse.STATEMENT_FORM) {
+            if (n.functionForm !== parser.DECLARED_FORM) {
+                if (!n.name || n.functionForm === parser.STATEMENT_FORM) {
                     v = newFunction(n, x);
-                    if (n.functionForm == jsparse.STATEMENT_FORM)
-                        jsdefs.defineProperty(x.scope.object, n.name, v, true);
+                    if (n.functionForm === parser.STATEMENT_FORM)
+                        definitions.defineProperty(x.scope.object, n.name, v, true);
                 } else {
                     t = new Object;
                     x.scope = {object: t, parent: x.scope};
                     try {
                         v = newFunction(n, x);
-                        jsdefs.defineProperty(t, n.name, v, true, true);
+                        definitions.defineProperty(t, n.name, v, true, true);
                     } finally {
                         x.scope = x.scope.parent;
                     }
                 }
             }
             break;
 
           case SCRIPT:
             t = x.scope.object;
             a = n.funDecls;
             for (i = 0, j = a.length; i < j; i++) {
                 s = a[i].name;
                 f = newFunction(a[i], x);
-                jsdefs.defineProperty(t, s, f, x.type != EVAL_CODE);
+                definitions.defineProperty(t, s, f, x.type !== EVAL_CODE);
             }
             a = n.varDecls;
             for (i = 0, j = a.length; i < j; i++) {
                 u = a[i];
                 s = u.name;
                 if (u.readOnly && hasDirectProperty(t, s)) {
                     throw new TypeError("Redeclaration of const " + s,
                                         u.filename, u.lineno);
                 }
                 if (u.readOnly || !hasDirectProperty(t, s)) {
-                    jsdefs.defineProperty(t, s, undefined, x.type != EVAL_CODE, u.readOnly);
+                    definitions.defineProperty(t, s, undefined, x.type !== EVAL_CODE, u.readOnly);
                 }
             }
             // FALL THROUGH
 
           case BLOCK:
             for (i = 0, j = n.length; i < j; i++)
                 execute(n[i], x);
             break;
@@ -340,122 +340,124 @@ Narcissus.jsexec = (function() {
             break;
 
           case SWITCH:
             s = getValue(execute(n.discriminant, x));
             a = n.cases;
             var matchDefault = false;
           switch_loop:
             for (i = 0, j = a.length; ; i++) {
-                if (i == j) {
+                if (i === j) {
                     if (n.defaultIndex >= 0) {
                         i = n.defaultIndex - 1; // no case matched, do default
                         matchDefault = true;
                         continue;
                     }
                     break;                      // no default, exit switch_loop
                 }
                 t = a[i];                       // next case (might be default!)
-                if (t.type == CASE) {
+                if (t.type === CASE) {
                     u = getValue(execute(t.caseLabel, x));
                 } else {
                     if (!matchDefault)          // not defaulting, skip for now
                         continue;
                     u = s;                      // force match to do default
                 }
                 if (u === s) {
                     for (;;) {                  // this loop exits switch_loop
                         if (t.statements.length) {
                             try {
                                 execute(t.statements, x);
-                            } catch (e if e == BREAK && x.target == n) {
+                            } catch (e if e === BREAK && x.target == n) {
                                 break switch_loop;
                             }
                         }
-                        if (++i == j)
+                        if (++i === j)
                             break switch_loop;
                         t = a[i];
                     }
                     // NOT REACHED
                 }
             }
             break;
 
           case FOR:
             n.setup && getValue(execute(n.setup, x));
             // FALL THROUGH
           case WHILE:
             while (!n.condition || getValue(execute(n.condition, x))) {
                 try {
                     execute(n.body, x);
-                } catch (e if e == BREAK && x.target == n) {
+                } catch (e if e === BREAK && x.target === n) {
                     break;
-                } catch (e if e == CONTINUE && x.target == n) {
+                } catch (e if e === CONTINUE && x.target === n) {
                     // Must run the update expression.
                 }
                 n.update && getValue(execute(n.update, x));
             }
             break;
 
           case FOR_IN:
             u = n.varDecl;
             if (u)
                 execute(u, x);
             r = n.iterator;
             s = execute(n.object, x);
             v = getValue(s);
 
             // ECMA deviation to track extant browser JS implementation behavior.
-            t = (v == null && !x.ecma3OnlyMode) ? v : toObject(v, s, n.object);
+            t = ((v === null || v === undefined) && !x.ecma3OnlyMode)
+              ? v
+              : toObject(v, s, n.object);
             a = [];
             for (i in t)
                 a.push(i);
             for (i = 0, j = a.length; i < j; i++) {
                 putValue(execute(r, x), a[i], r);
                 try {
                     execute(n.body, x);
-                } catch (e if e == BREAK && x.target == n) {
+                } catch (e if e === BREAK && x.target === n) {
                     break;
-                } catch (e if e == CONTINUE && x.target == n) {
+                } catch (e if e === CONTINUE && x.target === n) {
                     continue;
                 }
             }
             break;
 
           case DO:
             do {
                 try {
                     execute(n.body, x);
-                } catch (e if e == BREAK && x.target == n) {
+                } catch (e if e === BREAK && x.target === n) {
                     break;
-                } catch (e if e == CONTINUE && x.target == n) {
+                } catch (e if e === CONTINUE && x.target === n) {
                     continue;
                 }
             } while (getValue(execute(n.condition, x)));
             break;
 
           case BREAK:
           case CONTINUE:
             x.target = n.target;
             throw n.type;
 
           case TRY:
             try {
                 execute(n.tryBlock, x);
-            } catch (e if e == THROW && (j = n.catchClauses.length)) {
+            } catch (e if e === THROW && (j = n.catchClauses.length)) {
                 e = x.result;
                 x.result = undefined;
                 for (i = 0; ; i++) {
-                    if (i == j) {
+                    if (i === j) {
                         x.result = e;
                         throw THROW;
                     }
                     t = n.catchClauses[i];
                     x.scope = {object: {}, parent: x.scope};
-                    jsdefs.defineProperty(x.scope.object, t.varName, e, true);
+                    definitions.defineProperty(x.scope.object, t.varName, e, true);
                     try {
                         if (t.guard && !getValue(execute(t.guard, x)))
                             continue;
                         execute(t.block, x);
                         break;
                     } finally {
                         x.scope = x.scope.parent;
                     }
@@ -492,35 +494,35 @@ Narcissus.jsexec = (function() {
                 if (!u)
                     continue;
                 t = n[i].name;
                 for (s = x.scope; s; s = s.parent) {
                     if (hasDirectProperty(s.object, t))
                         break;
                 }
                 u = getValue(execute(u, x));
-                if (n.type == CONST)
-                    jsdefs.defineProperty(s.object, t, u, x.type != EVAL_CODE, true);
+                if (n.type === CONST)
+                    definitions.defineProperty(s.object, t, u, x.type !== EVAL_CODE, true);
                 else
                     s.object[t] = u;
             }
             break;
 
           case DEBUGGER:
-            throw "NYI: " + jsdefs.tokens[n.type];
+            throw "NYI: " + definitions.tokens[n.type];
 
           case SEMICOLON:
             if (n.expression)
                 x.result = getValue(execute(n.expression, x));
             break;
 
           case LABEL:
             try {
                 execute(n.statement, x);
-            } catch (e if e == BREAK && x.target == n) {
+            } catch (e if e === BREAK && x.target === n) {
             }
             break;
 
           case COMMA:
             for (i = 0, j = n.length; i < j; i++)
                 v = getValue(execute(n[i], x));
             break;
 
@@ -607,17 +609,17 @@ Narcissus.jsexec = (function() {
 
           case IN:
             v = getValue(execute(n[0], x)) in getValue(execute(n[1], x));
             break;
 
           case INSTANCEOF:
             t = getValue(execute(n[0], x));
             u = getValue(execute(n[1], x));
-            if (isObject(u) && typeof u.__hasInstance__ == "function")
+            if (isObject(u) && typeof u.__hasInstance__ === "function")
                 v = u.__hasInstance__(t);
             else
                 v = t instanceof u;
             break;
 
           case LSH:
             v = getValue(execute(n[0], x)) << getValue(execute(n[1], x));
             break;
@@ -683,17 +685,17 @@ Narcissus.jsexec = (function() {
             break;
 
           case INCREMENT:
           case DECREMENT:
             t = execute(n[0], x);
             u = Number(getValue(t));
             if (n.postfix)
                 v = u;
-            putValue(t, (n.type == INCREMENT) ? ++u : --u, n[0]);
+            putValue(t, (n.type === INCREMENT) ? ++u : --u, n[0]);
             if (!n.postfix)
                 v = u;
             break;
 
           case DOT:
             r = execute(n[0], x);
             t = getValue(r);
             u = n[1].value;
@@ -707,46 +709,46 @@ Narcissus.jsexec = (function() {
             v = new Reference(toObject(t, r, n[0]), String(u), n);
             break;
 
           case LIST:
             // Curse ECMA for specifying that arguments is not an Array object!
             v = {};
             for (i = 0, j = n.length; i < j; i++) {
                 u = getValue(execute(n[i], x));
-                jsdefs.defineProperty(v, i, u, false, false, true);
+                definitions.defineProperty(v, i, u, false, false, true);
             }
-            jsdefs.defineProperty(v, "length", i, false, false, true);
+            definitions.defineProperty(v, "length", i, false, false, true);
             break;
 
           case CALL:
             r = execute(n[0], x);
             a = execute(n[1], x);
             f = getValue(r);
-            if (isPrimitive(f) || typeof f.__call__ != "function") {
+            if (isPrimitive(f) || typeof f.__call__ !== "function") {
                 throw new TypeError(r + " is not callable",
                                     n[0].filename, n[0].lineno);
             }
             t = (r instanceof Reference) ? r.base : null;
             if (t instanceof Activation)
                 t = null;
             v = f.__call__(t, a, x);
             break;
 
           case NEW:
           case NEW_WITH_ARGS:
             r = execute(n[0], x);
             f = getValue(r);
-            if (n.type == NEW) {
+            if (n.type === NEW) {
                 a = {};
-                jsdefs.defineProperty(a, "length", 0, false, false, true);
+                definitions.defineProperty(a, "length", 0, false, false, true);
             } else {
                 a = execute(n[1], x);
             }
-            if (isPrimitive(f) || typeof f.__construct__ != "function") {
+            if (isPrimitive(f) || typeof f.__construct__ !== "function") {
                 throw new TypeError(r + " is not a constructor",
                                     n[0].filename, n[0].lineno);
             }
             v = f.__construct__(a, x);
             break;
 
           case ARRAY_INIT:
             v = [];
@@ -756,22 +758,22 @@ Narcissus.jsexec = (function() {
             }
             v.length = j;
             break;
 
           case OBJECT_INIT:
             v = {};
             for (i = 0, j = n.length; i < j; i++) {
                 t = n[i];
-                if (t.type == PROPERTY_INIT) {
+                if (t.type === PROPERTY_INIT) {
                     v[t[0].value] = getValue(execute(t[1], x));
                 } else {
                     f = newFunction(t, x);
-                    u = (t.type == GETTER) ? '__defineGetter__'
-                                           : '__defineSetter__';
+                    u = (t.type === GETTER) ? '__defineGetter__'
+                                            : '__defineSetter__';
                     v[u](t.name, thunk(f, x));
                 }
             }
             break;
 
           case NULL:
             v = null;
             break;
@@ -810,34 +812,34 @@ Narcissus.jsexec = (function() {
             throw "PANIC: unknown operation " + n.type + ": " + uneval(n);
         }
 
         return v;
     }
 
     function Activation(f, a) {
         for (var i = 0, j = f.params.length; i < j; i++)
-            jsdefs.defineProperty(this, f.params[i], a[i], true);
-        jsdefs.defineProperty(this, "arguments", a, true);
+            definitions.defineProperty(this, f.params[i], a[i], true);
+        definitions.defineProperty(this, "arguments", a, true);
     }
 
     // Null Activation.prototype's proto slot so that Object.prototype.* does not
     // pollute the scope of heavyweight functions.  Also delete its 'constructor'
     // property so that it doesn't pollute function scopes.
 
     Activation.prototype.__proto__ = null;
     delete Activation.prototype.constructor;
 
     function FunctionObject(node, scope) {
         this.node = node;
         this.scope = scope;
-        jsdefs.defineProperty(this, "length", node.params.length, true, true, true);
+        definitions.defineProperty(this, "length", node.params.length, true, true, true);
         var proto = {};
-        jsdefs.defineProperty(this, "prototype", proto, true);
-        jsdefs.defineProperty(proto, "constructor", this, false, false, true);
+        definitions.defineProperty(this, "prototype", proto, true);
+        definitions.defineProperty(proto, "constructor", this, false, false, true);
     }
 
     function getPropertyDescriptor(obj, name) {
         while (obj) {
             if (({}).hasOwnProperty.call(obj, name))
                 return Object.getOwnPropertyDescriptor(obj, name);
             obj = Object.getPrototypeOf(obj);
         }
@@ -909,26 +911,26 @@ Narcissus.jsexec = (function() {
     var FOp = FunctionObject.prototype = {
 
         // Internal methods.
         __call__: function (t, a, x) {
             var x2 = new ExecutionContext(FUNCTION_CODE);
             x2.thisObject = t || global;
             x2.caller = x;
             x2.callee = this;
-            jsdefs.defineProperty(a, "callee", this, false, false, true);
+            definitions.defineProperty(a, "callee", this, false, false, true);
             var f = this.node;
             x2.scope = {object: new Activation(f, a), parent: this.scope};
 
             ExecutionContext.current = x2;
             try {
                 execute(f.body, x2);
-            } catch (e if e == RETURN) {
+            } catch (e if e === RETURN) {
                 return x2.result;
-            } catch (e if e == THROW) {
+            } catch (e if e === THROW) {
                 x.result = x2.result;
                 throw THROW;
             } finally {
                 ExecutionContext.current = x;
             }
             return undefined;
         },
 
@@ -950,48 +952,48 @@ Narcissus.jsexec = (function() {
                 return false;
             var p = this.prototype;
             if (isPrimitive(p)) {
                 throw new TypeError("'prototype' property is not an object",
                                     this.node.filename, this.node.lineno);
             }
             var o;
             while ((o = v.__proto__)) {
-                if (o == p)
+                if (o === p)
                     return true;
                 v = o;
             }
             return false;
         },
 
         // Standard methods.
         toString: function () {
             return this.node.getSource();
         },
 
         apply: function (t, a) {
             // Curse ECMA again!
-            if (typeof this.__call__ != "function") {
+            if (typeof this.__call__ !== "function") {
                 throw new TypeError("Function.prototype.apply called on" +
                                     " uncallable object");
             }
 
             if (t === undefined || t === null)
                 t = global;
-            else if (typeof t != "object")
+            else if (typeof t !== "object")
                 t = toObject(t, t);
 
             if (a === undefined || a === null) {
                 a = {};
-                jsdefs.defineProperty(a, "length", 0, false, false, true);
+                definitions.defineProperty(a, "length", 0, false, false, true);
             } else if (a instanceof Array) {
                 var v = {};
                 for (var i = 0, j = a.length; i < j; i++)
-                    jsdefs.defineProperty(v, i, a[i], false, false, true);
-                jsdefs.defineProperty(v, "length", i, false, false, true);
+                    definitions.defineProperty(v, i, a[i], false, false, true);
+                definitions.defineProperty(v, "length", i, false, false, true);
                 a = v;
             } else if (!(a instanceof Object)) {
                 // XXX check for a non-arguments object
                 throw new TypeError("Second argument to Function.prototype.apply" +
                                     " must be an array or arguments object",
                                     this.node.filename, this.node.lineno);
             }
 
@@ -1008,28 +1010,28 @@ Narcissus.jsexec = (function() {
     // Connect Function.prototype and Function.prototype.constructor in global.
     reflectClass('Function', FOp);
 
     // Help native and host-scripted functions be like FunctionObjects.
     var Fp = Function.prototype;
     var REp = RegExp.prototype;
 
     if (!('__call__' in Fp)) {
-        jsdefs.defineProperty(Fp, "__call__",
+        definitions.defineProperty(Fp, "__call__",
                        function (t, a, x) {
                            // Curse ECMA yet again!
                            a = Array.prototype.splice.call(a, 0, a.length);
                            return this.apply(t, a);
                        }, true, true, true);
-        jsdefs.defineProperty(REp, "__call__",
+        definitions.defineProperty(REp, "__call__",
                        function (t, a, x) {
                            a = Array.prototype.splice.call(a, 0, a.length);
                            return this.exec.apply(this, a);
                        }, true, true, true);
-        jsdefs.defineProperty(Fp, "__construct__",
+        definitions.defineProperty(Fp, "__construct__",
                        function (a, x) {
                            a = Array.prototype.splice.call(a, 0, a.length);
                            switch (a.length) {
                              case 0:
                                return new this();
                              case 1:
                                return new this(a[0]);
                              case 2:
@@ -1043,103 +1045,108 @@ Narcissus.jsexec = (function() {
                                }
                                return eval('new this(' + argStr.slice(0,-1) + ');');
                            }
                        }, true, true, true);
 
         // Since we use native functions such as Date along with host ones such
         // as global.eval, we want both to be considered instances of the native
         // Function constructor.
-        jsdefs.defineProperty(Fp, "__hasInstance__",
+        definitions.defineProperty(Fp, "__hasInstance__",
                        function (v) {
                            return v instanceof Function || v instanceof global.Function;
                        }, true, true, true);
     }
 
     function thunk(f, x) {
         return function () { return f.__call__(this, arguments, x); };
     }
 
     function evaluate(s, f, l) {
-        if (typeof s != "string")
+        if (typeof s !== "string")
             return s;
 
         var x = ExecutionContext.current;
         var x2 = new ExecutionContext(GLOBAL_CODE);
         ExecutionContext.current = x2;
         try {
-            execute(jsparse.parse(new jsparse.VanillaBuilder, s, f, l), x2);
-        } catch (e if e == THROW) {
+            execute(parser.parse(new parser.VanillaBuilder, s, f, l), x2);
+        } catch (e if e === THROW) {
             if (x) {
                 x.result = x2.result;
                 throw THROW;
             }
             throw x2.result;
         } finally {
             ExecutionContext.current = x;
         }
         return x2.result;
     }
 
     // A read-eval-print-loop that roughly tracks the behavior of the js shell.
     function repl() {
 
         // Display a value similarly to the js shell.
         function display(x) {
-            if (typeof x == "object") {
+            if (typeof x === "object") {
                 // At the js shell, objects with no |toSource| don't print.
-                if (x != null && "toSource" in x) {
+                if (x !== null && "toSource" in x) {
                     try {
                         print(x.toSource());
                     } catch (e) {
                     }
                 } else {
                     print("null");
                 }
-            } else if (typeof x == "string") {
+            } else if (typeof x === "string") {
                 print(uneval(x));
-            } else if (typeof x != "undefined") {
+            } else if (typeof x !== "undefined") {
                 // Since x must be primitive, String can't throw.
                 print(String(x));
             }
         }
 
         // String conversion that never throws.
         function string(x) {
             try {
                 return String(x);
             } catch (e) {
                 return "unknown (can't convert to string)";
             }
         }
 
-        var b = new jsparse.VanillaBuilder;
+        var b = new parser.VanillaBuilder;
         var x = new ExecutionContext(GLOBAL_CODE);
 
         x.run(function() {
             for (;;) {
+                x.result = undefined;
                 putstr("njs> ");
                 var line = readline();
-                x.result = undefined;
+                // If readline receives EOF it returns null.
+                if (line === null) {
+                    print("");
+                    break;
+                }
                 try {
-                    execute(jsparse.parse(b, line, "stdin", 1), x);
+                    execute(parser.parse(b, line, "stdin", 1), x);
                     display(x.result);
-                } catch (e if e == THROW) {
+                } catch (e if e === THROW) {
                     print("uncaught exception: " + string(x.result));
-                } catch (e if e == END) {
+                } catch (e if e === END) {
                     break;
                 } catch (e if e instanceof SyntaxError) {
                     print(e.toString());
                 } catch (e) {
                     print("internal Narcissus error");
                     throw e;
                 }
             }
         });
     }
 
     return {
-        "evaluate": evaluate,
-        "repl": repl
+        evaluate: evaluate,
+        repl: repl
     };
 
 }());
 
--- a/js/narcissus/jslex.js
+++ b/js/narcissus/jslex.js
@@ -36,26 +36,26 @@
  * ***** END LICENSE BLOCK ***** */
 
 /*
  * Narcissus - JS implemented in JS.
  *
  * Lexical scanner.
  */
 
-Narcissus.jslex = (function() {
+Narcissus.lexer = (function() {
 
-    var jsdefs = Narcissus.jsdefs;
+    var definitions = Narcissus.definitions;
 
     // Set constants in the local scope.
-    eval(jsdefs.consts);
+    eval(definitions.consts);
 
     // Build up a trie of operator tokens.
     var opTokens = {};
-    for (var op in jsdefs.opTypeNames) {
+    for (var op in definitions.opTypeNames) {
         if (op === '\n' || op === '.')
             continue;
 
         var node = opTokens;
         for (var i = 0; i < op.length; i++) {
             var ch = op[i];
             if (!(ch in node))
                 node[ch] = {};
@@ -77,38 +77,38 @@ Narcissus.jslex = (function() {
         this.filename = f || "";
         this.lineno = l || 1;
     }
 
     Tokenizer.prototype = {
         get done() {
             // We need to set scanOperand to true here because the first thing
             // might be a regexp.
-            return this.peek(true) == END;
+            return this.peek(true) === END;
         },
 
         get token() {
             return this.tokens[this.tokenIndex];
         },
 
         match: function (tt, scanOperand) {
-            return this.get(scanOperand) == tt || this.unget();
+            return this.get(scanOperand) === tt || this.unget();
         },
 
         mustMatch: function (tt) {
             if (!this.match(tt))
                 throw this.newSyntaxError("Missing " + tokens[tt].toLowerCase());
             return this.token;
         },
 
         peek: function (scanOperand) {
             var tt, next;
             if (this.lookahead) {
                 next = this.tokens[(this.tokenIndex + this.lookahead) & 3];
-                tt = (this.scanNewlines && next.lineno != this.lineno)
+                tt = (this.scanNewlines && next.lineno !== this.lineno)
                      ? NEWLINE
                      : next.type;
             } else {
                 tt = this.get(scanOperand);
                 this.unget();
             }
             return tt;
         },
@@ -332,23 +332,23 @@ Narcissus.jslex = (function() {
                 if (next in node) {
                     node = node[next];
                     this.cursor++;
                     next = input[this.cursor];
                 }
             }
 
             var op = node.op;
-            if (jsdefs.assignOps[op] && input[this.cursor] === '=') {
+            if (definitions.assignOps[op] && input[this.cursor] === '=') {
                 this.cursor++;
                 token.type = ASSIGN;
-                token.assignOp = jsdefs.tokenIds[jsdefs.opTypeNames[op]];
+                token.assignOp = definitions.tokenIds[definitions.opTypeNames[op]];
                 op += '=';
             } else {
-                token.type = jsdefs.tokenIds[jsdefs.opTypeNames[op]];
+                token.type = definitions.tokenIds[definitions.opTypeNames[op]];
                 token.assignOp = null;
             }
 
             token.value = op;
         },
 
         // FIXME: Unicode escape sequences
         // FIXME: Unicode identifiers
@@ -358,33 +358,33 @@ Narcissus.jslex = (function() {
             do {
                 ch = input[this.cursor++];
             } while ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
                      (ch >= '0' && ch <= '9') || ch === '$' || ch === '_');
 
             this.cursor--;  // Put the non-word character back.
 
             var id = input.substring(token.start, this.cursor);
-            token.type = jsdefs.keywords[id] || IDENTIFIER;
+            token.type = definitions.keywords[id] || IDENTIFIER;
             token.value = id;
         },
 
         /*
          * Tokenizer.get :: void -> token type
          *
          * Consumes input *only* if there is no lookahead.
          * Dispatch to the appropriate lexing function depending on the input.
          */
         get: function (scanOperand) {
             var token;
             while (this.lookahead) {
                 --this.lookahead;
                 this.tokenIndex = (this.tokenIndex + 1) & 3;
                 token = this.tokens[this.tokenIndex];
-                if (token.type != NEWLINE || this.scanNewlines)
+                if (token.type !== NEWLINE || this.scanNewlines)
                     return token.type;
             }
 
             this.skip();
 
             this.tokenIndex = (this.tokenIndex + 1) & 3;
             token = this.tokens[this.tokenIndex];
             if (!token)
@@ -426,17 +426,17 @@ Narcissus.jslex = (function() {
         },
 
         /*
          * Tokenizer.unget :: void -> undefined
          *
          * Match depends on unget returning undefined.
          */
         unget: function () {
-            if (++this.lookahead == 4) throw "PANIC: too much lookahead!";
+            if (++this.lookahead === 4) throw "PANIC: too much lookahead!";
             this.tokenIndex = (this.tokenIndex - 1) & 3;
         },
 
         newSyntaxError: function (m) {
             var e = new SyntaxError(m, this.filename, this.lineno);
             e.source = this.source;
             e.cursor = this.cursor;
             return e;
@@ -458,12 +458,12 @@ Narcissus.jslex = (function() {
             this.tokenIndex = point.tokenIndex;
             this.tokens = point.tokens.slice();
             this.lookahead = point.lookahead;
             this.scanNewline = point.scanNewline;
             this.lineno = point.lineno;
         }
     };
 
-    return { "Tokenizer": Tokenizer };
+    return { Tokenizer: Tokenizer };
 
 }());
 
--- a/js/narcissus/jsparse.js
+++ b/js/narcissus/jsparse.js
@@ -38,23 +38,23 @@
  * ***** END LICENSE BLOCK ***** */
 
 /*
  * Narcissus - JS implemented in JS.
  *
  * Parser.
  */
 
-Narcissus.jsparse = (function() {
+Narcissus.parser = (function() {
 
-    var jslex = Narcissus.jslex;
-    var jsdefs = Narcissus.jsdefs;
+    var lexer = Narcissus.lexer;
+    var definitions = Narcissus.definitions;
 
     // Set constants in the local scope.
-    eval(jsdefs.consts);
+    eval(definitions.consts);
 
    /*
     * The vanilla AST builder.
     */
 
     VanillaBuilder = function VanillaBuilder() {
     }
 
@@ -378,18 +378,18 @@ Narcissus.jsparse = (function() {
             n.statement = s;
         },
 
         LABEL$finish: function(n) {
         },
 
         FUNCTION$build: function(t) {
             var n = new Node(t);
-            if (n.type != FUNCTION)
-                n.type = (n.value == "get") ? GETTER : SETTER;
+            if (n.type !== FUNCTION)
+                n.type = (n.value === "get") ? GETTER : SETTER;
             n.params = [];
             return n;
         },
 
         FUNCTION$setName: function(n, v) {
             n.name = v;
         },
 
@@ -655,19 +655,19 @@ Narcissus.jsparse = (function() {
         },
 
         MULTIPLY$finish: function(n) {
         },
 
         UNARY$build: function(t) {
             // NB t.token.type must be DELETE, VOID, TYPEOF, NOT, BITWISE_NOT,
             // UNARY_PLUS, UNARY_MINUS, INCREMENT, or DECREMENT.
-            if (t.token.type == PLUS)
+            if (t.token.type === PLUS)
                 t.token.type = UNARY_PLUS;
-            else if (t.token.type == MINUS)
+            else if (t.token.type === MINUS)
                 t.token.type = UNARY_MINUS;
             return new Node(t);
         },
 
         UNARY$addOperand: function(n, n2) {
             n.push(n2);
         },
 
@@ -822,17 +822,17 @@ Narcissus.jsparse = (function() {
         var n = Statements(t, x);
         n.type = SCRIPT;
         n.funDecls = x.funDecls;
         n.varDecls = x.varDecls;
         return n;
     }
 
     // Node extends Array, which we extend slightly with a top-of-stack method.
-    jsdefs.defineProperty(Array.prototype, "top",
+    definitions.defineProperty(Array.prototype, "top",
                    function() {
                        return this.length && this[this.length-1];
                    }, false, false, true);
 
     /*
      * Node :: (tokenizer, optional type) -> node
      */
     function Node(t, type) {
@@ -869,24 +869,24 @@ Narcissus.jsparse = (function() {
                 this.end = kid.end;
         }
         return Array.prototype.push.call(this, kid);
     }
 
     Node.indentLevel = 0;
 
     function tokenstr(tt) {
-        var t = jsdefs.tokens[tt];
-        return /^\W/.test(t) ? jsdefs.opTypeNames[t] : t.toUpperCase();
+        var t = definitions.tokens[tt];
+        return /^\W/.test(t) ? definitions.opTypeNames[t] : t.toUpperCase();
     }
 
     Np.toString = function () {
         var a = [];
         for (var i in this) {
-            if (this.hasOwnProperty(i) && i != 'type' && i != 'target')
+            if (this.hasOwnProperty(i) && i !== 'type' && i !== 'target')
                 a.push({id: i, value: this[i]});
         }
         a.sort(function (a,b) { return (a.id < b.id) ? -1 : 1; });
         const INDENTATION = "    ";
         var n = ++Node.indentLevel;
         var s = "{\n" + INDENTATION.repeat(n) + "type: " + tokenstr(this.type);
         for (i = 0; i < a.length; i++)
             s += ",\n" + INDENTATION.repeat(n) + a[i].id + ": " + a[i].value;
@@ -894,22 +894,22 @@ Narcissus.jsparse = (function() {
         s += "\n" + INDENTATION.repeat(n) + "}";
         return s;
     }
 
     Np.getSource = function () {
         return this.tokenizer.source.slice(this.start, this.end);
     };
 
-    jsdefs.defineGetter(Np, "filename",
+    definitions.defineGetter(Np, "filename",
                  function() {
                      return this.tokenizer.filename;
                  });
 
-    jsdefs.defineProperty(String.prototype, "repeat",
+    definitions.defineProperty(String.prototype, "repeat",
                    function(n) {
                        var s = "", t = this + s;
                        while (--n >= 0)
                            s += t;
                        return s;
                    }, false, false, true);
 
     // Statement stack and nested statement handler.
@@ -922,27 +922,40 @@ Narcissus.jsparse = (function() {
     }
 
     /*
      * Statements :: (tokenizer, compiler context) -> node
      *
      * Parses a list of Statements.
      */
     function Statements(t, x) {
+        /*
+         * Blocks are uniquely numbered by a blockId within a function that is
+         * at the top level of the program. blockId starts from 0.
+         *
+         * This is done to aid hoisting for parse-time analyses done in custom
+         * builders.
+         *
+         * For more details in its interaction with hoisting, see comments in
+         * FunctionDefinition.
+         */
         var b = x.builder;
         var n = b.BLOCK$build(t, x.blockId++);
         b.BLOCK$hoistLets(n);
         x.stmtStack.push(n);
-        while (!t.done && t.peek(true) != RIGHT_CURLY)
+        while (!t.done && t.peek(true) !== RIGHT_CURLY)
             b.BLOCK$addStatement(n, Statement(t, x));
         x.stmtStack.pop();
         b.BLOCK$finish(n);
         if (n.needsHoisting) {
             b.setHoists(n.id, n.varDecls);
-            // Propagate up to the function.
+            /*
+             * If a block needs hoisting, we need to propagate this flag up to
+             * the CompilerContext.
+             */
             x.needsHoisting = true;
         }
         return n;
     }
 
     function Block(t, x) {
         t.mustMatch(LEFT_CURLY);
         var n = Statements(t, x);
@@ -988,64 +1001,64 @@ Narcissus.jsparse = (function() {
             return n;
 
           case SWITCH:
             // This allows CASEs after a DEFAULT, which is in the standard.
             n = b.SWITCH$build(t);
             b.SWITCH$setDiscriminant(n, ParenExpression(t, x));
             x.stmtStack.push(n);
             t.mustMatch(LEFT_CURLY);
-            while ((tt = t.get()) != RIGHT_CURLY) {
+            while ((tt = t.get()) !== RIGHT_CURLY) {
                 switch (tt) {
                   case DEFAULT:
                     if (n.defaultIndex >= 0)
                         throw t.newSyntaxError("More than one switch default");
                     n2 = b.DEFAULT$build(t);
                     b.SWITCH$setDefaultIndex(n, n.cases.length);
                     t.mustMatch(COLON);
                     b.DEFAULT$initializeStatements(n2, t);
-                    while ((tt=t.peek(true)) != CASE && tt != DEFAULT &&
-                           tt != RIGHT_CURLY)
+                    while ((tt=t.peek(true)) !== CASE && tt !== DEFAULT &&
+                           tt !== RIGHT_CURLY)
                         b.DEFAULT$addStatement(n2, Statement(t, x));
                     b.DEFAULT$finish(n2);
                     break;
 
                   case CASE:
                     n2 = b.CASE$build(t);
                     b.CASE$setLabel(n2, Expression(t, x, COLON));
                     t.mustMatch(COLON);
                     b.CASE$initializeStatements(n2, t);
-                    while ((tt=t.peek(true)) != CASE && tt != DEFAULT &&
-                           tt != RIGHT_CURLY)
+                    while ((tt=t.peek(true)) !== CASE && tt !== DEFAULT &&
+                           tt !== RIGHT_CURLY)
                         b.CASE$addStatement(n2, Statement(t, x));
                     b.CASE$finish(n2);
                     break;
 
                   default:
                     throw t.newSyntaxError("Invalid switch case");
                 }
                 b.SWITCH$addCase(n, n2);
             }
             x.stmtStack.pop();
             b.SWITCH$finish(n);
             return n;
 
           case FOR:
             n = b.FOR$build(t);
-            if (t.match(IDENTIFIER) && t.token.value == "each")
+            if (t.match(IDENTIFIER) && t.token.value === "each")
                 b.FOR$rebuildForEach(n);
             t.mustMatch(LEFT_PAREN);
-            if ((tt = t.peek()) != SEMICOLON) {
+            if ((tt = t.peek()) !== SEMICOLON) {
                 x.inForLoopInit = true;
-                if (tt == VAR || tt == CONST) {
+                if (tt === VAR || tt === CONST) {
                     t.get();
                     n2 = Variables(t, x);
-                } else if (tt == LET) {
+                } else if (tt === LET) {
                     t.get();
-                    if (t.peek() == LEFT_PAREN) {
+                    if (t.peek() === LEFT_PAREN) {
                         n2 = LetBlock(t, x, false);
                     } else {
                         /*
                          * Let in for head, we need to add an implicit block
                          * around the rest of the for.
                          */
                         var forBlock = b.BLOCK$build(t, x.blockId++);
                         x.stmtStack.push(forBlock);
@@ -1054,35 +1067,35 @@ Narcissus.jsparse = (function() {
                 } else {
                     n2 = Expression(t, x);
                 }
                 x.inForLoopInit = false;
             }
             if (n2 && t.match(IN)) {
                 b.FOR$rebuildForIn(n);
                 b.FOR$setObject(n, Expression(t, x), forBlock);
-                if (n2.type == VAR || n2.type == LET) {
-                    if (n2.length != 1) {
+                if (n2.type === VAR || n2.type === LET) {
+                    if (n2.length !== 1) {
                         throw new SyntaxError("Invalid for..in left-hand side",
                                               t.filename, n2.lineno);
                     }
                     b.FOR$setIterator(n, n2[0], n2, forBlock);
                 } else {
                     b.FOR$setIterator(n, n2, null, forBlock);
                 }
             } else {
                 b.FOR$setSetup(n, n2);
                 t.mustMatch(SEMICOLON);
                 if (n.isEach)
                     throw t.newSyntaxError("Invalid for each..in loop");
-                b.FOR$setCondition(n, (t.peek() == SEMICOLON)
+                b.FOR$setCondition(n, (t.peek() === SEMICOLON)
                                   ? null
                                   : Expression(t, x));
                 t.mustMatch(SEMICOLON);
-                b.FOR$setUpdate(n, (t.peek() == RIGHT_PAREN)
+                b.FOR$setUpdate(n, (t.peek() === RIGHT_PAREN)
                                    ? null
                                    : Expression(t, x));
             }
             t.mustMatch(RIGHT_PAREN);
             b.FOR$setBody(n, nest(t, x, n, Statement));
             if (forBlock) {
                 b.BLOCK$finish(forBlock);
                 x.stmtStack.pop();
@@ -1108,59 +1121,59 @@ Narcissus.jsparse = (function() {
                 // See http://bugzilla.mozilla.org/show_bug.cgi?id=238945.
                 t.match(SEMICOLON);
                 return n;
             }
             break;
 
           case BREAK:
           case CONTINUE:
-            n = tt == BREAK ? b.BREAK$build(t) : b.CONTINUE$build(t);
+            n = tt === BREAK ? b.BREAK$build(t) : b.CONTINUE$build(t);
 
-            if (t.peekOnSameLine() == IDENTIFIER) {
+            if (t.peekOnSameLine() === IDENTIFIER) {
                 t.get();
-                if (tt == BREAK)
+                if (tt === BREAK)
                     b.BREAK$setLabel(n, t.token.value);
                 else
                     b.CONTINUE$setLabel(n, t.token.value);
             }
 
             ss = x.stmtStack;
             i = ss.length;
             label = n.label;
 
             if (label) {
                 do {
                     if (--i < 0)
                         throw t.newSyntaxError("Label not found");
-                } while (ss[i].label != label);
+                } while (ss[i].label !== label);
 
                 /*
                  * Both break and continue to label need to be handled specially
                  * within a labeled loop, so that they target that loop. If not in
                  * a loop, then break targets its labeled statement. Labels can be
                  * nested so we skip all labels immediately enclosing the nearest
                  * non-label statement.
                  */
-                while (i < ss.length - 1 && ss[i+1].type == LABEL)
+                while (i < ss.length - 1 && ss[i+1].type === LABEL)
                     i++;
                 if (i < ss.length - 1 && ss[i+1].isLoop)
                     i++;
-                else if (tt == CONTINUE)
+                else if (tt === CONTINUE)
                     throw t.newSyntaxError("Invalid continue");
             } else {
                 do {
                     if (--i < 0) {
-                        throw t.newSyntaxError("Invalid " + ((tt == BREAK)
+                        throw t.newSyntaxError("Invalid " + ((tt === BREAK)
                                                              ? "break"
                                                              : "continue"));
                     }
-                } while (!ss[i].isLoop && !(tt == BREAK && ss[i].type == SWITCH));
+                } while (!ss[i].isLoop && !(tt === BREAK && ss[i].type === SWITCH));
             }
-            if (tt == BREAK) {
+            if (tt === BREAK) {
                 b.BREAK$setTarget(n, ss[i]);
                 b.BREAK$finish(n);
             } else {
                 b.CONTINUE$setTarget(n, ss[i]);
                 b.CONTINUE$finish(n);
             }
             break;
 
@@ -1202,17 +1215,17 @@ Narcissus.jsparse = (function() {
                 b.TRY$setFinallyBlock(n, Block(t, x));
             if (!n.catchClauses.length && !n.finallyBlock)
                 throw t.newSyntaxError("Invalid try statement");
             b.TRY$finish(n);
             return n;
 
           case CATCH:
           case FINALLY:
-            throw t.newSyntaxError(jsdefs.tokens[tt] + " without preceding try");
+            throw t.newSyntaxError(definitions.tokens[tt] + " without preceding try");
 
           case THROW:
             n = b.THROW$build(t);
             b.THROW$setException(n, Expression(t, x));
             b.THROW$finish(n);
             break;
 
           case RETURN:
@@ -1227,17 +1240,17 @@ Narcissus.jsparse = (function() {
             return n;
 
           case VAR:
           case CONST:
             n = Variables(t, x);
             break;
 
           case LET:
-            if (t.peek() == LEFT_PAREN)
+            if (t.peek() === LEFT_PAREN)
                 n = LetBlock(t, x, true);
             else
                 n = Variables(t, x);
             break;
 
           case DEBUGGER:
             n = b.DEBUGGER$build(t);
             break;
@@ -1245,24 +1258,24 @@ Narcissus.jsparse = (function() {
           case NEWLINE:
           case SEMICOLON:
             n = b.SEMICOLON$build(t);
             b.SEMICOLON$setExpression(n, null);
             b.SEMICOLON$finish(t);
             return n;
 
           default:
-            if (tt == IDENTIFIER) {
+            if (tt === IDENTIFIER) {
                 tt = t.peek();
                 // Labeled statement.
-                if (tt == COLON) {
+                if (tt === COLON) {
                     label = t.token.value;
                     ss = x.stmtStack;
                     for (i = ss.length-1; i >= 0; --i) {
-                        if (ss[i].label == label)
+                        if (ss[i].label === label)
                             throw t.newSyntaxError("Duplicate label");
                     }
                     t.get();
                     n = b.LABEL$build(t);
                     b.LABEL$setLabel(n, label)
                     b.LABEL$setStatement(n, nest(t, x, n, Statement));
                     b.LABEL$finish(n);
                     return n;
@@ -1280,58 +1293,58 @@ Narcissus.jsparse = (function() {
         }
 
         MagicalSemicolon(t);
         return n;
     }
 
     function MagicalSemicolon(t) {
         var tt;
-        if (t.lineno == t.token.lineno) {
+        if (t.lineno === t.token.lineno) {
             tt = t.peekOnSameLine();
-            if (tt != END && tt != NEWLINE && tt != SEMICOLON && tt != RIGHT_CURLY)
+            if (tt !== END && tt !== NEWLINE && tt !== SEMICOLON && tt !== RIGHT_CURLY)
                 throw t.newSyntaxError("missing ; before statement");
         }
         t.match(SEMICOLON);
     }
 
     function returnOrYield(t, x) {
         var n, b = x.builder, tt = t.token.type, tt2;
 
-        if (tt == RETURN) {
+        if (tt === RETURN) {
             if (!x.inFunction)
                 throw t.newSyntaxError("Return not in function");
             n = b.RETURN$build(t);
-        } else /* (tt == YIELD) */ {
+        } else /* (tt === YIELD) */ {
             if (!x.inFunction)
                 throw t.newSyntaxError("Yield not in function");
             x.isGenerator = true;
             n = b.YIELD$build(t);
         }
 
         tt2 = t.peek(true);
-        if (tt2 != END && tt2 != NEWLINE && tt2 != SEMICOLON && tt2 != RIGHT_CURLY
-            && (tt != YIELD ||
-                (tt2 != tt && tt2 != RIGHT_BRACKET && tt2 != RIGHT_PAREN &&
-                 tt2 != COLON && tt2 != COMMA))) {
-            if (tt == RETURN) {
+        if (tt2 !== END && tt2 !== NEWLINE && tt2 !== SEMICOLON && tt2 !== RIGHT_CURLY
+            && (tt !== YIELD ||
+                (tt2 !== tt && tt2 !== RIGHT_BRACKET && tt2 !== RIGHT_PAREN &&
+                 tt2 !== COLON && tt2 !== COMMA))) {
+            if (tt === RETURN) {
                 b.RETURN$setValue(n, Expression(t, x));
                 x.hasReturnWithValue = true;
             } else {
                 b.YIELD$setValue(n, AssignExpression(t, x));
             }
-        } else if (tt == RETURN) {
+        } else if (tt === RETURN) {
             x.hasEmptyReturn = true;
         }
 
         // Disallow return v; in generator.
         if (x.hasReturnWithValue && x.isGenerator)
             throw t.newSyntaxError("Generator returns a value");
 
-        if (tt == RETURN)
+        if (tt === RETURN)
             b.RETURN$finish(n);
         else
             b.YIELD$finish(n);
 
         return n;
     }
 
     /*
@@ -1365,91 +1378,122 @@ Narcissus.jsparse = (function() {
                     break;
                 }
             } while (t.match(COMMA));
             t.mustMatch(RIGHT_PAREN);
         }
 
         // Do we have an expression closure or a normal body?
         var tt = t.get();
-        if (tt != LEFT_CURLY)
+        if (tt !== LEFT_CURLY)
             t.unget();
 
         var x2 = new StaticContext(true, b);
         var rp = t.save();
         if (x.inFunction) {
             /*
-             * Inner functions don't reset block numbering. They also need to
-             * remember which block they were parsed in for hoisting (see comment
-             * below).
+             * Inner functions don't reset block numbering, only functions at
+             * the top level of the program do.
              */
             x2.blockId = x.blockId;
         }
 
-        if (tt != LEFT_CURLY) {
+        if (tt !== LEFT_CURLY) {
             b.FUNCTION$setBody(f, AssignExpression(t, x));
             if (x.isGenerator)
                 throw t.newSyntaxError("Generator returns a value");
         } else {
             b.FUNCTION$hoistVars(x2.blockId);
             b.FUNCTION$setBody(f, Script(t, x2));
         }
 
         /*
-         * To linearize hoisting with nested blocks needing hoists, if a toplevel
-         * function has any hoists we reparse the entire thing. Each toplevel
-         * function is parsed at most twice.
+         * Hoisting makes parse-time binding analysis tricky. A taxonomy of hoists:
+         *
+         * 1. vars hoist to the top of their function:
          *
-         * Pass 1: If there needs to be hoisting at any child block or inner
-         * function, the entire function gets reparsed.
+         *    var x = 'global';
+         *    function f() {
+         *      x = 'f';
+         *      if (false)
+         *        var x;
+         *    }
+         *    f();
+         *    print(x); // "global"
          *
-         * Pass 2: It's possible that hoisting has changed the upvars of
-         * functions. That is, consider:
+         * 2. lets hoist to the top of their block:
          *
-         * function f() {
-         *   x = 0;
-         *   g();
-         *   x; // x's forward pointer should be invalidated!
-         *   function g() {
-         *     x = 'g';
-         *   }
-         *   var x;
-         * }
+         *    function f() { // id: 0
+         *      var x = 'f';
+         *      {
+         *        {
+         *          print(x); // "undefined"
+         *        }
+         *        let x;
+         *      }
+         *    }
+         *    f();
+         *
+         * 3. inner functions at function top-level hoist to the beginning
+         *    of the function.
          *
-         * So, a function needs to remember in which block it is parsed under
-         * (since the function body is _not_ hoisted, only the declaration) and
-         * upon hoisting, needs to recalculate all its upvars up front.
+         * If the builder used is doing parse-time analyses, hoisting may
+         * invalidate earlier conclusions it makes about variable scope.
+         *
+         * The builder can opt to set the needsHoisting flag in a
+         * CompilerContext (in the case of var and function hoisting) or in a
+         * node of type BLOCK (in the case of let hoisting). This signals for
+         * the parser to reparse sections of code.
+         *
+         * To avoid exponential blowup, if a function at the program top-level
+         * has any hoists in its child blocks or inner functions, we reparse
+         * the entire toplevel function. Each toplevel function is parsed at
+         * most twice.
+         *
+         * The list of declarations can be tied to block ids to aid talking
+         * about declarations of blocks that have not yet been fully parsed.
+         *
+         * Blocks are already uniquely numbered; see the comment in
+         * Statements.
          */
         if (x2.needsHoisting) {
-            // Order is important here! funDecls must come _after_ varDecls!
+
+            /*
+             * Order is important here! Builders expect funDecls to come after
+             * varDecls!
+             */
             b.setHoists(f.body.id, x2.varDecls.concat(x2.funDecls));
 
             if (x.inFunction) {
-                // Propagate up to the parent function if we're an inner function.
+                /*
+                 * If an inner function needs hoisting, we need to propagate
+                 * this flag up to the parent function.
+                 */
                 x.needsHoisting = true;
             } else {
-                // Only re-parse toplevel functions.
-                var x3 = x2;
+                // Only re-parse functions at the top level of the program.
                 x2 = new StaticContext(true, b);
                 t.rewind(rp);
-                // Set a flag in case the builder wants to have different behavior
-                // on the second pass.
+                /*
+                 * Set a flag in case the builder wants to have different behavior
+                 * on the second pass.
+                 */
                 b.secondPass = true;
                 b.FUNCTION$hoistVars(f.body.id, true);
                 b.FUNCTION$setBody(f, Script(t, x2));
                 b.secondPass = false;
             }
         }
 
-        if (tt == LEFT_CURLY)
+        if (tt === LEFT_CURLY)
             t.mustMatch(RIGHT_CURLY);
 
         f.end = t.token.end;
         f.functionForm = functionForm;
-        if (functionForm == DECLARED_FORM)
+        if (functionForm === DECLARED_FORM)
             x.funDecls.push(f);
         b.FUNCTION$finish(f, x);
         return f;
     }
 
     /*
      * Variables :: (tokenizer, compiler context) -> node
      *
@@ -1481,17 +1525,17 @@ Narcissus.jsparse = (function() {
             if (!letBlock) {
                 ss = x.stmtStack;
                 i = ss.length;
                 while (ss[--i].type !== BLOCK) ; // a BLOCK *must* be found.
                 /*
                  * Lets at the function toplevel are just vars, at least in
                  * SpiderMonkey.
                  */
-                if (i == 0) {
+                if (i === 0) {
                     build = b.VAR$build;
                     addDecl = b.VAR$addDecl;
                     finish = b.VAR$finish;
                     s = x;
                 } else {
                     s = ss[i];
                 }
             } else {
@@ -1504,24 +1548,24 @@ Narcissus.jsparse = (function() {
         do {
             var tt = t.get();
             /*
              * FIXME Should have a special DECLARATION node instead of overloading
              * IDENTIFIER to mean both identifier declarations and destructured
              * declarations.
              */
             var n2 = b.DECL$build(t);
-            if (tt == LEFT_BRACKET || tt == LEFT_CURLY) {
+            if (tt === LEFT_BRACKET || tt === LEFT_CURLY) {
                 // Pass in s if we need to add each pattern matched into
                 // its varDecls, else pass in x.
                 var data = null;
                 // Need to unget to parse the full destructured expression.
                 t.unget();
                 b.DECL$setName(n2, DestructuringExpression(t, x, true, s));
-                if (x.inForLoopInit && t.peek() == IN) {
+                if (x.inForLoopInit && t.peek() === IN) {
                     addDecl.call(b, n, n2, s);
                     continue;
                 }
 
                 t.mustMatch(ASSIGN);
                 if (t.token.assignOp)
                     throw t.newSyntaxError("Invalid variable initialization");
 
@@ -1533,21 +1577,21 @@ Narcissus.jsparse = (function() {
 
                 // But only add the rhs as the initializer.
                 b.DECL$setInitializer(n2, n3[1]);
                 b.DECL$finish(n2);
                 addDecl.call(b, n, n2, s);
                 continue;
             }
 
-            if (tt != IDENTIFIER)
+            if (tt !== IDENTIFIER)
                 throw t.newSyntaxError("missing variable name");
 
             b.DECL$setName(n2, t.token.value);
-            b.DECL$setReadOnly(n2, n.type == CONST);
+            b.DECL$setReadOnly(n2, n.type === CONST);
             addDecl.call(b, n, n2, s);
 
             if (t.match(ASSIGN)) {
                 if (t.token.assignOp)
                     throw t.newSyntaxError("Invalid variable initialization");
 
                 // Parse the init as a normal assignment with a fake lhs.
                 var id = new Node(n2.tokenizer, IDENTIFIER);
@@ -1579,17 +1623,17 @@ Narcissus.jsparse = (function() {
         var b = x.builder;
 
         // t.token.type must be LET
         n = b.LET_BLOCK$build(t);
         t.mustMatch(LEFT_PAREN);
         b.LET_BLOCK$setVariables(n, Variables(t, x, n));
         t.mustMatch(RIGHT_PAREN);
 
-        if (isStatement && t.peek() != LEFT_CURLY) {
+        if (isStatement && t.peek() !== LEFT_CURLY) {
             /*
              * If this is really an expression in let statement guise, then we
              * need to wrap the LET_BLOCK node in a SEMICOLON node so that we pop
              * the return value of the expression.
              */
             n2 = b.SEMICOLON$build(t);
             b.SEMICOLON$setExpression(n2, n);
             b.SEMICOLON$finish(n2);
@@ -1605,36 +1649,36 @@ Narcissus.jsparse = (function() {
         }
 
         b.LET_BLOCK$finish(n);
 
         return n;
     }
 
     function checkDestructuring(t, x, n, simpleNamesOnly, data) {
-        if (n.type == ARRAY_COMP)
+        if (n.type === ARRAY_COMP)
             throw t.newSyntaxError("Invalid array comprehension left-hand side");
-        if (n.type != ARRAY_INIT && n.type != OBJECT_INIT)
+        if (n.type !== ARRAY_INIT && n.type !== OBJECT_INIT)
             return;
 
         var b = x.builder;
 
         for (var i = 0, j = n.length; i < j; i++) {
             var nn = n[i], lhs, rhs;
             if (!nn)
                 continue;
-            if (nn.type == PROPERTY_INIT)
+            if (nn.type === PROPERTY_INIT)
                 lhs = nn[0], rhs = nn[1];
             else
                 lhs = null, rhs = null;
-            if (rhs && (rhs.type == ARRAY_INIT || rhs.type == OBJECT_INIT))
+            if (rhs && (rhs.type === ARRAY_INIT || rhs.type === OBJECT_INIT))
                 checkDestructuring(t, x, rhs, simpleNamesOnly, data);
             if (lhs && simpleNamesOnly) {
                 // In declarations, lhs must be simple names
-                if (lhs.type != IDENTIFIER) {
+                if (lhs.type !== IDENTIFIER) {
                     throw t.newSyntaxError("missing name in pattern");
                 } else if (data) {
                     var n2 = b.DECL$build(t);
                     b.DECL$setName(n2, lhs.value);
                     // Don't need to set initializer because it's just for
                     // hoisting anyways.
                     b.DECL$finish(n2);
                     // Each pattern needs to be added to varDecls.
@@ -1668,17 +1712,17 @@ Narcissus.jsparse = (function() {
         body = b.COMP_TAIL$build(t);
 
         do {
             n = b.FOR$build(t);
             // Comprehension tails are always for..in loops.
             b.FOR$rebuildForIn(n);
             if (t.match(IDENTIFIER)) {
                 // But sometimes they're for each..in.
-                if (t.token.value == "each")
+                if (t.token.value === "each")
                     b.FOR$rebuildForEach(n);
                 else
                     t.unget();
             }
             t.mustMatch(LEFT_PAREN);
             switch(t.get()) {
               case LEFT_BRACKET:
               case LEFT_CURLY:
@@ -1729,19 +1773,19 @@ Narcissus.jsparse = (function() {
          */
         var oldLoopInit = x.inForLoopInit;
         x.inForLoopInit = false;
         var n = Expression(t, x);
         x.inForLoopInit = oldLoopInit;
 
         var err = "expression must be parenthesized";
         if (t.match(FOR)) {
-            if (n.type == YIELD && !n.parenthesized)
+            if (n.type === YIELD && !n.parenthesized)
                 throw t.newSyntaxError("Yield " + err);
-            if (n.type == COMMA && !n.parenthesized)
+            if (n.type === COMMA && !n.parenthesized)
                 throw t.newSyntaxError("Generator " + err);
             n = GeneratorExpression(t, x, n);
         }
 
         t.mustMatch(RIGHT_PAREN);
 
         return n;
     }
@@ -1757,17 +1801,17 @@ Narcissus.jsparse = (function() {
 
         n = AssignExpression(t, x);
         if (t.match(COMMA)) {
             n2 = b.COMMA$build(t);
             b.COMMA$addOperand(n2, n);
             n = n2;
             do {
                 n2 = n[n.length-1];
-                if (n2.type == YIELD && !n2.parenthesized)
+                if (n2.type === YIELD && !n2.parenthesized)
                     throw t.newSyntaxError("Yield expression must be parenthesized");
                 b.COMMA$addOperand(n, AssignExpression(t, x));
             } while (t.match(COMMA));
             b.COMMA$finish(n);
         }
 
         return n;
     }
@@ -1940,17 +1984,17 @@ Narcissus.jsparse = (function() {
 
         /*
          * Uses of the in operator in shiftExprs are always unambiguous,
          * so unset the flag that prohibits recognizing it.
          */
         x.inForLoopInit = false;
         n = ShiftExpression(t, x);
         while ((t.match(LT) || t.match(LE) || t.match(GE) || t.match(GT) ||
-               (oldLoopInit == false && t.match(IN)) ||
+               (oldLoopInit === false && t.match(IN)) ||
                t.match(INSTANCEOF))) {
             n2 = b.RELATIONAL$build(t);
             b.RELATIONAL$addOperand(n2, n);
             b.RELATIONAL$addOperand(n2, ShiftExpression(t, x));
             b.RELATIONAL$finish(n2);
             n = n2;
         }
         x.inForLoopInit = oldLoopInit;
@@ -2024,17 +2068,17 @@ Narcissus.jsparse = (function() {
             b.UNARY$addOperand(n, MemberExpression(t, x, true));
             break;
 
           default:
             t.unget();
             n = MemberExpression(t, x, true);
 
             // Don't look across a newline boundary for a postfix {in,de}crement.
-            if (t.tokens[(t.tokenIndex + t.lookahead - 1) & 3].lineno ==
+            if (t.tokens[(t.tokenIndex + t.lookahead - 1) & 3].lineno ===
                 t.lineno) {
                 if (t.match(INCREMENT) || t.match(DECREMENT)) {
                     n2 = b.UNARY$build(t);
                     b.UNARY$setPostfix(n2);
                     b.UNARY$finish(n);
                     b.UNARY$addOperand(n2, n);
                     n = n2;
                 }
@@ -2057,17 +2101,17 @@ Narcissus.jsparse = (function() {
                 b.MEMBER$rebuildNewWithArgs(n);
                 b.MEMBER$addOperand(n, ArgumentList(t, x));
             }
             b.MEMBER$finish(n);
         } else {
             n = PrimaryExpression(t, x);
         }
 
-        while ((tt = t.get()) != END) {
+        while ((tt = t.get()) !== END) {
             switch (tt) {
               case DOT:
                 n2 = b.MEMBER$build(t);
                 b.MEMBER$addOperand(n2, n);
                 t.mustMatch(IDENTIFIER);
                 b.MEMBER$addOperand(n2, b.MEMBER$build(t));
                 break;
 
@@ -2104,21 +2148,21 @@ Narcissus.jsparse = (function() {
         var b = x.builder;
         var err = "expression must be parenthesized";
 
         n = b.LIST$build(t);
         if (t.match(RIGHT_PAREN, true))
             return n;
         do {
             n2 = AssignExpression(t, x);
-            if (n2.type == YIELD && !n2.parenthesized && t.peek() == COMMA)
+            if (n2.type === YIELD && !n2.parenthesized && t.peek() === COMMA)
                 throw t.newSyntaxError("Yield " + err);
             if (t.match(FOR)) {
                 n2 = GeneratorExpression(t, x, n2);
-                if (n.length > 1 || t.peek(true) == COMMA)
+                if (n.length > 1 || t.peek(true) === COMMA)
                     throw t.newSyntaxError("Generator " + err);
             }
             b.LIST$addOperand(n, n2);
         } while (t.match(COMMA));
         t.mustMatch(RIGHT_PAREN);
         b.LIST$finish(n);
 
         return n;
@@ -2130,30 +2174,30 @@ Narcissus.jsparse = (function() {
 
         switch (tt) {
           case FUNCTION:
             n = FunctionDefinition(t, x, false, EXPRESSED_FORM);
             break;
 
           case LEFT_BRACKET:
             n = b.ARRAY_INIT$build(t);
-            while ((tt = t.peek()) != RIGHT_BRACKET) {
-                if (tt == COMMA) {
+            while ((tt = t.peek()) !== RIGHT_BRACKET) {
+                if (tt === COMMA) {
                     t.get();
                     b.ARRAY_INIT$addElement(n, null);
                     continue;
                 }
                 b.ARRAY_INIT$addElement(n, AssignExpression(t, x));
-                if (tt != COMMA && !t.match(COMMA))
+                if (tt !== COMMA && !t.match(COMMA))
                     break;
             }
 
             // If we matched exactly one element and got a FOR, we have an
             // array comprehension.
-            if (n.length == 1 && t.match(FOR)) {
+            if (n.length === 1 && t.match(FOR)) {
                 n2 = b.ARRAY_COMP$build(t);
                 b.ARRAY_COMP$setExpression(n2, n[0]);
                 b.ARRAY_COMP$setTail(n2, comprehensionTail(t, x));
                 n = n2;
             }
             t.mustMatch(RIGHT_BRACKET);
             b.PRIMARY$finish(n);
             break;
@@ -2161,50 +2205,50 @@ Narcissus.jsparse = (function() {
           case LEFT_CURLY:
             var id;
             n = b.OBJECT_INIT$build(t);
 
           object_init:
             if (!t.match(RIGHT_CURLY)) {
                 do {
                     tt = t.get();
-                    if ((t.token.value == "get" || t.token.value == "set") &&
-                        t.peek() == IDENTIFIER) {
+                    if ((t.token.value === "get" || t.token.value === "set") &&
+                        t.peek() === IDENTIFIER) {
                         if (x.ecma3OnlyMode)
                             throw t.newSyntaxError("Illegal property accessor");
                         var fd = FunctionDefinition(t, x, true, EXPRESSED_FORM);
                         b.OBJECT_INIT$addProperty(n, fd);
                     } else {
                         switch (tt) {
                           case IDENTIFIER: case NUMBER: case STRING:
                             id = b.PRIMARY$build(t, IDENTIFIER);
                             b.PRIMARY$finish(id);
                             break;
                           case RIGHT_CURLY:
                             if (x.ecma3OnlyMode)
                                 throw t.newSyntaxError("Illegal trailing ,");
                             break object_init;
                           default:
-                            if (t.token.value in jsdefs.keywords) {
+                            if (t.token.value in definitions.keywords) {
                                 id = b.PRIMARY$build(t, IDENTIFIER);
                                 b.PRIMARY$finish(id);
                                 break;
                             }
                             throw t.newSyntaxError("Invalid property name");
                         }
                         if (t.match(COLON)) {
                             n2 = b.PROPERTY_INIT$build(t);
                             b.PROPERTY_INIT$addOperand(n2, id);
                             b.PROPERTY_INIT$addOperand(n2, AssignExpression(t, x));
                             b.PROPERTY_INIT$finish(n2);
                             b.OBJECT_INIT$addProperty(n, n2);
                         } else {
                             // Support, e.g., |var {x, y} = o| as destructuring shorthand
                             // for |var {x: x, y: y} = o|, per proposed JS2/ES4 for JS1.8.
-                            if (t.peek() != COMMA && t.peek() != RIGHT_CURLY)
+                            if (t.peek() !== COMMA && t.peek() !== RIGHT_CURLY)
                                 throw t.newSyntaxError("missing : after property");
                             b.OBJECT_INIT$addProperty(n, id);
                         }
                     }
                 } while (t.match(COMMA));
                 t.mustMatch(RIGHT_CURLY);
             }
             b.OBJECT_INIT$finish(n);
@@ -2235,28 +2279,28 @@ Narcissus.jsparse = (function() {
 
         return n;
     }
 
     /*
      * parse :: (builder, file ptr, path, line number) -> node
      */
     function parse(b, s, f, l) {
-        var t = new jslex.Tokenizer(s, f, l);
+        var t = new lexer.Tokenizer(s, f, l);
         var x = new StaticContext(false, b);
         var n = Script(t, x);
         if (!t.done)
             throw t.newSyntaxError("Syntax error");
 
         return n;
     }
 
     return {
-        "parse": parse,
-        "VanillaBuilder": VanillaBuilder,
-        "DECLARED_FORM": DECLARED_FORM,
-        "EXPRESSED_FORM": EXPRESSED_FORM,
-        "STATEMENT_FORM": STATEMENT_FORM,
-        "Tokenizer": jslex.Tokenizer,
-        "FunctionDefinition": FunctionDefinition
+        parse: parse,
+        VanillaBuilder: VanillaBuilder,
+        DECLARED_FORM: DECLARED_FORM,
+        EXPRESSED_FORM: EXPRESSED_FORM,
+        STATEMENT_FORM: STATEMENT_FORM,
+        Tokenizer: lexer.Tokenizer,
+        FunctionDefinition: FunctionDefinition
     };
 
 }());
--- a/js/src/configure.in
+++ b/js/src/configure.in
@@ -4398,16 +4398,27 @@ if test "$JS_HAS_CTYPES"; then
   if test "$_MSC_VER" -a -z $AS; then
     # Error out if we're on MSVC and MASM is unavailable.
     AC_MSG_ERROR([No suitable assembler found. An assembler is required to build js-ctypes. If you are building with MS Visual Studio 8 Express, you may download the MASM 8.0 package, upgrade to Visual Studio 9 Express, or install the Vista SDK.])
   fi
   AC_DEFINE(JS_HAS_CTYPES)
 fi
 
 dnl ========================================================
+dnl = Use JS Call tracing
+dnl ========================================================
+MOZ_ARG_ENABLE_BOOL(trace-jscalls,
+[  --enable-trace-jscalls  Enable JS call enter/exit callback (default=no)],
+    MOZ_TRACE_JSCALLS=1,
+    MOZ_TRACE_JSCALLS= )
+if test -n "$MOZ_TRACE_JSCALLS"; then
+    AC_DEFINE(MOZ_TRACE_JSCALLS)
+fi
+
+dnl ========================================================
 dnl = Use TraceVis
 dnl ========================================================
 MOZ_ARG_ENABLE_BOOL(tracevis,
 [  --enable-tracevis       Enable TraceVis tracing tool (default=no)],
     MOZ_TRACEVIS=1,
     MOZ_TRACEVIS= )
 if test -n "$MOZ_TRACEVIS"; then
     AC_DEFINE(MOZ_TRACEVIS)
--- a/js/src/jsapi-tests/Makefile.in
+++ b/js/src/jsapi-tests/Makefile.in
@@ -52,16 +52,17 @@ CPPSRCS = \
   testClassGetter.cpp \
   testConservativeGC.cpp \
   testContexts.cpp \
   testDebugger.cpp \
   testDefineGetterSetterNonEnumerable.cpp \
   testDefineProperty.cpp \
   testExtendedEq.cpp \
   testGCChunkAlloc.cpp \
+  testFuncCallback.cpp \
   testIntString.cpp \
   testIsAboutToBeFinalized.cpp \
   testLookup.cpp \
   testNewObject.cpp \
   testOps.cpp \
   testPropCache.cpp \
   testTrap.cpp \
   testSameValue.cpp \
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testFuncCallback.cpp
@@ -0,0 +1,138 @@
+#include "tests.h"
+#include "jsfun.h"
+#include "jscntxt.h"
+
+// For TRACING_ENABLED
+#include "jstracer.h"
+
+#ifdef MOZ_TRACE_JSCALLS
+
+static int depth = 0;
+static int enters = 0;
+static int leaves = 0;
+static int interpreted = 0;
+
+static void
+funcTransition(const JSFunction *,
+               const JSScript *,
+               const JSContext *cx,
+               JSBool entering)
+{
+    if (entering) {
+        ++depth;
+        ++enters;
+        if (! JS_ON_TRACE(cx))
+            ++interpreted;
+    } else {
+        --depth;
+        ++leaves;
+    }
+}
+
+static JSBool called2 = false;
+
+static void
+funcTransition2(const JSFunction *, const JSScript*, const JSContext*, JSBool)
+{
+    called2 = true;
+}
+
+static int overlays = 0;
+static JSFunctionCallback innerCallback = NULL;
+static void
+funcTransitionOverlay(const JSFunction *fun,
+                      const JSScript *script,
+                      const JSContext *cx,
+                      JSBool entering)
+{
+    (*innerCallback)(fun, script, cx, entering);
+    overlays++;
+}
+#endif
+
+BEGIN_TEST(testFuncCallback_bug507012)
+{
+#ifdef MOZ_TRACE_JSCALLS
+    // Call funcTransition() whenever a Javascript method is invoked
+    JS_SetFunctionCallback(cx, funcTransition);
+
+    EXEC("x = 0; function f (n) { if (n > 1) { f(n - 1); } }");
+    interpreted = enters = leaves = depth = 0;
+
+    // Check whether JS_Execute() tracking works
+    EXEC("42");
+    CHECK(enters == 1 && leaves == 1 && depth == 0);
+    interpreted = enters = leaves = depth = 0;
+
+    // Check whether the basic function tracking works
+    EXEC("f(1)");
+    CHECK(enters == 2 && leaves == 2 && depth == 0);
+
+    // Can we switch to a different callback?
+    enters = 777;
+    JS_SetFunctionCallback(cx, funcTransition2);
+    EXEC("f(1)");
+    CHECK(called2 && enters == 777);
+
+    // Check whether we can turn off function tracing
+    JS_SetFunctionCallback(cx, NULL);
+    EXEC("f(1)");
+    CHECK(enters == 777);
+    interpreted = enters = leaves = depth = 0;
+
+    // Check nested invocations
+    JS_SetFunctionCallback(cx, funcTransition);
+    enters = leaves = depth = 0;
+    EXEC("f(3)");
+    CHECK(enters == 1+3 && leaves == 1+3 && depth == 0);
+    interpreted = enters = leaves = depth = 0;
+
+    // Check calls invoked while running on trace
+    EXEC("function g () { ++x; }");
+    interpreted = enters = leaves = depth = 0;
+    EXEC("for (i = 0; i < 50; ++i) { g(); }");
+    CHECK(enters == 50+1 && leaves == 50+1 && depth == 0);
+
+    // If this fails, it means that the code was interpreted rather
+    // than trace-JITted, and so is not testing what it's supposed to
+    // be testing. Which doesn't necessarily imply that the
+    // functionality is broken.
+#ifdef JS_TRACER
+    if (TRACING_ENABLED(cx))
+        CHECK(interpreted < enters);
+#endif
+
+    // Test nesting callbacks via JS_GetFunctionCallback()
+    JS_SetFunctionCallback(cx, funcTransition);
+    innerCallback = JS_GetFunctionCallback(cx);
+    JS_SetFunctionCallback(cx, funcTransitionOverlay);
+
+    EXEC("x = 0; function f (n) { if (n > 1) { f(n - 1); } }");
+    interpreted = enters = leaves = depth = overlays = 0;
+
+    EXEC("42.5");
+    CHECK(enters == 1);
+    CHECK(leaves == 1);
+    CHECK(depth == 0);
+    CHECK(overlays == 2); // 1 each for enter and exit
+    interpreted = enters = leaves = depth = overlays = 0;
+#endif
+
+    return true;
+}
+
+// Not strictly necessary, but part of the test attempts to check
+// whether these callbacks still trigger when traced, so force
+// JSOPTION_JIT just to be sure. Once the method jit and tracing jit
+// are integrated, this'll probably have to change (and we'll probably
+// want to test in all modes.)
+virtual
+JSContext *createContext()
+{
+    JSContext *cx = JSAPITest::createContext();
+    if (cx)
+        JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_JIT);
+    return cx;
+}
+
+END_TEST(testFuncCallback_bug507012)
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -800,16 +800,19 @@ JS_BeginRequest(JSContext *cx)
                 JS_AWAIT_GC_DONE(rt);
         }
 
         /* Indicate that a request is running. */
         cx->requestDepth = 1;
         cx->outstandingRequests++;
         cx->thread->requestContext = cx;
         rt->requestCount++;
+
+        if (rt->requestCount == 1 && rt->activityCallback)
+            rt->activityCallback(rt->activityCallbackArg, true);
     }
 #endif
 }
 
 #ifdef JS_THREADSAFE
 static void
 StopRequest(JSContext *cx)
 {
@@ -848,18 +851,21 @@ StopRequest(JSContext *cx)
         cx->outstandingRequests--;
         cx->thread->requestContext = NULL;
 
         js_ShareWaitingTitles(cx);
 
         /* Give the GC a chance to run if this was the last request running. */
         JS_ASSERT(rt->requestCount > 0);
         rt->requestCount--;
-        if (rt->requestCount == 0)
+        if (rt->requestCount == 0) {
             JS_NOTIFY_REQUEST_DONE(rt);
+            if (rt->activityCallback)
+                rt->activityCallback(rt->activityCallbackArg, false);
+        }
     }
 }
 #endif
 
 JS_PUBLIC_API(void)
 JS_EndRequest(JSContext *cx)
 {
 #ifdef JS_THREADSAFE
@@ -5655,16 +5661,30 @@ JS_ClearContextThread(JSContext *cx)
     js_WaitForGC(rt);
     js_ClearContextThread(cx);
     return reinterpret_cast<jsword>(old);
 #else
     return 0;
 #endif
 }
 
+#ifdef MOZ_TRACE_JSCALLS
+JS_PUBLIC_API(void)
+JS_SetFunctionCallback(JSContext *cx, JSFunctionCallback fcb)
+{
+    cx->functionCallback = fcb;
+}
+
+JS_PUBLIC_API(JSFunctionCallback)
+JS_GetFunctionCallback(JSContext *cx)
+{
+    return cx->functionCallback;
+}
+#endif
+
 #ifdef JS_GC_ZEAL
 JS_PUBLIC_API(void)
 JS_SetGCZeal(JSContext *cx, uint8 zeal)
 {
     cx->runtime->gcZeal = zeal;
 }
 #endif
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3024,16 +3024,29 @@ extern JS_PUBLIC_API(jsword)
 JS_GetContextThread(JSContext *cx);
 
 extern JS_PUBLIC_API(jsword)
 JS_SetContextThread(JSContext *cx);
 
 extern JS_PUBLIC_API(jsword)
 JS_ClearContextThread(JSContext *cx);
 
+#ifdef MOZ_TRACE_JSCALLS
+typedef void (*JSFunctionCallback)(const JSFunction *fun,
+                                   const JSScript *scr,
+                                   const JSContext *cx,
+                                   JSBool entering);
+
+extern JS_PUBLIC_API(void)
+JS_SetFunctionCallback(JSContext *cx, JSFunctionCallback fcb);
+
+extern JS_PUBLIC_API(JSFunctionCallback)
+JS_GetFunctionCallback(JSContext *cx);
+#endif
+
 /************************************************************************/
 
 #ifdef DEBUG
 #define JS_GC_ZEAL 1
 #endif
 
 #ifdef JS_GC_ZEAL
 extern JS_PUBLIC_API(void)
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -1256,16 +1256,19 @@ struct JSCompartment {
     bool wrap(JSContext *cx, js::PropertyOp *op);
     bool wrap(JSContext *cx, js::PropertyDescriptor *desc);
     bool wrap(JSContext *cx, js::AutoIdVector &props);
     bool wrapException(JSContext *cx);
 
     void sweep(JSContext *cx);
 };
 
+typedef void
+(* JSActivityCallback)(void *arg, JSBool active);
+
 struct JSRuntime {
     /* Default compartment. */
     JSCompartment       *defaultCompartment;
 
     /* List of compartments (protected by the GC lock). */
     js::Vector<JSCompartment *, 0, js::SystemAllocPolicy> compartments;
 
     /* Runtime state, synchronized by the stateChange/gcLock condvar/lock. */
@@ -1273,16 +1276,30 @@ struct JSRuntime {
 
     /* Context create/destroy callback. */
     JSContextCallback   cxCallback;
 
     /* Compartment create/destroy callback. */
     JSCompartmentCallback compartmentCallback;
 
     /*
+     * Sets a callback that is run whenever the runtime goes idle - the
+     * last active request ceases - and begins activity - when it was
+     * idle and a request begins. Note: The callback is called under the
+     * GC lock.
+     */
+    void setActivityCallback(JSActivityCallback cb, void *arg) {
+        activityCallback = cb;
+        activityCallbackArg = arg;
+    }
+
+    JSActivityCallback    activityCallback;
+    void                 *activityCallbackArg;
+
+    /*
      * Shape regenerated whenever a prototype implicated by an "add property"
      * property cache fill and induced trace guard has a readonly property or a
      * setter defined on it. This number proxies for the shapes of all objects
      * along the prototype chain of all objects in the runtime on which such an
      * add-property result has been cached/traced.
      *
      * See bug 492355 for more details.
      *
@@ -1982,16 +1999,29 @@ struct JSContext
 #ifdef JS_TRACER
         jitEnabled = ((options & JSOPTION_JIT) &&
                       (debugHooks == &js_NullDebugHooks ||
                        (debugHooks == &runtime->globalDebugHooks &&
                         !runtime->debuggerInhibitsJIT())));
 #endif
     }
 
+#ifdef MOZ_TRACE_JSCALLS
+    /* Function entry/exit debugging callback. */
+    JSFunctionCallback    functionCallback;
+
+    void doFunctionCallback(const JSFunction *fun,
+                            const JSScript *scr,
+                            JSBool entering) const
+    {
+        if (functionCallback)
+            functionCallback(fun, scr, this, entering);
+    }
+#endif
+
     DSTOffsetCache dstOffsetCache;
 
     /* List of currently active non-escaping enumerators (for-in). */
     JSObject *enumerators;
 
   private:
     /*
      * To go from a live generator frame (on the stack) to its generator object
@@ -3192,21 +3222,35 @@ class AutoValueVector : private AutoGCRo
 
     size_t length() const { return vector.length(); }
 
     bool append(const Value &v) { return vector.append(v); }
 
     void popBack() { vector.popBack(); }
 
     bool growBy(size_t inc) {
-        return vector.growBy(inc);
+        /* N.B. Value's default ctor leaves the Value undefined */
+        size_t oldLength = vector.length();
+        if (!vector.growByUninitialized(inc))
+            return false;
+        MakeValueRangeGCSafe(vector.begin() + oldLength, vector.end());
+        return true;
     }
 
     bool resize(size_t newLength) {
-        return vector.resize(newLength);
+        size_t oldLength = vector.length();
+        if (newLength <= oldLength) {
+            vector.shrinkBy(oldLength - newLength);
+            return true;
+        }
+        /* N.B. Value's default ctor leaves the Value undefined */
+        if (!vector.growByUninitialized(newLength - oldLength))
+            return false;
+        MakeValueRangeGCSafe(vector.begin() + oldLength, vector.end());
+        return true;
     }
 
     bool reserve(size_t newLength) {
         return vector.reserve(newLength);
     }
 
     Value &operator[](size_t i) { return vector[i]; }
     const Value &operator[](size_t i) const { return vector[i]; }
@@ -3238,21 +3282,35 @@ class AutoIdVector : private AutoGCRoote
 
     size_t length() const { return vector.length(); }
 
     bool append(jsid id) { return vector.append(id); }
 
     void popBack() { vector.popBack(); }
 
     bool growBy(size_t inc) {
-        return vector.growBy(inc);
+        /* N.B. jsid's default ctor leaves the jsid undefined */
+        size_t oldLength = vector.length();
+        if (!vector.growByUninitialized(inc))
+            return false;
+        MakeIdRangeGCSafe(vector.begin() + oldLength, vector.end());
+        return true;
     }
 
     bool resize(size_t newLength) {
-        return vector.resize(newLength);
+        size_t oldLength = vector.length();
+        if (newLength <= oldLength) {
+            vector.shrinkBy(oldLength - newLength);
+            return true;
+        }
+        /* N.B. jsid's default ctor leaves the jsid undefined */
+        if (!vector.growByUninitialized(newLength - oldLength))
+            return false;
+        MakeIdRangeGCSafe(vector.begin() + oldLength, vector.end());
+        return true;
     }
 
     bool reserve(size_t newLength) {
         return vector.reserve(newLength);
     }
 
     jsid &operator[](size_t i) { return vector[i]; }
     const jsid &operator[](size_t i) const { return vector[i]; }
@@ -3270,54 +3328,16 @@ class AutoIdVector : private AutoGCRoote
   private:
     Vector<jsid, 8> vector;
     JS_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 JSIdArray *
 NewIdArray(JSContext *cx, jsint length);
 
-static JS_ALWAYS_INLINE void
-MakeValueRangeGCSafe(Value *vec, uintN len)
-{
-    PodZero(vec, len);
-}
-
-static JS_ALWAYS_INLINE void
-MakeValueRangeGCSafe(Value *beg, Value *end)
-{
-    PodZero(beg, end - beg);
-}
-
-static JS_ALWAYS_INLINE void
-SetValueRangeToUndefined(Value *beg, Value *end)
-{
-    for (Value *v = beg; v != end; ++v)
-        v->setUndefined();
-}
-
-static JS_ALWAYS_INLINE void
-SetValueRangeToUndefined(Value *vec, uintN len)
-{
-    return SetValueRangeToUndefined(vec, vec + len);
-}
-
-static JS_ALWAYS_INLINE void
-SetValueRangeToNull(Value *beg, Value *end)
-{
-    for (Value *v = beg; v != end; ++v)
-        v->setNull();
-}
-
-static JS_ALWAYS_INLINE void
-SetValueRangeToNull(Value *vec, uintN len)
-{
-    return SetValueRangeToNull(vec, vec + len);
-}
-
 } /* namespace js */
 
 #ifdef _MSC_VER
 #pragma warning(pop)
 #pragma warning(pop)
 #endif
 
 #ifdef JS_UNDEFD_MOZALLOC_WRAPPERS
--- a/js/src/jsdtracef.h
+++ b/js/src/jsdtracef.h
@@ -64,21 +64,22 @@ class DTrace {
                            js::Value *lval = NULL);
     static void exitJSFun(JSContext *cx, JSStackFrame *fp, JSFunction *fun,
                           const js::Value &rval,
                           js::Value *lval = NULL);
 
     static void finalizeObject(JSObject *obj);
 
     class ExecutionScope {
+        const JSContext *cx;
         const JSScript *script;
         void startExecution();
         void endExecution();
       public:
-        explicit ExecutionScope(JSScript *script);
+        explicit ExecutionScope(JSContext *cx, JSScript *script);
         ~ExecutionScope();
     };
 
     class ObjectCreationScope {
         JSContext       * const cx;
         JSStackFrame    * const fp;
         js::Class       * const clasp;
         void handleCreationStart();
@@ -101,60 +102,72 @@ DTrace::enterJSFun(JSContext *cx, JSStac
         if (JAVASCRIPT_FUNCTION_ENTRY_ENABLED())
             enterJSFunImpl(cx, fp, fun);
         if (JAVASCRIPT_FUNCTION_INFO_ENABLED())
             handleFunctionInfo(cx, fp, dfp, fun);
         if (JAVASCRIPT_FUNCTION_ARGS_ENABLED())
             handleFunctionArgs(cx, fp, fun, argc, argv);
     }
 #endif
+#ifdef MOZ_TRACE_JSCALLS
+    cx->doFunctionCallback(fun, fun ? FUN_SCRIPT(fun) : NULL, true);
+#endif
 }
 
 inline void
 DTrace::exitJSFun(JSContext *cx, JSStackFrame *fp, JSFunction *fun,
                   const js::Value &rval, js::Value *lval)
 {
 #ifdef INCLUDE_MOZILLA_DTRACE
     if (!lval || IsFunctionObject(*lval)) {
         if (JAVASCRIPT_FUNCTION_RVAL_ENABLED())
             handleFunctionRval(cx, fp, fun, rval);
         if (JAVASCRIPT_FUNCTION_RETURN_ENABLED())
             handleFunctionReturn(cx, fp, fun);
     }
 #endif
+#ifdef MOZ_TRACE_JSCALLS
+    cx->doFunctionCallback(fun, fun ? FUN_SCRIPT(fun) : NULL, false);
+#endif
 }
 
 inline void
 DTrace::finalizeObject(JSObject *obj)
 {
 #ifdef INCLUDE_MOZILLA_DTRACE
     if (JAVASCRIPT_OBJECT_FINALIZE_ENABLED())
         finalizeObjectImpl(obj);
 #endif
 }
 
 /* Execution scope. */
 
 inline
-DTrace::ExecutionScope::ExecutionScope(JSScript *script)
-  : script(script)
+DTrace::ExecutionScope::ExecutionScope(JSContext *cx, JSScript *script)
+  : cx(cx), script(script)
 {
 #ifdef INCLUDE_MOZILLA_DTRACE
     if (JAVASCRIPT_EXECUTE_START_ENABLED())
         startExecution();
 #endif
+#ifdef MOZ_TRACE_JSCALLS
+    cx->doFunctionCallback(NULL, script, true);
+#endif
 }
 
 inline
 DTrace::ExecutionScope::~ExecutionScope()
 {
 #ifdef INCLUDE_MOZILLA_DTRACE
     if (JAVASCRIPT_EXECUTE_DONE_ENABLED())
         endExecution();
 #endif
+#ifdef MOZ_TRACE_JSCALLS
+    cx->doFunctionCallback(NULL, script, false);
+#endif
 }
 
 /* Object creation scope. */
 
 inline
 DTrace::ObjectCreationScope::ObjectCreationScope(JSContext *cx, JSStackFrame *fp, js::Class *clasp)
   : cx(cx), fp(fp), clasp(clasp)
 {
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -836,17 +836,17 @@ Execute(JSContext *cx, JSObject *chain, 
     if (script->isEmpty()) {
         if (result)
             result->setUndefined();
         return JS_TRUE;
     }
 
     LeaveTrace(cx);
 
-    DTrace::ExecutionScope executionScope(script);
+    DTrace::ExecutionScope executionScope(cx, script);
     /*
      * Get a pointer to new frame/slots. This memory is not "claimed", so the
      * code before pushExecuteFrame must not reenter the interpreter.
      *
      * N.B. when fp->argv is removed (bug 539144), argv will have to be copied
      * in before execution and copied out after.
      */
     JSFrameRegs regs;
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1638,17 +1638,17 @@ JSBool
 obj_getPrototypeOf(JSContext *cx, uintN argc, Value *vp)
 {
     if (argc == 0) {
         js_ReportMissingArg(cx, *vp, 0);
         return JS_FALSE;
     }
 
     if (vp[2].isPrimitive()) {
-        char *bytes = DecompileValueGenerator(cx, 0 - argc, vp[2], NULL);
+        char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, vp[2], NULL);
         if (!bytes)
             return JS_FALSE;
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                              JSMSG_UNEXPECTED_TYPE, bytes, "not an object");
         JS_free(cx, bytes);
         return JS_FALSE;
     }
 
--- a/js/src/jstl.h
+++ b/js/src/jstl.h
@@ -370,47 +370,16 @@ class Conditionally {
   public:
     Conditionally(bool b) { if (b) t.construct(); }
 
     template <class T1>
     Conditionally(bool b, const T1 &t1) { if (b) t.construct(t1); }
 };
 
 template <class T>
-JS_ALWAYS_INLINE static void
-PodZero(T *t)
-{
-    memset(t, 0, sizeof(T));
-}
-
-template <class T>
-JS_ALWAYS_INLINE static void
-PodZero(T *t, size_t nelem)
-{
-    memset(t, 0, nelem * sizeof(T));
-}
-
-/*
- * Arrays implicitly convert to pointers to their first element, which is
- * dangerous when combined with the above PodZero definitions. Adding an
- * overload for arrays is ambiguous, so we need another identifier. The
- * ambiguous overload is left to catch mistaken uses of PodZero; if you get a
- * compile error involving PodZero and array types, use PodArrayZero instead.
- */
-template <class T, size_t N> static void PodZero(T (&)[N]);          /* undefined */
-template <class T, size_t N> static void PodZero(T (&)[N], size_t);  /* undefined */
-
-template <class T, size_t N>
-JS_ALWAYS_INLINE static void
-PodArrayZero(T (&t)[N])
-{
-    memset(t, 0, N * sizeof(T));
-}
-
-template <class T>
 class AlignedPtrAndFlag
 {
     uintptr_t bits;
 
   public:
     AlignedPtrAndFlag(T *t, bool flag) {
         JS_ASSERT((uintptr_t(t) & 1) == 0);
         bits = uintptr_t(t) | uintptr_t(flag);
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -10561,16 +10561,32 @@ TraceRecorder::record_JSOP_ENTERWITH()
 }
 
 JS_REQUIRES_STACK AbortableRecordingStatus
 TraceRecorder::record_JSOP_LEAVEWITH()
 {
     return ARECORD_STOP;
 }
 
+#ifdef MOZ_TRACE_JSCALLS
+// Usually, cx->doFunctionCallback() is invoked via DTrace::enterJSFun
+// and friends, but the DTrace:: probes use fp and therefore would
+// need to break out of tracing. So we define a functionProbe()
+// callback to be called by generated code when a Javascript function
+// is entered or exited.
+static JSBool JS_FASTCALL
+functionProbe(JSContext *cx, JSFunction *fun, JSBool enter)
+{
+    cx->doFunctionCallback(fun, FUN_SCRIPT(fun), enter);
+    return true;
+}
+
+JS_DEFINE_CALLINFO_3(static, BOOL, functionProbe, CONTEXT, FUNCTION, BOOL, 0, 0)
+#endif
+
 JS_REQUIRES_STACK AbortableRecordingStatus
 TraceRecorder::record_JSOP_RETURN()
 {
     /* A return from callDepth 0 terminates the current loop, except for recursion. */
     if (callDepth == 0) {
 #if 0
         if (IsTraceableRecursion(cx) && tree->recursion != Recursion_Disallowed &&
             tree->script == cx->fp->script) {
@@ -10580,16 +10596,24 @@ TraceRecorder::record_JSOP_RETURN()
         {
             AUDIT(returnLoopExits);
             return endLoop();
         }
     }
 
     putActivationObjects();
 
+#ifdef MOZ_TRACE_JSCALLS
+    if (cx->functionCallback) {
+        LIns* args[] = { INS_CONST(0), INS_CONSTPTR(cx->fp->fun), cx_ins };
+        LIns* call_ins = lir->insCall(&functionProbe_ci, args);
+        guard(false, lir->insEqI_0(call_ins), MISMATCH_EXIT);
+    }
+#endif
+
     /* If we inlined this function call, make the return value available to the caller code. */
     Value& rval = stackval(-1);
     JSStackFrame *fp = cx->fp;
     if ((cx->fp->flags & JSFRAME_CONSTRUCTING) && rval.isPrimitive()) {
         JS_ASSERT(fp->thisv == fp->argv[-1]);
         rval_ins = get(&fp->argv[-1]);
     } else {
         rval_ins = get(&rval);
@@ -11602,16 +11626,27 @@ TraceRecorder::functionCall(uintN argc, 
      * was found. So it's sufficient to test here that the particular function
      * is interpreted, not guard on that condition.
      *
      * Bytecode sequences that push shapeless callees must guard on the callee
      * class being Function and the function being interpreted.
      */
     JSFunction* fun = GET_FUNCTION_PRIVATE(cx, &fval.toObject());
 
+#ifdef MOZ_TRACE_JSCALLS
+    if (cx->functionCallback) {
+        JSScript *script = FUN_SCRIPT(fun);
+        if (! script || ! script->isEmpty()) {
+            LIns* args[] = { INS_CONST(1), INS_CONSTPTR(fun), cx_ins };
+            LIns* call_ins = lir->insCall(&functionProbe_ci, args);
+            guard(false, lir->insEqI_0(call_ins), MISMATCH_EXIT);
+        }
+    }
+#endif
+
     if (FUN_INTERPRETED(fun)) {
         if (mode == JSOP_NEW) {
             LIns* args[] = { get(&fval), INS_CONSTPTR(&js_ObjectClass), cx_ins };
             LIns* tv_ins = lir->insCall(&js_NewInstance_ci, args);
             guard(false, lir->insEqP_0(tv_ins), OOM_EXIT);
             set(&tval, tv_ins);
         }
         return interpretedFunctionCall(fval, fun, argc, mode == JSOP_NEW);
@@ -11628,17 +11663,25 @@ TraceRecorder::functionCall(uintN argc, 
             CHECK_STATUS(guardNativeConversion(argv[0]));
             return callImacro(call_imacros.String);
         }
         set(&fval, stringify(argv[0]));
         pendingSpecializedNative = IGNORE_NATIVE_CALL_COMPLETE_CALLBACK;
         return RECORD_CONTINUE;
     }
 
-    return callNative(argc, mode);
+    RecordingStatus rs = callNative(argc, mode);
+#ifdef MOZ_TRACE_JSCALLS
+    if (cx->functionCallback) {
+        LIns* args[] = { INS_CONST(0), INS_CONSTPTR(fun), cx_ins };
+        LIns* call_ins = lir->insCall(&functionProbe_ci, args);
+        guard(false, lir->insEqI_0(call_ins), MISMATCH_EXIT);
+    }
+#endif
+    return rs;
 }
 
 JS_REQUIRES_STACK AbortableRecordingStatus
 TraceRecorder::record_JSOP_NEW()
 {
     uintN argc = GET_ARGC(cx->regs->pc);
     cx->assertValidStackDepth(argc + 2);
     return InjectStatus(functionCall(argc, JSOP_NEW));
@@ -15668,16 +15711,24 @@ TraceRecorder::record_JSOP_STOP()
          * the pc after the calling op, still in the same JSStackFrame.
          */
         updateAtoms(fp->script);
         return ARECORD_CONTINUE;
     }
 
     putActivationObjects();
 
+#ifdef MOZ_TRACE_JSCALLS
+    if (cx->functionCallback) {
+        LIns* args[] = { INS_CONST(0), INS_CONSTPTR(cx->fp->fun), cx_ins };
+        LIns* call_ins = lir->insCall(&functionProbe_ci, args);
+        guard(false, lir->insEqI_0(call_ins), MISMATCH_EXIT);
+    }
+#endif
+
     /*
      * We know falling off the end of a constructor returns the new object that
      * was passed in via fp->argv[-1], while falling off the end of a function
      * returns undefined.
      *
      * NB: we do not support script rval (eval, API users who want the result
      * of the last expression-statement, debugger API calls).
      */
--- a/js/src/jsutil.cpp
+++ b/js/src/jsutil.cpp
@@ -41,17 +41,16 @@
 /*
  * PR assertion checker.
  */
 #include <stdio.h>
 #include <stdlib.h>
 #include "jstypes.h"
 #include "jsstdint.h"
 #include "jsutil.h"
-#include "jstl.h"
 
 #ifdef WIN32
 #    include "jswin.h"
 #else
 #    include <signal.h>
 #endif
 
 using namespace js;
--- a/js/src/jsutil.h
+++ b/js/src/jsutil.h
@@ -298,11 +298,46 @@ public:
 #else /* defined(DEBUG) */
 
 #define JS_DECL_USE_GUARD_OBJECT_NOTIFIER
 #define JS_GUARD_OBJECT_NOTIFIER_PARAM
 #define JS_GUARD_OBJECT_NOTIFIER_INIT JS_BEGIN_MACRO JS_END_MACRO
 
 #endif /* !defined(DEBUG) */
 
+namespace js {
+
+template <class T>
+JS_ALWAYS_INLINE static void
+PodZero(T *t)
+{
+    memset(t, 0, sizeof(T));
+}
+
+template <class T>
+JS_ALWAYS_INLINE static void
+PodZero(T *t, size_t nelem)
+{
+    memset(t, 0, nelem * sizeof(T));
+}
+
+/*
+ * Arrays implicitly convert to pointers to their first element, which is
+ * dangerous when combined with the above PodZero definitions. Adding an
+ * overload for arrays is ambiguous, so we need another identifier. The
+ * ambiguous overload is left to catch mistaken uses of PodZero; if you get a
+ * compile error involving PodZero and array types, use PodArrayZero instead.
+ */
+template <class T, size_t N> static void PodZero(T (&)[N]);          /* undefined */
+template <class T, size_t N> static void PodZero(T (&)[N], size_t);  /* undefined */
+
+template <class T, size_t N>
+JS_ALWAYS_INLINE static void
+PodArrayZero(T (&t)[N])
+{
+    memset(t, 0, N * sizeof(T));
+}
+
+} /* namespace js */
+
 #endif /* defined(__cplusplus) */
 
 #endif /* jsutil_h___ */
--- a/js/src/jsvalue.h
+++ b/js/src/jsvalue.h
@@ -965,10 +965,63 @@ typedef js::Value        ValueArgType;
 
 static JS_ALWAYS_INLINE const Value &
 ValueArgToConstRef(const Value &v)
 {
     return v;
 }
 #endif
 
+/******************************************************************************/
+
+static JS_ALWAYS_INLINE void
+MakeValueRangeGCSafe(Value *vec, size_t len)
+{
+    PodZero(vec, len);
+}
+
+static JS_ALWAYS_INLINE void
+MakeValueRangeGCSafe(Value *beg, Value *end)
+{
+    PodZero(beg, end - beg);
+}
+
+static JS_ALWAYS_INLINE void
+MakeIdRangeGCSafe(jsid *beg, jsid *end)
+{
+    for (jsid *id = beg; id != end; ++id)
+        *id = INT_TO_JSID(0);
+}
+
+static JS_ALWAYS_INLINE void
+MakeIdRangeGCSafe(jsid *vec, size_t len)
+{
+    MakeIdRangeGCSafe(vec, vec + len);
+}
+
+static JS_ALWAYS_INLINE void
+SetValueRangeToUndefined(Value *beg, Value *end)
+{
+    for (Value *v = beg; v != end; ++v)
+        v->setUndefined();
+}
+
+static JS_ALWAYS_INLINE void
+SetValueRangeToUndefined(Value *vec, size_t len)
+{
+    return SetValueRangeToUndefined(vec, vec + len);
+}
+
+static JS_ALWAYS_INLINE void
+SetValueRangeToNull(Value *beg, Value *end)
+{
+    for (Value *v = beg; v != end; ++v)
+        v->setNull();
+}
+
+static JS_ALWAYS_INLINE void
+SetValueRangeToNull(Value *vec, size_t len)
+{
+    return SetValueRangeToNull(vec, vec + len);
+}
+
 }      /* namespace js */
 #endif /* jsvalue_h__ */
--- a/js/src/jsxml.cpp
+++ b/js/src/jsxml.cpp
@@ -1678,16 +1678,18 @@ ParseXMLSource(JSContext *cx, JSString *
     static const char suffix[] = "</parent>";
 
 #define constrlen(constr)   (sizeof(constr) - 1)
 
     if (!js_GetDefaultXMLNamespace(cx, &nsval))
         return NULL;
     uri = GetURI(JSVAL_TO_OBJECT(nsval));
     uri = js_EscapeAttributeValue(cx, uri, JS_FALSE);
+    if (!uri)
+        return NULL;
 
     urilen = uri->length();
     srclen = src->length();
     length = constrlen(prefix) + urilen + constrlen(middle) + srclen +
              constrlen(suffix);
 
     chars = (jschar *) cx->malloc((length + 1) * sizeof(jschar));
     if (!chars)
--- a/js/src/shell/njs
+++ b/js/src/shell/njs
@@ -1,28 +1,34 @@
 #!/usr/bin/python
 #
 # Narcissus 'shell' for use with jstests.py
 # Expects to be in the same directory as ./js
 # Expects the Narcissus src files to be in ./narcissus/
 
-import os, re, sys
+import os, re, sys, signal
 from subprocess import *
 from optparse import OptionParser
 
 THIS_DIR = os.path.dirname(__file__)
 NARC_JS_DIR = os.path.abspath(os.path.join(THIS_DIR, 'narcissus'))
 
 js_cmd = os.path.abspath(os.path.join(THIS_DIR, "js"))
 
 narc_jsdefs = os.path.join(NARC_JS_DIR, "jsdefs.js")
 narc_jslex = os.path.join(NARC_JS_DIR, "jslex.js")
 narc_jsparse = os.path.join(NARC_JS_DIR, "jsparse.js")
 narc_jsexec = os.path.join(NARC_JS_DIR, "jsexec.js")
 
+def handler(signum, frame):
+    print ''
+    # the exit code produced by ./js on SIGINT
+    sys.exit(130)
+
+signal.signal(signal.SIGINT, handler)
 
 if __name__ == '__main__':
     op = OptionParser(usage='%prog [TEST-SPECS]')
     op.add_option('-f', '--file', dest='js_files', action='append',
             help='JS file to load', metavar='FILE')
     op.add_option('-e', '--expression', dest='js_exps', action='append',
             help='JS expression to evaluate')
     op.add_option('-i', '--interactive', dest='js_interactive', action='store_true',
@@ -34,22 +40,22 @@ if __name__ == '__main__':
 
     cmd = ""
 
     if options.js_harmony:
         cmd += 'Narcissus.options={version:"harmony"}; '
 
     if options.js_exps:
         for exp in options.js_exps:
-            cmd += 'Narcissus.jsexec.evaluate("%s"); ' % exp.replace('"', '\\"')
+            cmd += 'Narcissus.interpreter.evaluate("%s"); ' % exp.replace('"', '\\"')
 
     if options.js_files:
         for file in options.js_files:
-            cmd += 'Narcissus.jsexec.evaluate(snarf("%(file)s"), "%(file)s", 1); ' % { 'file':file }
+            cmd += 'Narcissus.interpreter.evaluate(snarf("%(file)s"), "%(file)s", 1); ' % { 'file':file }
 
     if (not options.js_exps) and (not options.js_files):
         options.js_interactive = True
 
     if options.js_interactive:
-        cmd += 'Narcissus.jsexec.repl();'
+        cmd += 'Narcissus.interpreter.repl();'
 
     Popen([js_cmd, '-f', narc_jsdefs, '-f', narc_jslex, '-f', narc_jsparse, '-f', narc_jsexec, '-e', cmd]).wait()
 
new file mode 100644
--- /dev/null
+++ b/js/src/trace-test/tests/basic/bug583615.js
@@ -0,0 +1,12 @@
+// |trace-test| slow;
+
+try {
+    x = <x><y/></x>
+    x += /x /
+} catch (e) {}
+for each(a in [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+0, 0]) {
+    x += x;
+}
+default xml namespace = x;
+<x></x>
new file mode 100644
--- /dev/null
+++ b/js/src/trace-test/tests/basic/testBug584650.js
@@ -0,0 +1,9 @@
+if (typeof gczeal != "function")
+    gczeal = function() {}
+
+// don't crash
+x = (evalcx('lazy'))
+x.watch("", function () {})
+gczeal(1)
+for (w in x) {}
+
new file mode 100644
--- /dev/null
+++ b/js/src/trace-test/tests/basic/testErrorReportIn_getPrototypeOf.js
@@ -0,0 +1,9 @@
+actual = "";
+expect = "TypeError: \"kittens\" is not an object";
+try {
+    Object.getPrototypeOf.apply(null, ["kittens",4,3])
+} catch (e) {
+    actual = "" + e;
+}
+
+assertEq(actual, expect);
--- a/js/src/xpconnect/src/xpcjsruntime.cpp
+++ b/js/src/xpconnect/src/xpcjsruntime.cpp
@@ -798,35 +798,60 @@ private:
 void
 XPCJSRuntime::WatchdogMain(void *arg)
 {
     XPCJSRuntime* self = static_cast<XPCJSRuntime*>(arg);
 
     // Lock lasts until we return
     AutoLockJSGC lock(self->mJSRuntime);
 
+    PRIntervalTime sleepInterval;
     while (self->mWatchdogThread)
     {
+        // Sleep only 1 second if recently (or currently) active; otherwise, hibernate
+        if (self->mLastActiveTime == -1 || PR_Now() - self->mLastActiveTime <= 2*PR_USEC_PER_SEC)
+            sleepInterval = PR_TicksPerSecond();
+        else
+        {
+            sleepInterval = PR_INTERVAL_NO_TIMEOUT;
+            self->mWatchdogHibernating = PR_TRUE;
+        }
 #ifdef DEBUG
         PRStatus status =
 #endif
-            PR_WaitCondVar(self->mWatchdogWakeup, PR_TicksPerSecond());
+            PR_WaitCondVar(self->mWatchdogWakeup, sleepInterval);
         JS_ASSERT(status == PR_SUCCESS);
-
         JSContext* cx = nsnull;
         while((cx = js_NextActiveContext(self->mJSRuntime, cx)))
         {
             JS_TriggerOperationCallback(cx);
         }
     }
 
     /* Wake up the main thread waiting for the watchdog to terminate. */
     PR_NotifyCondVar(self->mWatchdogWakeup);
 }
 
+//static
+void
+XPCJSRuntime::ActivityCallback(void *arg, PRBool active)
+{
+    XPCJSRuntime* self = static_cast<XPCJSRuntime*>(arg);
+    if (active) {
+        self->mLastActiveTime = -1;
+        if (self->mWatchdogHibernating)
+        {
+            self->mWatchdogHibernating = PR_FALSE;
+            PR_NotifyCondVar(self->mWatchdogWakeup);
+        }
+    } else {
+        self->mLastActiveTime = PR_Now();
+    }
+}
+
 
 /***************************************************************************/
 
 #ifdef XPC_CHECK_WRAPPERS_AT_SHUTDOWN
 static JSDHashOperator
 DEBUG_WrapperChecker(JSDHashTable *table, JSDHashEntryHdr *hdr,
                      uint32 number, void *arg)
 {
@@ -1103,17 +1128,19 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
    mThreadRunningGC(nsnull),
    mWrappedJSToReleaseArray(),
    mNativesToReleaseArray(),
    mDoingFinalization(JS_FALSE),
    mVariantRoots(nsnull),
    mWrappedJSRoots(nsnull),
    mObjectHolderRoots(nsnull),
    mWatchdogWakeup(nsnull),
-   mWatchdogThread(nsnull)
+   mWatchdogThread(nsnull),
+   mWatchdogHibernating(PR_FALSE),
+   mLastActiveTime(-1)
 {
 #ifdef XPC_CHECK_WRAPPERS_AT_SHUTDOWN
     DEBUG_WrappedNativeHashtable =
         JS_NewDHashTable(JS_DHashGetStubOps(), nsnull,
                          sizeof(JSDHashEntryStub), 128);
 #endif
     NS_TIME_FUNCTION;
 
@@ -1133,16 +1160,18 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
         // the GC's allocator.
         JS_SetGCParameter(mJSRuntime, JSGC_MAX_BYTES, 0xffffffff);
         JS_SetContextCallback(mJSRuntime, ContextCallback);
         JS_SetCompartmentCallback(mJSRuntime, CompartmentCallback);
         JS_SetGCCallbackRT(mJSRuntime, GCCallback);
         JS_SetExtraGCRoots(mJSRuntime, TraceJS, this);
         mWatchdogWakeup = JS_NEW_CONDVAR(mJSRuntime->gcLock);
 
+        mJSRuntime->setActivityCallback(ActivityCallback, this);
+
         mJSRuntime->setCustomGCChunkAllocator(&gXPCJSChunkAllocator);
 
         NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSRuntimeGCChunks));
     }
 
     if(!JS_DHashTableInit(&mJSHolders, JS_DHashGetStubOps(), nsnull,
                           sizeof(ObjectHolder), 512))
         mJSHolders.ops = nsnull;
--- a/js/src/xpconnect/src/xpcprivate.h
+++ b/js/src/xpconnect/src/xpcprivate.h
@@ -715,16 +715,18 @@ public:
 private:
    JSDHashTable* DEBUG_WrappedNativeHashtable;
 public:
 #endif
 
     void AddGCCallback(JSGCCallback cb);
     void RemoveGCCallback(JSGCCallback cb);
 
+    static void ActivityCallback(void *arg, PRBool active);
+
 private:
     XPCJSRuntime(); // no implementation
     XPCJSRuntime(nsXPConnect* aXPConnect);
 
     // The caller must be holding the GC lock
     void RescheduleWatchdog(XPCContext* ccx);
 
     static void WatchdogMain(void *arg);
@@ -753,16 +755,18 @@ private:
     JSBool mDoingFinalization;
     XPCRootSetElem *mVariantRoots;
     XPCRootSetElem *mWrappedJSRoots;
     XPCRootSetElem *mObjectHolderRoots;
     JSDHashTable mJSHolders;
     PRCondVar *mWatchdogWakeup;
     PRThread *mWatchdogThread;
     nsTArray<JSGCCallback> extraGCCallbacks;
+    PRBool mWatchdogHibernating;
+    PRTime mLastActiveTime; // -1 if active NOW
 };
 
 /***************************************************************************/
 /***************************************************************************/
 // XPCContext is mostly a dumb class to hold JSContext specific data and
 // maps that let us find wrappers created for the given JSContext.
 
 // no virtuals