Properly calculate tree exit/call guards (bug 453397, r=danderson).
authorAndreas Gal <gal@mozilla.com>
Fri, 19 Sep 2008 18:45:57 -0700
changeset 19590 f657d39d36e882bc4d0f19c69c2482c35e862440
parent 19589 40d4e16e94b09b5808d2b7e1da4eab10e894cb55
child 19591 1394251eb58b399caa6d6fe9c411480dff84bd63
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdanderson
bugs453397
milestone1.9.1b1pre
Properly calculate tree exit/call guards (bug 453397, r=danderson).
js/src/jsbuiltins.cpp
js/src/jstracer.cpp
js/src/nanojit/avmplus.h
--- a/js/src/jsbuiltins.cpp
+++ b/js/src/jsbuiltins.cpp
@@ -574,18 +574,29 @@ js_CallTree(InterpState* state, Fragment
     JS_ASSERT(u.code);
 
 #if defined(JS_NO_FASTCALL) && defined(NANOJIT_IA32)
     SIMULATE_FASTCALL(lr, state, NULL, u.func);
 #else
     lr = u.func(state, NULL);
 #endif
 
-    if (lr->exit->exitType == NESTED_EXIT)
-        lr = state->nestedExit;
+    if (lr->exit->exitType == NESTED_EXIT) {
+        /* This only occurs once a tree call guard mismatches and we unwind the tree call stack.
+           We store the first (innermost) tree call guard in state and we will try to grow
+           the outer tree the failing call was in starting at that guard. */
+        if (!state->lastTreeCallGuard)
+            state->lastTreeCallGuard = lr;
+    } else {
+        /* If the tree exits on a regular (non-nested) guard, keep updating lastTreeExitGuard
+           with that guard. If we mismatch on a tree call guard, this will contain the last
+           non-nested guard we encountered, which is the innermost loop or branch guard. */
+        state->lastTreeExitGuard = lr;
+    }
+
     return lr;
 }
 
 JS_STATIC_ASSERT(JSSLOT_PRIVATE == JSSLOT_ARRAY_LENGTH);
 JS_STATIC_ASSERT(JSSLOT_ARRAY_LENGTH + 1 == JSSLOT_ARRAY_COUNT);
 
 JSObject* FASTCALL
 js_FastNewArray(JSContext* cx, JSObject* proto)
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -866,17 +866,18 @@ TraceRecorder::TraceRecorder(JSContext* 
 
     /* read into registers all values on the stack and all globals we know so far */
     import(treeInfo, lirbuf->sp, ngslots, callDepth, globalTypeMap, stackTypeMap);
 
     /* If we are attached to a tree call guard, make sure the guard the inner tree exited from
        is what we expect it to be. */
     if (_anchor && _anchor->exit->exitType == NESTED_EXIT) {
         LIns* nested_ins = addName(lir->insLoad(LIR_ldp, lirbuf->state, 
-                                                offsetof(InterpState, nestedExit)), "nestedExit");
+                                                offsetof(InterpState, lastTreeExitGuard)), 
+                                                "lastTreeExitGuard");
         guard(true, lir->ins2(LIR_eq, nested_ins, INS_CONSTPTR(innermostNestedGuard)), NESTED_EXIT);
     }
 }
 
 TraceRecorder::~TraceRecorder()
 {
     JS_ASSERT(treeInfo);
     if (fragment->root == fragment && !fragment->root->code()) {
@@ -1832,18 +1833,16 @@ TraceRecorder::emitTreeCall(Fragment* in
     TreeInfo* ti = (TreeInfo*)inner->vmprivate;
     /* Invoke the inner tree. */
     LIns* args[] = { INS_CONSTPTR(inner), lirbuf->state }; /* reverse order */
     LIns* ret = lir->insCall(F_CallTree, args);
     /* Read back all registers, in case the called tree changed any of them. */
     SideExit* exit = lr->exit;
     import(ti, inner_sp_ins, exit->numGlobalSlots, exit->calldepth,
            exit->typeMap, exit->typeMap + exit->numGlobalSlots);
-    /* Store the guard pointer in case we exit on an unexpected guard */
-    lir->insStorei(ret, lirbuf->state, offsetof(InterpState, nestedExit));
     /* Restore sp and rp to their original values (we still have them in a register). */
     if (callDepth > 0) {
         lir->insStorei(lirbuf->sp, lirbuf->state, offsetof(InterpState, sp));
         lir->insStorei(lirbuf->rp, lirbuf->state, offsetof(InterpState, rp));
     }
     /* Guard that we come out of the inner tree along the same side exit we came out when
        we called the inner tree at recording time. */
     guard(true, lir->ins2(LIR_eq, ret, INS_CONSTPTR(lr)), NESTED_EXIT);
@@ -2348,19 +2347,18 @@ js_ExecuteTree(JSContext* cx, Fragment**
 
     InterpState state;
     state.sp = (void*)entry_sp;
     state.eos = ((double*)state.sp) + MAX_NATIVE_STACK_SLOTS;
     state.rp = callstack;
     state.eor = callstack + MAX_CALL_STACK_ENTRIES;
     state.gp = global;
     state.cx = cx;
-#ifdef DEBUG
-    state.nestedExit = NULL;
-#endif    
+    state.lastTreeExitGuard = NULL;
+    state.lastTreeCallGuard = NULL;
     union { NIns *code; GuardRecord* (FASTCALL *func)(InterpState*, Fragment*); } u;
     u.code = f->code();
 
 #ifdef DEBUG
 #if defined(NANOJIT_IA32) || (defined(NANOJIT_AMD64) && defined(__GNUC__))
     uint64 start = rdtsc();
 #endif
 #endif
@@ -2388,18 +2386,24 @@ js_ExecuteTree(JSContext* cx, Fragment**
        guards tell us by how much sp and rp should be incremented in case of a side exit. When
        calling a nested tree, however, we actively adjust sp and rp. If we have such frames
        from outer trees on the stack, then rp will have been adjusted. Before we can process
        the stack of the frames of the tree we directly exited from, we have to first work our
        way through the outer frames and generate interpreter frames for them. Once the call
        stack (rp) is empty, we can process the final frames (which again are not directly
        visible and only the guard we exited on will tells us about). */
     FrameInfo* rp = (FrameInfo*)state.rp;
-    if (lr->exit->exitType == NESTED_EXIT)
+    if (lr->exit->exitType == NESTED_EXIT) {
+        if (state.lastTreeCallGuard)
+            lr = state.lastTreeCallGuard;
+        JS_ASSERT(lr->exit->exitType == NESTED_EXIT);
+        if (innermostNestedGuardp)
+            *innermostNestedGuardp = lr;
         rp += lr->calldepth;
+    }
     while (callstack < rp) {
         /* Synthesize a stack frame and write out the values in it using the type map pointer
            on the native call stack. */
         js_SynthesizeFrame(cx, *callstack);
         int slots = FlushNativeStackFrame(cx, 1/*callDepth*/, callstack->typemap, stack, cx->fp);
 #ifdef DEBUG
         JSStackFrame* fp = cx->fp;
         debug_only_v(printf("synthesized deep frame for %s:%u@%u, slots=%d\n",
@@ -2409,30 +2413,22 @@ js_ExecuteTree(JSContext* cx, Fragment**
         if (slots < 0)
             return NULL;
         /* Keep track of the additional frames we put on the interpreter stack and the native
            stack slots we consumed. */
         ++inlineCallCount;
         ++callstack;
         stack += slots;
     }
-    
-    /* If we bail out on a nested exit, the compiled code returns the outermost nesting
-       guard but what we are really interested in is the innermost guard that we hit
-       instead of the guard we were expecting there. */
-    if (lr->exit->exitType == NESTED_EXIT) {
-        do {
-            if (innermostNestedGuardp)
-                *innermostNestedGuardp = lr;
-            JS_ASSERT(lr->guard->oprnd1()->oprnd2()->isconstp());
-            lr = (GuardRecord*)lr->guard->oprnd1()->oprnd2()->constvalp();
-        } while (lr->exit->exitType == NESTED_EXIT);
-        lr = state.nestedExit;
-        JS_ASSERT(lr);
-    }
+
+    /* If we bail out on a nested exit, the final state is contained in the innermost
+       guard which we stored in lastTreeExitGuard. */
+    if (lr->exit->exitType == NESTED_EXIT)
+        lr = state.lastTreeExitGuard;
+    JS_ASSERT(lr->exit->exitType != NESTED_EXIT);
 
     /* sp_adj and ip_adj are relative to the tree we exit out of, not the tree we
        entered into (which might be different in the presence of nested trees). */
     ti = (TreeInfo*)lr->from->root->vmprivate;
 
     /* We already synthesized the frames around the innermost guard. Here we just deal
        with additional frames inside the tree we are bailing out from. */
     JS_ASSERT(rp == callstack);
--- a/js/src/nanojit/avmplus.h
+++ b/js/src/nanojit/avmplus.h
@@ -342,17 +342,19 @@ namespace avmplus
     struct InterpState
     {
         void* sp; /* native stack pointer, stack[0] is spbase[0] */
         void* rp; /* call stack pointer */
         void* gp; /* global frame pointer */
         JSContext *cx; /* current VM context handle */
         void* eos; /* first unusable word after the native stack */
         void* eor; /* first unusable word after the call stack */
-        nanojit::GuardRecord* nestedExit; /* innermost nested guard for NESTED_EXIT exits */
+        nanojit::GuardRecord* lastTreeExitGuard; /* guard we exited on during a tree call */
+        nanojit::GuardRecord* lastTreeCallGuard; /* guard we want to grow from if the tree
+                                                    call exit guard mismatched */
     };
 
     class String
     {
     };
 
     typedef class String AvmString;