Bug 454184 - Implement eval caching (r=mrbkap).
authorBrendan Eich <brendan@mozilla.org>
Tue, 27 Jan 2009 16:40:40 -0800
changeset 24375 5174fbfd612da5444ac29f2712fea12e915ae714
parent 24374 21267f924b50134c7ff90cf49718f50b8f58c115
child 24378 b0592ff0f21ab843846f9d6fcd4cb51041dcb688
push idunknown
push userunknown
push dateunknown
reviewersmrbkap
bugs454184
milestone1.9.2a1pre
Bug 454184 - Implement eval caching (r=mrbkap).
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jsgc.cpp
js/src/jsobj.cpp
js/src/jsopcode.cpp
js/src/jsparse.cpp
js/src/jsparse.h
js/src/jsscript.h
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1178,16 +1178,20 @@ JS_GetOptions(JSContext *cx)
 }
 
 #define SYNC_OPTIONS_TO_VERSION(cx)                                           \
     JS_BEGIN_MACRO                                                            \
         if ((cx)->options & JSOPTION_XML)                                     \
             (cx)->version |= JSVERSION_HAS_XML;                               \
         else                                                                  \
             (cx)->version &= ~JSVERSION_HAS_XML;                              \
+        if ((cx)->options & JSOPTION_ANONFUNFIX)                              \
+            (cx)->version |= JSVERSION_ANONFUNFIX;                            \
+        else                                                                  \
+            (cx)->version &= ~JSVERSION_ANONFUNFIX;                           \
     JS_END_MACRO
 
 JS_PUBLIC_API(uint32)
 JS_SetOptions(JSContext *cx, uint32 options)
 {
     uint32 oldopts = cx->options;
     cx->options = options;
     SYNC_OPTIONS_TO_VERSION(cx);
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -591,18 +591,20 @@ JS_StringToVersion(const char *string);
 
 #define JSOPTION_JIT            JS_BIT(11)      /* Enable JIT compilation. */
 
 #define JSOPTION_NO_SCRIPT_RVAL JS_BIT(12)      /* A promise to the compiler
                                                    that a null rval out-param
                                                    will be passed to each call
                                                    to JS_ExecuteScript. */
 #define JSOPTION_UNROOTED_GLOBAL JS_BIT(13)     /* The GC will not root the
-                                                   global objects leaving
-                                                   that up to the embedding. */
+                                                   contexts' global objects
+                                                   (see JS_GetGlobalObject),
+                                                   leaving that up to the
+                                                   embedding. */
 
 extern JS_PUBLIC_API(uint32)
 JS_GetOptions(JSContext *cx);
 
 extern JS_PUBLIC_API(uint32)
 JS_SetOptions(JSContext *cx, uint32 options);
 
 extern JS_PUBLIC_API(uint32)
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -163,17 +163,17 @@ js_GetCurrentThread(JSRuntime *rt)
 
         JS_INIT_CLIST(&thread->contextList);
         thread->id = js_CurrentThreadId();
         thread->gcMallocBytes = 0;
 #ifdef JS_TRACER
         memset(&thread->traceMonitor, 0, sizeof(thread->traceMonitor));
         js_InitJIT(&thread->traceMonitor);
 #endif
-        thread->scriptsToGC = NULL;
+        memset(thread->scriptsToGC, 0, sizeof thread->scriptsToGC);
 
         /*
          * js_SetContextThread initializes the remaining fields as necessary.
          */
     }
     return thread;
 }
 
