Don't generate INITPROP/INITELEM for singleton constant initializers, bug 577359.
authorBrian Hackett <bhackett1024@gmail.com>
Thu, 30 Dec 2010 08:07:43 -0500
changeset 59962 d4f969511664e0b84d2b3c392de0b491d96b3800
parent 59961 7afbb62b190b3d40f9008e32ef74cc216ca54e45
child 59963 49eb06696448b3170d13cd63f592fde77de43e8b
push id17820
push usercleary@mozilla.com
push dateTue, 04 Jan 2011 21:40:57 +0000
treeherdermozilla-central@969691cfe40e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs577359
milestone2.0b9pre
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
Don't generate INITPROP/INITELEM for singleton constant initializers, bug 577359.
js/src/jit-test/tests/basic/testInitSingletons.js
js/src/jsemit.cpp
js/src/jsemit.h
js/src/jsinterp.cpp
js/src/jsobj.cpp
js/src/jsopcode.cpp
js/src/jsopcode.tbl
js/src/jsparse.cpp
js/src/jsparse.h
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jsxdrapi.h
js/src/methodjit/Compiler.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/testInitSingletons.js
@@ -0,0 +1,31 @@
+
+var x = [1,2.5,"three",true,false,null,[1,2,3],{a:0,b:1}];
+assertEq(String(x), "1,2.5,three,true,false,,1,2,3,[object Object]");
+assertEq(x.length, 8);
+assertEq(x[7].a, 0);
+assertEq(x[7].b, 1);
+
+var y = {a:0,a:1,a:2};
+assertEq(y.a,2);
+
+var z = {a:0,b:1,__proto__:{c:2,d:3}};
+assertEq(z.a,0);
+assertEq(z.b,1);
+assertEq(z.c,2);
+assertEq(z.d,3);
+
+function foo() {
+  var q = eval("[1,2,3]");
+  var r = eval("[1,2,3]");
+  assertEq(q === r, false);
+}
+foo();
+
+var q = {0x4fffffff: 0, 0x7fffffff: 1, 0xffffffff: 2};
+assertEq(q[1342177279], 0);
+assertEq(q[2147483647], 1);
+assertEq(q[4294967295], 2);
+
+try {
+  [1,2,3,{a:0,b:1}].foo.bar;
+} catch (e) { assertEq(e.message, "[1, 2, 3, {a:0, b:1}].foo is undefined"); }
--- a/js/src/jsemit.cpp
+++ b/js/src/jsemit.cpp
@@ -64,16 +64,17 @@
 #include "jsparse.h"
 #include "jsregexp.h"
 #include "jsscan.h"
 #include "jsscope.h"
 #include "jsscript.h"
 #include "jsautooplen.h"        // generated headers last
 #include "jsstaticcheck.h"
 
+#include "jsatominlines.h"
 #include "jsobjinlines.h"
 #include "jsscopeinlines.h"
 
 /* Allocation chunk counts, must be powers of two in general. */
 #define BYTECODE_CHUNK  256     /* code allocation increment */
 #define SRCNOTE_CHUNK   64      /* initial srcnote allocation increment */
 #define TRYNOTE_CHUNK   64      /* trynote allocation increment */
 
@@ -4535,16 +4536,120 @@ EmitEndInit(JSContext *cx, JSCodeGenerat
 #if JS_HAS_SHARP_VARS
     /* Emit an op for sharp array cleanup and decompilation. */
     if (cg->hasSharps() && count != 0)
         EMIT_UINT16_IMM_OP(JSOP_SHARPINIT, cg->sharpSlotBase);
 #endif
     return js_Emit1(cx, cg, JSOP_ENDINIT) >= 0;
 }
 
