Bug 1145491 part 7. Stop checking compileAndGo before emitting GNAME ops. r=luke
authorBoris Zbarsky <bzbarsky@mit.edu>
Fri, 20 Mar 2015 21:34:19 -0400
changeset 235088 55f700adddec0f3daf8eacacf5a5c2d9eb86c3b9
parent 235087 72accc37764a13c01209db6f1d963c428ea2bf6d
child 235089 97650ddc6032bdcf2fdee62fe1cfec3c7fc9edd9
push id28464
push userkwierso@gmail.com
push dateMon, 23 Mar 2015 23:41:23 +0000
treeherdermozilla-central@e642ae3c0496 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1145491
milestone39.0a1
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
Bug 1145491 part 7. Stop checking compileAndGo before emitting GNAME ops. r=luke
js/src/builtin/TestingFunctions.cpp
js/src/frontend/BytecodeCompiler.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/jit-test/tests/basic/bug1106982-2.js
js/src/jit-test/tests/basic/bug1106982.js
js/src/jit-test/tests/basic/eval-scopes.js
js/src/jit-test/tests/basic/function-gname.js
js/src/vm/Xdr.h
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -2435,16 +2435,33 @@ DumpStringRepresentation(JSContext *cx, 
 
     str->dumpRepresentation(stderr, 0);
 
     args.rval().setUndefined();
     return true;
 }
 #endif
 
+static bool
+SetLazyParsingEnabled(JSContext *cx, unsigned argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (argc < 1) {
+        JS_ReportError(cx, "setLazyParsingEnabled: need an argument");
+        return false;
+    }
+
+    bool arg = ToBoolean(args.get(0));
+    JS::CompartmentOptionsRef(cx->compartment()).setDiscardSource(!arg);
+
+    args.rval().setUndefined();
+    return true;
+}
+
 static const JSFunctionSpecWithHelp TestingFunctions[] = {
     JS_FN_HELP("gc", ::GC, 0, 0,
 "gc([obj] | 'compartment' [, 'shrinking'])",
 "  Run the garbage collector. When obj is given, GC only its compartment.\n"
 "  If 'compartment' is given, GC any compartments that were scheduled for\n"
 "  GC via schedulegc.\n"
 "  If 'shrinking' is passed as the optional second argument, perform a\n"
 "  shrinking GC rather than a normal GC."),
@@ -2817,16 +2834,20 @@ gc::ZealModeHelpText),
 "  because the object is a revoked proxy)."),
 
 #ifdef DEBUG
     JS_FN_HELP("dumpStringRepresentation", DumpStringRepresentation, 1, 0,
 "dumpStringRepresentation(str)",
 "  Print a human-readable description of how the string |str| is represented.\n"),
 #endif
 