@@ -192,18 +192,21 @@ js_SetContextThread(JSContext *cx)
         return JS_FALSE;
     }
 
     /*
      * Clear caches on each transition from 0 to 1 context active on the
      * current thread. See bug 425828.
      */
     if (JS_CLIST_IS_EMPTY(&thread->contextList)) {
-        memset(&thread->gsnCache, 0, sizeof(thread->gsnCache));
-        memset(&thread->propertyCache, 0, sizeof(thread->propertyCache));
+        memset(&thread->gsnCache, 0, sizeof thread->gsnCache);
+        memset(&thread->propertyCache, 0, sizeof thread->propertyCache);
+#ifdef DEBUG
+        memset(&thread->evalCacheMeter, 0, sizeof thread->evalCacheMeter);
+#endif
     }
 
     /* Assert that the previous cx->thread called JS_ClearContextThread(). */
     JS_ASSERT(!cx->thread || cx->thread == thread);
     if (!cx->thread)
         JS_APPEND_LINK(&cx->threadLinks, &thread->contextList);
     cx->thread = thread;
     return JS_TRUE;
@@ -345,16 +348,59 @@ js_NewContext(JSRuntime *rt, size_t stac
     if (cxCallback && !cxCallback(cx, JSCONTEXT_NEW)) {
         js_DestroyContext(cx, JSDCM_NEW_FAILED);
         return NULL;
     }
 
     return cx;
 }
 