+bool
+JSParseNode::getConstantValue(JSContext *cx, bool strictChecks, Value *vp)
+{
+    switch (pn_type) {
+      case TOK_NUMBER:
+        vp->setNumber(pn_dval);
+        return true;
+      case TOK_STRING:
+        vp->setString(ATOM_TO_STRING(pn_atom));
+        return true;
+      case TOK_PRIMARY:
+        switch (pn_op) {
+          case JSOP_NULL:
+            vp->setNull();
+            return true;
+          case JSOP_FALSE:
+            vp->setBoolean(false);
+            return true;
+          case JSOP_TRUE:
+            vp->setBoolean(true);
+            return true;
+          default:
+            JS_NOT_REACHED("Unexpected node");
+            return false;
+        }
+      case TOK_RB: {
+        JS_ASSERT((pn_op == JSOP_NEWINIT) && !(pn_xflags & PNX_NONCONST));
+ 
+        JSObject *obj = NewDenseAllocatedArray(cx, pn_count);
+        if (!obj || !obj->ensureSlots(cx, pn_count))
+            return false;
+
+        unsigned idx = 0;
+        for (JSParseNode *pn = pn_head; pn; idx++, pn = pn->pn_next) {
+            Value value;
+            if (!pn->getConstantValue(cx, strictChecks, &value))
+                return false;
+            obj->setDenseArrayElement(idx, value);
+        }
+        JS_ASSERT(idx == pn_count);
+
+        vp->setObject(*obj);
+        return true;
+      }
+      case TOK_RC: {
+        JS_ASSERT((pn_op == JSOP_NEWINIT) && !(pn_xflags & PNX_NONCONST));
+
+        gc::FinalizeKind kind = GuessObjectGCKind(pn_count, false);
+        JSObject *obj = NewBuiltinClassInstance(cx, &js_ObjectClass, kind);
+        if (!obj)
+            return false;
+
+        for (JSParseNode *pn = pn_head; pn; pn = pn->pn_next) {
+            Value value;
+            if (!pn->pn_right->getConstantValue(cx, strictChecks, &value))
+                return false;
+
+            JSParseNode *pnid = pn->pn_left;
+            if (pnid->pn_type == TOK_NUMBER) {
+                Value idvalue = NumberValue(pnid->pn_dval);
+                jsid id;
+                if (idvalue.isInt32() && INT_FITS_IN_JSID(idvalue.toInt32()))
+                    id = INT_TO_JSID(idvalue.toInt32());
+                else if (!js_InternNonIntElementId(cx, obj, idvalue, &id))
+                    return false;
+                if (!obj->defineProperty(cx, id, value, NULL, NULL, JSPROP_ENUMERATE))
+                    return false;
+            } else {
+                JS_ASSERT(pnid->pn_type == TOK_NAME ||
+                          pnid->pn_type == TOK_STRING);
+                jsid id = ATOM_TO_JSID(pnid->pn_atom);
+                if (!((pnid->pn_atom == cx->runtime->atomState.protoAtom)
+                      ? js_SetPropertyHelper(cx, obj, id, 0, &value, strictChecks)
+                      : js_DefineNativeProperty(cx, obj, id, value, NULL, NULL,
+                                                JSPROP_ENUMERATE, 0, 0, NULL, 0))) {
+                    return false;
+                }
+            }
+        }
+
+        vp->setObject(*obj);
+        return true;
+      }
+      default:
+        JS_NOT_REACHED("Unexpected node");
+    }
+    return false;
+}
+
+static bool
+EmitSingletonInitialiser(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
+{
+    Value value;
+    if (!pn->getConstantValue(cx, cg->needStrictChecks(), &value))
+        return false;
+
+    JS_ASSERT(value.isObject());
+    JSObjectBox *objbox = cg->parser->newObjectBox(&value.toObject());
+    if (!objbox)
+        return false;
+
+    return EmitObjectOp(cx, objbox, JSOP_OBJECT, cg);
+}
+
 /* See the SRC_FOR source note offsetBias comments later in this file. */
 JS_STATIC_ASSERT(JSOP_NOP_LENGTH == 1);
 JS_STATIC_ASSERT(JSOP_POP_LENGTH == 1);
 
 class EmitLevelManager
 {
 private:
     JSCodeGenerator *cg;
@@ -6834,16 +6939,22 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
 
             /* Emit the usual op needed for decompilation. */
             if (!EmitEndInit(cx, cg, 1))
                 return JS_FALSE;
             break;
         }
 #endif /* JS_HAS_GENERATORS */
 
+        if (!cg->hasSharps() && !(pn->pn_xflags & PNX_NONCONST) && cg->checkSingletonContext()) {
+            if (!EmitSingletonInitialiser(cx, cg, pn))
+                return JS_FALSE;
+            break;
+        }
+
         /* Use the slower NEWINIT for arrays in scripts containing sharps. */
         if (cg->hasSharps()) {
             if (!EmitNewInit(cx, cg, JSProto_Array, pn, sharpnum))
                 return JS_FALSE;
         } else {
             ptrdiff_t off = js_EmitN(cx, cg, JSOP_NEWARRAY, 3);
             if (off < 0)
                 return JS_FALSE;
@@ -6887,16 +6998,23 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
       do_emit_object:
 #endif
 #if JS_HAS_DESTRUCTURING_SHORTHAND
         if (pn->pn_xflags & PNX_DESTRUCT) {
             ReportCompileErrorNumber(cx, CG_TS(cg), pn, JSREPORT_ERROR, JSMSG_BAD_OBJECT_INIT);
             return JS_FALSE;
         }
 #endif
+
+        if (!cg->hasSharps() && !(pn->pn_xflags & PNX_NONCONST) && cg->checkSingletonContext()) {
+            if (!EmitSingletonInitialiser(cx, cg, pn))
+                return JS_FALSE;
+            break;
+        }
+
         /*
          * Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing
          * a new object and in source order evaluating each property value and
          * adding the property to the object, without invoking latent setters.
          * We use the JSOP_NEWINIT and JSOP_INITELEM/JSOP_INITPROP bytecodes to
          * ignore setters and to avoid dup'ing and popping the object as each
          * property is added, as JSOP_SETELEM/JSOP_SETPROP would do.
          */
@@ -7059,39 +7177,20 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
         ok = EmitAtomOp(cx, pn, PN_OP(pn), cg);
         break;
 
       case TOK_NUMBER:
         ok = EmitNumberOp(cx, pn->pn_dval, cg);
         break;
 
       case TOK_REGEXP: {
-        /*
-         * If the regexp's script is one-shot and the regexp is not used in a
-         * loop, we can avoid the extra fork-on-exec costs of JSOP_REGEXP by
-         * selecting JSOP_OBJECT. Otherwise, to avoid incorrect proto, parent,
-         * and lastIndex sharing, select JSOP_REGEXP.
-         */
         JS_ASSERT(pn->pn_op == JSOP_REGEXP);
-        bool singleton = !(cg->inFunction() ? cg->fun() : cg->scopeChain()) && cg->compileAndGo();
-        if (singleton) {
-            for (JSStmtInfo *stmt = cg->topStmt; stmt; stmt = stmt->down) {
-                if (STMT_IS_LOOP(stmt)) {
-                    singleton = false;
-                    break;
-                }
-            }
-        }
-        if (singleton) {
-            ok = EmitObjectOp(cx, pn->pn_objbox, JSOP_OBJECT, cg);
-        } else {
-            ok = EmitIndexOp(cx, JSOP_REGEXP,
-                             cg->regexpList.index(pn->pn_objbox),
-                             cg);
-        }
+        ok = EmitIndexOp(cx, JSOP_REGEXP,
+                         cg->regexpList.index(pn->pn_objbox),
+                         cg);
         break;
       }
 
 #if JS_HAS_XML_SUPPORT
       case TOK_ANYNAME:
 #endif
       case TOK_PRIMARY:
         if (js_Emit1(cx, cg, PN_OP(pn)) < 0)
--- a/js/src/jsemit.h
+++ b/js/src/jsemit.h
@@ -253,16 +253,21 @@ struct JSStmtInfo {
 
 /*
  * The function or a function that encloses it may define new local names
  * at runtime through means other than calling eval.
  */
 #define TCF_FUN_MIGHT_ALIAS_LOCALS  0x4000000
 
 /*
+ * The script contains singleton initialiser JSOP_OBJECT.
+ */
+#define TCF_HAS_SINGLETONS       0x8000000
+
+/*
  * Flags to check for return; vs. return expr; in a function.
  */
 #define TCF_RETURN_FLAGS        (TCF_RETURN_EXPR | TCF_RETURN_VOID)
 
 /*
  * Sticky deoptimization flags to propagate from FunctionBody.
  */
 #define TCF_FUN_FLAGS           (TCF_FUN_SETS_OUTER_NAME |                    \
@@ -646,16 +651,27 @@ struct JSCodeGenerator : public JSTreeCo
 
     uintN sharpSlots() {
         return hasSharps() ? SHARP_NSLOTS : 0;
     }
 
     bool compilingForEval() { return !!(flags & TCF_COMPILE_FOR_EVAL); }
 
     bool shouldNoteClosedName(JSParseNode *pn);
+
+    bool checkSingletonContext() {
+        if (!compileAndGo() || inFunction())
+            return false;
+        for (JSStmtInfo *stmt = topStmt; stmt; stmt = stmt->down) {
+            if (STMT_IS_LOOP(stmt))
+                return false;
+        }
+        flags |= TCF_HAS_SINGLETONS;
+        return true;
+    }
 };
 
 #define CG_TS(cg)               TS((cg)->parser)
 
 #define CG_BASE(cg)             ((cg)->current->base)
 #define CG_LIMIT(cg)            ((cg)->current->limit)
 #define CG_NEXT(cg)             ((cg)->current->next)
 #define CG_CODE(cg,offset)      (CG_BASE(cg) + (offset))
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -4964,17 +4964,16 @@ BEGIN_CASE(JSOP_STRING)
     PUSH_STRING(ATOM_TO_STRING(atom));
 }
 END_CASE(JSOP_STRING)
 
 BEGIN_CASE(JSOP_OBJECT)
 {
     JSObject *obj;
     LOAD_OBJECT(0, obj);
-    /* Only XML and RegExp objects are emitted. */
     PUSH_OBJECT(*obj);
 }
 END_CASE(JSOP_OBJECT)
 
 BEGIN_CASE(JSOP_REGEXP)
 {
     /*
      * Push a regexp object cloned from the regexp literal object mapped by the
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1039,16 +1039,17 @@ EvalCacheLookup(JSContext *cx, JSLinearS
 
     EVAL_CACHE_METER(probe);
     JSVersion version = cx->findVersion();
     JSScript *script;
     while ((script = *scriptp) != NULL) {
         if (script->savedCallerFun &&
             script->staticLevel == staticLevel &&
             script->version == version &&
+            !script->hasSingletons &&
             (script->principals == principals ||
              (principals->subsume(principals, script->principals) &&
               script->principals->subsume(script->principals, principals)))) {
             /*
              * Get the prior (cache-filling) eval's saved caller function.
              * See Compiler::compileScript in jsparse.cpp.
              */
             JSFunction *fun = script->getFunction(0);
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -4234,22 +4234,23 @@ Decompile(SprintStack *ss, jsbytecode *p
 
               case JSOP_CALLEE:
                 JS_ASSERT(jp->fun && jp->fun->atom);
                 todo = SprintString(&ss->sprinter, ATOM_TO_STRING(jp->fun->atom));
                 break;
 
               case JSOP_OBJECT:
                 LOAD_OBJECT(0);
-                LOCAL_ASSERT(obj->getClass() == &js_RegExpClass);
-                goto do_regexp;
+                str = js_ValueToSource(cx, ObjectValue(*obj));
+                if (!str)
+                    return NULL;
+                goto sprint_string;
 
               case JSOP_REGEXP:
                 GET_REGEXP_FROM_BYTECODE(jp->script, pc, 0, obj);
-              do_regexp:
                 if (!js_regexp_toString(cx, obj, Valueify(&val)))
                     return NULL;
                 str = JSVAL_TO_STRING(val);
                 goto sprint_string;
 
               case JSOP_TABLESWITCH:
               case JSOP_TABLESWITCHX:
               {
--- a/js/src/jsopcode.tbl
+++ b/js/src/jsopcode.tbl
@@ -219,17 +219,17 @@ OPDEF(JSOP_SETCALL,   74, "setcall",    
  */
 OPDEF(JSOP_ITER,      75, "iter",       NULL,         2,  1,  1,  0,  JOF_UINT8)
 OPDEF(JSOP_MOREITER,  76, "moreiter",   NULL,         1,  1,  2,  0,  JOF_BYTE)
 OPDEF(JSOP_ENDITER,   77, "enditer",    NULL,         1,  1,  0,  0,  JOF_BYTE)
 
 OPDEF(JSOP_FUNAPPLY,  78, "funapply",   NULL,         3, -1,  1, 18,  JOF_UINT16|JOF_INVOKE)
 OPDEF(JSOP_SWAP,      79, "swap",       NULL,         1,  2,  2,  0,  JOF_BYTE)
 
-/* Push object literal. */
+/* Push object literal: either an XML object or initialiser object. */
 OPDEF(JSOP_OBJECT,    80, "object",     NULL,         3,  0,  1, 19,  JOF_OBJECT)
 
 /* Pop value and discard it. */
 OPDEF(JSOP_POP,       81, "pop",        NULL,         1,  1,  0,  2,  JOF_BYTE)
 
 /* Convert value to number, for unary +. */
 OPDEF(JSOP_NEW,       82, js_new_str,   NULL,         3, -1,  1, 17,  JOF_UINT16|JOF_INVOKE)
 
--- a/js/src/jsparse.cpp
+++ b/js/src/jsparse.cpp
@@ -8176,16 +8176,40 @@ BlockIdInScope(uintN blockid, JSTreeCont
     for (JSStmtInfo *stmt = tc->topScopeStmt; stmt; stmt = stmt->downScope) {
         if (stmt->blockid == blockid)
             return true;
     }
     return false;
 }
 #endif
 
+bool
+JSParseNode::isConstant()
+{
+    switch (pn_type) {
+      case TOK_NUMBER:
+      case TOK_STRING:
+        return true;
+      case TOK_PRIMARY:
+        switch (pn_op) {
+          case JSOP_NULL:
+          case JSOP_FALSE:
+          case JSOP_TRUE:
+            return true;
+          default:
+            return false;
+        }
+      case TOK_RB:
+      case TOK_RC:
+        return (pn_op == JSOP_NEWINIT) && !(pn_xflags & PNX_NONCONST);
+      default:
+        return false;
+    }
+}
+
 JSParseNode *
 Parser::primaryExpr(TokenKind tt, JSBool afterDot)
 {
     JSParseNode *pn, *pn2, *pn3;
     JSOp op;
 
     JS_CHECK_RECURSION(context, return NULL);
 
@@ -8237,19 +8261,21 @@ Parser::primaryExpr(TokenKind tt, JSBool
                     pn->pn_xflags |= PNX_ENDCOMMA;
                     break;
                 }
 
                 if (tt == TOK_COMMA) {
                     /* So CURRENT_TOKEN gets TOK_COMMA and not TOK_LB. */
                     tokenStream.matchToken(TOK_COMMA);
                     pn2 = NullaryNode::create(tc);
-                    pn->pn_xflags |= PNX_HOLEY;
+                    pn->pn_xflags |= PNX_HOLEY | PNX_NONCONST;
                 } else {
                     pn2 = assignExpr();
+                    if (pn2 && !pn2->isConstant())
+                        pn->pn_xflags |= PNX_NONCONST;
                 }
                 if (!pn2)
                     return NULL;
                 pn->append(pn2);
 
                 if (tt != TOK_COMMA) {
                     /* If we didn't already match TOK_COMMA in above case. */
                     if (!tokenStream.matchToken(TOK_COMMA))
@@ -8404,16 +8430,18 @@ Parser::primaryExpr(TokenKind tt, JSBool
                         } else {
                             atom = NULL; /* for the compiler */
                         }
                     } else {
                         tokenStream.ungetToken();
                         goto property_name;
                     }
 
+                    pn->pn_xflags |= PNX_NONCONST;
+
                     /* NB: Getter function in { get x(){} } is unnamed. */
                     pn2 = functionDef(NULL, op == JSOP_SETTER ? SETTER : GETTER, JSFUN_LAMBDA);
                     pn2 = JSParseNode::newBinaryOrAppend(TOK_COLON, op, pn3, pn2, tc);
                     goto skip;
                 }
               property_name:
               case TOK_STRING:
                 atom = tokenStream.currentToken().t_atom;
@@ -8428,31 +8456,33 @@ Parser::primaryExpr(TokenKind tt, JSBool
                 reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_PROP_ID);
                 return NULL;
             }
 
             op = JSOP_INITPROP;
             tt = tokenStream.getToken();
             if (tt == TOK_COLON) {
                 pnval = assignExpr();
+                if (pnval && !pnval->isConstant())
+                    pn->pn_xflags |= PNX_NONCONST;
             } else {
 #if JS_HAS_DESTRUCTURING_SHORTHAND
                 if (tt != TOK_COMMA && tt != TOK_RC) {
 #endif
                     reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_COLON_AFTER_ID);
                     return NULL;
 #if JS_HAS_DESTRUCTURING_SHORTHAND
                 }
 
                 /*
                  * Support, e.g., |var {x, y} = o| as destructuring shorthand
                  * for |var {x: x, y: y} = o|, per proposed JS2/ES4 for JS1.8.
                  */
                 tokenStream.ungetToken();
-                pn->pn_xflags |= PNX_DESTRUCT;
+                pn->pn_xflags |= PNX_DESTRUCT | PNX_NONCONST;
                 pnval = pn3;
                 if (pnval->pn_type == TOK_NAME) {
                     pnval->pn_arity = PN_NAME;
                     ((NameNode *)pnval)->initCommon(tc);
                 }
 #endif
             }
 
--- a/js/src/jsparse.h
+++ b/js/src/jsparse.h
@@ -508,16 +508,17 @@ public:
 #define PNX_SETCALL    0x100            /* call expression in lvalue context */
 #define PNX_DESTRUCT   0x200            /* destructuring special cases:
                                            1. shorthand syntax used, at present
                                               object destructuring ({x,y}) only;
                                            2. code evaluating destructuring
                                               arguments occurs before function
                                               body */
 #define PNX_HOLEY      0x400            /* array initialiser has holes */
+#define PNX_NONCONST   0x800            /* initialiser has non-constants */
 
     uintN frameLevel() const {
         JS_ASSERT(pn_arity == PN_FUNC || pn_arity == PN_NAME);
         return pn_cookie.level();
     }
 
     uintN frameSlot() const {
         JS_ASSERT(pn_arity == PN_FUNC || pn_arity == PN_NAME);
@@ -655,16 +656,19 @@ public:
     }
 
     void append(JSParseNode *pn) {
         JS_ASSERT(pn_arity == PN_LIST);
         *pn_tail = pn;
         pn_tail = &pn->pn_next;
         pn_count++;
     }
+
+    bool getConstantValue(JSContext *cx, bool strictChecks, js::Value *vp);
+    inline bool isConstant();
 };
 
 namespace js {
 
 struct NullaryNode : public JSParseNode {
     static inline NullaryNode *create(JSTreeContext *tc) {
         return (NullaryNode *)JSParseNode::create(PN_NULLARY, tc);
     }
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -76,17 +76,16 @@ using namespace js::gc;
 #if JS_HAS_XDR
 
 enum ScriptBits {
     NoScriptRval,
     SavedCallerFun,
     HasSharps,
     StrictModeCode,
     UsesEval,
-    CompileAndGo,
     UsesArguments
 };
 
 JSBool
 js_XDRScript(JSXDRState *xdr, JSScript **scriptp, JSBool *hasMagic)
 {
     JSContext *cx;
     JSScript *script, *oldscript;
@@ -168,22 +167,22 @@ js_XDRScript(JSXDRState *xdr, JSScript *
         if (script->noScriptRval)
             scriptBits |= (1 << NoScriptRval);
         if (script->savedCallerFun)
             scriptBits |= (1 << SavedCallerFun);
         if (script->hasSharps)
             scriptBits |= (1 << HasSharps);
         if (script->strictModeCode)
             scriptBits |= (1 << StrictModeCode);
-        if (script->compileAndGo)
-            scriptBits |= (1 << CompileAndGo);
         if (script->usesEval)
             scriptBits |= (1 << UsesEval);
         if (script->usesArguments)
             scriptBits |= (1 << UsesArguments);
+        JS_ASSERT(!script->compileAndGo);
+        JS_ASSERT(!script->hasSingletons);
     }
 
     if (!JS_XDRUint32(xdr, &prologLength))
         return JS_FALSE;
     if (!JS_XDRUint32(xdr, &version))
         return JS_FALSE;
 
     /*
@@ -233,18 +232,16 @@ js_XDRScript(JSXDRState *xdr, JSScript *
         if (scriptBits & (1 << NoScriptRval))
             script->noScriptRval = true;
         if (scriptBits & (1 << SavedCallerFun))
             script->savedCallerFun = true;
         if (scriptBits & (1 << HasSharps))
             script->hasSharps = true;
         if (scriptBits & (1 << StrictModeCode))
             script->strictModeCode = true;
-        if (scriptBits & (1 << CompileAndGo))
-            script->compileAndGo = true;
         if (scriptBits & (1 << UsesEval))
             script->usesEval = true;
         if (scriptBits & (1 << UsesArguments))
             script->usesArguments = true;
     }
 
     /*
      * Control hereafter must goto error on failure, in order for the
@@ -1110,16 +1107,18 @@ JSScript::NewScriptFromCG(JSContext *cx,
     if (cg->flags & TCF_STRICT_MODE_CODE)
         script->strictModeCode = true;
     if (cg->flags & TCF_COMPILE_N_GO)
         script->compileAndGo = true;
     if (cg->callsEval())
         script->usesEval = true;
     if (cg->flags & TCF_FUN_USES_ARGUMENTS)
         script->usesArguments = true;
+    if (cg->flags & TCF_HAS_SINGLETONS)
+        script->hasSingletons = true;
 
     if (cg->upvarList.count != 0) {
         JS_ASSERT(cg->upvarList.count <= cg->upvarMap.length);
         memcpy(script->upvars()->vector, cg->upvarMap.vector,
                cg->upvarList.count * sizeof(uint32));
         cg->upvarList.clear();
         cx->free(cg->upvarMap.vector);
         cg->upvarMap.vector = NULL;
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -235,16 +235,17 @@ struct JSScript {
     bool            hasSharps:1;      /* script uses sharp variables */
     bool            strictModeCode:1; /* code is in strict mode */
     bool            compileAndGo:1;   /* script was compiled with TCF_COMPILE_N_GO */
     bool            usesEval:1;       /* script uses eval() */
     bool            usesArguments:1;  /* script uses arguments */
     bool            warnedAboutTwoArgumentEval:1; /* have warned about use of
                                                      obsolete eval(s, o) in
                                                      this script */
+    bool            hasSingletons:1;  /* script has singleton objects */
 #ifdef JS_METHODJIT
     bool            debugMode:1;      /* script was compiled in debug mode */
     bool            singleStepMode:1; /* compile script in single-step mode */
 #endif
 
     jsbytecode      *main;      /* main entry point, after predef'ing prolog */
     JSAtomMap       atomMap;    /* maps immediate index to literal struct */
     JSCompartment   *compartment; /* compartment the script was compiled for */
--- a/js/src/jsxdrapi.h
+++ b/js/src/jsxdrapi.h
@@ -200,17 +200,17 @@ JS_XDRFindClassById(JSXDRState *xdr, uin
  * Bytecode version number. Increment the subtrahend whenever JS bytecode
  * changes incompatibly.
  *
  * This version number should be XDR'ed once near the front of any file or
  * larger storage unit containing XDR'ed bytecode and other data, and checked
  * before deserialization of bytecode.  If the saved version does not match
  * the current version, abort deserialization and invalidate the file.
  */
-#define JSXDR_BYTECODE_VERSION      (0xb973c0de - 78)
+#define JSXDR_BYTECODE_VERSION      (0xb973c0de - 79)
 
 /*
  * Library-private functions.
  */
 extern JSBool
 js_XDRAtom(JSXDRState *xdr, JSAtom **atomp);
 
 JS_END_EXTERN_C
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -1914,16 +1914,25 @@ mjit::Compiler::generateMethod()
             prepareStubCall(Uses(0));
             masm.move(ImmPtr(regex), Registers::ArgReg1);
             INLINE_STUBCALL(stubs::RegExp);
             frame.takeReg(Registers::ReturnReg);
             frame.pushTypedPayload(JSVAL_TYPE_OBJECT, Registers::ReturnReg);
           }
           END_CASE(JSOP_REGEXP)
 
+          BEGIN_CASE(JSOP_OBJECT)
+          {
+            JSObject *object = script->getObject(fullAtomIndex(PC));
+            RegisterID reg = frame.allocReg();
+            masm.move(ImmPtr(object), reg);
+            frame.pushTypedPayload(JSVAL_TYPE_OBJECT, reg);
+          }
+          END_CASE(JSOP_OBJECT)
+
           BEGIN_CASE(JSOP_CALLPROP)
             if (!jsop_callprop(script->getAtom(fullAtomIndex(PC))))
                 return Compile_Error;
           END_CASE(JSOP_CALLPROP)
 
           BEGIN_CASE(JSOP_GETUPVAR)
           BEGIN_CASE(JSOP_CALLUPVAR)
           {