+    JS_FN_HELP("setLazyParsingEnabled", SetLazyParsingEnabled, 1, 0,
+"setLazyParsingEnabled(bool)",
+"  Enable or disable lazy parsing in the current compartment.  The default is enabled."),
+
     JS_FS_HELP_END
 };
 
 static const JSPropertySpec TestingProperties[] = {
     JS_PSG("timesAccessed", TimesAccessed, 0),
     JS_PS_END
 };
 
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -150,18 +150,17 @@ CanLazilyParse(ExclusiveContext *cx, con
         !options.hasPollutedGlobalScope &&
         !cx->compartment()->options().discardSource() &&
         !options.sourceIsLazy;
 }
 
 static void
 MarkFunctionsWithinEvalScript(JSScript *script)
 {
-    // Mark top level functions in an eval script as being within an eval and,
-    // if applicable, inside a with statement.
+    // Mark top level functions in an eval script as being within an eval.
 
     if (!script->hasObjects())
         return;
 
     ObjectArray *objects = script->objects();
     size_t start = script->innerObjectsStart();
 
     for (size_t i = start; i < objects->length; i++) {
@@ -287,27 +286,24 @@ frontend::CompileScript(ExclusiveContext
 
     bool savedCallerFun = evalCaller && evalCaller->functionOrCallerFunction();
     Rooted<JSScript*> script(cx, JSScript::Create(cx, evalStaticScope, savedCallerFun,
                                                   options, staticLevel, sourceObject, 0,
                                                   srcBuf.length()));
     if (!script)
         return nullptr;
 
-    // We can specialize a bit for the given scope chain if that scope chain is the global object.
-    JSObject *globalScope =
-        scopeChain && scopeChain == &scopeChain->global() ? (JSObject*) scopeChain : nullptr;
-    MOZ_ASSERT_IF(globalScope, globalScope->isNative());
-    MOZ_ASSERT_IF(globalScope, JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(globalScope->getClass()));
-
+    bool insideNonGlobalEval =
+        evalStaticScope && evalStaticScope->enclosingScopeForStaticScopeIter();
     BytecodeEmitter::EmitterMode emitterMode =
         options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal;
     BytecodeEmitter bce(/* parent = */ nullptr, &parser, &globalsc, script,
                         /* lazyScript = */ js::NullPtr(), options.forEval,
-                        evalCaller, evalStaticScope, !!globalScope, options.lineno, emitterMode);
+                        evalCaller, evalStaticScope, insideNonGlobalEval,
+                        options.lineno, emitterMode);
     if (!bce.init())
         return nullptr;
 
     // Syntax parsing may cause us to restart processing of top level
     // statements in the script. Use Maybe<> so that the parse context can be
     // reset when this occurs.
     Maybe<ParseContext<FullParseHandler> > pc;
 
@@ -511,33 +507,40 @@ frontend::CompileLazyFunction(JSContext 
 
     if (lazy->directlyInsideEval())
         script->setDirectlyInsideEval();
     if (lazy->usesArgumentsApplyAndThis())
         script->setUsesArgumentsApplyAndThis();
     if (lazy->hasBeenCloned())
         script->setHasBeenCloned();
 
+    /*
+     * We just pass false for insideNonGlobalEval and insideEval, because we
+     * don't actually know whether we are or not.  The only consumer of those
+     * booleans is TryConvertFreeName, and it has special machinery to avoid
+     * doing bad things when a lazy function is inside eval.
+     */
+    MOZ_ASSERT(!options.forEval);
     BytecodeEmitter bce(/* parent = */ nullptr, &parser, pn->pn_funbox, script, lazy,
-                        options.forEval, /* evalCaller = */ js::NullPtr(),
+                        /* insideEval = */ false, /* evalCaller = */ js::NullPtr(),
                         /* evalStaticScope = */ js::NullPtr(),
-                        /* hasGlobalScope = */ true, options.lineno,
+                        /* insideNonGlobalEval = */ false, options.lineno,
                         BytecodeEmitter::LazyFunction);
     if (!bce.init())
         return false;
 
     return EmitFunctionScript(cx, &bce, pn->pn_body);
 }
 
 // Compile a JS function body, which might appear as the value of an event
 // handler attribute in an HTML <INPUT> tag, or in a Function() constructor.
 static bool
 CompileFunctionBody(JSContext *cx, MutableHandleFunction fun, const ReadOnlyCompileOptions &options,
                     const AutoNameVector &formals, SourceBufferHolder &srcBuf,
-                    HandleObject enclosingScope, GeneratorKind generatorKind)
+                    HandleObject enclosingStaticScope, GeneratorKind generatorKind)
 {
     js::TraceLoggerThread *logger = js::TraceLoggerForMainThread(cx->runtime());
     js::TraceLoggerEvent event(logger, TraceLogger_AnnotateScripts, options);
     js::AutoTraceLog scriptLogger(logger, event);
     js::AutoTraceLog typeLogger(logger, TraceLogger_ParserCompileFunction);
 
     // FIXME: make Function pass in two strings and parse them as arguments and
     // ProgramElements respectively.
@@ -628,37 +631,29 @@ CompileFunctionBody(JSContext *cx, Mutab
         return false;
 
     if (!SetSourceMap(cx, parser.tokenStream, ss))
         return false;
 
     if (fn->pn_funbox->function()->isInterpreted()) {
         MOZ_ASSERT(fun == fn->pn_funbox->function());
 
-        Rooted<JSScript*> script(cx, JSScript::Create(cx, enclosingScope, false, options,
+        Rooted<JSScript*> script(cx, JSScript::Create(cx, enclosingStaticScope, false, options,
                                                       /* staticLevel = */ 0, sourceObject,
                                                       /* sourceStart = */ 0, srcBuf.length()));
         if (!script)
             return false;
 
         script->bindings = fn->pn_funbox->bindings;
 
-        /*
-         * The reason for checking fun->environment() below is that certain
-         * consumers of JS::CompileFunction, namely
-         * EventListenerManager::CompileEventHandlerInternal, passes in a
-         * nullptr environment. This compiled function is never used, but
-         * instead is cloned immediately onto the right scope chain.
-         */
         BytecodeEmitter funbce(/* parent = */ nullptr, &parser, fn->pn_funbox, script,
                                /* lazyScript = */ js::NullPtr(), /* insideEval = */ false,
                                /* evalCaller = */ js::NullPtr(),
                                /* evalStaticScope = */ js::NullPtr(),
-                               fun->environment() && fun->environment()->is<GlobalObject>(),
-                               options.lineno);
+                               /* insideNonGlobalEval = */ false, options.lineno);
         if (!funbce.init())
             return false;
 
         if (!EmitFunctionScript(cx, &funbce, fn->pn_body))
             return false;
     } else {
         fun.set(fn->pn_funbox->function());
         MOZ_ASSERT(IsAsmJSModuleNative(fun->native()));
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -106,17 +106,18 @@ struct LoopStmtInfo : public StmtInfoBCE
 
 } // anonymous namespace
 
 BytecodeEmitter::BytecodeEmitter(BytecodeEmitter *parent,
                                  Parser<FullParseHandler> *parser, SharedContext *sc,
                                  HandleScript script, Handle<LazyScript *> lazyScript,
                                  bool insideEval, HandleScript evalCaller,
                                  Handle<StaticEvalObject *> staticEvalScope,
-                                 bool hasGlobalScope, uint32_t lineNum, EmitterMode emitterMode)
+                                 bool insideNonGlobalEval, uint32_t lineNum,
+                                 EmitterMode emitterMode)
   : sc(sc),
     cx(sc->context),
     parent(parent),
     script(cx, script),
     lazyScript(cx, lazyScript),
     prolog(cx, lineNum),
     main(cx, lineNum),
     current(&main),
@@ -137,17 +138,17 @@ BytecodeEmitter::BytecodeEmitter(Bytecod
     blockScopeList(cx),
     yieldOffsetList(cx),
     typesetCount(0),
     hasSingletons(false),
     hasTryFinally(false),
     emittingForInit(false),
     emittingRunOnceLambda(false),
     insideEval(insideEval),
-    hasGlobalScope(hasGlobalScope),
+    insideNonGlobalEval(insideNonGlobalEval),
     emitterMode(emitterMode)
 {
     MOZ_ASSERT_IF(evalCaller, insideEval);
     MOZ_ASSERT_IF(emitterMode == LazyFunction, lazyScript);
     // Function scripts are never eval scripts.
     MOZ_ASSERT_IF(evalStaticScope, !sc->isFunctionBox());
 }
 
@@ -1582,24 +1583,34 @@ TryConvertFreeName(BytecodeEmitter *bce,
 
                     pn->setOp(op);
                     JS_ALWAYS_TRUE(pn->pn_cookie.set(bce->parser->tokenStream, hops, slot));
                     return true;
                 }
                 hops++;
             }
 
+            // If this walk up and check for directlyInsideEval is ever removed,
+            // we'll need to adjust CompileLazyFunction to better communicate
+            // whether we're inside eval to the BytecodeEmitter.  For now, this
+            // walk is why CompileLazyFunction can claim that it's never inside
+            // eval.
             if (script->funHasExtensibleScope() || script->directlyInsideEval())
                 return false;
         }
     }
 
     // Unbound names aren't recognizable global-property references if the
-    // script isn't running against its global object.
-    if (!bce->script->compileAndGo() || !bce->hasGlobalScope)
+    // script is inside a non-global eval call.
+    if (bce->insideNonGlobalEval)
+        return false;
+
+    // Skip trying to use GNAME ops if we know our script has a polluted
+    // global scope, since they'll just get treated as NAME ops anyway.
+    if (bce->script->hasPollutedGlobalScope())
         return false;
 
     // Deoptimized names also aren't necessarily globals.
     if (pn->isDeoptimized())
         return false;
 
     if (bce->sc->isFunctionBox()) {
         // Unbound names in function code may not be globals if new locals can
@@ -1618,16 +1629,20 @@ TryConvertFreeName(BytecodeEmitter *bce,
     //        'var x; ' +
     //        'eval("print(x)");'); // "undefined", not "GLOBAL"
     //
     // Given the enclosing eval code's strictness and its bindings (neither is
     // readily available now), we could exactly check global-ness, but it's not
     // worth the trouble for doubly-nested eval code.  So we conservatively
     // approximate.  If the outer eval code is strict, then this eval code will
     // be: thus, don't optimize if we're compiling strict code inside an eval.
+    //
+    // Though actually, we don't even need an inner eval.  We could just as well
+    // have a lambda inside that outer strict mode eval and it would run into
+    // the same issue.
     if (bce->insideEval && bce->sc->strict())
         return false;
 
     JSOp op;
     switch (pn->getOp()) {
       case JSOP_GETNAME:  op = JSOP_GETGNAME; break;
       case JSOP_SETNAME:  op = StrictifySetNameOp(JSOP_SETGNAME, bce); break;
       case JSOP_SETCONST:
@@ -5305,17 +5320,17 @@ EmitFunc(ExclusiveContext *cx, BytecodeE
                 return false;
 
             script->bindings = funbox->bindings;
 
             uint32_t lineNum = bce->parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin);
             BytecodeEmitter bce2(bce, bce->parser, funbox, script, /* lazyScript = */ js::NullPtr(),
                                  bce->insideEval, bce->evalCaller,
                                  /* evalStaticScope = */ js::NullPtr(),
-                                 bce->hasGlobalScope, lineNum, bce->emitterMode);
+                                 bce->insideNonGlobalEval, lineNum, bce->emitterMode);
             if (!bce2.init())
                 return false;
 
             /* We measured the max scope depth when we parsed the function. */
             if (!EmitFunctionScript(cx, &bce2, pn->pn_body))
                 return false;
 
             if (funbox->usesArguments && funbox->usesApply && funbox->usesThis)
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -185,18 +185,18 @@ struct BytecodeEmitter
     bool            emittingRunOnceLambda:1; /* true while emitting a lambda which is only
                                                 expected to run once. */
 
     bool isRunOnceLambda();
 
     bool            insideEval:1;       /* True if compiling an eval-expression or a function
                                            nested inside an eval. */
 
-    const bool      hasGlobalScope:1;   /* frontend::CompileScript's scope chain is the
-                                           global object */
+    const bool      insideNonGlobalEval:1;  /* True if this is a direct eval
+                                               call in some non-global scope. */
 
     enum EmitterMode {
         Normal,
 
         /*
          * Emit JSOP_GETINTRINSIC instead of JSOP_GETNAME and assert that
          * JSOP_GETNAME and JSOP_*GNAME don't ever get emitted. See the comment
          * for the field |selfHostingMode| in Parser.h for details.
@@ -216,17 +216,17 @@ struct BytecodeEmitter
      * Note that BytecodeEmitters are magic: they own the arena "top-of-stack"
      * space above their tempMark points. This means that you cannot alloc from
      * tempLifoAlloc and save the pointer beyond the next BytecodeEmitter
      * destruction.
      */
     BytecodeEmitter(BytecodeEmitter *parent, Parser<FullParseHandler> *parser, SharedContext *sc,
                     HandleScript script, Handle<LazyScript *> lazyScript,
                     bool insideEval, HandleScript evalCaller,
-                    Handle<StaticEvalObject *> evalStaticScope, bool hasGlobalScope,
+                    Handle<StaticEvalObject *> evalStaticScope, bool insideNonGlobalEval,
                     uint32_t lineNum, EmitterMode emitterMode = Normal);
     bool init();
     bool updateLocalsToFrameSlots();
 
     bool isAliasedName(ParseNode *pn);
 
     MOZ_ALWAYS_INLINE
     bool makeAtomIndex(JSAtom *atom, jsatomid *indexp) {
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1106982-2.js
@@ -0,0 +1,20 @@
+var x = "wrong";
+var t = {x: "x"};
+var hits = 0;
+var p = new Proxy(t, {
+    has(t, id) {
+        var found = id in t;
+        if (++hits == 2)
+            delete t[id];
+        return found;
+    },
+    get(t, id) { return t[id]; }
+});
+evaluate(`function testFunc() {
+    x += " x";
+}`, { compileAndGo: false });
+
+var cloneFunc = clone(testFunc, p);
+cloneFunc();
+assertEq(hits, 2);
+assertEq(t.x, "undefined x");
--- a/js/src/jit-test/tests/basic/bug1106982.js
+++ b/js/src/jit-test/tests/basic/bug1106982.js
@@ -7,10 +7,13 @@ var p = new Proxy(t, {
         if (++hits == 2)
             delete t[id];
         return found;
     },
     get(t, id) { return t[id]; }
 });
 with (p)
     x += " x";
+// If you change this testcase (e.g. because we fix the number of
+// has() calls we end up making to match spec better), don't forget to
+// update bug1106982-2.js too.  See also bug 1145641.
 assertEq(hits, 2);
 assertEq(t.x, "undefined x");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/eval-scopes.js
@@ -0,0 +1,135 @@
+function bytecode(f) {
+    if (typeof disassemble !== "function")
+        return "unavailable";
+    var d = disassemble(f);
+    return d.slice(d.indexOf("main:"), d.indexOf("\n\n"));
+}
+
+function hasGname(f, v, hasIt = true) {
+    // Do a try-catch that prints the full stack, so we can tell
+    // _which_ part of this test failed.
+    try {
+	var b = bytecode(f);
+	if (b != "unavailable") {
+	    assertEq(b.contains(`getgname "${v}"`), hasIt);
+	    assertEq(b.contains(`getname "${v}"`), !hasIt);
+	}
+    } catch (e) {
+	print(e.stack);
+	throw e;
+    }
+}
+
+var x = "outer";
+
+setLazyParsingEnabled(false);
+{
+    let x = "inner";
+    eval("function g() { assertEq(x, 'inner');} g()");
+    eval("function g2() { (function nest() { assertEq(x, 'inner'); })(); } g2()");
+}
+eval(`
+     function g3() {
+	 assertEq(x, 'outer');
+     }
+     g3();
+     hasGname(g3, 'x');
+     `);
+eval(`
+     function g4() {
+	 function nest() { assertEq(x, 'outer'); }
+	 nest();
+	 return nest;
+     }
+     hasGname(g4(), 'x');
+     `);
+setLazyParsingEnabled(true);
+
+{
+    let x = "inner";
+    eval("function h() { assertEq(x, 'inner');} h()");
+    eval("function h2() { (function nest() { assertEq(x, 'inner'); })(); } h2()");
+}
+// It sure would be nice if we could run the h3/h4 tests below, but it turns out
+// that lazy functions and eval don't play together all that well.  See bug
+// 1146080.  For now, assert we have no gname, so people will notice if they
+// accidentally fix it and adjust this test accordingly.
+eval(`
+     function h3() {
+	 assertEq(x, 'outer');
+     }
+     h3();
+     hasGname(h3, 'x', false);
+     `);
+eval(`
+     function h4() {
+	 function nest() { assertEq(x, 'outer'); }
+	 nest();
+	 return nest;
+     }
+     hasGname(h4(), 'x', false);
+     `);
+
+setLazyParsingEnabled(false);
+with ({}) {
+    let x = "inner";
+    eval("function i() { assertEq(x, 'inner');} i()");
+    eval("function i2() { (function nest() { assertEq(x, 'inner'); })(); } i2()");
+}
+setLazyParsingEnabled(true);
+
+with ({}) {
+    let x = "inner";
+    eval("function j() { assertEq(x, 'inner');} j()");
+    eval("function j2() { (function nest() { assertEq(x, 'inner'); })(); } j2()");
+}
+
+setLazyParsingEnabled(false);
+(function () {
+    var x = "inner";
+    eval("function k() { assertEq(x, 'inner');} k()");
+    eval("function k2() { (function nest() { assertEq(x, 'inner'); })(); } k2()");
+})();
+setLazyParsingEnabled(true);
+
+(function () {
+    let x = "inner";
+    eval("function l() { assertEq(x, 'inner');} l()");
+    eval("function l2() { (function nest() { assertEq(x, 'inner'); })(); } l2()");
+})();
+
+var y1 = 5;
+eval(`
+     'use strict';
+     var y1 = 6;
+     assertEq(y1, 6);
+     (function() { assertEq(y1, 6); })()
+     `);
+assertEq(y1, 5);
+
+eval(`
+     'use strict';
+     var y2 = 6;
+     assertEq(y2, 6);
+     (function() { assertEq(y2, 6); })()
+     `);
+
+setLazyParsingEnabled(false);
+
+var y3 = 5;
+eval(`
+     'use strict';
+     var y3 = 6;
+     assertEq(y3, 6);
+     (function() { assertEq(y3, 6); })()
+     `);
+assertEq(y3, 5);
+
+eval(`
+     'use strict';
+     var y4 = 6;
+     assertEq(y4, 6);
+     (function() { assertEq(y4, 6); })()
+     `);
+
+setLazyParsingEnabled(true);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/function-gname.js
@@ -0,0 +1,49 @@
+function bytecode(f) {
+    if (typeof disassemble !== "function")
+        return "unavailable";
+    var d = disassemble(f);
+    return d.slice(d.indexOf("main:"), d.indexOf("\n\n"));
+}
+
+function hasGname(f, v) {
+    // Do a try-catch that prints the full stack, so we can tell
+    // _which_ part of this test failed.
+    try {
+	var b = bytecode(f);
+	if (b != "unavailable") {
+	    assertEq(b.contains(`getgname "${v}"`), true);
+	    assertEq(b.contains(`getname "${v}"`), false);
+	}
+    } catch (e) {
+	print(e.stack);
+	throw e;
+    }
+}
+
+var x = "outer";
+
+var f1 = new Function("assertEq(x, 'outer')");
+f1();
+hasGname(f1, 'x');
+
+setLazyParsingEnabled(false);
+var f2 = new Function("assertEq(x, 'outer')");
+f2();
+hasGname(f2, 'x');
+setLazyParsingEnabled(true);
+
+{
+    let x = "inner";
+    var f3 = new Function("assertEq(x, 'outer')");
+    f3();
+    hasGname(f3, 'x');
+}
+
+setLazyParsingEnabled(false);
+{
+    let x = "inner";
+    var f4 = new Function("assertEq(x, 'outer')");
+    f4();
+    hasGname(f4, 'x');
+}
+setLazyParsingEnabled(true);
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -24,17 +24,17 @@ namespace js {
  * versions.  If deserialization fails, the data should be invalidated if
  * possible.
  *
  * When you change this, run make_opcode_doc.py and copy the new output into
  * this wiki page:
  *
  *  https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
  */
-static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 266;
+static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 267;
 static const uint32_t XDR_BYTECODE_VERSION =
     uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
 
 static_assert(JSErr_Limit == 388,
               "GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or "
               "removed MSG_DEFs from js.msg, you should increment "
               "XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's "
               "expected JSErr_Limit value.");