+#if defined DEBUG && defined XP_UNIX
+# include <stdio.h>
+
+static void
+DumpEvalCacheMeter(JSContext *cx)
+{
+    struct {
+        const char *name;
+        ptrdiff_t  offset;
+    } table[] = {
+#define frob(x) { #x, offsetof(JSEvalCacheMeter, x) }
+        EVAL_CACHE_METER_LIST(frob)
+#undef frob
+    };
+    JSEvalCacheMeter *ecm = &JS_CACHE_LOCUS(cx)->evalCacheMeter;
+
+    static FILE *fp;
+    if (!fp) {
+        fp = fopen("/tmp/evalcache.stats", "w");
+        if (!fp)
+            return;
+    }
+
+    fprintf(fp, "eval cache meter (%p):\n",
+#ifdef JS_THREADSAFE
+            cx->thread
+#else
+            cx->runtime
+#endif
+            );
+    for (uintN i = 0; i < JS_ARRAY_LENGTH(table); ++i) {
+        fprintf(fp, "%-8.8s  %llu\n",
+                table[i].name, *(uint64 *)((uint8 *)ecm + table[i].offset));
+    }
+    fprintf(fp, "hit ratio %g%%\n", ecm->hit * 100. / ecm->probe);
+    fprintf(fp, "avg steps %g\n", double(ecm->step) / ecm->probe);
+    fflush(fp);
+}
+# define DUMP_EVAL_CACHE_METER(cx) DumpEvalCacheMeter(cx)
+#else
+# define DUMP_EVAL_CACHE_METER(cx) ((void) 0)
+#endif
+
 void
 js_DestroyContext(JSContext *cx, JSDestroyContextMode mode)
 {
     JSRuntime *rt;
     JSContextCallback cxCallback;
     JSBool last;
     JSArgumentFormatMap *map;
     JSLocalRootStack *lrs;
@@ -433,16 +479,17 @@ js_DestroyContext(JSContext *cx, JSDestr
      * data structure.
      */
     while (cx->requestDepth != 0)
         JS_EndRequest(cx);
 #endif
 
     if (last) {
         js_GC(cx, GC_LAST_CONTEXT);
+        DUMP_EVAL_CACHE_METER(cx);
 
         /*
          * Free the script filename table if it exists and is empty. Do this
          * after the last GC to avoid finalizers tripping on free memory.
          */
         if (rt->scriptFilenameTable && rt->scriptFilenameTable->nentries == 0)
             js_FinishRuntimeScriptState(rt);
 
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -154,16 +154,41 @@ typedef struct JSTraceMonitor {
 #ifdef JS_TRACER
 # define JS_ON_TRACE(cx)            (JS_TRACE_MONITOR(cx).onTrace)
 # define JS_EXECUTING_TRACE(cx)     (JS_ON_TRACE(cx) && !JS_TRACE_MONITOR(cx).recorder)
 #else
 # define JS_ON_TRACE(cx)            JS_FALSE
 # define JS_EXECUTING_TRACE(cx)     JS_FALSE
 #endif
 
+#ifdef DEBUG
+# define JS_EVAL_CACHE_METERING 1
+#endif
+
+/* Number of potentially reusable scriptsToGC to search for the eval cache. */
+#ifndef JS_EVAL_CACHE_SHIFT
+# define JS_EVAL_CACHE_SHIFT        6
+#endif
+#define JS_EVAL_CACHE_SIZE          JS_BIT(JS_EVAL_CACHE_SHIFT)
+
+#ifdef JS_EVAL_CACHE_METERING
+# define EVAL_CACHE_METER_LIST(_)   _(probe), _(hit), _(step), _(noscope)
+# define ID(x)                      x
+
+/* Have to typedef this for LiveConnect C code, which includes us. */
+typedef struct JSEvalCacheMeter {
+    uint64 EVAL_CACHE_METER_LIST(ID);
+} JSEvalCacheMeter;
+
+# undef ID
+# define DECLARE_EVAL_CACHE_METER   JSEvalCacheMeter evalCacheMeter;
+#else
+# define DECLARE_EVAL_CACHE_METER   /* nothing */
+#endif
+
 #ifdef JS_THREADSAFE
 
 /*
  * Structure uniquely representing a thread.  It holds thread-private data
  * that can be accessed without a global lock.
  */
 struct JSThread {
     /* Linked list of all contexts active on this thread. */
@@ -190,24 +215,23 @@ struct JSThread {
     /* Property cache for faster call/get/set invocation. */
     JSPropertyCache     propertyCache;
 
 #ifdef JS_TRACER
     /* Trace-tree JIT recorder/interpreter state. */
     JSTraceMonitor      traceMonitor;
 #endif
 
-    /* Lock-free list of scripts created by eval to garbage-collect. */
-    JSScript            *scriptsToGC;
+    /* Lock-free hashed lists of scripts created by eval to garbage-collect. */
+    JSScript            *scriptsToGC[JS_EVAL_CACHE_SIZE];
+
+    DECLARE_EVAL_CACHE_METER
 };
 
-#define JS_GSN_CACHE(cx)        ((cx)->thread->gsnCache)
-#define JS_PROPERTY_CACHE(cx)   ((cx)->thread->propertyCache)
-#define JS_TRACE_MONITOR(cx)    ((cx)->thread->traceMonitor)
-#define JS_SCRIPTS_TO_GC(cx)    ((cx)->thread->scriptsToGC)
+#define JS_CACHE_LOCUS(cx)      ((cx)->thread)
 
 extern void
 js_ThreadDestructorCB(void *ptr);
 
 extern JSBool
 js_SetContextThread(JSContext *cx);
 
 extern void
@@ -458,23 +482,22 @@ struct JSRuntime {
     JSGSNCache          gsnCache;
 
     /* Property cache for faster call/get/set invocation. */
     JSPropertyCache     propertyCache;
 
     /* Trace-tree JIT recorder/interpreter state. */
     JSTraceMonitor      traceMonitor;
 
-    /* Lock-free list of scripts created by eval to garbage-collect. */
-    JSScript            *scriptsToGC;
+    /* Lock-free hashed lists of scripts created by eval to garbage-collect. */
+    JSScript            *scriptsToGC[JS_EVAL_CACHE_SIZE];
 
-#define JS_GSN_CACHE(cx)        ((cx)->runtime->gsnCache)
-#define JS_PROPERTY_CACHE(cx)   ((cx)->runtime->propertyCache)
-#define JS_TRACE_MONITOR(cx)    ((cx)->runtime->traceMonitor)
-#define JS_SCRIPTS_TO_GC(cx)    ((cx)->runtime->scriptsToGC)
+    DECLARE_EVAL_CACHE_METER
+
+#define JS_CACHE_LOCUS(cx)      ((cx)->runtime)
 #endif
 
     /*
      * Object shape (property cache structural type) identifier generator.
      *
      * Type 0 stands for the empty scope, and must not be regenerated due to
      * uint32 wrap-around. Since we use atomic pre-increment, the initial
      * value for the first typed non-empty scope will be 1.
@@ -582,16 +605,29 @@ struct JSRuntime {
     JSBasicStats        lexicalScopeDepthStats;
 #endif
 
 #ifdef JS_GCMETER
     JSGCStats           gcStats;
 #endif
 };
 
+/* Common macros to access thread-local caches in JSThread or JSRuntime. */
+#define JS_GSN_CACHE(cx)        (JS_CACHE_LOCUS(cx)->gsnCache)
+#define JS_PROPERTY_CACHE(cx)   (JS_CACHE_LOCUS(cx)->propertyCache)
+#define JS_TRACE_MONITOR(cx)    (JS_CACHE_LOCUS(cx)->traceMonitor)
+#define JS_SCRIPTS_TO_GC(cx)    (JS_CACHE_LOCUS(cx)->scriptsToGC)
+
+#ifdef JS_EVAL_CACHE_METERING
+# define EVAL_CACHE_METER(x)    (JS_CACHE_LOCUS(cx)->evalCacheMeter.x++)
+#else
+# define EVAL_CACHE_METER(x)    ((void) 0)
+#endif
+#undef DECLARE_EVAL_CACHE_METER
+
 #ifdef DEBUG
 # define JS_RUNTIME_METER(rt, which)    JS_ATOMIC_INCREMENT(&(rt)->which)
 # define JS_RUNTIME_UNMETER(rt, which)  JS_ATOMIC_DECREMENT(&(rt)->which)
 #else
 # define JS_RUNTIME_METER(rt, which)    /* nothing */
 # define JS_RUNTIME_UNMETER(rt, which)  /* nothing */
 #endif
 
@@ -1017,16 +1053,18 @@ class JSAutoResolveFlags
 #define JS_HAS_OPTION(cx,option)        (((cx)->options & (option)) != 0)
 #define JS_HAS_STRICT_OPTION(cx)        JS_HAS_OPTION(cx, JSOPTION_STRICT)
 #define JS_HAS_WERROR_OPTION(cx)        JS_HAS_OPTION(cx, JSOPTION_WERROR)
 #define JS_HAS_COMPILE_N_GO_OPTION(cx)  JS_HAS_OPTION(cx, JSOPTION_COMPILE_N_GO)
 #define JS_HAS_ATLINE_OPTION(cx)        JS_HAS_OPTION(cx, JSOPTION_ATLINE)
 
 #define JSVERSION_MASK                  0x0FFF  /* see JSVersion in jspubtd.h */
 #define JSVERSION_HAS_XML               0x1000  /* flag induced by XML option */
+#define JSVERSION_ANONFUNFIX            0x2000  /* see jsapi.h, the comments
+                                                   for JSOPTION_ANONFUNFIX */
 
 #define JSVERSION_NUMBER(cx)            ((JSVersion)((cx)->version &          \
                                                      JSVERSION_MASK))
 #define JS_HAS_XML_OPTION(cx)           ((cx)->version & JSVERSION_HAS_XML || \
                                          JSVERSION_NUMBER(cx) >= JSVERSION_1_6)
 
 /*
  * Initialize a library-wide thread private data index, and remember that it
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -3465,17 +3465,18 @@ js_GC(JSContext *cx, JSGCInvocationKind 
 
     /* Clear property and JIT oracle caches (only for cx->thread if JS_THREADSAFE). */
     js_FlushPropertyCache(cx);
 #ifdef JS_TRACER
     js_FlushJITOracle(cx);
 #endif
 
     /* Destroy eval'ed scripts. */
-    DestroyScriptsToGC(cx, &JS_SCRIPTS_TO_GC(cx));
+    for (i = 0; i < JS_ARRAY_LENGTH(JS_SCRIPTS_TO_GC(cx)); i++)
+        DestroyScriptsToGC(cx, &JS_SCRIPTS_TO_GC(cx)[i]);
 
 #ifdef JS_THREADSAFE
     /*
      * Clear thread-based caches. To avoid redundant clearing we unroll the
      * current thread's step.
      *
      * In case a JSScript wrapped within an object was finalized, we null
      * acx->thread->gsnCache.script and finish the cache's hashtable. Note
@@ -3487,17 +3488,18 @@ js_GC(JSContext *cx, JSGCInvocationKind 
     while ((acx = js_ContextIterator(rt, JS_FALSE, &iter)) != NULL) {
         if (!acx->thread || acx->thread == cx->thread)
             continue;
         GSN_CACHE_CLEAR(&acx->thread->gsnCache);
         js_FlushPropertyCache(acx);
 #ifdef JS_TRACER
         js_FlushJITOracle(acx);
 #endif
-        DestroyScriptsToGC(cx, &acx->thread->scriptsToGC);
+        for (i = 0; i < JS_ARRAY_LENGTH(acx->thread->scriptsToGC); i++)
+            DestroyScriptsToGC(cx, &acx->thread->scriptsToGC[i]);
     }
 #else
     /* The thread-unsafe case just has to clear the runtime's GSN cache. */
     GSN_CACHE_CLEAR(&rt->gsnCache);
 #endif
 
   restart:
     rt->gcNumber++;
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1186,29 +1186,52 @@ js_ComputeFilename(JSContext *cx, JSStac
         JS_ASSERT(caller->regs->pc[JSOP_EVAL_LENGTH] == JSOP_LINENO);
         *linenop = GET_UINT16(caller->regs->pc + JSOP_EVAL_LENGTH);
     } else {
         *linenop = js_FramePCToLineNumber(cx, caller);
     }
     return caller->script->filename;
 }
 
+#ifndef EVAL_CACHE_CHAIN_LIMIT
+# define EVAL_CACHE_CHAIN_LIMIT 4
+#endif
+
+static inline JSScript **
+EvalCacheHash(JSContext *cx, JSString *str)
+{
+    const jschar *s;
+    size_t n;
+    uint32 h;
+
+    JSSTRING_CHARS_AND_LENGTH(str, s, n);
+    if (n > 100)
+        n = 100;
+    for (h = 0; n; s++, n--)
+        h = JS_ROTATE_LEFT32(h, 4) ^ *s;
+
+    h *= JS_GOLDEN_RATIO;
+    h >>= 32 - JS_EVAL_CACHE_SHIFT;
+    return &JS_SCRIPTS_TO_GC(cx)[h];
+}
+
 static JSBool
 obj_eval(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 {
     JSStackFrame *fp, *caller;
     JSBool indirectCall;
     JSObject *scopeobj;
-    JSString *str;
+    uint32 tcflags;
+    JSPrincipals *principals;
     const char *file;
     uintN line;
-    JSPrincipals *principals;
-    uint32 tcflags;
+    JSString *str;
     JSScript *script;
     JSBool ok;
+    JSScript **bucket = NULL;   /* avoid GCC warning with early decl&init */
 #if JS_HAS_EVAL_THIS_SCOPE
     JSObject *callerScopeChain = NULL, *callerVarObj = NULL;
     JSObject *setCallerScopeChain = NULL;
     JSBool setCallerVarObj = JS_FALSE;
 #endif
 
     fp = js_GetTopStackFrame(cx);
     caller = js_GetScriptedCaller(cx, fp);
@@ -1331,35 +1354,104 @@ obj_eval(JSContext *cx, JSObject *obj, u
 
     /* Ensure we compile this eval with the right object in the scope chain. */
     scopeobj = js_CheckScopeChainValidity(cx, scopeobj, js_eval_str);
     if (!scopeobj) {
         ok = JS_FALSE;
         goto out;
     }
 
-    str = JSVAL_TO_STRING(argv[0]);
+    tcflags = TCF_COMPILE_N_GO;
     if (caller) {
+        tcflags |= TCF_PUT_STATIC_DEPTH(caller->script->staticDepth + 1);
         principals = JS_EvalFramePrincipals(cx, fp, caller);
         file = js_ComputeFilename(cx, caller, principals, &line);
     } else {
+        principals = NULL;
         file = NULL;
         line = 0;
-        principals = NULL;
     }
 
-    tcflags = TCF_COMPILE_N_GO;
-    if (caller)
-        tcflags |= TCF_PUT_STATIC_DEPTH(caller->script->staticDepth + 1);
-    script = js_CompileScript(cx, scopeobj, caller, principals, tcflags,
-                              JSSTRING_CHARS(str), JSSTRING_LENGTH(str),
-                              NULL, file, line);
+    str = JSVAL_TO_STRING(argv[0]);
+    script = NULL;
+
+    /* Cache local eval scripts indexed by source qualified by scope. */
+    bucket = EvalCacheHash(cx, str);
+    if (caller->fun) {
+        uintN count = 0;
+        JSScript **scriptp = bucket;
+
+        EVAL_CACHE_METER(probe);
+        while ((script = *scriptp) != NULL) {
+            if ((script->flags & JSSF_SAVED_CALLER_FUN) &&
+                script->version == cx->version &&
+                (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 js_CompileScript in jsparse.cpp.
+                 */
+                JSFunction *fun;
+                JS_GET_SCRIPT_FUNCTION(script, 0, fun);
+
+                if (fun == caller->fun) {
+                    /*
+                     * Get the source string passed for safekeeping in the
+                     * atom map by the prior eval to js_CompileScript.
+                     */
+                    JSString *src = ATOM_TO_STRING(script->atomMap.vector[0]);
+
+                    if (src == str || js_EqualStrings(src, str)) {
+                        /*
+                         * Source matches, qualify by comparing scopeobj to the
+                         * COMPILE_N_GO-memoized parent of the first literal
+                         * function or regexp object if any. If none, then this
+                         * script has no compiled-in dependencies on the prior
+                         * eval's scopeobj.
+                         */
+                        JSObjectArray *objarray = JS_SCRIPT_OBJECTS(script);
+                        int i = 1;
+                        if (objarray->length == 1) {
+                            if (script->regexpsOffset != 0) {
+                                objarray = JS_SCRIPT_REGEXPS(script);
+                                i = 0;
+                            } else {
+                                EVAL_CACHE_METER(noscope);
+                                i = -1;
+                            }
+                        }
+                        if (i < 0 ||
+                            STOBJ_GET_PARENT(objarray->vector[i]) == scopeobj) {
+                            EVAL_CACHE_METER(hit);
+                            *scriptp = script->u.nextToGC;
+                            script->u.nextToGC = NULL;
+                            break;
+                        }
+                    }
+                }
+            }
+
+            if (++count == EVAL_CACHE_CHAIN_LIMIT) {
+                script = NULL;
+                break;
+            }
+            EVAL_CACHE_METER(step);
+            scriptp = &script->u.nextToGC;
+        }
+    }
+
     if (!script) {
-        ok = JS_FALSE;
-        goto out;
+        script = js_CompileScript(cx, scopeobj, caller, principals, tcflags,
+                                  JSSTRING_CHARS(str), JSSTRING_LENGTH(str),
+                                  NULL, file, line, str);
+        if (!script) {
+            ok = JS_FALSE;
+            goto out;
+        }
     }
 
     if (argc < 2) {
         /* Execute using caller's new scope object (might be a Call object). */
         if (caller)
             scopeobj = caller->scopeChain;
     }
 
@@ -1367,18 +1459,18 @@ obj_eval(JSContext *cx, JSObject *obj, u
      * Belt-and-braces: check that the lesser of eval's principals and the
      * caller's principals has access to scopeobj.
      */
     ok = js_CheckPrincipalsAccess(cx, scopeobj, principals,
                                   cx->runtime->atomState.evalAtom);
     if (ok)
         ok = js_Execute(cx, scopeobj, script, caller, JSFRAME_EVAL, rval);
 
-    script->u.nextToGC = JS_SCRIPTS_TO_GC(cx);
-    JS_SCRIPTS_TO_GC(cx) = script;
+    script->u.nextToGC = *bucket;
+    *bucket = script;
 #ifdef CHECK_SCRIPT_OWNER
     script->owner = NULL;
 #endif
 
 out:
 #if JS_HAS_EVAL_THIS_SCOPE
     /* Restore OBJ_GET_PARENT(scopeobj) not callerScopeChain in case of Call. */
     if (setCallerScopeChain) {
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -2747,16 +2747,17 @@ Decompile(SprintStack *ss, jsbytecode *p
                 ss->sprinter.offset = GetOff(ss, top);
                 if (op == JSOP_LEAVEBLOCKEXPR)
                     todo = SprintCString(&ss->sprinter, rval);
                 break;
               }
 
               case JSOP_CALLUPVAR:
               case JSOP_GETUPVAR:
+                JS_ASSERT(jp->script->flags & JSSF_SAVED_CALLER_FUN);
 
                 if (!jp->fun)
                     JS_GET_SCRIPT_FUNCTION(jp->script, 0, jp->fun);
 
                 if (!jp->localNames)
                     jp->localNames = js_GetLocalNameArray(cx, jp->fun, &jp->pool);
 
                 i = JS_UPVAR_LOCAL_NAME_START(jp->fun) + GET_UINT16(pc);
--- a/js/src/jsparse.cpp
+++ b/js/src/jsparse.cpp
@@ -498,17 +498,18 @@ js_ParseScript(JSContext *cx, JSObject *
 
 /*
  * Compile a top-level script.
  */
 extern JSScript *
 js_CompileScript(JSContext *cx, JSObject *scopeChain, JSStackFrame *callerFrame,
                  JSPrincipals *principals, uint32 tcflags,
                  const jschar *chars, size_t length,
-                 FILE *file, const char *filename, uintN lineno)
+                 FILE *file, const char *filename, uintN lineno,
+                 JSString *source)
 {
     JSParseContext pc;
     JSArenaPool codePool, notePool;
     JSCodeGenerator cg;
     JSTokenType tt;
     JSParseNode *pn;
     uint32 scriptGlobals;
     JSScript *script;
@@ -538,26 +539,46 @@ js_CompileScript(JSContext *cx, JSObject
     js_InitCodeGenerator(cx, &cg, &pc, &codePool, &notePool,
                          pc.tokenStream.lineno);
 
     MUST_FLOW_THROUGH("out");
     cg.treeContext.flags |= (uint16) tcflags;
     cg.treeContext.u.scopeChain = scopeChain;
     cg.staticDepth = TCF_GET_STATIC_DEPTH(tcflags);
 
-    if ((tcflags & TCF_COMPILE_N_GO) && callerFrame && callerFrame->fun) {
-        /*
-         * An eval script in a caller frame needs to have its enclosing function
-         * captured in case it uses an upvar reference, and someone wishes to
-         * decompile it while running.
-         */
-        JSParsedObjectBox *pob = js_NewParsedObjectBox(cx, &pc, FUN_OBJECT(callerFrame->fun));
-        pob->emitLink = cg.objectList.lastPob;
-        cg.objectList.lastPob = pob;
-        cg.objectList.length++;
+    /*
+     * If funpob is non-null after we create the new script, callerFrame->fun
+     * was saved in the 0th object table entry.
+     */
+    JSParsedObjectBox *funpob = NULL;
+
+    if (tcflags & TCF_COMPILE_N_GO) {
+        if (source) {
+            /*
+             * Save eval program source in script->atomMap.vector[0] for the
+             * eval cache (see obj_eval in jsobj.cpp).
+             */
+            JSAtom *atom = js_AtomizeString(cx, source, 0);
+            if (!atom || !js_IndexAtom(cx, atom, &cg.atomList))
+                return NULL;
+        }
+
+        if (callerFrame && callerFrame->fun) {
+            /*
+             * An eval script in a caller frame needs to have its enclosing
+             * function captured in case it uses an upvar reference, and
+             * someone wishes to decompile it while it's running.
+             */
+            funpob = js_NewParsedObjectBox(cx, &pc, FUN_OBJECT(callerFrame->fun));
+            if (!funpob)
+                return NULL;
+            funpob->emitLink = cg.objectList.lastPob;
+            cg.objectList.lastPob = funpob;
+            cg.objectList.length++;
+        }
     }
 
     /* Inline Statements() to emit as we go to save space. */
     for (;;) {
         pc.tokenStream.flags |= TSF_OPERAND;
         tt = js_PeekToken(cx, &pc.tokenStream);
         pc.tokenStream.flags &= ~TSF_OPERAND;
         if (tt <= TOK_EOF) {
@@ -642,16 +663,18 @@ js_CompileScript(JSContext *cx, JSObject
 #ifdef METER_PARSENODES
     printf("Code-gen growth: %d (%u bytecodes, %u srcnotes)\n",
            (char *)sbrk(0) - (char *)before, CG_OFFSET(cg), cg->noteCount);
 #endif
 #ifdef JS_ARENAMETER
     JS_DumpArenaStats(stdout);
 #endif
     script = js_NewScriptFromCG(cx, &cg);
+    if (script && funpob)
+        script->flags |= JSSF_SAVED_CALLER_FUN;
 
 #ifdef JS_SCOPE_DEPTH_METER
     if (script) {
         JSObject *obj = scopeChain;
         uintN depth = 1;
         while ((obj = OBJ_GET_PARENT(cx, obj)) != NULL)
             ++depth;
         JS_BASIC_STATS_ACCUM(&cx->runtime->hostenvScopeDepthStats, depth);
--- a/js/src/jsparse.h
+++ b/js/src/jsparse.h
@@ -451,17 +451,18 @@ struct JSParseContext {
  */
 extern JSParseNode *
 js_ParseScript(JSContext *cx, JSObject *chain, JSParseContext *pc);
 
 extern JSScript *
 js_CompileScript(JSContext *cx, JSObject *scopeChain, JSStackFrame *callerFrame,
                  JSPrincipals *principals, uint32 tcflags,
                  const jschar *chars, size_t length,
-                 FILE *file, const char *filename, uintN lineno);
+                 FILE *file, const char *filename, uintN lineno,
+                 JSString *source = NULL);
 
 extern JSBool
 js_CompileFunctionBody(JSContext *cx, JSFunction *fun, JSPrincipals *principals,
                        const jschar *chars, size_t length,
                        const char *filename, uintN lineno);
 
 extern JSBool
 js_FoldConstants(JSContext *cx, JSParseNode *pn, JSTreeContext *tc,
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -125,16 +125,17 @@ struct JSScript {
     } u;
 #ifdef CHECK_SCRIPT_OWNER
     JSThread        *owner;     /* for thread-safe life-cycle assertions */
 #endif
 };
 
 #define JSSF_NO_SCRIPT_RVAL     0x01    /* no need for result value of last
                                            expression statement */
+#define JSSF_SAVED_CALLER_FUN   0x02    /* object 0 is caller function */
 
 static JS_INLINE uintN
 StackDepth(JSScript *script)
 {
     return script->nslots - script->nfixed;
 }
 
 /* No need to store script->notes now that it is allocated right after code. */