Closed Bug 473688 Opened 16 years ago Closed 15 years ago

TM: provide a 2-level hash map (global, pc) -> tree

Categories

(Core :: JavaScript Engine, defect, P3)

x86
macOS
defect

Tracking

()

RESOLVED FIXED
mozilla1.9.1b3

People

(Reporter: gal, Assigned: graydon)

References

Details

(Keywords: fixed1.9.1)

Attachments

(1 file, 1 obsolete file)

      No description provided.
Blocks: 468988
This blocks a blocker.
Assignee: general → gal
No longer blocks: 468988
Priority: -- → P3
Target Milestone: --- → mozilla1.9.1b3
Blocks: 468988
Assignee: gal → graydon
Attached patch Sketch of solution (obsolete) — Splinter Review
Here's a sketch of the code we discussed. Let me know if this is what you had in mind.  It currently crashes but I doubt it's a terribly deep bug. Gotta head home for the night.
Comment on attachment 358326 [details] [diff] [review]
Sketch of solution

>diff -r 5728b9d90bcd js/src/jscntxt.h
>--- a/js/src/jscntxt.h	Thu Jan 22 18:15:57 2009 -0800
>+++ b/js/src/jscntxt.h	Thu Jan 22 19:52:30 2009 -0800
>@@ -108,6 +108,15 @@
> # define CLS(T)  void*
> #endif
> 
>+#define FRAGMENT_TABLE_SIZE 4096
>+struct JSFragmentBucket
>+{
>+    struct JSFragmentBucket* next;
>+    const void* ip;
>+    uint32 globalShape;
>+    CLS(nanojit::Fragment) fragment;
>+};

We currently have Fragment and TreeInfo. I was thinking we change that to a sub-class VMFragment that derives from Fragment and contains next and globalShape and then we place VMFragment into the lirbuf and instead of JSFragmentBucket* the hash buckets are
VMFragment*. What do you think?


