Avoid staying in Interpret() after recording (bug 593532, r=dmandelin).
☠☠ backed out by 15638b838d43 ☠ ☠
authorDavid Anderson <danderson@mozilla.com>
Tue, 07 Sep 2010 22:52:15 -0700
changeset 53604 7219df6c126c7ef0dff4021cfddafde05e725344
parent 53603 6a6d76a7670b5a3e9a0dea7f4893da8436ef75a0
child 53605 15638b838d43f7f18150fd90f796c3f328dc4b2f
push idunknown
push userunknown
push dateunknown
reviewersdmandelin
bugs593532
milestone2.0b6pre
Avoid staying in Interpret() after recording (bug 593532, r=dmandelin).
js/src/jscntxt.h
js/src/jsinterp.cpp
js/src/jsinterp.h
js/src/jstracer.cpp
js/src/methodjit/InvokeHelpers.cpp
js/src/trace-test/lib/prolog.js
js/src/trace-test/tests/basic/testBug504520.js
js/src/trace-test/tests/basic/testBug504520Harder.js
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -2045,17 +2045,17 @@ struct JSContext
 
     bool hasfp() {
         JS_ASSERT_IF(regs, regs->fp);
         return !!regs;
     }
 
   public:
     friend class js::StackSpace;
-    friend bool js::Interpret(JSContext *, JSStackFrame *, uintN);
+    friend bool js::Interpret(JSContext *, JSStackFrame *, uintN, uintN);
 
     /* 'regs' must only be changed by calling this function. */
     void setCurrentRegs(JSFrameRegs *regs) {
         this->regs = regs;
     }
 
     /* Temporary arena pool used while compiling and decompiling. */
     JSArenaPool         tempPool;
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -1953,17 +1953,17 @@ IteratorNext(JSContext *cx, JSObject *it
     }
     return js_IteratorNext(cx, iterobj, rval);
 }
 
 
 namespace js {
 
 JS_REQUIRES_STACK bool
-Interpret(JSContext *cx, JSStackFrame *entryFrame, uintN inlineCallCount)
+Interpret(JSContext *cx, JSStackFrame *entryFrame, uintN inlineCallCount, uintN interpFlags)
 {
 #ifdef MOZ_TRACEVIS
     TraceVisStateObj tvso(cx, S_INTERP);
 #endif
     JSAutoResolveFlags rf(cx, JSRESOLVE_INFER);
 
 # ifdef DEBUG
     /*
@@ -2110,18 +2110,17 @@ Interpret(JSContext *cx, JSStackFrame *e
      * GET_FULL_INDEX macros below. As a register we use a pointer based on
      * the atom map to turn frequently executed LOAD_ATOM into simple array
      * access. For less frequent object and regexp loads we have to recover
      * the segment from atoms pointer first.
      */
     JSAtom **atoms = script->atomMap.vector;
 
 #ifdef JS_METHODJIT
-    bool leaveOnTracePoint = (fp->flags & JSFRAME_BAILING) && !fp->hasIMacroPC();
-# define CLEAR_LEAVE_ON_TRACE_POINT() ((void) (leaveOnTracePoint = false))
+# define CLEAR_LEAVE_ON_TRACE_POINT() ((void) (leaveOnSafePoint = false))
 #else
 # define CLEAR_LEAVE_ON_TRACE_POINT() ((void) 0)
 #endif
 
 #define LOAD_ATOM(PCOFF, atom)                                                \
     JS_BEGIN_MACRO                                                            \
         JS_ASSERT(fp->hasIMacroPC()                                           \
                   ? atoms == COMMON_ATOMS_START(&rt->atomState) &&            \
@@ -2207,26 +2206,28 @@ Interpret(JSContext *cx, JSStackFrame *e
             goto error;                                                       \
     JS_END_MACRO
 
 #ifndef TRACE_RECORDER
 #define TRACE_RECORDER(cx) (false)
 #endif
 
 #ifdef JS_METHODJIT
-# define LEAVE_ON_TRACE_POINT()                                               \
+# define LEAVE_ON_SAFE_POINT()                                               \
     do {                                                                      \
-        if (leaveOnTracePoint && !fp->hasIMacroPC() &&                        \
+        JS_ASSERT_IF(leaveOnSafePoint, !TRACE_RECORDER(cx));                  \
+        if (leaveOnSafePoint && !fp->hasIMacroPC() &&                         \
             script->nmap && script->nmap[regs.pc - script->code]) {           \
+            JS_ASSERT(!TRACE_RECORDER(cx));                                   \
             interpReturnOK = true;                                            \
             goto stop_recording;                                              \
         }                                                                     \
     } while (0)
 #else
-# define LEAVE_ON_TRACE_POINT() /* nop */
+# define LEAVE_ON_SAFE_POINT() /* nop */
 #endif
 
 #define BRANCH(n)                                                             \
     JS_BEGIN_MACRO                                                            \
         regs.pc += (n);                                                       \
         op = (JSOp) *regs.pc;                                                 \
         if ((n) <= 0) {                                                       \
             CHECK_BRANCH();                                                   \
@@ -2237,25 +2238,24 @@ Interpret(JSContext *cx, JSStackFrame *e
                 } else {                                                      \
                     op = (JSOp) *++regs.pc;                                   \
                 }                                                             \
             } else if (op == JSOP_TRACE) {                                    \
                 MONITOR_BRANCH();                                             \
                 op = (JSOp) *regs.pc;                                         \
             }                                                                 \
         }                                                                     \
-        LEAVE_ON_TRACE_POINT();                                               \
+        LEAVE_ON_SAFE_POINT();                                                \
         DO_OP();                                                              \
     JS_END_MACRO
 
     MUST_FLOW_THROUGH("exit");
 
-#ifdef JS_TRACER
-    bool wasRecording = !!(fp->flags & JSFRAME_RECORDING);
-    bool wasImacroRun = fp->hasIMacroPC() && !TRACE_RECORDER(cx);
+#if defined(JS_TRACER) && defined(JS_METHODJIT)
+    bool leaveOnSafePoint = !!(interpFlags & JSINTERP_SAFEPOINT);
 #endif
 
     ++cx->interpLevel;
 
     /*
      * Optimized Get and SetVersion for proper script language versioning.
      *
      * If any native method or a Class or ObjectOps hook calls js_SetVersion
@@ -2307,17 +2307,17 @@ Interpret(JSContext *cx, JSStackFrame *e
 #endif
 
 #ifdef JS_TRACER
     /*
      * The method JIT may have already initiated a recording, in which case
      * there should already be a valid recorder. Otherwise...
      * we cannot reenter the interpreter while recording.
      */
-    if (wasRecording) {
+    if (interpFlags & JSINTERP_RECORD) {
         JS_ASSERT(TRACE_RECORDER(cx));
         ENABLE_INTERRUPTS();
     } else if (TRACE_RECORDER(cx)) {
         AbortRecording(cx, "attempt to reenter interpreter while recording");
     }
 
     if (fp->hasIMacroPC())
         atoms = COMMON_ATOMS_START(&rt->atomState);
@@ -2402,16 +2402,31 @@ Interpret(JSContext *cx, JSStackFrame *e
             }
             moreInterrupts = true;
         }
 
 #ifdef JS_TRACER
         if (TraceRecorder* tr = TRACE_RECORDER(cx)) {
             AbortableRecordingStatus status = tr->monitorRecording(op);
             JS_ASSERT_IF(cx->throwing, status == ARECORD_ERROR);
+
+            if (interpFlags & (JSINTERP_RECORD | JSINTERP_SAFEPOINT)) {
+                switch (status) {
+                  case ARECORD_IMACRO_ABORTED:
+                  case ARECORD_ABORTED:
+                  case ARECORD_COMPLETED:
+                  case ARECORD_STOP:
+                    leaveOnSafePoint = true;
+                    LEAVE_ON_SAFE_POINT();
+                    break;
+                  default:
+                    break;
+                }
+            }
+
             switch (status) {
               case ARECORD_CONTINUE:
                 moreInterrupts = true;
                 break;
               case ARECORD_IMACRO:
               case ARECORD_IMACRO_ABORTED:
                 atoms = COMMON_ATOMS_START(&rt->atomState);
                 op = JSOp(*regs.pc);
@@ -2425,19 +2440,16 @@ Interpret(JSContext *cx, JSStackFrame *e
               case ARECORD_ABORTED:
               case ARECORD_COMPLETED:
                 break;
               case ARECORD_STOP:
                 /* A 'stop' error should have already aborted recording. */
               default:
                 JS_NOT_REACHED("Bad recording status");
             }
-        } else if (wasRecording) {
-            interpReturnOK = true;
-            goto stop_recording;
         }
 #endif /* !JS_TRACER */
 
 #if JS_THREADED_INTERP
 #ifdef MOZ_TRACEVIS
         if (!moreInterrupts)
             ExitTraceVisState(cx, R_ABORT);
 #endif
@@ -2458,17 +2470,17 @@ ADD_EMPTY_CASE(JSOP_TRY)
 ADD_EMPTY_CASE(JSOP_STARTXML)
 ADD_EMPTY_CASE(JSOP_STARTXMLEXPR)
 #endif
 ADD_EMPTY_CASE(JSOP_UNUSED180)
 END_EMPTY_CASES
 
 BEGIN_CASE(JSOP_TRACE)
 #ifdef JS_METHODJIT
-    LEAVE_ON_TRACE_POINT();
+    LEAVE_ON_SAFE_POINT();
 #endif
 END_CASE(JSOP_TRACE)
 
 /* ADD_EMPTY_CASE is not used here as JSOP_LINENO_LENGTH == 3. */
 BEGIN_CASE(JSOP_LINENO)
 BEGIN_CASE(JSOP_DEFUPVAR)
 END_CASE(JSOP_DEFUPVAR)
 
@@ -2551,27 +2563,17 @@ BEGIN_CASE(JSOP_STOP)
          * If we are at the end of an imacro, return to its caller in the
          * current frame.
          */
         JS_ASSERT(op == JSOP_STOP);
         JS_ASSERT((uintN)(regs.sp - fp->slots()) <= script->nslots);
         jsbytecode *imacpc = fp->getIMacroPC();
         regs.pc = imacpc + js_CodeSpec[*imacpc].length;
         fp->clearIMacroPC();
-# ifdef JS_METHODJIT
-        if ((wasImacroRun || wasRecording) && !TRACE_RECORDER(cx)) {
-            if (script->nmap && script->nmap[regs.pc - script->code]) {
-                interpReturnOK = true;
-                goto stop_recording;
-            }
-            leaveOnTracePoint = true;
-        }
-# endif
-        JS_ASSERT_IF(!(op == JSOP_STOP || (wasImacroRun && op == JSOP_IMACOP)),
-                     !(fp->flags & JSFRAME_RECORDING));
+        LEAVE_ON_SAFE_POINT();
         atoms = script->atomMap.vector;
         op = JSOp(*regs.pc);
         DO_OP();
     }
 #endif
 
     JS_ASSERT(regs.sp == fp->base());
     if ((fp->flags & JSFRAME_CONSTRUCTING) && fp->getReturnValue().isPrimitive()) {
@@ -6863,17 +6865,17 @@ END_CASE(JSOP_ARRAYPUSH)
      * frame pc.
      */
     JS_ASSERT(entryFrame == fp);
     JS_ASSERT(cx->regs == &regs);
     *prevContextRegs = regs;
     cx->setCurrentRegs(prevContextRegs);
 
 #ifdef JS_TRACER
-    JS_ASSERT_IF(interpReturnOK && wasRecording, !TRACE_RECORDER(cx));
+    JS_ASSERT_IF(interpReturnOK && (interpFlags & JSINTERP_RECORD), !TRACE_RECORDER(cx));
     if (TRACE_RECORDER(cx))
         AbortRecording(cx, "recording out of Interpret");
 #endif
 
     JS_ASSERT_IF(!fp->isGenerator(), !fp->hasBlockChain());
     JS_ASSERT_IF(!fp->isGenerator(), !js_IsActiveWithOrBlock(cx, fp->getScopeChain(), 0));
 
     /* Undo the remaining effects committed on entry to Interpret. */
--- a/js/src/jsinterp.h
+++ b/js/src/jsinterp.h
@@ -62,25 +62,29 @@ enum JSFrameFlags {
     JSFRAME_OVERRIDE_ARGS      =   0x02, /* overridden arguments local variable */
     JSFRAME_ASSIGNING          =   0x04, /* a complex (not simplex JOF_ASSIGNING) op
                                            is currently assigning to a property */
     JSFRAME_DEBUGGER           =   0x08, /* frame for JS_EvaluateInStackFrame */
     JSFRAME_EVAL               =   0x10, /* frame for obj_eval */
     JSFRAME_FLOATING_GENERATOR =   0x20, /* frame copy stored in a generator obj */
     JSFRAME_YIELDING           =   0x40, /* js_Interpret dispatched JSOP_YIELD */
     JSFRAME_GENERATOR          =   0x80, /* frame belongs to generator-iterator */
-    JSFRAME_BAILING            =  0x100, /* walking out of a method JIT'd frame */
-    JSFRAME_RECORDING          =  0x200, /* recording a trace */
-    JSFRAME_BAILED_AT_RETURN   =  0x400, /* bailed at JSOP_RETURN */
-    JSFRAME_DUMMY              =  0x800, /* frame is a dummy frame */
-    JSFRAME_IN_IMACRO          = 0x1000, /* frame has imacpc value available */
+    JSFRAME_BAILED_AT_RETURN   =  0x100, /* bailed at JSOP_RETURN */
+    JSFRAME_DUMMY              =  0x200, /* frame is a dummy frame */
+    JSFRAME_IN_IMACRO          =  0x400, /* frame has imacpc value available */
 	
     JSFRAME_SPECIAL            = JSFRAME_DEBUGGER | JSFRAME_EVAL
 };
 
+/* Flags to toggle Interpret() execution. */
+enum JSInterpFlags {
+    JSINTERP_RECORD         =   0x01, /* interpreter has been started to record/run traces */
+    JSINTERP_SAFEPOINT      =   0x02  /* interpreter should leave on a method JIT safe point */
+};
+
 namespace js { namespace mjit {
     class Compiler;
     class InlineFrameAssembler;
 } }
 
 /*
  * JS stack frame, may be allocated on the C stack by native callers.  Always
  * allocated on cx->stackPool for calls from the interpreter to an interpreted
@@ -752,17 +756,17 @@ extern JS_FORCES_STACK bool
 Execute(JSContext *cx, JSObject *chain, JSScript *script,
         JSStackFrame *down, uintN flags, Value *result);
 
 /*
  * Execute the caller-initialized frame for a user-defined script or function
  * pointed to by cx->fp until completion or error.
  */
 extern JS_REQUIRES_STACK bool
-Interpret(JSContext *cx, JSStackFrame *stopFp, uintN inlineCallCount = 0);
+Interpret(JSContext *cx, JSStackFrame *stopFp, uintN inlineCallCount = 0, uintN interpFlags = 0);
 
 extern JS_REQUIRES_STACK bool
 RunScript(JSContext *cx, JSScript *script, JSFunction *fun, JSObject *scopeChain);
 
 #define JSPROP_INITIALIZER 0x100   /* NB: Not a valid property attribute. */
 
 extern bool
 CheckRedeclaration(JSContext *cx, JSObject *obj, jsid id, uintN attrs,
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -290,26 +290,26 @@ ValueToTypeChar(const Value &v)
 
 
 /* Blacklist parameters. */
 
 /*
  * Number of iterations of a loop where we start tracing.  That is, we don't
  * start tracing until the beginning of the HOTLOOP-th iteration.
  */
-#define HOTLOOP 4
+#define HOTLOOP 8
 
 /* Attempt recording this many times before blacklisting permanently. */
 #define BL_ATTEMPTS 2
 
 /* Skip this many hits before attempting recording again, after an aborted attempt. */
 #define BL_BACKOFF 32
 
 /* Minimum number of times a loop must execute, or else it is blacklisted. */
-#define MIN_LOOP_ITERS 2
+#define MIN_LOOP_ITERS 8
 
 /* Number of times we wait to exit on a side exit before we try to extend the tree. */
 #define HOTEXIT 1
 
 /* Number of times we try to extend the tree along a side exit. */
 #define MAXEXIT 3
 
 /* Maximum number of peer trees allowed. */
@@ -16217,20 +16217,17 @@ MonitorTracePoint(JSContext* cx, uintN& 
         return TPA_Nothing;
     if (!RecordTree(cx, tree->first, NULL, 0, globalSlots))
         return TPA_Nothing;
 
   interpret:
     JS_ASSERT(TRACE_RECORDER(cx));
 
     /* Locked and loaded with a recorder. Ask the interperter to go run some code. */
-    fp->flags |= JSFRAME_RECORDING;
-    if (!Interpret(cx, fp, inlineCallCount))
+    if (!Interpret(cx, fp, inlineCallCount, JSINTERP_RECORD))
         return TPA_Error;
 
-    fp->flags &= ~JSFRAME_RECORDING;
-
     return TPA_RanStuff;
 }
 
 #endif
 
 } /* namespace js */
--- a/js/src/methodjit/InvokeHelpers.cpp
+++ b/js/src/methodjit/InvokeHelpers.cpp
@@ -776,19 +776,17 @@ PartialInterpret(VMFrame &f)
 {
     JSContext *cx = f.cx;
     JSStackFrame *fp = cx->fp();
 
     JS_ASSERT(fp->hasIMacroPC() || !fp->getScript()->nmap ||
               !fp->getScript()->nmap[cx->regs->pc - fp->getScript()->code]);
 
     JSBool ok = JS_TRUE;
-    fp->flags |= JSFRAME_BAILING;
-    ok = Interpret(cx, fp);
-    fp->flags &= ~JSFRAME_BAILING;
+    ok = Interpret(cx, fp, 0, JSINTERP_SAFEPOINT);
 
     f.fp() = cx->fp();
 
     return ok;
 }
 
 JS_STATIC_ASSERT(JSOP_NOP == 0);
 
@@ -802,41 +800,41 @@ FrameIsFinished(JSContext *cx)
         ? op
         : JSOP_NOP;
 }
 
 static bool
 RemoveExcessFrames(VMFrame &f, JSStackFrame *entryFrame)
 {
     JSContext *cx = f.cx;
-    while (cx->fp() != entryFrame) {
+    while (cx->fp() != entryFrame || entryFrame->hasIMacroPC()) {
         JSStackFrame *fp = cx->fp();
-        fp->flags &= ~JSFRAME_RECORDING;
 
         if (AtSafePoint(cx)) {
             JSScript *script = fp->getScript();
             if (!JaegerShotAtSafePoint(cx, script->nmap[cx->regs->pc - script->code])) {
                 if (!SwallowErrors(f, entryFrame))
                     return false;
 
                 /* Could be anywhere - restart outer loop. */
                 continue;
             }
             InlineReturn(f, JS_TRUE);
             AdvanceReturnPC(cx);
         } else {
             if (!PartialInterpret(f)) {
                 if (!SwallowErrors(f, entryFrame))
                     return false;
-            } else {
+            } else if (cx->fp() != entryFrame) {
                 /*
                  * Partial interpret could have dropped us anywhere. Deduce the
                  * edge case: at a RETURN, needing to pop a frame.
                  */
-                if (!cx->fp()->hasIMacroPC() && FrameIsFinished(cx)) {
+                JS_ASSERT(!cx->fp()->hasIMacroPC());
+                if (FrameIsFinished(cx)) {
                     JSOp op = JSOp(*cx->regs->pc);
                     if (op == JSOP_RETURN && !(cx->fp()->flags & JSFRAME_BAILED_AT_RETURN))
                         fp->setReturnValue(f.regs.sp[-1]);
                     InlineReturn(f, JS_TRUE);
                     AdvanceReturnPC(cx);
                 }
             }
         }
@@ -944,52 +942,43 @@ RunTracer(VMFrame &f)
      * moves |oldFp->rval| into the scripted return registers.
      */
 
   restart:
     /* Step 1. Initial removal of excess frames. */
     if (!RemoveExcessFrames(f, entryFrame))
         THROWV(NULL);
 
-    /* Step 2. If there's an imacro on the entry frame, remove it. */
-    entryFrame->flags &= ~JSFRAME_RECORDING;
-    while (entryFrame->hasIMacroPC()) {
-        if (!PartialInterpret(f)) {
-            if (!SwallowErrors(f, entryFrame))
-                THROWV(NULL);
-        }
+    /* IMacros are guaranteed to have been removed by now. */
+    JS_ASSERT(!entryFrame->hasIMacroPC());
 
-        /* After partial interpreting, we could have more frames again. */
-        goto restart;
-    }
-
-    /* Step 3.1. If entryFrame is at a safe point, just leave. */
+    /* Step 2. If entryFrame is at a safe point, just leave. */
     if (AtSafePoint(cx)) {
         uint32 offs = uint32(cx->regs->pc - entryFrame->getScript()->code);
         JS_ASSERT(entryFrame->getScript()->nmap[offs]);
         return entryFrame->getScript()->nmap[offs];
     }
 
-    /* Step 3.2. If entryFrame is at a RETURN, then leave slightly differently. */
+    /* Step 3. If entryFrame is at a RETURN, then leave slightly differently. */
     if (JSOp op = FrameIsFinished(cx)) {
         /* We're not guaranteed that the RETURN was run. */
         if (op == JSOP_RETURN && !(entryFrame->flags & JSFRAME_BAILED_AT_RETURN))
             entryFrame->setReturnValue(f.regs.sp[-1]);
 
         /* Don't pop the frame if it's maybe owned by an Invoke. */
         if (f.fp() != f.entryFp) {
             if (!InlineReturn(f, JS_TRUE))
                 THROWV(NULL);
         }
         void *retPtr = JS_FUNC_TO_DATA_PTR(void *, InjectJaegerReturn);
         *f.returnAddressLocation() = retPtr;
         return NULL;
     }
 
-    /* Step 3.3. Do a partial interp, then restart the whole process. */
+    /* Step 4. Do a partial interp, then restart the whole process. */
     if (!PartialInterpret(f)) {
         if (!SwallowErrors(f, entryFrame))
             THROWV(NULL);
     }
 
     goto restart;
 }
 
--- a/js/src/trace-test/lib/prolog.js
+++ b/js/src/trace-test/lib/prolog.js
@@ -1,13 +1,13 @@
 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 
 const HAVE_TM = 'tracemonkey' in this;
 
-const HOTLOOP = HAVE_TM ? tracemonkey.HOTLOOP : 2;
+const HOTLOOP = HAVE_TM ? tracemonkey.HOTLOOP : 8;
 const RECORDLOOP = HOTLOOP;
 const RUNLOOP = HOTLOOP + 1;
 
 var checkStats;
 if (HAVE_TM) {
     checkStats = function(stats)
     {
         // Temporarily disabled while we work on heuristics.
--- a/js/src/trace-test/tests/basic/testBug504520.js
+++ b/js/src/trace-test/tests/basic/testBug504520.js
@@ -1,11 +1,11 @@
 function testBug504520() {
     // A bug involving comparisons.
-    var arr = [1/0, 1/0, 1/0, 1/0, 1/0, 0];
+    var arr = [1/0, 1/0, 1/0, 1/0, 1/0, 1/0, 1/0, 1/0, 1/0, 0];
     assertEq(arr.length > RUNLOOP, true);
 
     var s = '';
     for (var i = 0; i < arr.length; i++)
         arr[i] >= 1/0 ? null : (s += i);
-    assertEq(s, '5');
+    assertEq(s, '9');
 }
 testBug504520();
--- a/js/src/trace-test/tests/basic/testBug504520Harder.js
+++ b/js/src/trace-test/tests/basic/testBug504520Harder.js
@@ -6,19 +6,19 @@ function testBug504520Harder() {
         for each (var y in vals) {
             for each (var op in ops) {
                 for each (var z in vals) {
                     // Assume eval is correct. This depends on the global
                     // Infinity property not having been reassigned.
                     var xz = eval(x + op + z);
                     var yz = eval(y + op + z);
 
-                    var arr = [x, x, x, x, x, y];
+                    var arr = [x, x, x, x, x, x, x, x, x, y];
                     assertEq(arr.length > RUNLOOP, true);
-                    var expected = [xz, xz, xz, xz, xz, yz];
+                    var expected = [xz, xz, xz, xz, xz, xz, xz, xz, xz, yz];
 
                     // ?: looks superfluous but that's what we're testing here
                     var fun = eval(
                         '(function (arr, results) {\n' +
                         '    for (let i = 0; i < arr.length; i++)\n' +
                         '        results.push(arr[i]' + op + z + ' ? "true" : "false");\n' +
                         '});\n');
                     var actual = [];