>+
> /*
>  * Trace monitor. Every JSThread (if JS_THREADSAFE) or JSRuntime (if not
>  * JS_THREADSAFE) has an associated trace monitor that keeps track of loop
>@@ -129,6 +138,8 @@
>     jsval                   *reservedDoublePool;
>     jsval                   *reservedDoublePoolPtr;
> 
>+    struct JSFragmentBucket* fragmentBuckets[FRAGMENT_TABLE_SIZE];
>+
>     /*
>      * reservedObjects is a linked list (via fslots[0]) of preallocated JSObjects.
>      * The JIT uses this to ensure that leaving a trace tree can't fail.
>diff -r 5728b9d90bcd js/src/jstracer.cpp
>--- a/js/src/jstracer.cpp	Thu Jan 22 18:15:57 2009 -0800
>+++ b/js/src/jstracer.cpp	Thu Jan 22 19:52:30 2009 -0800
>@@ -223,7 +223,7 @@
> 
> #ifdef JS_JIT_SPEW
> void
>-js_DumpPeerStability(Fragmento* frago, const void* ip);
>+js_DumpPeerStability(JSTraceMonitor* tm, const void* ip);
> #endif
> 
> /* We really need a better way to configure the JIT. Shaver, where is my fancy JIT object? */
>@@ -243,7 +243,7 @@
> /* Blacklists the root peer fragment at a fragment's PC.  This is so blacklisting stays at the 
>    top of the peer list and not scattered around. */
> void
>-js_BlacklistPC(Fragmento* frago, Fragment* frag);
>+js_BlacklistPC(JSTraceMonitor* tm, Fragment* frag);
> 
> Tracker::Tracker()
> {
>@@ -365,43 +365,46 @@
>  */
> 
> #define ORACLE_MASK (ORACLE_SIZE - 1)
>+#define FRAGMENT_TABLE_MASK (FRAGMENT_TABLE_SIZE - 1)
>+#define HASH_SEED 5381
> 
> static inline void
>-hash_accum(uintptr_t& h, uintptr_t i)
>-{
>-    h = ((h << 5) + h + (ORACLE_MASK & i)) & ORACLE_MASK;
>+hash_accum(uintptr_t& h, uintptr_t i, uintptr_t mask)
>+{
>+    h = ((h << 5) + h + (mask & i)) & mask;
> }
> 
> JS_REQUIRES_STACK static inline int
> stackSlotHash(JSContext* cx, unsigned slot)
> {
>-    uintptr_t h = 5381;
>-    hash_accum(h, uintptr_t(cx->fp->script));
>-    hash_accum(h, uintptr_t(cx->fp->regs->pc));
>-    hash_accum(h, uintptr_t(slot));
>+    uintptr_t h = HASH_SEED;
>+    hash_accum(h, uintptr_t(cx->fp->script), ORACLE_MASK);
>+    hash_accum(h, uintptr_t(cx->fp->regs->pc), ORACLE_MASK);
>+    hash_accum(h, uintptr_t(slot), ORACLE_MASK);
>     return int(h);
> }
> 
> JS_REQUIRES_STACK static inline int
> globalSlotHash(JSContext* cx, unsigned slot)
> {    
>-    uintptr_t h = 5381;
>+    uintptr_t h = HASH_SEED;
>     JSStackFrame* fp = cx->fp;
> 
>     while (fp->down)
>         fp = fp->down;        
> 
>-    hash_accum(h, uintptr_t(fp->script)); 
>-    hash_accum(h, uintptr_t(OBJ_SHAPE(JS_GetGlobalForObject(cx, fp->scopeChain))));
>-    hash_accum(h, uintptr_t(slot));
>+    hash_accum(h, uintptr_t(fp->script), ORACLE_MASK); 
>+    hash_accum(h, uintptr_t(OBJ_SHAPE(JS_GetGlobalForObject(cx, fp->scopeChain))), 

shaver suggested handing in globalObj from the caller instead of recalculating here.

>+               ORACLE_MASK);
>+    hash_accum(h, uintptr_t(slot), ORACLE_MASK);
>     return int(h);
> }
> 
> static inline size_t
> hitHash(const void* ip)
> {    
>-    uintptr_t h = 5381;
>-    hash_accum(h, uintptr_t(ip));
>+    uintptr_t h = HASH_SEED;
>+    hash_accum(h, uintptr_t(ip), ORACLE_MASK);
>     return size_t(h);
> }
> 
>@@ -503,6 +506,77 @@
>     _globalDontDemote.reset();
> }
> 
>+static inline size_t 
>+fragmentHash(const void *ip, uint32 globalShape)
>+{
>+    uintptr_t h = HASH_SEED;
>+    hash_accum(h, uintptr_t(ip), FRAGMENT_TABLE_MASK);
>+    hash_accum(h, uintptr_t(globalShape), FRAGMENT_TABLE_MASK);
>+    return size_t(h);
>+}
>+
>+static JSFragmentBucket*
>+getFragmentBucket(JSTraceMonitor* tm, const void *ip, uint32 globalShape)
>+{
>+    size_t h = fragmentHash(ip, globalShape);
>+    JSFragmentBucket* bucket = tm->fragmentBuckets[h];
>+    while (bucket && 
>+           ! (bucket->globalShape == globalShape &&
>+              bucket->ip == ip)) {
>+        bucket = bucket->next;
>+    }
>+    if (!bucket) {
>+        LirBufWriter writer(tm->lirbuf);
>+        bucket = (JSFragmentBucket*) writer.skip(sizeof(JSFragmentBucket));

check for OOM?

>+        JS_ASSERT(bucket);
>+        bucket->ip = ip;
>+        bucket->globalShape = globalShape;
>+        bucket->fragment = NULL;
>+        bucket->next = tm->fragmentBuckets[h];
>+        tm->fragmentBuckets[h] = bucket;
>+    }
>+    JS_ASSERT(bucket);
>+    return bucket;        
>+}
>+
>+// FIXME: remove the default parameters for globalShape when we're
>+// actually keying by it.
>+
>+static nanojit::Fragment*
>+getLoop(JSTraceMonitor* tm, const void *ip, uint32 globalShape = 0)
>+{
>+    return getFragmentBucket(tm, ip, globalShape)->fragment;
>+}

I like the idea. We can get the patch in like this and then in a separate patch enable the ip/shape lookup.

>+
>+static nanojit::Fragment*
>+getAnchor(JSTraceMonitor* tm, const void *ip, uint32 globalShape = 0)
>+{
>+    LirBufWriter writer(tm->lirbuf);
>+    char *fragmem = (char*) writer.skip(sizeof(Fragment));

Same here. This might fail. Maybe we should stop using the constructor? new (fragmem) looks disgusting.

>+    Fragment *f = new (fragmem) Fragment(ip);
>+    JS_ASSERT(f);
>+
>+    JSFragmentBucket* bucket = getFragmentBucket(tm, ip, globalShape);
>+    Fragment *p = bucket->fragment;
>+
>+    if (p) {
>+        f->first = p;
>+        /* append at the end of the peer list */
>+        Fragment* next;
>+        while ((next = p->peer) != NULL)
>+            p = next;
>+        p->peer = f;
>+    } else {
>+        /* this is the first fragment */
>+        f->first = f;
>+        bucket->fragment = f;
>+    }
>+    f->anchor = f;
>+    f->root = f;
>+    f->kind = LoopTrace;
>+    return f;
>+}
>+
> 
> #if defined(NJ_SOFTFLOAT)
> JS_DEFINE_CALLINFO_1(static, DOUBLE,    i2f, INT32,                 1, 1)
>@@ -2381,11 +2455,12 @@
> 
> /* Compile the current fragment. */
> JS_REQUIRES_STACK void
>-TraceRecorder::compile(Fragmento* fragmento)
>-{
>+TraceRecorder::compile(JSTraceMonitor* tm)
>+{
>+    Fragmento* fragmento = tm->fragmento;
>     if (treeInfo->maxNativeStackSlots >= MAX_NATIVE_STACK_SLOTS) {
>         debug_only_v(printf("Trace rejected: excessive stack use.\n"));
>-        js_BlacklistPC(fragmento, fragment);
>+        js_BlacklistPC(tm, fragment);
>         return;
>     }
>     ++treeInfo->branchCount;
>@@ -2397,7 +2472,7 @@
>     if (fragmento->assm()->error() == nanojit::OutOMem)
>         return;
>     if (fragmento->assm()->error() != nanojit::None) {
>-        js_BlacklistPC(fragmento, fragment);
>+        js_BlacklistPC(tm, fragment);
>         return;
>     }
>     if (anchor) 
>@@ -2440,27 +2515,28 @@
> 
> /* Complete and compile a trace and link it to the existing tree if appropriate. */
> JS_REQUIRES_STACK bool
>-TraceRecorder::closeLoop(Fragmento* fragmento, bool& demote)
>+TraceRecorder::closeLoop(JSTraceMonitor* tm, bool& demote)
> {
>     bool stable;
>     LIns* exitIns;
>     Fragment* peer;
>     VMSideExit* exit;
>     Fragment* peer_root;
>+    Fragmento* fragmento = tm->fragmento;
> 
>     exitIns = snapshot(UNSTABLE_LOOP_EXIT);
>     exit = (VMSideExit*)((GuardRecord*)exitIns->payload())->exit;
> 
>     if (callDepth != 0) {
>         debug_only_v(printf("Stack depth mismatch, possible recursion\n");)
>-        js_BlacklistPC(fragmento, fragment);
>+        js_BlacklistPC(tm, fragment);
>         trashSelf = true;
>         return false;
>     }
> 
>     JS_ASSERT(exit->numStackSlots == treeInfo->stackSlots);
> 
>-    peer_root = fragmento->getLoop(fragment->root->ip);
>+    peer_root = getLoop(traceMonitor, fragment->root->ip);
>     JS_ASSERT(peer_root != NULL);
>     stable = deduceTypeStability(peer_root, &peer, demote);
> 
>@@ -2514,11 +2590,11 @@
>             ((TreeInfo*)peer->vmprivate)->dependentTrees.addUnique(fragment->root);
>         }
> 
>-        compile(fragmento);
>+        compile(tm);
>     } else {
>         exit->target = fragment->root;
>         fragment->lastIns = lir->insGuard(LIR_loop, lir->insImm(1), exitIns);
>-        compile(fragmento);
>+        compile(tm);
>     }
> 
>     if (fragmento->assm()->error() != nanojit::None)
>@@ -2610,29 +2686,29 @@
>         } 
>     }
> 
>-    debug_only_v(js_DumpPeerStability(fragmento, peer_root->ip);)
>+    debug_only_v(js_DumpPeerStability(traceMonitor, peer_root->ip);)
> }
> 
> /* Emit an always-exit guard and compile the tree (used for break statements. */
> JS_REQUIRES_STACK void
>-TraceRecorder::endLoop(Fragmento* fragmento)
>+TraceRecorder::endLoop(JSTraceMonitor* tm)
> {
>     LIns* exitIns = snapshot(LOOP_EXIT);
> 
>     if (callDepth != 0) {
>         debug_only_v(printf("Stack depth mismatch, possible recursion\n");)
>-        js_BlacklistPC(fragmento, fragment);
>+        js_BlacklistPC(tm, fragment);
>         trashSelf = true;
>         return;
>     }
> 
>     fragment->lastIns = lir->insGuard(LIR_x, lir->insImm(1), exitIns);
>-    compile(fragmento);
>-
>-    if (fragmento->assm()->error() != nanojit::None)
>-        return;
>-
>-    joinEdgesToEntry(fragmento, fragmento->getLoop(fragment->root->ip));
>+    compile(tm);
>+
>+    if (tm->fragmento->assm()->error() != nanojit::None)
>+        return;
>+
>+    joinEdgesToEntry(tm->fragmento, getLoop(tm, fragment->root->ip));
> 
>     debug_only_v(printf("recording completed at %s:%u@%u via endLoop\n",
>                         cx->fp->script->filename,
>@@ -3148,7 +3224,7 @@
>     while (f->code() && f->peer)
>         f = f->peer;
>     if (f->code())
>-        f = JS_TRACE_MONITOR(cx).fragmento->getAnchor(f->root->ip);
>+        f = getAnchor(&JS_TRACE_MONITOR(cx), f->root->ip);
> 
>     f->recordAttempts++;
>     f->root = f;
>@@ -3174,7 +3250,7 @@
>        since we are trying to stabilize something without properly connecting peer edges. */
>     #ifdef DEBUG
>     TreeInfo* ti_other;
>-    for (Fragment* peer = tm->fragmento->getLoop(f->root->ip); peer != NULL; peer = peer->peer) {
>+    for (Fragment* peer = getLoop(tm, f->root->ip); peer != NULL; peer = peer->peer) {
>         if (!peer->code() || peer == f)
>             continue;
>         ti_other = (TreeInfo*)peer->vmprivate;
>@@ -3348,7 +3424,7 @@
> 
>     bool demote;
>     Fragment* f = r->getFragment();
>-    r->closeLoop(fragmento, demote);
>+    r->closeLoop(tm, demote);
>     js_DeleteRecorder(cx);
>     
>     /*
>@@ -3369,7 +3445,7 @@
>         return false; /* we stay away from shared global objects */
>     }
> #endif
>-    Fragmento* fragmento = JS_TRACE_MONITOR(cx).fragmento;
>+    JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx);
>     /* Process deep abort requests. */
>     if (r->wasDeepAborted()) {
>         js_AbortRecording(cx, "deep abort requested");
>@@ -3379,10 +3455,9 @@
>     if (r->isLoopHeader(cx))
>         return js_CloseLoop(cx);
>     /* does this branch go to an inner loop? */
>-    Fragment* f = fragmento->getLoop(cx->fp->regs->pc);
>+    Fragment* f = getLoop(&JS_TRACE_MONITOR(cx), cx->fp->regs->pc);
>     Fragment* peer_root = f;
>     if (nesting_enabled && f) {
>-        JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx);
>         
>         /* Make sure inner tree call will not run into an out-of-memory condition. */
>         if (tm->reservedDoublePoolPtr < (tm->reservedDoublePool + MAX_NATIVE_STACK_SLOTS) &&
>@@ -3416,7 +3491,7 @@
>             AUDIT(noCompatInnerTrees);
>             debug_only_v(printf("No compatible inner tree (%p).\n", f);)
> 
>-            Fragment* old = fragmento->getLoop(tm->recorder->getFragment()->root->ip);
>+            Fragment* old = getLoop(tm, tm->recorder->getFragment()->root->ip);
>             if (old == NULL)
>                 old = tm->recorder->getFragment();
>             js_AbortRecording(cx, "No compatible inner tree");
>@@ -3424,7 +3499,7 @@
>                 return false;
>             if (old->recordAttempts < MAX_MISMATCH)
>                 oracle.resetHits(old->ip);
>-            f = empty ? empty : tm->fragmento->getAnchor(cx->fp->regs->pc);
>+            f = empty ? empty : getAnchor(tm, cx->fp->regs->pc);
>             return js_RecordTree(cx, tm, f, old);
>         }
> 
>@@ -3448,13 +3523,13 @@
>             return true;
>         case UNSTABLE_LOOP_EXIT:
>             /* abort recording so the inner loop can become type stable. */
>-            old = fragmento->getLoop(tm->recorder->getFragment()->root->ip);
>+            old = getLoop(tm, tm->recorder->getFragment()->root->ip);
>             js_AbortRecording(cx, "Inner tree is trying to stabilize, abort outer recording");
>             oracle.resetHits(old->ip);
>             return js_AttemptToStabilizeTree(cx, lr, old);
>         case BRANCH_EXIT:
>             /* abort recording the outer tree, extend the inner tree */
>-            old = fragmento->getLoop(tm->recorder->getFragment()->root->ip);
>+            old = getLoop(tm, tm->recorder->getFragment()->root->ip);
>             js_AbortRecording(cx, "Inner tree is trying to grow, abort outer recording");
>             oracle.resetHits(old->ip);
>             return js_AttemptToExtendTree(cx, lr, NULL, old);
>@@ -3943,11 +4018,9 @@
>         return false;
>     }
> 
>-    Fragmento* fragmento = tm->fragmento;
>-    Fragment* f;
>-    f = fragmento->getLoop(pc);
>+    Fragment* f = getLoop(tm, pc);
>     if (!f)
>-        f = fragmento->getAnchor(pc);
>+        f = getAnchor(tm, pc);
> 
>     /* If we have no code in the anchor and no peers, we definitively won't be able to 
>        activate any trees so, start compiling. */
>@@ -4033,7 +4106,7 @@
>             jssrcnote* sn = js_GetSrcNote(cx->fp->script, pc);
>             if (sn && SN_TYPE(sn) == SRC_BREAK) {
>                 AUDIT(breakLoopExits);
>-                tr->endLoop(JS_TRACE_MONITOR(cx).fragmento);
>+                tr->endLoop(&JS_TRACE_MONITOR(cx));
>                 js_DeleteRecorder(cx);
>                 return JSMRS_STOP; /* done recording */
>             }
>@@ -4042,7 +4115,7 @@
>         /* An explicit return from callDepth 0 should end the loop, not abort it. */
>         if (*pc == JSOP_RETURN && tr->callDepth == 0) {
>             AUDIT(returnLoopExits);
>-            tr->endLoop(JS_TRACE_MONITOR(cx).fragmento);
>+            tr->endLoop(&JS_TRACE_MONITOR(cx));
>             js_DeleteRecorder(cx);
>             return JSMRS_STOP; /* done recording */
>         }
>@@ -4100,10 +4173,10 @@
> 
> /* If used on a loop trace, blacklists the root peer instead of the given fragment. */
> void
>-js_BlacklistPC(Fragmento* frago, Fragment* frag)
>+js_BlacklistPC(JSTraceMonitor* tm, Fragment* frag)
> {
>     if (frag->kind == LoopTrace)
>-        frag = frago->getLoop(frag->ip);
>+        frag = getLoop(tm, frag->ip);
>     oracle.blacklist(frag->ip);
> }
> 
>@@ -4128,11 +4201,11 @@
>         return;
>     }
>     JS_ASSERT(!f->vmprivate);
>-    js_BlacklistPC(tm->fragmento, f);
>+    js_BlacklistPC(tm, f);
>     Fragment* outer = tm->recorder->getOuterToBlacklist();
>     /* Give outer two chances to stabilize before we start blacklisting. */
>     if (outer != NULL && outer->recordAttempts >= 2)
>-        js_BlacklistPC(tm->fragmento, outer);
>+        js_BlacklistPC(tm, outer);
>     js_DeleteRecorder(cx);
>     /* If this is the primary trace and we didn't succeed compiling, trash the TreeInfo object. */
>     if (!f->code() && (f->root == f)) 
>@@ -4200,6 +4273,7 @@
> #endif
>         tm->globalSlots = new (&gc) SlotList();
>         tm->reservedDoublePoolPtr = tm->reservedDoublePool = new jsval[MAX_NATIVE_STACK_SLOTS];
>+        memset(tm->fragmentBuckets, 0, sizeof(tm->fragmentBuckets));
>     }
>     if (!tm->reFragmento) {
>         Fragmento* fragmento = new (&gc) Fragmento(core, 20);
>@@ -4307,6 +4381,7 @@
>         fragmento->labels = new (&gc) LabelMap(core, NULL);
> #endif
>         tm->lirbuf->rewind();
>+        memset(tm->fragmentBuckets, 0, sizeof(tm->fragmentBuckets));
>     }
>     if (cx->fp) {
>         tm->globalShape = OBJ_SHAPE(JS_GetGlobalForObject(cx, cx->fp->scopeChain));
>@@ -8774,14 +8849,14 @@
> #ifdef JS_JIT_SPEW
> /* Prints information about entry typemaps and unstable exits for all peers at a PC */
> void
>-js_DumpPeerStability(Fragmento* frago, const void* ip)
>+js_DumpPeerStability(JSTraceMonitor* tm, const void* ip)
> {
>     Fragment* f;
>     TreeInfo* ti;
>     bool looped = false;
>     unsigned length = 0;
> 
>-    for (f = frago->getLoop(ip); f != NULL; f = f->peer) {
>+    for (f = getLoop(tm, ip); f != NULL; f = f->peer) {
>         if (!f->vmprivate)
>             continue;
>         printf("fragment %p:\nENTRY: ", f);
>diff -r 5728b9d90bcd js/src/jstracer.h
>--- a/js/src/jstracer.h	Thu Jan 22 18:15:57 2009 -0800
>+++ b/js/src/jstracer.h	Thu Jan 22 19:52:30 2009 -0800
>@@ -496,9 +496,9 @@
>     JS_REQUIRES_STACK nanojit::LIns* snapshot(ExitType exitType);
>     nanojit::Fragment* getFragment() const { return fragment; }
>     JS_REQUIRES_STACK bool isLoopHeader(JSContext* cx) const;
>-    JS_REQUIRES_STACK void compile(nanojit::Fragmento* fragmento);
>-    JS_REQUIRES_STACK bool closeLoop(nanojit::Fragmento* fragmento, bool& demote);
>-    JS_REQUIRES_STACK void endLoop(nanojit::Fragmento* fragmento);
>+    JS_REQUIRES_STACK void compile(JSTraceMonitor* tm);
>+    JS_REQUIRES_STACK bool closeLoop(JSTraceMonitor* tm, bool& demote);
>+    JS_REQUIRES_STACK void endLoop(JSTraceMonitor* tm);
>     JS_REQUIRES_STACK void joinEdgesToEntry(nanojit::Fragmento* fragmento,
>                                             nanojit::Fragment* peer_root);
>     void blacklist() { fragment->blacklist(); }
>diff -r 5728b9d90bcd js/src/nanojit/avmplus.h
>--- a/js/src/nanojit/avmplus.h	Thu Jan 22 18:15:57 2009 -0800
>+++ b/js/src/nanojit/avmplus.h	Thu Jan 22 19:52:30 2009 -0800
>@@ -157,6 +157,14 @@
>         {
>             return calloc(1, size);
>         }
>+        
>+        inline void*
>+        operator new(size_t size, char* c)
>+        {
>+          // We use placement-new in LIR buffers sometimes.
>+          memset(c, 0, size);
>+          return c;
>+        }

Ugly like sin.

>     
>         static void operator delete (void *gcObject)
>         {
Attached patch Round twoSplinter Review
This variant incorporates the suggestions for OOM handling and use of a VMFragment subclass, removing the separate FragmentBucket type, and shrinks the hashtable size from 4095 buckets to 512. Ignores (for now) shaver's suggestion of calculating the global for a scope only once. 

With these changes we seem to be ahead of trunk by a small margin: 1590ms -> 1580ms avg on a mac doing sunspider, 25s -> 22s on linux machine doing trace-test.

I think I'm getting happy with the patch. Any further suggestions?
Attachment #358326 - Attachment is obsolete: true
Attachment #358964 - Flags: review?(gal)
Looks good. I still hate the new placement. We can do the regexp's jits fragment lookup in a separate patch.
Attachment #358964 - Flags: review?(gal) → review+
This is also pretty high risk, so needs a clean tree to go in.
Keywords: checkin-needed
http://hg.mozilla.org/tracemonkey/rev/72dd6ee4baf7
Status: NEW → RESOLVED
Closed: 15 years ago
Resolution: --- → FIXED
Flags: in-testsuite+
Flags: in-litmus-
Flags: in-testsuite+ → in-testsuite-
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: