Remove JSOP_TRAP, bug 707454. r=jorendorff
authorBrian Hackett <bhackett1024@gmail.com>
Wed, 07 Dec 2011 13:15:48 -0800
changeset 83847 dfd8e10f71559d06f46b438e2ac166fab0e79541
parent 83846 ae823406bcb3ff98e5c36ccd97018aa420e6db82
child 83848 31b6da96f5e03574f666b908727575f158fa4f9f
push id519
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 00:38:35 +0000
treeherdermozilla-beta@788ea1ef610b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs707454
milestone11.0a1
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
Remove JSOP_TRAP, bug 707454. r=jorendorff
js/src/jsanalyze.cpp
js/src/jsanalyze.h
js/src/jscntxt.cpp
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsdbgapi.cpp
js/src/jsdbgapi.h
js/src/jsgcmark.cpp
js/src/jsinfer.cpp
js/src/jsinterp.cpp
js/src/jsobj.cpp
js/src/jsopcode.cpp
js/src/jsopcode.h
js/src/jsopcode.tbl
js/src/jsopcodeinlines.h
js/src/jspropertycache.cpp
js/src/jsprvtd.h
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jsscriptinlines.h
js/src/jsstr.cpp
js/src/methodjit/Compiler.cpp
js/src/methodjit/FrameState.cpp
js/src/methodjit/InvokeHelpers.cpp
js/src/methodjit/LoopState.cpp
js/src/methodjit/PolyIC.cpp
js/src/methodjit/Retcon.cpp
js/src/methodjit/Retcon.h
js/src/methodjit/StubCalls.cpp
js/src/shell/js.cpp
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/Stack.cpp
--- a/js/src/jsanalyze.cpp
+++ b/js/src/jsanalyze.cpp
@@ -85,19 +85,16 @@ ScriptAnalysis::addJump(JSContext *cx, u
         }
         code->stackDepth = stackDepth;
     }
     JS_ASSERT(code->stackDepth == stackDepth);
 
     code->jumpTarget = true;
 
     if (offset < *currentOffset) {
-        jsbytecode *pc = script->code + offset;
-        UntrapOpcode untrap(cx, script, pc);
-
         /* Scripts containing loops are never inlined. */
         isInlineable = false;
 
         /* Don't follow back edges to bytecode which has already been analyzed. */
         if (!code->analyzed) {
             if (*forwardJump == 0)
                 *forwardJump = *currentOffset;
             *currentOffset = offset;
@@ -277,18 +274,16 @@ ScriptAnalysis::analyzeBytecode(JSContex
         if (forwardJump && forwardJump == offset)
             forwardJump = 0;
         if (forwardCatch && forwardCatch == offset)
             forwardCatch = 0;
 
         Bytecode *code = maybeCode(offset);
         jsbytecode *pc = script->code + offset;
 
-        UntrapOpcode untrap(cx, script, pc);
-
         JSOp op = (JSOp)*pc;
         JS_ASSERT(op < JSOP_LIMIT);
 
         /* Immediate successor of this bytecode. */
         unsigned successorOffset = offset + GetBytecodeLength(pc);
 
         /*
          * Next bytecode to analyze.  This is either the successor, or is an
@@ -306,17 +301,17 @@ ScriptAnalysis::analyzeBytecode(JSContex
             continue;
         }
 
         code->analyzed = true;
 
         if (forwardCatch)
             code->inTryBlock = true;
 
-        if (untrap.trap) {
+        if (script->hasBreakpointsAt(pc)) {
             code->safePoint = true;
             isInlineable = canTrackVars = false;
         }
 
         unsigned stackDepth = code->stackDepth;
 
         if (!forwardJump)
             code->unconditional = true;
@@ -658,17 +653,16 @@ ScriptAnalysis::analyzeLifetimes(JSConte
             offset--;
             continue;
         }
 
         if (loop && code->safePoint)
             loop->hasSafePoints = true;
 
         jsbytecode *pc = script->code + offset;
-        UntrapOpcode untrap(cx, script, pc);
 
         JSOp op = (JSOp) *pc;
 
         if (op == JSOP_LOOPHEAD && code->loop) {
             /*
              * This is the head of a loop, we need to go and make sure that any
              * variables live at the head are live at the backedge and points prior.
              * For each such variable, look for the last lifetime segment in the body
@@ -809,17 +803,17 @@ ScriptAnalysis::analyzeLifetimes(JSConte
             if (loop && loop->entry == targetOffset && loop->entry > loop->lastBlock)
                 loop->lastBlock = loop->entry;
 
             if (targetOffset < offset) {
                 /* This is a loop back edge, no lifetime to pull in yet. */
 
 #ifdef DEBUG
                 JSOp nop = JSOp(script->code[targetOffset]);
-                JS_ASSERT(nop == JSOP_LOOPHEAD || nop == JSOP_TRAP);
+                JS_ASSERT(nop == JSOP_LOOPHEAD);
 #endif
 
                 /*
                  * If we already have a loop, it is an outer loop and we
                  * need to prune the last block in the loop --- we do not
                  * track 'continue' statements for outer loops.
                  */
                 if (loop && loop->entry > loop->lastBlock)
@@ -850,17 +844,16 @@ ScriptAnalysis::analyzeLifetimes(JSConte
                  */
                 uint32 entry = targetOffset;
                 if (entry) {
                     do {
                         entry--;
                     } while (!maybeCode(entry));
 
                     jsbytecode *entrypc = script->code + entry;
-                    UntrapOpcode untrap(cx, script, entrypc);
 
                     if (JSOp(*entrypc) == JSOP_GOTO || JSOp(*entrypc) == JSOP_GOTOX)
                         loop->entry = entry + GetJumpOffset(entrypc, entrypc);
                     else
                         loop->entry = targetOffset;
                 } else {
                     /* Do-while loop at the start of the script. */
                     loop->entry = targetOffset;
@@ -1151,17 +1144,16 @@ ScriptAnalysis::analyzeSSA(JSContext *cx
      * pending entries at these targets for the original value of variables
      * modified before the branch rejoins.
      */
     Vector<uint32> branchTargets(cx);
 
     uint32 offset = 0;
     while (offset < script->length) {
         jsbytecode *pc = script->code + offset;
-        UntrapOpcode untrap(cx, script, pc);
         JSOp op = (JSOp)*pc;
 
         uint32 successorOffset = offset + GetBytecodeLength(pc);
 
         Bytecode *code = maybeCode(pc);
         if (!code) {
             offset = successorOffset;
             continue;
@@ -1744,17 +1736,16 @@ CrossScriptSSA::foldValue(const CrossSSA
             uint32 argc = GET_ARGC(frame.parentpc);
             SSAValue argv = parentAnalysis->poppedValue(frame.parentpc, argc - 1 - (slot - ArgSlot(0)));
             return foldValue(CrossSSAValue(frame.parent, argv));
         }
     }
 
     if (v.kind() == SSAValue::PUSHED) {
         jsbytecode *pc = frame.script->code + v.pushedOffset();
-        UntrapOpcode untrap(cx, frame.script, pc);
 
         switch (JSOp(*pc)) {
           case JSOP_THIS:
             if (parentScript) {
                 uint32 argc = GET_ARGC(frame.parentpc);
                 SSAValue thisv = parentAnalysis->poppedValue(frame.parentpc, argc);
                 return foldValue(CrossSSAValue(frame.parent, thisv));
             }
@@ -1775,17 +1766,16 @@ CrossScriptSSA::foldValue(const CrossSSA
                     calleeFrame = iterFrame(i).index;
                 }
             }
             if (callee && callee->analysis()->numReturnSites() == 1) {
                 ScriptAnalysis *analysis = callee->analysis();
                 uint32 offset = 0;
                 while (offset < callee->length) {
                     jsbytecode *pc = callee->code + offset;
-                    UntrapOpcode untrap(cx, callee, pc);
                     if (analysis->maybeCode(pc) && JSOp(*pc) == JSOP_RETURN)
                         return foldValue(CrossSSAValue(calleeFrame, analysis->poppedValue(pc, 0)));
                     offset += GetBytecodeLength(pc);
                 }
             }
             break;
           }
 
@@ -1834,17 +1824,16 @@ ScriptAnalysis::printSSA(JSContext *cx)
     printf("\n");
 
     for (unsigned offset = 0; offset < script->length; offset++) {
         Bytecode *code = maybeCode(offset);
         if (!code)
             continue;
 
         jsbytecode *pc = script->code + offset;
-        UntrapOpcode untrap(cx, script, pc);
 
         PrintBytecode(cx, script, pc);
 
         SlotValue *newv = code->newValues;
         if (newv) {
             while (newv->slot) {
                 if (newv->value.kind() != SSAValue::PHI || newv->value.phiOffset() != offset) {
                     newv++;
--- a/js/src/jsanalyze.h
+++ b/js/src/jsanalyze.h
@@ -193,33 +193,20 @@ class Bytecode
     /* Types for all values pushed by this bytecode. */
     types::TypeSet *pushedTypes;
 
     /* Any type barriers in place at this bytecode. */
     types::TypeBarrier *typeBarriers;
 };
 
 static inline unsigned
-GetBytecodeLength(jsbytecode *pc)
-{
-    JSOp op = (JSOp)*pc;
-    JS_ASSERT(op < JSOP_LIMIT);
-    JS_ASSERT(op != JSOP_TRAP);
-
-    if (js_CodeSpec[op].length != -1)
-        return js_CodeSpec[op].length;
-    return js_GetVariableBytecodeLength(pc);
-}
-
-static inline unsigned
 GetDefCount(JSScript *script, unsigned offset)
 {
     JS_ASSERT(offset < script->length);
     jsbytecode *pc = script->code + offset;
-    JS_ASSERT(JSOp(*pc) != JSOP_TRAP);
 
     if (js_CodeSpec[*pc].ndefs == -1)
         return js_GetEnterBlockStackDefs(NULL, script, pc);
 
     /*
      * Add an extra pushed value for OR/AND opcodes, so that they are included
      * in the pushed array of stack values for type inference.
      */
@@ -244,34 +231,31 @@ GetDefCount(JSScript *script, unsigned o
     }
 }
 
 static inline unsigned
 GetUseCount(JSScript *script, unsigned offset)
 {
     JS_ASSERT(offset < script->length);
     jsbytecode *pc = script->code + offset;
-    JS_ASSERT(JSOp(*pc) != JSOP_TRAP);
 
     if (JSOp(*pc) == JSOP_PICK)
         return (pc[1] + 1);
     if (js_CodeSpec[*pc].nuses == -1)
         return js_GetVariableStackUses(JSOp(*pc), pc);
     return js_CodeSpec[*pc].nuses;
 }
 
 /*
  * For opcodes which assign to a local variable or argument, track an extra def
  * during SSA analysis for the value's use chain and assigned type.
  */
 static inline bool
 ExtendedDef(jsbytecode *pc)
 {
-    JS_ASSERT(JSOp(*pc) != JSOP_TRAP);
-
     switch ((JSOp)*pc) {
       case JSOP_SETARG:
       case JSOP_INCARG:
       case JSOP_DECARG:
       case JSOP_ARGINC:
       case JSOP_ARGDEC:
       case JSOP_SETLOCAL:
       case JSOP_SETLOCALPOP:
@@ -305,18 +289,16 @@ ExtendedUse(jsbytecode *pc)
       default:
         return false;
     }
 }
 
 static inline ptrdiff_t
 GetJumpOffset(jsbytecode *pc, jsbytecode *pc2)
 {
-    JS_ASSERT(JSOp(*pc) != JSOP_TRAP);
-
     uint32 type = JOF_OPTYPE(*pc);
     if (JOF_TYPE_IS_EXTENDED_JUMP(type))
         return GET_JUMPX_OFFSET(pc2);
     return GET_JUMP_OFFSET(pc2);
 }
 
 static inline JSOp
 ReverseCompareOp(JSOp op)
@@ -331,56 +313,28 @@ ReverseCompareOp(JSOp op)
       case JSOP_LE:
         return JSOP_GE;
       default:
         JS_NOT_REACHED("unrecognized op");
         return op;
     }
 }
 
-/* Untrap a single PC, and retrap it at scope exit. */
-struct UntrapOpcode
-{
-    jsbytecode *pc;
-    bool trap;
-
-    UntrapOpcode(JSContext *cx, JSScript *script, jsbytecode *pc)
-        : pc(pc), trap(JSOp(*pc) == JSOP_TRAP)
-    {
-        if (trap)
-            *pc = JS_GetTrapOpcode(cx, script, pc);
-    }
-
-    void retrap()
-    {
-        if (trap) {
-            *pc = JSOP_TRAP;
-            trap = false;
-        }
-    }
-
-    ~UntrapOpcode()
-    {
-        retrap();
-    }
-};
-
 static inline unsigned
 FollowBranch(JSContext *cx, JSScript *script, unsigned offset)
 {
     /*
      * Get the target offset of a branch. For GOTO opcodes implementing
      * 'continue' statements, short circuit any artificial backwards jump
      * inserted by the emitter.
      */
     jsbytecode *pc = script->code + offset;
     unsigned targetOffset = offset + GetJumpOffset(pc, pc);
     if (targetOffset < offset) {
         jsbytecode *target = script->code + targetOffset;
-        UntrapOpcode untrap(cx, script, target);
         JSOp nop = JSOp(*target);
         if (nop == JSOP_GOTO || nop == JSOP_GOTOX)
             return targetOffset + GetJumpOffset(target, target);
     }
     return targetOffset;
 }
 
 /* Common representation of slots throughout analyses and the compiler. */
@@ -966,42 +920,40 @@ class ScriptAnalysis
     }
 
     bool incrementInitialValueObserved(jsbytecode *pc) {
         const JSCodeSpec *cs = &js_CodeSpec[*pc];
         return (cs->format & JOF_POST) && !popGuaranteed(pc);
     }
 
     types::TypeSet *bytecodeTypes(const jsbytecode *pc) {
-        JS_ASSERT(JSOp(*pc) == JSOP_TRAP || (js_CodeSpec[*pc].format & JOF_TYPESET));
+        JS_ASSERT(js_CodeSpec[*pc].format & JOF_TYPESET);
         return getCode(pc).observedTypes;
     }
 
     const SSAValue &poppedValue(uint32 offset, uint32 which) {
         JS_ASSERT(offset < script->length);
-        JS_ASSERT_IF(script->code[offset] != JSOP_TRAP,
-                     which < GetUseCount(script, offset) +
-                     (ExtendedUse(script->code + offset) ? 1 : 0));
+        JS_ASSERT(which < GetUseCount(script, offset) +
+                  (ExtendedUse(script->code + offset) ? 1 : 0));
         return getCode(offset).poppedValues[which];
     }
     const SSAValue &poppedValue(const jsbytecode *pc, uint32 which) {
         return poppedValue(pc - script->code, which);
     }
 
     const SlotValue *newValues(uint32 offset) {
         JS_ASSERT(offset < script->length);
         return getCode(offset).newValues;
     }
     const SlotValue *newValues(const jsbytecode *pc) { return newValues(pc - script->code); }
 
     types::TypeSet *pushedTypes(uint32 offset, uint32 which = 0) {
         JS_ASSERT(offset < script->length);
-        JS_ASSERT_IF(script->code[offset] != JSOP_TRAP,
-                     which < GetDefCount(script, offset) +
-                     (ExtendedDef(script->code + offset) ? 1 : 0));
+        JS_ASSERT(which < GetDefCount(script, offset) +
+                  (ExtendedDef(script->code + offset) ? 1 : 0));
         types::TypeSet *array = getCode(offset).pushedTypes;
         JS_ASSERT(array);
         return array + which;
     }
     types::TypeSet *pushedTypes(const jsbytecode *pc, uint32 which) {
         return pushedTypes(pc - script->code, which);
     }
 
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -612,17 +612,17 @@ js_DestroyContext(JSContext *cx, JSDestr
                     c->types.print(cx, false);
             }
 
             /* Unpin all common atoms before final GC. */
             js_FinishCommonAtoms(cx);
 
             /* Clear debugging state to remove GC roots. */
             for (CompartmentsIter c(rt); !c.done(); c.next())
-                c->clearTraps(cx, NULL);
+                c->clearTraps(cx);
             JS_ClearAllWatchPoints(cx);
         }
 
 #ifdef JS_THREADSAFE
         /*
          * Destroying a context implicitly calls JS_EndRequest().  Also, we must
          * end our request here in case we are "last" -- in that event, another
          * js_DestroyContext that was not last might be waiting in the GC for our
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -82,17 +82,16 @@ JSCompartment::JSCompartment(JSRuntime *
     hasDebugModeCodeToDrop(false),
 #ifdef JS_METHODJIT
     jaegerCompartment_(NULL),
 #endif
     propertyTree(thisForCtor()),
     emptyTypeObject(NULL),
     debugModeBits(rt->debugMode ? DebugFromC : 0),
     mathCache(NULL),
-    breakpointSites(rt),
     watchpointMap(NULL)
 {
     PodArrayZero(evalCache);
 }
 
 JSCompartment::~JSCompartment()
 {
 #ifdef JS_METHODJIT
@@ -117,17 +116,17 @@ JSCompartment::init(JSContext *cx)
     newObjectCache.reset();
 
     if (!crossCompartmentWrappers.init())
         return false;
 
     if (!scriptFilenameTable.init())
         return false;
 
-    return debuggees.init() && breakpointSites.init();
+    return debuggees.init();
 }
 
 #ifdef JS_METHODJIT
 bool
 JSCompartment::ensureJaegerCompartmentExists(JSContext *cx)
 {
     if (jaegerCompartment_)
         return true;
@@ -682,117 +681,60 @@ JSCompartment::removeDebuggee(JSContext 
 
     if (debuggees.empty()) {
         debugModeBits &= ~DebugFromJS;
         if (wasEnabled && !debugMode())
             updateForDebugMode(cx);
     }
 }
 
-BreakpointSite *
-JSCompartment::getBreakpointSite(jsbytecode *pc)
-{
-    BreakpointSiteMap::Ptr p = breakpointSites.lookup(pc);
-    return p ? p->value : NULL;
-}
-
-BreakpointSite *
-JSCompartment::getOrCreateBreakpointSite(JSContext *cx, JSScript *script, jsbytecode *pc,
-                                         GlobalObject *scriptGlobal)
+void
+JSCompartment::clearBreakpointsIn(JSContext *cx, js::Debugger *dbg, JSObject *handler)
 {
-    JS_ASSERT(script->code <= pc);
-    JS_ASSERT(pc < script->code + script->length);
-
-    BreakpointSiteMap::AddPtr p = breakpointSites.lookupForAdd(pc);
-    if (!p) {
-        BreakpointSite *site = cx->runtime->new_<BreakpointSite>(script, pc);
-        if (!site || !breakpointSites.add(p, pc, site)) {
-            js_ReportOutOfMemory(cx);
-            return NULL;
-        }
-    }
-
-    BreakpointSite *site = p->value;
-    if (site->scriptGlobal)
-        JS_ASSERT_IF(scriptGlobal, site->scriptGlobal == scriptGlobal);
-    else
-        site->scriptGlobal = scriptGlobal;
-
-    return site;
-}
-
-void
-JSCompartment::clearBreakpointsIn(JSContext *cx, js::Debugger *dbg, JSScript *script,
-                                  JSObject *handler)
-{
-    JS_ASSERT_IF(script, script->compartment() == this);
-
-    for (BreakpointSiteMap::Enum e(breakpointSites); !e.empty(); e.popFront()) {
-        BreakpointSite *site = e.front().value;
-        if (!script || site->script == script) {
-            Breakpoint *nextbp;
-            for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = nextbp) {
-                nextbp = bp->nextInSite();
-                if ((!dbg || bp->debugger == dbg) && (!handler || bp->getHandler() == handler))
-                    bp->destroy(cx, &e);
-            }
-        }
+    for (gc::CellIter i(cx, this, gc::FINALIZE_SCRIPT); !i.done(); i.next()) {
+        JSScript *script = i.get<JSScript>();
+        if (script->hasAnyBreakpointsOrStepMode())
+            script->clearBreakpointsIn(cx, dbg, handler);
     }
 }
 
 void
-JSCompartment::clearTraps(JSContext *cx, JSScript *script)
-{
-    for (BreakpointSiteMap::Enum e(breakpointSites); !e.empty(); e.popFront()) {
-        BreakpointSite *site = e.front().value;
-        if (!script || site->script == script)
-            site->clearTrap(cx, &e);
-    }
-}
-
-bool
-JSCompartment::markTrapClosuresIteratively(JSTracer *trc)
+JSCompartment::clearTraps(JSContext *cx)
 {
-    bool markedAny = false;
-    JSContext *cx = trc->context;
-    for (BreakpointSiteMap::Range r = breakpointSites.all(); !r.empty(); r.popFront()) {
-        BreakpointSite *site = r.front().value;
-
-        // Put off marking trap state until we know the script is live.
-        if (site->trapHandler && !IsAboutToBeFinalized(cx, site->script)) {
-            if (site->trapClosure.isMarkable() &&
-                IsAboutToBeFinalized(cx, site->trapClosure))
-            {
-                markedAny = true;
-            }
-            MarkValue(trc, site->trapClosure, "trap closure");
-        }
+    for (gc::CellIter i(cx, this, gc::FINALIZE_SCRIPT); !i.done(); i.next()) {
+        JSScript *script = i.get<JSScript>();
+        if (script->hasAnyBreakpointsOrStepMode())
+            script->clearTraps(cx);
     }
-    return markedAny;
 }
 
 void
 JSCompartment::sweepBreakpoints(JSContext *cx)
 {
-    for (BreakpointSiteMap::Enum e(breakpointSites); !e.empty(); e.popFront()) {
-        BreakpointSite *site = e.front().value;
-        // clearTrap and nextbp are necessary here to avoid possibly
-        // reading *site or *bp after destroying it.
-        bool scriptGone = IsAboutToBeFinalized(cx, site->script);
-        bool clearTrap = scriptGone && site->hasTrap();
-        
-        Breakpoint *nextbp;
-        for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = nextbp) {
-            nextbp = bp->nextInSite();
-            if (scriptGone || IsAboutToBeFinalized(cx, bp->debugger->toJSObject()))
-                bp->destroy(cx, &e);
+    if (JS_CLIST_IS_EMPTY(&cx->runtime->debuggerList))
+        return;
+
+    for (CellIterUnderGC i(this, FINALIZE_SCRIPT); !i.done(); i.next()) {
+        JSScript *script = i.get<JSScript>();
+        if (!script->hasAnyBreakpointsOrStepMode())
+            continue;
+        bool scriptGone = IsAboutToBeFinalized(cx, script);
+        for (unsigned i = 0; i < script->length; i++) {
+            BreakpointSite *site = script->getBreakpointSite(script->code + i);
+            if (!site)
+                continue;
+            // nextbp is necessary here to avoid possibly reading *bp after
+            // destroying it.
+            Breakpoint *nextbp;
+            for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = nextbp) {
+                nextbp = bp->nextInSite();
+                if (scriptGone || IsAboutToBeFinalized(cx, bp->debugger->toJSObject()))
+                    bp->destroy(cx);
+            }
         }
-        
-        if (clearTrap)
-            site->clearTrap(cx, &e);
     }
 }
 
 GCMarker *
 JSCompartment::createBarrierTracer()
 {
     JS_ASSERT(!gcIncrementalTracer);
     return NULL;
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -318,19 +318,16 @@ struct JS_FRIEND_API(JSCompartment) {
     js::MathCache *allocMathCache(JSContext *cx);
 
     /*
      * Weak reference to each global in this compartment that is a debuggee.
      * Each global has its own list of debuggers.
      */
     js::GlobalObjectSet              debuggees;
 
-  public:
-    js::BreakpointSiteMap            breakpointSites;
-
   private:
     JSCompartment *thisForCtor() { return this; }
 
   public:
     js::MathCache *getMathCache(JSContext *cx) {
         return mathCache ? mathCache : allocMathCache(cx);
     }
 
@@ -356,22 +353,18 @@ struct JS_FRIEND_API(JSCompartment) {
 
   public:
     js::GlobalObjectSet &getDebuggees() { return debuggees; }
     bool addDebuggee(JSContext *cx, js::GlobalObject *global);
     void removeDebuggee(JSContext *cx, js::GlobalObject *global,
                         js::GlobalObjectSet::Enum *debuggeesEnum = NULL);
     bool setDebugModeFromC(JSContext *cx, bool b);
 
-    js::BreakpointSite *getBreakpointSite(jsbytecode *pc);
-    js::BreakpointSite *getOrCreateBreakpointSite(JSContext *cx, JSScript *script, jsbytecode *pc,
-                                                  js::GlobalObject *scriptGlobal);
-    void clearBreakpointsIn(JSContext *cx, js::Debugger *dbg, JSScript *script, JSObject *handler);
-    void clearTraps(JSContext *cx, JSScript *script);
-    bool markTrapClosuresIteratively(JSTracer *trc);
+    void clearBreakpointsIn(JSContext *cx, js::Debugger *dbg, JSObject *handler);
+    void clearTraps(JSContext *cx);
 
   private:
     void sweepBreakpoints(JSContext *cx);
 
     js::GCMarker *createBarrierTracer();
 
   public:
     js::WatchpointMap *watchpointMap;
--- a/js/src/jsdbgapi.cpp
+++ b/js/src/jsdbgapi.cpp
@@ -193,57 +193,52 @@ JS_SetSingleStepMode(JSContext *cx, JSSc
         return JS_FALSE;
 
     return script->setStepModeFlag(cx, singleStep);
 }
 
 JS_PUBLIC_API(JSBool)
 JS_SetTrap(JSContext *cx, JSScript *script, jsbytecode *pc, JSTrapHandler handler, jsval closure)
 {
+    assertSameCompartment(cx, script, closure);
+
     if (!CheckDebugMode(cx))
         return false;
 
-    BreakpointSite *site = script->compartment()->getOrCreateBreakpointSite(cx, script, pc, NULL);
+    BreakpointSite *site = script->getOrCreateBreakpointSite(cx, pc, NULL);
     if (!site)
         return false;
     site->setTrap(cx, handler, closure);
     return true;
 }
 
-JS_PUBLIC_API(JSOp)
-JS_GetTrapOpcode(JSContext *cx, JSScript *script, jsbytecode *pc)
-{
-    BreakpointSite *site = script->compartment()->getBreakpointSite(pc);
-    return site ? site->realOpcode : JSOp(*pc);
-}
-
 JS_PUBLIC_API(void)
 JS_ClearTrap(JSContext *cx, JSScript *script, jsbytecode *pc,
              JSTrapHandler *handlerp, jsval *closurep)
 {
-    if (BreakpointSite *site = script->compartment()->getBreakpointSite(pc)) {
-        site->clearTrap(cx, NULL, handlerp, closurep);
+    if (BreakpointSite *site = script->getBreakpointSite(pc)) {
+        site->clearTrap(cx, handlerp, closurep);
     } else {
         if (handlerp)
             *handlerp = NULL;
         if (closurep)
             *closurep = JSVAL_VOID;
     }
 }
 
 JS_PUBLIC_API(void)
 JS_ClearScriptTraps(JSContext *cx, JSScript *script)
 {
-    script->compartment()->clearTraps(cx, script);
+    script->clearTraps(cx);
 }
 
 JS_PUBLIC_API(void)
 JS_ClearAllTrapsForCompartment(JSContext *cx)
 {
-    cx->compartment->clearTraps(cx, NULL);
+    cx->compartment->clearTraps(cx);
 }
 
 JS_PUBLIC_API(JSBool)
 JS_SetInterrupt(JSRuntime *rt, JSInterruptHook hook, void *closure)
 {
     rt->globalDebugHooks.interruptHook = hook;
     rt->globalDebugHooks.interruptHookData = closure;
     return JS_TRUE;
--- a/js/src/jsdbgapi.h
+++ b/js/src/jsdbgapi.h
@@ -142,19 +142,16 @@ JS_SetDebugMode(JSContext *cx, JSBool de
 extern JS_PUBLIC_API(JSBool)
 JS_SetSingleStepMode(JSContext *cx, JSScript *script, JSBool singleStep);
 
 /* The closure argument will be marked. */
 extern JS_PUBLIC_API(JSBool)
 JS_SetTrap(JSContext *cx, JSScript *script, jsbytecode *pc,
            JSTrapHandler handler, jsval closure);
 
-extern JS_PUBLIC_API(JSOp)
-JS_GetTrapOpcode(JSContext *cx, JSScript *script, jsbytecode *pc);
-
 extern JS_PUBLIC_API(void)
 JS_ClearTrap(JSContext *cx, JSScript *script, jsbytecode *pc,
              JSTrapHandler *handlerp, jsval *closurep);
 
 extern JS_PUBLIC_API(void)
 JS_ClearScriptTraps(JSContext *cx, JSScript *script);
 
 extern JS_PUBLIC_API(void)
--- a/js/src/jsgcmark.cpp
+++ b/js/src/jsgcmark.cpp
@@ -954,16 +954,19 @@ MarkChildren(JSTracer *trc, JSScript *sc
 
     if (IS_GC_MARKING_TRACER(trc) && script->filename)
         js_MarkScriptFilename(script->filename);
 
     script->bindings.trace(trc);
 
     if (script->types)
         script->types->trace(trc);
+
+    if (script->hasAnyBreakpointsOrStepMode())
+        script->markTrapClosures(trc);
 }
 
 const Shape *
 MarkShapeChildrenAcyclic(JSTracer *trc, const Shape *shape)
 {
     /*
      * This function is used by the cycle collector to ensure that we use O(1)
      * stack space when building the CC graph. It must avoid traversing through
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -601,17 +601,16 @@ void
 TypeSet::addCallProperty(JSContext *cx, JSScript *script, jsbytecode *pc, jsid id)
 {
     /*
      * For calls which will go through JSOP_NEW, don't add any constraints to
      * modify the 'this' types of callees. The initial 'this' value will be
      * outright ignored.
      */
     jsbytecode *callpc = script->analysis()->getCallPC(pc);
-    UntrapOpcode untrap(cx, script, callpc);
     if (JSOp(*callpc) == JSOP_NEW)
         return;
 
     add(cx, cx->typeLifoAlloc().new_<TypeConstraintCallProp>(script, callpc, id));
 }
 
 /*
  * Constraints for generating 'set' property constraints on a SETELEM only if
@@ -735,17 +734,16 @@ public:
     void newType(JSContext *cx, TypeSet *source, Type type);
 };
 
 void
 TypeSet::addPropagateThis(JSContext *cx, JSScript *script, jsbytecode *pc, Type type, TypeSet *types)
 {
     /* Don't add constraints when the call will be 'new' (see addCallProperty). */
     jsbytecode *callpc = script->analysis()->getCallPC(pc);
-    UntrapOpcode untrap(cx, script, callpc);
     if (JSOp(*callpc) == JSOP_NEW)
         return;
 
     add(cx, cx->typeLifoAlloc().new_<TypeConstraintPropagateThis>(script, callpc, type, types));
 }
 
 /* Subset constraint which filters out primitive types. */
 class TypeConstraintFilterPrimitive : public TypeConstraint
@@ -1062,18 +1060,16 @@ UnknownPropertyAccess(JSScript *script, 
     return type.isUnknown()
         || type.isAnyObject()
         || (!type.isObject() && !script->hasGlobal());
 }
 
 void
 TypeConstraintProp::newType(JSContext *cx, TypeSet *source, Type type)
 {
-    UntrapOpcode untrap(cx, script, pc);
-
     if (UnknownPropertyAccess(script, type)) {
         /*
          * Access on an unknown object. Reads produce an unknown result, writes
          * need to be monitored.
          */
         if (assign)
             cx->compartment->types.monitorBytecode(cx, script, pc - script->code);
         else
@@ -1096,18 +1092,16 @@ TypeConstraintProp::newType(JSContext *c
     TypeObject *object = GetPropertyObject(cx, script, type);
     if (object)
         PropertyAccess(cx, script, pc, object, assign, target, id);
 }
 
 void
 TypeConstraintCallProp::newType(JSContext *cx, TypeSet *source, Type type)
 {
-    UntrapOpcode untrap(cx, script, callpc);
-
     /*
      * For CALLPROP, we need to update not just the pushed types but also the
      * 'this' types of possible callees. If we can't figure out that set of
      * callees, monitor the call to make sure discovered callees get their
      * 'this' types updated.
      */
 
     if (UnknownPropertyAccess(script, type)) {
@@ -2009,17 +2003,16 @@ TypeCompartment::newAllocationSiteTypeOb
 
     TypeObject *res = newTypeObject(cx, key.script, key.kind, proto);
     if (!res) {
         cx->compartment->types.setPendingNukeTypes(cx);
         return NULL;
     }
 
     jsbytecode *pc = key.script->code + key.offset;
-    UntrapOpcode untrap(cx, key.script, pc);
 
     if (JSOp(*pc) == JSOP_NEWOBJECT) {
         /*
          * This object is always constructed the same way and will not be
          * observed by other code before all properties have been added. Mark
          * all the properties as definite properties of the object.
          */
         JSObject *baseobj = key.script->getObject(GET_SLOTNO(pc));
@@ -2057,18 +2050,16 @@ GetScriptConst(JSContext *cx, JSScript *
     return script->getConst(index);
 }
 
 bool
 types::UseNewType(JSContext *cx, JSScript *script, jsbytecode *pc)
 {
     JS_ASSERT(cx->typeInferenceEnabled());
 
-    UntrapOpcode untrap(cx, script, pc);
-
     /*
      * Make a heuristic guess at a use of JSOP_NEW that the constructed object
      * should have a fresh type object. We do this when the NEW is immediately
      * followed by a simple assignment to an object's .prototype field.
      * This is designed to catch common patterns for subclassing in JS:
      *
      * function Super() { ... }
      * function Sub1() { ... }
@@ -2241,17 +2232,16 @@ TypeCompartment::addPendingRecompile(JSC
 void
 TypeCompartment::monitorBytecode(JSContext *cx, JSScript *script, uint32 offset,
                                  bool returnOnly)
 {
     ScriptAnalysis *analysis = script->analysis();
     JS_ASSERT(analysis->ranInference());
 
     jsbytecode *pc = script->code + offset;
-    UntrapOpcode untrap(cx, script, pc);
 
     JS_ASSERT_IF(returnOnly, js_CodeSpec[*pc].format & JOF_INVOKE);
 
     Bytecode &code = analysis->getCode(pc);
 
     if (returnOnly ? code.monitoredTypesReturn : code.monitoredTypes)
         return;
 
@@ -2320,17 +2310,16 @@ TypeCompartment::markSetsUnknown(JSConte
                     typeArray[i].addType(cx, Type::AnyObjectType());
             }
         }
         if (script->hasAnalysis() && script->analysis()->ranInference()) {
             for (unsigned i = 0; i < script->length; i++) {
                 if (!script->analysis()->maybeCode(i))
                     continue;
                 jsbytecode *pc = script->code + i;
-                UntrapOpcode untrap(cx, script, pc);
                 if (js_CodeSpec[*pc].format & JOF_DECOMPOSE)
                     continue;
                 unsigned defCount = GetDefCount(script, i);
                 if (ExtendedDef(pc))
                     defCount++;
                 for (unsigned j = 0; j < defCount; j++) {
                     TypeSet *types = script->analysis()->pushedTypes(pc, j);
                     if (types->hasType(Type::ObjectType(target)))
@@ -3205,18 +3194,16 @@ CheckNextTest(jsbytecode *pc)
 }
 
 static inline TypeObject *
 GetInitializerType(JSContext *cx, JSScript *script, jsbytecode *pc)
 {
     if (!script->hasGlobal())
         return NULL;
 
-    UntrapOpcode untrap(cx, script, pc);
-
     JSOp op = JSOp(*pc);
     JS_ASSERT(op == JSOP_NEWARRAY || op == JSOP_NEWOBJECT || op == JSOP_NEWINIT);
 
     bool isArray = (op == JSOP_NEWARRAY || (op == JSOP_NEWINIT && pc[1] == JSProto_Array));
     return TypeScript::InitObject(cx, script, pc, isArray ? JSProto_Array : JSProto_Object);
 }
 
 /*
@@ -3267,17 +3254,17 @@ ScriptAnalysis::resolveNameAccess(JSCont
          * Don't resolve names in scripts which use 'let' or 'with'. New names
          * bound here can mask variables of the script itself.
          *
          * Also, don't resolve names in scripts which are generators. Frame
          * balancing works differently for generators and we do not maintain
          * active frame counts for such scripts.
          */
         if (script->analysis()->addsScopeObjects() ||
-            js_GetOpcode(cx, script, script->code) == JSOP_GENERATOR) {
+            JSOp(*script->code) == JSOP_GENERATOR) {
             return access;
         }
 
         /* Check if the script definitely binds the identifier. */
         uintN index;
         BindingKind kind = script->bindings.lookup(cx, atom, &index);
         if (kind == ARGUMENT || kind == VARIABLE) {
             TypeObject *obj = script->function()->getType(cx);
@@ -4188,30 +4175,29 @@ ScriptAnalysis::analyzeTypes(JSContext *
         }
 
         /*
          * Don't track for parents which add call objects or are generators,
          * don't resolve NAME accesses into the parent.
          */
         if (!detached &&
             (nesting->parent->analysis()->addsScopeObjects() ||
-             js_GetOpcode(cx, nesting->parent, nesting->parent->code) == JSOP_GENERATOR)) {
+             JSOp(*nesting->parent->code) == JSOP_GENERATOR)) {
             DetachNestingParent(script);
             detached = true;
         }
     }
 
     TypeInferenceState state(cx);
 
     unsigned offset = 0;
     while (offset < script->length) {
         Bytecode *code = maybeCode(offset);
 
         jsbytecode *pc = script->code + offset;
-        UntrapOpcode untrap(cx, script, pc);
 
         if (code && !analyzeTypesBytecode(cx, offset, state)) {
             cx->compartment->types.setPendingNukeTypes(cx);
             return;
         }
 
         offset += GetBytecodeLength(pc);
     }
@@ -4319,17 +4305,16 @@ ScriptAnalysis::followEscapingArguments(
 {
     if (!use->popped)
         return followEscapingArguments(cx, SSAValue::PhiValue(use->offset, use->u.phi), seen);
 
     jsbytecode *pc = script->code + use->offset;
     uint32 which = use->u.which;
 
     JSOp op = JSOp(*pc);
-    JS_ASSERT(op != JSOP_TRAP);
 
     if (op == JSOP_POP || op == JSOP_POPN)
         return true;
 
     /* Allow GETELEM and LENGTH on arguments objects that don't escape. */
 
     /*
      * Note: if the element index is not an integer we will mark the arguments
@@ -4497,17 +4482,16 @@ AnalyzeNewScriptProperties(JSContext *cx
      * a 'this' is pushed before the previous 'this' value was popped.
      */
     uint32 lastThisPopped = 0;
 
     unsigned nextOffset = 0;
     while (nextOffset < script->length) {
         unsigned offset = nextOffset;
         jsbytecode *pc = script->code + offset;
-        UntrapOpcode untrap(cx, script, pc);
 
         JSOp op = JSOp(*pc);
 
         nextOffset += GetBytecodeLength(pc);
 
         Bytecode *code = analysis->maybeCode(pc);
         if (!code)
             continue;
@@ -4555,18 +4539,16 @@ AnalyzeNewScriptProperties(JSContext *cx
         lastThisPopped = uses->offset;
 
         /* Only handle 'this' values popped in unconditional code. */
         Bytecode *poppedCode = analysis->maybeCode(uses->offset);
         if (!poppedCode || !poppedCode->unconditional)
             return false;
 
         pc = script->code + uses->offset;
-        UntrapOpcode untrapUse(cx, script, pc);
-
         op = JSOp(*pc);
 
         JSObject *obj = *pbaseobj;
 
         if (op == JSOP_SETPROP && uses->u.which == 1) {
             /*
              * Don't use GetAtomId here, we need to watch for SETPROP on
              * integer properties and bail out. We can't mark the aggregate
@@ -4638,17 +4620,16 @@ AnalyzeNewScriptProperties(JSContext *cx
              * particular script, removing definite properties from the result
              */
 
             /* Callee/this must have been pushed by a CALLPROP. */
             SSAValue calleev = analysis->poppedValue(pc, GET_ARGC(pc) + 1);
             if (calleev.kind() != SSAValue::PUSHED)
                 return false;
             jsbytecode *calleepc = script->code + calleev.pushedOffset();
-            UntrapOpcode untrapCallee(cx, script, calleepc);
             if (JSOp(*calleepc) != JSOP_CALLPROP || calleev.pushedIndex() != 0)
                 return false;
 
             /*
              * This code may not have run yet, break any type barriers involved
              * in performing the call (for the greater good!).
              */
             analysis->breakTypeBarriersSSA(cx, analysis->poppedValue(calleepc, 0));
@@ -4800,17 +4781,16 @@ ScriptAnalysis::printTypes(JSContext *cx
      * Check if there are warnings for used values with unknown types, and build
      * statistics about the size of type sets found for stack values.
      */
     for (unsigned offset = 0; offset < script->length; offset++) {
         if (!maybeCode(offset))
             continue;
 
         jsbytecode *pc = script->code + offset;
-        UntrapOpcode untrap(cx, script, pc);
 
         if (js_CodeSpec[*pc].format & JOF_DECOMPOSE)
             continue;
 
         unsigned defCount = GetDefCount(script, offset);
         if (!defCount)
             continue;
 
@@ -4879,17 +4859,16 @@ ScriptAnalysis::printTypes(JSContext *cx
     }
     printf("\n");
 
     for (unsigned offset = 0; offset < script->length; offset++) {
         if (!maybeCode(offset))
             continue;
 
         jsbytecode *pc = script->code + offset;
-        UntrapOpcode untrap(cx, script, pc);
 
         PrintBytecode(cx, script, pc);
 
         if (js_CodeSpec[*pc].format & JOF_DECOMPOSE)
             continue;
 
         if (js_CodeSpec[*pc].format & JOF_TYPESET) {
             TypeSet *types = script->analysis()->bytecodeTypes(pc);
@@ -4937,18 +4916,16 @@ MarkIteratorUnknownSlow(JSContext *cx)
 {
     /* Check whether we are actually at an ITER opcode. */
 
     jsbytecode *pc;
     JSScript *script = cx->stack.currentScript(&pc);
     if (!script || !pc)
         return;
 
-    UntrapOpcode untrap(cx, script, pc);
-
     if (JSOp(*pc) != JSOP_ITER)
         return;
 
     AutoEnterTypeInference enter(cx);
 
     /*
      * This script is iterating over an actual Iterator or Generator object, or
      * an object with a custom __iterator__ hook. In such cases 'for in' loops
@@ -4982,17 +4959,17 @@ MarkIteratorUnknownSlow(JSContext *cx)
         return;
 
     ScriptAnalysis *analysis = script->analysis();
 
     for (unsigned i = 0; i < script->length; i++) {
         jsbytecode *pc = script->code + i;
         if (!analysis->maybeCode(pc))
             continue;
-        if (js_GetOpcode(cx, script, pc) == JSOP_ITERNEXT)
+        if (JSOp(*pc) == JSOP_ITERNEXT)
             analysis->pushedTypes(pc, 0)->addType(cx, Type::UnknownType());
     }
 
     /* Trigger recompilation of any inline callers. */
     if (script->function() && !script->function()->hasLazyType())
         ObjectStateChange(cx, script->function()->type(), false, true);
 }
 
@@ -5028,18 +5005,16 @@ IsAboutToBeFinalized(JSContext *cx, Type
 }
 
 void
 TypeDynamicResult(JSContext *cx, JSScript *script, jsbytecode *pc, Type type)
 {
     JS_ASSERT(cx->typeInferenceEnabled());
     AutoEnterTypeInference enter(cx);
 
-    UntrapOpcode untrap(cx, script, pc);
-
     /* Directly update associated type sets for applicable bytecodes. */
     if (js_CodeSpec[*pc].format & JOF_TYPESET) {
         if (!script->ensureRanAnalysis(cx, NULL)) {
             cx->compartment->types.setPendingNukeTypes(cx);
             return;
         }
         TypeSet *types = script->analysis()->bytecodeTypes(pc);
         if (!types->hasType(type)) {
@@ -5132,18 +5107,16 @@ TypeDynamicResult(JSContext *cx, JSScrip
     /* Trigger recompilation of any inline callers. */
     if (script->function() && !script->function()->hasLazyType())
         ObjectStateChange(cx, script->function()->type(), false, true);
 }
 
 void
 TypeMonitorResult(JSContext *cx, JSScript *script, jsbytecode *pc, const js::Value &rval)
 {
-    UntrapOpcode untrap(cx, script, pc);
-
     /* Allow the non-TYPESET scenario to simplify stubs used in compound opcodes. */
     if (!(js_CodeSpec[*pc].format & JOF_TYPESET))
         return;
 
     AutoEnterTypeInference enter(cx);
 
     if (!script->ensureRanAnalysis(cx, NULL)) {
         cx->compartment->types.setPendingNukeTypes(cx);
@@ -5410,18 +5383,16 @@ NestingEpilogue(StackFrame *fp)
 
 /*
  * Returns true if we don't expect to compute the correct types for some value
  * pushed by the specified bytecode.
  */
 static inline bool
 IgnorePushed(const jsbytecode *pc, unsigned index)
 {
-    JS_ASSERT(JSOp(*pc) != JSOP_TRAP);
-
     switch (JSOp(*pc)) {
       /* We keep track of the scopes pushed by BINDNAME separately. */
       case JSOP_BINDNAME:
       case JSOP_BINDGNAME:
       case JSOP_BINDXMLNAME:
         return true;
 
       /* Stack not consistent in TRY_BRANCH_AFTER_COND. */
@@ -5588,17 +5559,16 @@ JSScript::typeSetFunction(JSContext *cx,
 }
 
 #ifdef DEBUG
 
 /* static */ void
 TypeScript::CheckBytecode(JSContext *cx, JSScript *script, jsbytecode *pc, const js::Value *sp)
 {
     AutoEnterTypeInference enter(cx);
-    UntrapOpcode untrap(cx, script, pc);
 
     if (js_CodeSpec[*pc].format & JOF_DECOMPOSE)
         return;
 
     if (!script->hasAnalysis() || !script->analysis()->ranInference())
         return;
     ScriptAnalysis *analysis = script->analysis();
 
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -156,31 +156,31 @@ js::GetBlockChain(JSContext *cx, StackFr
 
     JSScript *script = fp->script();
     jsbytecode *start = script->code;
 
     /*
      * If the debugger asks for the scope chain at a pc where we are about to
      * fix it up, advance target past the fixup. See bug 672804.
      */
-    JSOp op = js_GetOpcode(cx, script, target);
+    JSOp op = JSOp(*target);
     while (op == JSOP_NOP || op == JSOP_INDEXBASE || op == JSOP_INDEXBASE1 ||
            op == JSOP_INDEXBASE2 || op == JSOP_INDEXBASE3 ||
            op == JSOP_BLOCKCHAIN || op == JSOP_NULLBLOCKCHAIN)
     {
         target += js_CodeSpec[op].length;
-        op = js_GetOpcode(cx, script, target);
+        op = JSOp(*target);
     }
     JS_ASSERT(target >= start && target < start + script->length);
 
     JSObject *blockChain = NULL;
     uintN indexBase = 0;
     ptrdiff_t oplen;
     for (jsbytecode *pc = start; pc < target; pc += oplen) {
-        JSOp op = js_GetOpcode(cx, script, pc);
+        JSOp op = JSOp(*pc);
         const JSCodeSpec *cs = &js_CodeSpec[op];
         oplen = cs->length;
         if (oplen < 0)
             oplen = js_GetVariableBytecodeLength(pc);
 
         if (op == JSOP_INDEXBASE)
             indexBase = GET_INDEXBASE(pc);
         else if (op == JSOP_INDEXBASE1 || op == JSOP_INDEXBASE2 || op == JSOP_INDEXBASE3)
@@ -207,22 +207,22 @@ js::GetBlockChain(JSContext *cx, StackFr
  * We ensure this happens for a few important ops like DEFFUN.
  * |oplen| is the length of opcode at the current PC.
  */
 JSObject *
 js::GetBlockChainFast(JSContext *cx, StackFrame *fp, JSOp op, size_t oplen)
 {
     /* Assume that we're in a script frame. */
     jsbytecode *pc = fp->pcQuadratic(cx->stack);
-    JS_ASSERT(js_GetOpcode(cx, fp->script(), pc) == op);
+    JS_ASSERT(JSOp(*pc) == op);
 
     pc += oplen;
     op = JSOp(*pc);
 
-    /* The fast paths assume no JSOP_RESETBASE/INDEXBASE or JSOP_TRAP noise. */
+    /* The fast paths assume no JSOP_RESETBASE/INDEXBASE noise. */
     if (op == JSOP_NULLBLOCKCHAIN)
         return NULL;
     if (op == JSOP_BLOCKCHAIN)
         return fp->script()->getObject(GET_INDEX(pc));
 
     return GetBlockChain(cx, fp);
 }
 
@@ -1530,18 +1530,17 @@ IteratorNext(JSContext *cx, JSObject *it
  * For bytecodes which push values and then fall through, make sure the
  * types of the pushed values are consistent with type inference information.
  */
 static inline void
 TypeCheckNextBytecode(JSContext *cx, JSScript *script, unsigned n, const FrameRegs &regs)
 {
 #ifdef DEBUG
     if (cx->typeInferenceEnabled() &&
-        *regs.pc != JSOP_TRAP &&
-        n == analyze::GetBytecodeLength(regs.pc)) {
+        n == GetBytecodeLength(regs.pc)) {
         TypeScript::CheckBytecode(cx, script, regs.pc, regs.sp);
     }
 #endif
 }
 
 JS_NEVER_INLINE bool
 js::Interpret(JSContext *cx, StackFrame *entryFrame, InterpMode interpMode)
 {
@@ -1667,26 +1666,25 @@ js::Interpret(JSContext *cx, StackFrame 
             script->getJITStatus(regs.fp()->isConstructing()) != JITScript_Invalid && \
            (interpMode == JSINTERP_NORMAL ||                                  \
             interpMode == JSINTERP_REJOIN ||                                  \
             interpMode == JSINTERP_SKIP_TRAP);                                \
     JS_END_MACRO
 
 #define CHECK_PARTIAL_METHODJIT(status)                                       \
     JS_BEGIN_MACRO                                                            \
-        if (status == mjit::Jaeger_Unfinished) {                              \
+        switch (status) {                                                     \
+          case mjit::Jaeger_UnfinishedAtTrap:                                 \
+            interpMode = JSINTERP_SKIP_TRAP;                                  \
+            /* FALLTHROUGH */                                                 \
+          case mjit::Jaeger_Unfinished:                                       \
             op = (JSOp) *regs.pc;                                             \
             RESTORE_INTERP_VARS_CHECK_EXCEPTION();                            \
             DO_OP();                                                          \
-        } else if (status == mjit::Jaeger_UnfinishedAtTrap) {                 \
-            interpMode = JSINTERP_SKIP_TRAP;                                  \
-            JS_ASSERT(JSOp(*regs.pc) == JSOP_TRAP);                           \
-            op = JSOP_TRAP;                                                   \
-            RESTORE_INTERP_VARS_CHECK_EXCEPTION();                            \
-            DO_OP();                                                          \
+          default:;                                                           \
         }                                                                     \
     JS_END_MACRO
 
 #else
 
 #define RESET_USE_METHODJIT() ((void) 0)
 
 #endif
@@ -1724,20 +1722,22 @@ js::Interpret(JSContext *cx, StackFrame 
         if ((n) <= 0)                                                         \
             goto check_backedge;                                              \
         DO_OP();                                                              \
     JS_END_MACRO
 
 #define SET_SCRIPT(s)                                                         \
     JS_BEGIN_MACRO                                                            \
         script = (s);                                                         \
-        if (script->stepModeEnabled())                                        \
+        if (script->hasAnyBreakpointsOrStepMode())                            \
             ENABLE_INTERRUPTS();                                              \
-        if (script->pcCounters)                                             \
+        if (script->pcCounters)                                               \
             ENABLE_INTERRUPTS();                                              \
+        JS_ASSERT_IF(interpMode == JSINTERP_SKIP_TRAP,                        \
+                     script->hasAnyBreakpointsOrStepMode());                  \
     JS_END_MACRO
 
 #define CHECK_INTERRUPT_HANDLER()                                             \
     JS_BEGIN_MACRO                                                            \
         if (cx->debugHooks->interruptHook)                                    \
             ENABLE_INTERRUPTS();                                              \
     JS_END_MACRO
 
@@ -1810,18 +1810,16 @@ js::Interpret(JSContext *cx, StackFrame 
             }
         }
     }
 
     /* The REJOIN mode acts like the normal mode, except the prologue is skipped. */
     if (interpMode == JSINTERP_REJOIN)
         interpMode = JSINTERP_NORMAL;
 
-    JS_ASSERT_IF(interpMode == JSINTERP_SKIP_TRAP, JSOp(*regs.pc) == JSOP_TRAP);
-
     CHECK_INTERRUPT_HANDLER();
 
     RESET_USE_METHODJIT();
 
     /*
      * It is important that "op" be initialized before calling DO_OP because
      * it is possible for "op" to be specially assigned during the normal
      * processing of an opcode while looping. We rely on DO_NEXT_OP to manage
@@ -1898,29 +1896,56 @@ js::Interpret(JSContext *cx, StackFrame 
               case JSTRAP_THROW:
                 cx->setPendingException(rval);
                 goto error;
               default:;
             }
             moreInterrupts = true;
         }
 
+        if (script->hasAnyBreakpointsOrStepMode())
+            moreInterrupts = true;
+
+        if (script->hasBreakpointsAt(regs.pc) && interpMode != JSINTERP_SKIP_TRAP) {
+            Value rval;
+            JSTrapStatus status = Debugger::onTrap(cx, &rval);
+            switch (status) {
+              case JSTRAP_ERROR:
+                goto error;
+              case JSTRAP_RETURN:
+                regs.fp()->setReturnValue(rval);
+                interpReturnOK = JS_TRUE;
+                goto forced_return;
+              case JSTRAP_THROW:
+                cx->setPendingException(rval);
+                goto error;
+              default:
+                break;
+            }
+            JS_ASSERT(status == JSTRAP_CONTINUE);
+            CHECK_INTERRUPT_HANDLER();
+            JS_ASSERT(rval.isInt32() && rval.toInt32() == op);
+        }
+
+        interpMode = JSINTERP_NORMAL;
+
 #if JS_THREADED_INTERP
         jumpTable = moreInterrupts ? interruptJumpTable : normalJumpTable;
         JS_EXTENSION_(goto *normalJumpTable[op]);
 #else
         switchMask = moreInterrupts ? -1 : 0;
         switchOp = intN(op);
         goto do_switch;
 #endif
     }
 
 /* No-ops for ease of decompilation. */
 ADD_EMPTY_CASE(JSOP_NOP)
 ADD_EMPTY_CASE(JSOP_UNUSED0)
+ADD_EMPTY_CASE(JSOP_UNUSED1)
 ADD_EMPTY_CASE(JSOP_CONDSWITCH)
 ADD_EMPTY_CASE(JSOP_TRY)
 #if JS_HAS_XML_SUPPORT
 ADD_EMPTY_CASE(JSOP_STARTXML)
 ADD_EMPTY_CASE(JSOP_STARTXMLEXPR)
 #endif
 ADD_EMPTY_CASE(JSOP_NULLBLOCKCHAIN)
 ADD_EMPTY_CASE(JSOP_LOOPHEAD)
@@ -2059,17 +2084,17 @@ BEGIN_CASE(JSOP_STOP)
 
         /* The results of lowered call/apply frames need to be shifted. */
         bool shiftResult = regs.fp()->loweredCallOrApply();
 
         cx->stack.popInlineFrame(regs);
 
         RESTORE_INTERP_VARS();
 
-        JS_ASSERT(*regs.pc == JSOP_TRAP || *regs.pc == JSOP_NEW || *regs.pc == JSOP_CALL ||
+        JS_ASSERT(*regs.pc == JSOP_NEW || *regs.pc == JSOP_CALL ||
                   *regs.pc == JSOP_FUNCALL || *regs.pc == JSOP_FUNAPPLY);
 
         /* Resume execution in the calling frame. */
         RESET_USE_METHODJIT();
         if (JS_LIKELY(interpReturnOK)) {
             TypeScript::Monitor(cx, script, regs.pc, regs.sp[-1]);
 
             op = JSOp(*regs.pc);
@@ -3595,17 +3620,17 @@ BEGIN_CASE(JSOP_CALLNAME)
     }
 
     jsid id = ATOM_TO_JSID(atom);
     JSProperty *prop;
     if (!js_FindPropertyHelper(cx, id, true, global, &obj, &obj2, &prop))
         goto error;
     if (!prop) {
         /* Kludge to allow (typeof foo == "undefined") tests. */
-        JSOp op2 = js_GetOpcode(cx, script, regs.pc + JSOP_NAME_LENGTH);
+        JSOp op2 = JSOp(regs.pc[JSOP_NAME_LENGTH]);
         if (op2 == JSOP_TYPEOF) {
             PUSH_UNDEFINED();
             TypeScript::Monitor(cx, script, regs.pc, regs.sp[-1]);
             len = JSOP_NAME_LENGTH;
             DO_NEXT_OP(len);
         }
         atomNotDefined = atom;
         goto atom_not_defined;
@@ -3874,47 +3899,16 @@ BEGIN_CASE(JSOP_LOOKUPSWITCH)
   end_lookup_switch:
     len = (op == JSOP_LOOKUPSWITCH)
           ? GET_JUMP_OFFSET(pc2)
           : GET_JUMPX_OFFSET(pc2);
 }
 END_VARLEN_CASE
 }
 
-BEGIN_CASE(JSOP_TRAP)
-{
-    if (interpMode == JSINTERP_SKIP_TRAP) {
-        interpMode = JSINTERP_NORMAL;
-        op = JS_GetTrapOpcode(cx, script, regs.pc);
-        DO_OP();
-    }
-
-    Value rval;
-    JSTrapStatus status = Debugger::onTrap(cx, &rval);
-    switch (status) {
-      case JSTRAP_ERROR:
-        goto error;
-      case JSTRAP_RETURN:
-        regs.fp()->setReturnValue(rval);
-        interpReturnOK = JS_TRUE;
-        goto forced_return;
-      case JSTRAP_THROW:
-        cx->setPendingException(rval);
-        goto error;
-      default:
-        break;
-    }
-    JS_ASSERT(status == JSTRAP_CONTINUE);
-    CHECK_INTERRUPT_HANDLER();
-    JS_ASSERT(rval.isInt32());
-    op = (JSOp) rval.toInt32();
-    JS_ASSERT((uintN)op < (uintN)JSOP_LIMIT);
-    DO_OP();
-}
-
 BEGIN_CASE(JSOP_ARGUMENTS)
 {
     Value rval;
     if (cx->typeInferenceEnabled() && !script->strictModeCode) {
         if (!script->ensureRanInference(cx))
             goto error;
         if (script->createdArgs) {
             if (!js_GetArgsValue(cx, regs.fp(), &rval))
@@ -4272,17 +4266,17 @@ BEGIN_CASE(JSOP_LAMBDA)
                     JS_ASSERT(obj2->isObject());
 #endif
                     JS_ASSERT(fun->methodAtom() == script->getAtom(GET_FULL_INDEX(pc2 - regs.pc)));
                     break;
                 }
 
                 if (op2 == JSOP_SETMETHOD) {
 #ifdef DEBUG
-                    op2 = js_GetOpcode(cx, script, pc2 + JSOP_SETMETHOD_LENGTH);
+                    op2 = JSOp(pc2[JSOP_SETMETHOD_LENGTH]);
                     JS_ASSERT(op2 == JSOP_POP || op2 == JSOP_POPV);
 #endif
                     const Value &lref = regs.sp[-1];
                     if (lref.isObject() && lref.toObject().canHaveMethodBarrier()) {
                         JS_ASSERT(fun->methodAtom() == script->getAtom(GET_FULL_INDEX(pc2 - regs.pc)));
                         break;
                     }
                 } else if (op2 == JSOP_CALL) {
@@ -4355,17 +4349,17 @@ BEGIN_CASE(JSOP_CALLEE)
     JS_ASSERT(regs.fp()->isNonEvalFunctionFrame());
     PUSH_COPY(argv[-2]);
 END_CASE(JSOP_CALLEE)
 
 BEGIN_CASE(JSOP_GETTER)
 BEGIN_CASE(JSOP_SETTER)
 {
   do_getter_setter:
-    JSOp op2 = js_GetOpcode(cx, script, ++regs.pc);
+    JSOp op2 = JSOp(*++regs.pc);
     jsid id;
     Value rval;
     jsint i;
     JSObject *obj;
     switch (op2) {
       case JSOP_INDEXBASE:
         atoms += GET_INDEXBASE(regs.pc);
         regs.pc += JSOP_INDEXBASE_LENGTH - 1;
@@ -4613,17 +4607,17 @@ BEGIN_CASE(JSOP_INITELEM)
      * If rref is a hole, do not call JSObject::defineProperty. In this case,
      * obj must be an array, so if the current op is the last element
      * initialiser, set the array length to one greater than id.
      */
     if (rref.isMagic(JS_ARRAY_HOLE)) {
         JS_ASSERT(obj->isArray());
         JS_ASSERT(JSID_IS_INT(id));
         JS_ASSERT(jsuint(JSID_TO_INT(id)) < StackSpace::ARGS_LENGTH_MAX);
-        if (js_GetOpcode(cx, script, regs.pc + JSOP_INITELEM_LENGTH) == JSOP_ENDINIT &&
+        if (JSOp(regs.pc[JSOP_INITELEM_LENGTH]) == JSOP_ENDINIT &&
             !js_SetLengthProperty(cx, obj, (jsuint) (JSID_TO_INT(id) + 1))) {
             goto error;
         }
     } else {
         if (!obj->defineGeneric(cx, id, rref, NULL, NULL, JSPROP_ENUMERATE))
             goto error;
     }
     regs.sp -= 2;
@@ -5505,17 +5499,17 @@ END_CASE(JSOP_ARRAYPUSH)
                 PUSH_BOOLEAN(true);
                 PUSH_COPY(cx->getPendingException());
                 cx->clearPendingException();
                 len = 0;
                 DO_NEXT_OP(len);
 
               case JSTRY_ITER: {
                 /* This is similar to JSOP_ENDITER in the interpreter loop. */
-                JS_ASSERT(js_GetOpcode(cx, regs.fp()->script(), regs.pc) == JSOP_ENDITER);
+                JS_ASSERT(JSOp(*regs.pc) == JSOP_ENDITER);
                 Value v = cx->getPendingException();
                 cx->clearPendingException();
                 ok = js_CloseIterator(cx, &regs.sp[-1].toObject());
                 regs.sp -= 1;
                 if (!ok)
                     goto error;
                 cx->setPendingException(v);
               }
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1164,17 +1164,17 @@ EvalKernel(JSContext *cx, const CallArgs
          * the eval code to use.
          */
         if (!ComputeThis(cx, caller))
             return false;
         thisv = caller->thisValue();
 
 #ifdef DEBUG
         jsbytecode *callerPC = caller->pcQuadratic(cx);
-        JS_ASSERT(callerPC && js_GetOpcode(cx, caller->script(), callerPC) == JSOP_EVAL);
+        JS_ASSERT(callerPC && JSOp(*callerPC) == JSOP_EVAL);
 #endif
     } else {
         JS_ASSERT(args.callee().getGlobal() == &scopeobj);
         staticLevel = 0;
 
         /* Use the global as 'this', modulo outerization. */
         JSObject *thisobj = scopeobj.thisObject(cx);
         if (!thisobj)
@@ -1303,17 +1303,17 @@ eval(JSContext *cx, uintN argc, Value *v
 
 bool
 DirectEval(JSContext *cx, const CallArgs &args)
 {
     /* Direct eval can assume it was called from an interpreted frame. */
     StackFrame *caller = cx->fp();
     JS_ASSERT(caller->isScriptFrame());
     JS_ASSERT(IsBuiltinEvalForScope(&caller->scopeChain(), args.calleev()));
-    JS_ASSERT(js_GetOpcode(cx, cx->fp()->script(), cx->regs().pc) == JSOP_EVAL);
+    JS_ASSERT(JSOp(*cx->regs().pc) == JSOP_EVAL);
 
     AutoFunctionCallProbe callProbe(cx, args.callee().toFunction(), caller->script());
 
     JSObject *scopeChain =
         GetScopeChainFast(cx, caller, JSOP_EVAL, JSOP_EVAL_LENGTH + JSOP_LINENO_LENGTH);
 
     return scopeChain &&
            WarnOnTooManyArgs(cx, args) &&
@@ -3168,43 +3168,43 @@ Detecting(JSContext *cx, jsbytecode *pc)
     JSAtom *atom;
 
     JSScript *script = cx->stack.currentScript();
     endpc = script->code + script->length;
     for (;; pc += js_CodeSpec[op].length) {
         JS_ASSERT(script->code <= pc && pc < endpc);
 
         /* General case: a branch or equality op follows the access. */
-        op = js_GetOpcode(cx, script, pc);
+        op = JSOp(*pc);
         if (js_CodeSpec[op].format & JOF_DETECTING)
             return JS_TRUE;
 
         switch (op) {
           case JSOP_NULL:
             /*
              * Special case #1: handle (document.all == null).  Don't sweat
              * about JS1.2's revision of the equality operators here.
              */
             if (++pc < endpc) {
-                op = js_GetOpcode(cx, script, pc);
+                op = JSOp(*pc);
                 return *pc == JSOP_EQ || *pc == JSOP_NE;
             }
             return JS_FALSE;
 
           case JSOP_GETGNAME:
           case JSOP_NAME:
             /*
              * Special case #2: handle (document.all == undefined).  Don't
              * worry about someone redefining undefined, which was added by
              * Edition 3, so is read/write for backward compatibility.
              */
             GET_ATOM_FROM_BYTECODE(script, pc, 0, atom);
             if (atom == cx->runtime->atomState.typeAtoms[JSTYPE_VOID] &&
                 (pc += js_CodeSpec[op].length) < endpc) {
-                op = js_GetOpcode(cx, script, pc);
+                op = JSOp(*pc);
                 return op == JSOP_EQ || op == JSOP_NE ||
                        op == JSOP_STRICTEQ || op == JSOP_STRICTNE;
             }
             return JS_FALSE;
 
           default:
             /*
              * At this point, anything but an extended atom index prefix means
@@ -3230,17 +3230,17 @@ js_InferFlags(JSContext *cx, uintN defau
     uint32 format;
     uintN flags = 0;
 
     jsbytecode *pc;
     JSScript *script = cx->stack.currentScript(&pc);
     if (!script || !pc)
         return defaultFlags;
 
-    cs = &js_CodeSpec[js_GetOpcode(cx, script, pc)];
+    cs = &js_CodeSpec[*pc];
     format = cs->format;
     if (JOF_MODE(format) != JOF_NAME)
         flags |= JSRESOLVE_QUALIFIED;
     if (format & JOF_SET) {
         flags |= JSRESOLVE_ASSIGNING;
     } else if (cs->length >= 0) {
         pc += cs->length;
         if (pc < script->code + script->length && Detecting(cx, pc))
@@ -5915,18 +5915,16 @@ js_GetPropertyHelperInline(JSContext *cx
          * object foo with no property named 'bar'.
          */
         jsbytecode *pc;
         if (vp->isUndefined() && ((pc = js_GetCurrentBytecodePC(cx)) != NULL)) {
             JSOp op;
             uintN flags;
 
             op = (JSOp) *pc;
-            if (op == JSOP_TRAP)
-                op = JS_GetTrapOpcode(cx, cx->fp()->script(), pc);
             if (op == JSOP_GETXPROP) {
                 flags = JSREPORT_ERROR;
             } else {
                 if (!cx->hasStrictOption() ||
                     cx->stack.currentScript()->warnedAboutUndefinedProp ||
                     (op != JSOP_GETPROP && op != JSOP_GETELEM)) {
                     return JS_TRUE;
                 }
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -157,45 +157,46 @@ GetJumpOffset(jsbytecode *pc, jsbytecode
         return GET_JUMPX_OFFSET(pc2);
     return GET_JUMP_OFFSET(pc2);
 }
 
 uintN
 js_GetIndexFromBytecode(JSContext *cx, JSScript *script, jsbytecode *pc,
                         ptrdiff_t pcoff)
 {
-    JSOp op = js_GetOpcode(cx, script, pc);
+    JSOp op = JSOp(*pc);
     JS_ASSERT(js_CodeSpec[op].length >= 1 + pcoff + UINT16_LEN);
 
     /*
      * We need to detect index base prefix. It presents when resetbase
      * follows the bytecode.
      */
     uintN span = js_CodeSpec[op].length;
     uintN base = 0;
     if (pc - script->code + span < script->length) {
-        JSOp next = js_GetOpcode(cx, script, pc + span);
+        JSOp next = JSOp(pc[span]);
         if (next == JSOP_RESETBASE) {
-            JS_ASSERT(js_GetOpcode(cx, script, pc - JSOP_INDEXBASE_LENGTH) == JSOP_INDEXBASE);
+            JS_ASSERT(JSOp(pc[-JSOP_INDEXBASE_LENGTH]) == JSOP_INDEXBASE);
             base = GET_INDEXBASE(pc - JSOP_INDEXBASE_LENGTH);
         } else if (next == JSOP_RESETBASE0) {
-            JSOp prev = js_GetOpcode(cx, script, pc - 1);
+            JSOp prev = JSOp(pc[-1]);
             JS_ASSERT(JSOP_INDEXBASE1 <= prev && prev <= JSOP_INDEXBASE3);
             base = (prev - JSOP_INDEXBASE1 + 1) << 16;
         }
     }
     return base + GET_UINT16(pc + pcoff);
 }
 
 size_t
-js_GetVariableBytecodeLength(JSOp op, jsbytecode *pc)
+js_GetVariableBytecodeLength(jsbytecode *pc)
 {
     uintN jmplen, ncases;
     jsint low, high;
 
+    JSOp op = JSOp(*pc);
     JS_ASSERT(js_CodeSpec[op].length == -1);
     switch (op) {
       case JSOP_TABLESWITCHX:
         jmplen = JUMPX_OFFSET_LEN;
         goto do_table;
       case JSOP_TABLESWITCH:
         jmplen = JUMP_OFFSET_LEN;
       do_table:
@@ -219,17 +220,17 @@ js_GetVariableBytecodeLength(JSOp op, js
         ncases = GET_UINT16(pc);
         return 1 + jmplen + INDEX_LEN + ncases * (INDEX_LEN + jmplen);
     }
 }
 
 uintN
 js_GetVariableStackUses(JSOp op, jsbytecode *pc)
 {
-    JS_ASSERT(*pc == op || *pc == JSOP_TRAP);
+    JS_ASSERT(*pc == op);
     JS_ASSERT(js_CodeSpec[op].nuses == -1);
     switch (op) {
       case JSOP_POPN:
         return GET_UINT16(pc);
       case JSOP_LEAVEBLOCK:
         return GET_UINT16(pc);
       case JSOP_LEAVEBLOCKEXPR:
         return GET_UINT16(pc) + 1;
@@ -241,73 +242,21 @@ js_GetVariableStackUses(JSOp op, jsbytec
     }
 }
 
 uintN
 js_GetEnterBlockStackDefs(JSContext *cx, JSScript *script, jsbytecode *pc)
 {
     JSObject *obj;
 
-    JS_ASSERT(*pc == JSOP_ENTERBLOCK || *pc == JSOP_TRAP);
+    JS_ASSERT(*pc == JSOP_ENTERBLOCK);
     GET_OBJECT_FROM_BYTECODE(script, pc, 0, obj);
     return OBJ_BLOCK_COUNT(cx, obj);
 }
 
-AutoScriptUntrapper::AutoScriptUntrapper()
- : origScript(NULL), origCode(NULL)
-{}
-
-bool
-AutoScriptUntrapper::untrap(JSContext *cx, JSScript *script)
-{
-    JS_ASSERT(!origScript && !origCode);
-
-    BreakpointSiteMap &sites = script->compartment()->breakpointSites;
-    for (BreakpointSiteMap::Range r = sites.all(); !r.empty(); r.popFront()) {
-        BreakpointSite *site = r.front().value;
-        if (site->script == script) {
-            JS_ASSERT(size_t(site->pc - script->code) < script->length);
-            if (size_t(site->pc - script->code) >= script->length)
-                continue;
-            ptrdiff_t off = site->pc - script->code;
-            if (script->code[off] == site->realOpcode)
-                continue;
-            if (!origCode && !saveOriginal(script))
-                return false;
-            script->code[site->pc - script->code] = site->realOpcode;
-        }
-    }
-    if (origCode)
-        GetGSNCache(cx)->purge();
-    return true;
-}
-
-bool
-AutoScriptUntrapper::saveOriginal(JSScript *script)
-{
-    nbytes = script->length * sizeof(jsbytecode);
-
-    origCode = (jsbytecode *) OffTheBooks::malloc_(nbytes);
-    if (!origCode)
-        return false;
-
-    memcpy(origCode, script->code, nbytes);
-    origScript = script;
-    return true;
-}
-
-AutoScriptUntrapper::~AutoScriptUntrapper()
-{
-    JS_ASSERT(!!origCode == !!origScript);
-    if (origCode) {
-        memcpy(origScript->code, origCode, nbytes);
-        Foreground::free_(origCode);
-    }
-}
-
 static const char * countBaseNames[] = {
     "interp",
     "mjit",
     "mjit_calls",
     "mjit_code",
     "mjit_pics"
 };
 
@@ -396,17 +345,17 @@ OpcodeCounts::countName(JSOp op, size_t 
 
 JS_FRIEND_API(void)
 js_DumpPCCounts(JSContext *cx, JSScript *script, js::Sprinter *sp)
 {
     JS_ASSERT(script->pcCounters);
 
     jsbytecode *pc = script->code;
     while (pc < script->code + script->length) {
-        JSOp op = js_GetOpcode(cx, script, pc);
+        JSOp op = JSOp(*pc);
 
         int len = js_CodeSpec[op].length;
         jsbytecode *next = (len != -1) ? pc + len : pc + js_GetVariableBytecodeLength(pc);
 
         if (!js_Disassemble1(cx, script, pc, pc - script->code, true, sp))
             return;
 
         size_t total = OpcodeCounts::numCounts(op);
@@ -573,20 +522,16 @@ ToDisassemblySource(JSContext *cx, jsval
 
     return !!js_ValueToPrintable(cx, v, bytes, true);
 }
 
 JS_FRIEND_API(uintN)
 js_Disassemble1(JSContext *cx, JSScript *script, jsbytecode *pc,
                 uintN loc, JSBool lines, Sprinter *sp)
 {
-    AutoScriptUntrapper untrapper;
-    if (!untrapper.untrap(cx, script))
-        return 0;
-
     JSOp op = (JSOp)*pc;
     if (op >= JSOP_LIMIT) {
         char numBuf1[12], numBuf2[12];
         JS_snprintf(numBuf1, sizeof numBuf1, "%d", op);
         JS_snprintf(numBuf2, sizeof numBuf2, "%d", JSOP_LIMIT);
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                              JSMSG_BYTECODE_TOO_BIG, numBuf1, numBuf2);
         return 0;
@@ -4556,28 +4501,16 @@ Decompile(SprintStack *ss, jsbytecode *p
 
               case JSOP_DEFFUN:
               case JSOP_DEFFUN_FC:
                 LOAD_FUNCTION(0);
                 todo = -2;
                 goto do_function;
                 break;
 
-              case JSOP_TRAP:
-                saveop = op = JS_GetTrapOpcode(cx, jp->script, pc);
-                *pc = op;
-                cs = &js_CodeSpec[op];
-                len = cs->length;
-                DECOMPILE_CODE(pc, len);
-                if (js_CodeSpec[*pc].format & JOF_DECOMPOSE)
-                    len += GetDecomposeLength(pc, js_CodeSpec[*pc].length);
-                *pc = JSOP_TRAP;
-                todo = -2;
-                break;
-
               case JSOP_HOLE:
                 todo = SprintPut(&ss->sprinter, "", 0);
                 break;
 
               case JSOP_NEWINIT:
               {
                 i = pc[1];
                 LOCAL_ASSERT(i == JSProto_Array || i == JSProto_Object);
@@ -4931,19 +4864,16 @@ Decompile(SprintStack *ss, jsbytecode *p
     return pc;
 }
 
 static JSBool
 DecompileCode(JSPrinter *jp, JSScript *script, jsbytecode *pc, uintN len,
               uintN pcdepth)
 {
     JSContext *cx = jp->sprinter.context;
-    AutoScriptUntrapper untrapper;
-    if (!untrapper.untrap(cx, script))
-        return false;
 
     uintN depth = StackDepth(script);
     JS_ASSERT(pcdepth <= depth);
 
     /* Initialize a sprinter for use with the offset stack. */
     LifoAllocScope las(&cx->tempLifoAlloc());
     SprintStack ss;
     if (!InitSprintStack(cx, &ss, jp, depth))
@@ -5088,19 +5018,16 @@ js_DecompileFunction(JSPrinter *jp)
     } else {
         JSScript *script = fun->script();
 #if JS_HAS_DESTRUCTURING
         SprintStack ss;
 #endif
 
         /* Print the parameters. */
         jsbytecode *pc = script->main();
-        AutoScriptUntrapper untrapper;
-        if (!untrapper.untrap(jp->sprinter.context, script))
-            return JS_FALSE;;
         jsbytecode *endpc = pc + script->length;
         JSBool ok = JS_TRUE;
 
 #if JS_HAS_DESTRUCTURING
         ss.printer = NULL;
         jp->script = script;
         LifoAllocScope las(&jp->sprinter.context->tempLifoAlloc());
 #endif
@@ -5289,19 +5216,16 @@ js_DecompileValueGenerator(JSContext *cx
     return DeflateString(cx, chars, length);
 }
 
 static char *
 DecompileExpression(JSContext *cx, JSScript *script, JSFunction *fun,
                     jsbytecode *pc)
 {
     JS_ASSERT(script->code <= pc && pc < script->code + script->length);
-    AutoScriptUntrapper untrapper;
-    if (!untrapper.untrap(cx, script))
-        return NULL;
 
     JSOp op = (JSOp) *pc;
 
     /* None of these stack-writing ops generates novel values. */
     JS_ASSERT(op != JSOP_CASE && op != JSOP_CASEX &&
               op != JSOP_DUP && op != JSOP_DUP2);
 
     /* JSOP_PUSH is used to generate undefined for group assignment holes. */
@@ -5463,17 +5387,17 @@ ReconstructPCStack(JSContext *cx, JSScri
      * FIXME: Optimize to use last empty-stack sequence point.
      */
 
     LOCAL_ASSERT(script->code <= target && target < script->code + script->length);
     jsbytecode *pc = script->code;
     uintN pcdepth = 0;
     ptrdiff_t oplen;
     for (; pc < target; pc += oplen) {
-        JSOp op = js_GetOpcode(cx, script, pc);
+        JSOp op = JSOp(*pc);
         const JSCodeSpec *cs = &js_CodeSpec[op];
         oplen = cs->length;
         if (oplen < 0)
             oplen = js_GetVariableBytecodeLength(pc);
 
         if (cs->format & JOF_DECOMPOSE) {
             if (lastDecomposedPC)
                 *lastDecomposedPC = pc;
@@ -5487,17 +5411,17 @@ ReconstructPCStack(JSContext *cx, JSScri
          * tests condition C.  We know that the stack depth can't change from
          * what it was with C on top of stack.
          */
         jssrcnote *sn = js_GetSrcNote(script, pc);
         if (sn && SN_TYPE(sn) == SRC_COND) {
             ptrdiff_t jmpoff = js_GetSrcNoteOffset(sn, 0);
             if (pc + jmpoff < target) {
                 pc += jmpoff;
-                op = js_GetOpcode(cx, script, pc);
+                op = JSOp(*pc);
                 JS_ASSERT(op == JSOP_GOTO || op == JSOP_GOTOX);
                 cs = &js_CodeSpec[op];
                 oplen = cs->length;
                 JS_ASSERT(oplen > 0);
                 ptrdiff_t jmplen = GetJumpOffset(pc, pc);
                 if (pc + jmplen < target) {
                     oplen = (uintN) jmplen;
                     continue;
@@ -5550,32 +5474,21 @@ CallResultEscapes(jsbytecode *pc)
         return false;
 
     if (*pc == JSOP_NOT)
         pc += JSOP_NOT_LENGTH;
 
     return (*pc != JSOP_IFEQ);
 }
 
-size_t
-GetBytecodeLength(JSContext *cx, JSScript *script, jsbytecode *pc)
-{
-    JSOp op = js_GetOpcode(cx, script, pc);
-    JS_ASSERT(op < JSOP_LIMIT);
-    JS_ASSERT(op != JSOP_TRAP);
-    if (js_CodeSpec[op].length != -1)
-        return js_CodeSpec[op].length;
-    return js_GetVariableBytecodeLength(op, pc);
-}
-
 extern bool
 IsValidBytecodeOffset(JSContext *cx, JSScript *script, size_t offset)
 {
     // This could be faster (by following jump instructions if the target is <= offset).
-    for (BytecodeRange r(cx, script); !r.empty(); r.popFront()) {
+    for (BytecodeRange r(script); !r.empty(); r.popFront()) {
         size_t here = r.frontOffset();
         if (here >= offset)
             return here == offset;
     }
     return false;
 }
 
 } // namespace js
--- a/js/src/jsopcode.h
+++ b/js/src/jsopcode.h
@@ -214,18 +214,17 @@ typedef enum JSOp {
  * JSOP_INDEXBASE and JSOP_RESETBASE pair.
  */
 #define INDEX_LEN               2
 #define INDEX_HI(i)             ((jsbytecode)((i) >> 8))
 #define INDEX_LO(i)             ((jsbytecode)(i))
 #define GET_INDEX(pc)           GET_UINT16(pc)
 #define SET_INDEX(pc,i)         ((pc)[1] = INDEX_HI(i), (pc)[2] = INDEX_LO(i))
 
-#define GET_INDEXBASE(pc)       (JS_ASSERT(*(pc) == JSOP_INDEXBASE            \
-                                           || *(pc) == JSOP_TRAP),            \
+#define GET_INDEXBASE(pc)       (JS_ASSERT(*(pc) == JSOP_INDEXBASE),          \
                                  ((uintN)((pc)[1])) << 16)
 #define INDEXBASE_LEN           1
 
 #define UINT24_HI(i)            ((jsbytecode)((i) >> 16))
 #define UINT24_MID(i)           ((jsbytecode)((i) >> 8))
 #define UINT24_LO(i)            ((jsbytecode)(i))
 #define GET_UINT24(pc)          ((jsatomid)(((pc)[1] << 16) |                 \
                                             ((pc)[2] << 8) |                  \
@@ -467,24 +466,17 @@ JS_END_EXTERN_C
 #define JSDVG_IGNORE_STACK      0
 #define JSDVG_SEARCH_STACK      1
 
 #ifdef __cplusplus
 /*
  * Get the length of variable-length bytecode like JSOP_TABLESWITCH.
  */
 extern size_t
-js_GetVariableBytecodeLength(JSOp op, jsbytecode *pc);
-
-inline size_t
-js_GetVariableBytecodeLength(jsbytecode *pc)
-{
-    JS_ASSERT(*pc != JSOP_TRAP);
-    return js_GetVariableBytecodeLength(JSOp(*pc), pc);
-}
+js_GetVariableBytecodeLength(jsbytecode *pc);
 
 namespace js {
 
 static inline char *
 DecompileValueGenerator(JSContext *cx, intN spindex, const Value &v,
                         JSString *fallback)
 {
     return js_DecompileValueGenerator(cx, spindex, v, fallback);
@@ -531,53 +523,43 @@ CallResultEscapes(jsbytecode *pc);
 
 static inline uintN
 GetDecomposeLength(jsbytecode *pc, size_t len)
 {
     /*
      * The last byte of a DECOMPOSE op stores the decomposed length. This can
      * vary across different instances of an opcode due to INDEXBASE ops.
      */
-    JS_ASSERT_IF(JSOp(*pc) != JSOP_TRAP, size_t(js_CodeSpec[*pc].length) == len);
+    JS_ASSERT(size_t(js_CodeSpec[*pc].length) == len);
     return (uintN) pc[len - 1];
 }
 
-extern size_t
-GetBytecodeLength(JSContext *cx, JSScript *script, jsbytecode *pc);
+static inline uintN
+GetBytecodeLength(jsbytecode *pc)
+{
+    JSOp op = (JSOp)*pc;
+    JS_ASSERT(op < JSOP_LIMIT);
+
+    if (js_CodeSpec[op].length != -1)
+        return js_CodeSpec[op].length;
+    return js_GetVariableBytecodeLength(pc);
+}
 
 extern bool
 IsValidBytecodeOffset(JSContext *cx, JSScript *script, size_t offset);
 
 inline bool
 FlowsIntoNext(JSOp op)
 {
     /* JSOP_YIELD is considered to flow into the next instruction, like JSOP_CALL. */
     return op != JSOP_STOP && op != JSOP_RETURN && op != JSOP_RETRVAL && op != JSOP_THROW &&
            op != JSOP_GOTO && op != JSOP_GOTOX && op != JSOP_RETSUB;
 }
 
 /*
- * AutoScriptUntrapper mutates the given script in place to replace JSOP_TRAP
- * opcodes with the original opcode they replaced. The destructor mutates the
- * script back into its original state.
- */
-class AutoScriptUntrapper
-{
-    JSContext *cx;
-    JSScript *origScript;
-    jsbytecode *origCode;
-    size_t nbytes;
-    bool saveOriginal(JSScript *script);
-  public:
-    AutoScriptUntrapper();
-    bool untrap(JSContext *cx, JSScript *script);
-    ~AutoScriptUntrapper();
-};
-
-/*
  * Counts accumulated for a single opcode in a script. The counts tracked vary
  * between opcodes, and this structure ensures that counts are accessed in
  * a coherent fashion.
  */
 class OpcodeCounts
 {
     friend struct ::JSScript;
     double *counts;
@@ -617,17 +599,16 @@ class OpcodeCounts
         ACCESS_COUNT
     };
 
     static bool accessOp(JSOp op) {
         /*
          * Access ops include all name, element and property reads, as well as
          * SETELEM and SETPROP (for ElementCounts/PropertyCounts alignment).
          */
-        JS_ASSERT(op != JSOP_TRAP);
         if (op == JSOP_SETELEM || op == JSOP_SETPROP || op == JSOP_SETMETHOD)
             return true;
         int format = js_CodeSpec[op].format;
         return !!(format & (JOF_NAME | JOF_GNAME | JOF_ELEM | JOF_PROP))
             && !(format & (JOF_SET | JOF_INCDEC));
     }
 
     enum ElementCounts {
@@ -665,17 +646,16 @@ class OpcodeCounts
         ARITH_DOUBLE,
         ARITH_OTHER,
         ARITH_UNKNOWN,
 
         ARITH_COUNT
     };
 
     static bool arithOp(JSOp op) {
-        JS_ASSERT(op != JSOP_TRAP);
         return !!(js_CodeSpec[op].format & (JOF_INCDEC | JOF_ARITH));
     }
 
     static size_t numCounts(JSOp op)
     {
         if (accessOp(op)) {
             if (elementOp(op))
                 return ELEM_COUNT;
--- a/js/src/jsopcode.tbl
+++ b/js/src/jsopcode.tbl
@@ -227,18 +227,17 @@ OPDEF(JSOP_FUNAPPLY,  79, "funapply",   
 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)
 
 /* Call a function as a constructor; operand is argc. */
 OPDEF(JSOP_NEW,       82, js_new_str,   NULL,         3, -1,  1, 17,  JOF_UINT16|JOF_INVOKE|JOF_TYPESET)
 
-/* Trap into debugger for breakpoint, etc. */
-OPDEF(JSOP_TRAP,      83, "trap",       NULL,         1,  0,  0,  0,  JOF_BYTE)
+OPDEF(JSOP_UNUSED1,   83, "unused1",    NULL,         1,  0,  0,  0,  JOF_BYTE)
 
 /* Fast get/set ops for function arguments and local variables. */
 OPDEF(JSOP_GETARG,    84, "getarg",     NULL,         3,  0,  1, 19,  JOF_QARG |JOF_NAME)
 OPDEF(JSOP_SETARG,    85, "setarg",     NULL,         3,  1,  1,  3,  JOF_QARG |JOF_NAME|JOF_SET)
 OPDEF(JSOP_GETLOCAL,  86,"getlocal",    NULL,         3,  0,  1, 19,  JOF_LOCAL|JOF_NAME)
 OPDEF(JSOP_SETLOCAL,  87,"setlocal",    NULL,         3,  1,  1,  3,  JOF_LOCAL|JOF_NAME|JOF_SET|JOF_DETECTING)
 
 /* Push unsigned 16-bit int constant. */
--- a/js/src/jsopcodeinlines.h
+++ b/js/src/jsopcodeinlines.h
@@ -37,26 +37,25 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "jsautooplen.h"
 
 namespace js {
 
 class BytecodeRange {
   public:
-    BytecodeRange(JSContext *cx, JSScript *script)
-      : cx(cx), script(script), pc(script->code), end(pc + script->length) {}
+    BytecodeRange(JSScript *script)
+      : script(script), pc(script->code), end(pc + script->length) {}
     bool empty() const { return pc == end; }
     jsbytecode *frontPC() const { return pc; }
-    JSOp frontOpcode() const { return js_GetOpcode(cx, script, pc); }
+    JSOp frontOpcode() const { return JSOp(*pc); }
     size_t frontOffset() const { return pc - script->code; }
-    void popFront() { pc += GetBytecodeLength(cx, script, pc); }
+    void popFront() { pc += GetBytecodeLength(pc); }
 
   private:
-    JSContext *cx;
     JSScript *script;
     jsbytecode *pc, *end;
 };
 
 /* 
  * Warning: this does not skip JSOP_RESETBASE* or JSOP_INDEXBASE* ops, so it is
  * useful only when checking for optimization opportunities.
  */
--- a/js/src/jspropertycache.cpp
+++ b/js/src/jspropertycache.cpp
@@ -115,17 +115,17 @@ PropertyCache::fill(JSContext *cx, JSObj
     }
 
     /*
      * Optimize the cached vword based on our parameters and the current pc's
      * opcode format flags.
      */
     jsbytecode *pc;
     JSScript *script = cx->stack.currentScript(&pc);
-    op = js_GetOpcode(cx, script, pc);
+    op = JSOp(*pc);
     cs = &js_CodeSpec[op];
 
     if ((cs->format & JOF_SET) && obj->watched())
         return JS_NO_PROP_CACHE_FILL;
 
     if (obj == pobj) {
         JS_ASSERT(scopeIndex == 0 && protoIndex == 0);
     } else {
@@ -185,17 +185,17 @@ PropertyCache::fullTest(JSContext *cx, j
                         PropertyCacheEntry *entry)
 {
     JSObject *obj, *pobj, *tmp;
     JSScript *script = cx->stack.currentScript();
 
     JS_ASSERT(this == &JS_PROPERTY_CACHE(cx));
     JS_ASSERT(uint32(pc - script->code) < script->length);
 
-    JSOp op = js_GetOpcode(cx, script, pc);
+    JSOp op = JSOp(*pc);
     const JSCodeSpec &cs = js_CodeSpec[op];
 
     obj = *objp;
     uint32 vindex = entry->vindex;
 
     if (entry->kpc != pc) {
         PCMETER(kpcmisses++);
 
--- a/js/src/jsprvtd.h
+++ b/js/src/jsprvtd.h
@@ -229,18 +229,16 @@ class ParseMapPool;
 class DefnOrHeader;
 typedef InlineMap<JSAtom *, Definition *, 24> AtomDefnMap;
 typedef InlineMap<JSAtom *, jsatomid, 24> AtomIndexMap;
 typedef InlineMap<JSAtom *, DefnOrHeader, 24> AtomDOHMap;
 typedef Vector<UpvarCookie, 8> UpvarCookies;
 
 class Breakpoint;
 class BreakpointSite;
-typedef HashMap<jsbytecode *, BreakpointSite *, DefaultHasher<jsbytecode *>, RuntimeAllocPolicy>
-    BreakpointSiteMap;
 class Debugger;
 class WatchpointMap;
 
 typedef HashMap<JSAtom *,
                 detail::RegExpPrivateCacheValue,
                 DefaultHasher<JSAtom *>,
                 RuntimeAllocPolicy>
     RegExpPrivateCache;
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -565,20 +565,16 @@ js_XDRScript(JSXDRState *xdr, JSScript *
     }
 
     /*
      * Control hereafter must goto error on failure, in order for the
      * DECODE case to destroy script.
      */
     oldscript = xdr->script;
 
-    AutoScriptUntrapper untrapper;
-    if (xdr->mode == JSXDR_ENCODE && !untrapper.untrap(cx, script))
-        goto error;
-
     xdr->script = script;
     ok = JS_XDRBytes(xdr, (char *)script->code, length * sizeof(jsbytecode));
 
     if (!ok)
         goto error;
 
     if (!JS_XDRBytes(xdr, (char *)notes, nsrcnotes * sizeof(jssrcnote)) ||
         !JS_XDRUint32(xdr, &lineno) ||
@@ -741,40 +737,38 @@ bool
 JSScript::initCounts(JSContext *cx)
 {
     JS_ASSERT(!pcCounters);
 
     size_t count = 0;
 
     jsbytecode *pc, *next;
     for (pc = code; pc < code + length; pc = next) {
-        analyze::UntrapOpcode untrap(cx, this, pc);
         count += OpcodeCounts::numCounts(JSOp(*pc));
-        next = pc + analyze::GetBytecodeLength(pc);
+        next = pc + GetBytecodeLength(pc);
     }
 
     size_t bytes = (length * sizeof(OpcodeCounts)) + (count * sizeof(double));
     char *cursor = (char *) cx->calloc_(bytes);
     if (!cursor)
         return false;
 
     DebugOnly<char *> base = cursor;
 
     pcCounters.counts = (OpcodeCounts *) cursor;
     cursor += length * sizeof(OpcodeCounts);
 
     for (pc = code; pc < code + length; pc = next) {
-        analyze::UntrapOpcode untrap(cx, this, pc);
         pcCounters.counts[pc - code].counts = (double *) cursor;
         size_t capacity = OpcodeCounts::numCounts(JSOp(*pc));
 #ifdef DEBUG
         pcCounters.counts[pc - code].capacity = capacity;
 #endif
         cursor += capacity * sizeof(double);
-        next = pc + analyze::GetBytecodeLength(pc);
+        next = pc + GetBytecodeLength(pc);
     }
 
     JS_ASSERT(size_t(cursor - base) == bytes);
 
     return true;
 }
 
 void
@@ -1351,16 +1345,29 @@ JSScript::finalize(JSContext *cx, bool b
     mjit::ReleaseScriptCode(cx, this);
 #endif
 
     destroyCounts(cx);
 
     if (sourceMap)
         cx->free_(sourceMap);
 
+    if (debug) {
+        jsbytecode *end = code + length;
+        for (jsbytecode *pc = code; pc < end; pc++) {
+            if (BreakpointSite *site = getBreakpointSite(pc)) {
+                /* Breakpoints are swept before finalization. */
+                JS_ASSERT(site->firstBreakpoint() == NULL);
+                site->clearTrap(cx, NULL, NULL);
+                JS_ASSERT(getBreakpointSite(pc) == NULL);
+            }
+        }
+        cx->free_(debug);
+    }
+
 #if JS_SCRIPT_INLINE_DATA_LIMIT
     if (data != inlineData)
 #endif
     {
         JS_POISON(data, 0xdb, dataSize());
         cx->free_(data);
     }
 }
@@ -1447,17 +1454,17 @@ js_PCToLineNumber(JSContext *cx, JSScrip
     /* Cope with StackFrame.pc value prior to entering js_Interpret. */
     if (!pc)
         return 0;
 
     /*
      * Special case: function definition needs no line number note because
      * the function's script contains its starting line number.
      */
-    JSOp op = js_GetOpcode(cx, script, pc);
+    JSOp op = JSOp(*pc);
     if (js_CodeSpec[op].format & JOF_INDEXBASE)
         pc += js_CodeSpec[op].length;
     if (*pc == JSOP_DEFFUN) {
         JSFunction *fun;
         GET_FUNCTION_FROM_BYTECODE(script, pc, 0, fun);
         return fun->script()->lineno;
     }
 
@@ -1674,61 +1681,182 @@ js_CloneScript(JSContext *cx, JSScript *
 
 void
 JSScript::copyClosedSlotsTo(JSScript *other)
 {
     memcpy(other->closedSlots, closedSlots, nClosedArgs + nClosedVars);
 }
 
 bool
+JSScript::ensureHasDebug(JSContext *cx)
+{
+    if (debug)
+        return true;
+
+    size_t nbytes = offsetof(DebugScript, breakpoints) + length * sizeof(BreakpointSite*);
+    debug = (DebugScript *) cx->calloc_(nbytes);
+    if (!debug)
+        return false;
+
+    /*
+     * Ensure that any Interpret() instances running on this script have
+     * interrupts enabled. The interrupts must stay enabled until the
+     * debug state is destroyed.
+     */
+    InterpreterFrames *frames;
+    for (frames = JS_THREAD_DATA(cx)->interpreterFrames; frames; frames = frames->older)
+        frames->enableInterruptsIfRunning(this);
+
+    return true;
+}
+
+bool
 JSScript::recompileForStepMode(JSContext *cx)
 {
 #ifdef JS_METHODJIT
     js::mjit::JITScript *jit = jitNormal ? jitNormal : jitCtor;
     if (jit && stepModeEnabled() != jit->singleStepMode) {
         js::mjit::Recompiler recompiler(cx, this);
         recompiler.recompile();
     }
 #endif
     return true;
 }
 
 bool
 JSScript::tryNewStepMode(JSContext *cx, uint32 newValue)
 {
-    uint32 prior = stepMode;
-    stepMode = newValue;
+    JS_ASSERT(debug);
+
+    uint32 prior = debug->stepMode;
+    debug->stepMode = newValue;
 
     if (!prior != !newValue) {
         /* Step mode has been enabled or disabled. Alert the methodjit. */
         if (!recompileForStepMode(cx)) {
-            stepMode = prior;
+            debug->stepMode = prior;
             return false;
         }
 
-        if (newValue) {
-            /* Step mode has been enabled. Alert the interpreter. */
-            InterpreterFrames *frames;
-            for (frames = JS_THREAD_DATA(cx)->interpreterFrames; frames; frames = frames->older)
-                frames->enableInterruptsIfRunning(this);
+        if (!stepModeEnabled() && !debug->numSites) {
+            cx->free_(debug);
+            debug = NULL;
         }
     }
+
     return true;
 }
 
 bool
 JSScript::setStepModeFlag(JSContext *cx, bool step)
 {
-    return tryNewStepMode(cx, (stepMode & stepCountMask) | (step ? stepFlagMask : 0));
+    if (!ensureHasDebug(cx))
+        return false;
+
+    return tryNewStepMode(cx, (debug->stepMode & stepCountMask) | (step ? stepFlagMask : 0));
 }
 
 bool
 JSScript::changeStepModeCount(JSContext *cx, int delta)
 {
+    if (!ensureHasDebug(cx))
+        return false;
+
     assertSameCompartment(cx, this);
     JS_ASSERT_IF(delta > 0, cx->compartment->debugMode());
 
-    uint32 count = stepMode & stepCountMask;
+    uint32 count = debug->stepMode & stepCountMask;
     JS_ASSERT(((count + delta) & stepCountMask) == count + delta);
     return tryNewStepMode(cx,
-                          (stepMode & stepFlagMask) |
+                          (debug->stepMode & stepFlagMask) |
                           ((count + delta) & stepCountMask));
 }
+
+BreakpointSite *
+JSScript::getOrCreateBreakpointSite(JSContext *cx, jsbytecode *pc,
+                                    GlobalObject *scriptGlobal)
+{
+    JS_ASSERT(size_t(pc - code) < length);
+
+    if (!ensureHasDebug(cx))
+        return NULL;
+
+    BreakpointSite *&site = debug->breakpoints[pc - code];
+
+    if (!site) {
+        site = cx->runtime->new_<BreakpointSite>(this, pc);
+        if (!site) {
+            js_ReportOutOfMemory(cx);
+            return NULL;
+        }
+        debug->numSites++;
+    }
+
+    if (site->scriptGlobal)
+        JS_ASSERT_IF(scriptGlobal, site->scriptGlobal == scriptGlobal);
+    else
+        site->scriptGlobal = scriptGlobal;
+
+    return site;
+}
+
+void
+JSScript::destroyBreakpointSite(JSRuntime *rt, jsbytecode *pc)
+{
+    JS_ASSERT(unsigned(pc - code) < length);
+
+    BreakpointSite *&site = debug->breakpoints[pc - code];
+    JS_ASSERT(site);
+
+    rt->delete_(site);
+    site = NULL;
+
+    if (--debug->numSites == 0 && !stepModeEnabled()) {
+        rt->free_(debug);
+        debug = NULL;
+    }
+}
+
+void
+JSScript::clearBreakpointsIn(JSContext *cx, js::Debugger *dbg, JSObject *handler)
+{
+    if (!hasAnyBreakpointsOrStepMode())
+        return;
+
+    jsbytecode *end = code + length;
+    for (jsbytecode *pc = code; pc < end; pc++) {
+        BreakpointSite *site = getBreakpointSite(pc);
+        if (site) {
+            Breakpoint *nextbp;
+            for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = nextbp) {
+                nextbp = bp->nextInSite();
+                if ((!dbg || bp->debugger == dbg) && (!handler || bp->getHandler() == handler))
+                    bp->destroy(cx);
+            }
+        }
+    }
+}
+
+void
+JSScript::clearTraps(JSContext *cx)
+{
+    if (!hasAnyBreakpointsOrStepMode())
+        return;
+
+    jsbytecode *end = code + length;
+    for (jsbytecode *pc = code; pc < end; pc++) {
+        BreakpointSite *site = getBreakpointSite(pc);
+        if (site)
+            site->clearTrap(cx);
+    }
+}
+
+void
+JSScript::markTrapClosures(JSTracer *trc)
+{
+    JS_ASSERT(hasAnyBreakpointsOrStepMode());
+
+    for (unsigned i = 0; i < length; i++) {
+        BreakpointSite *site = debug->breakpoints[i];
+        if (site && site->trapHandler)
+            MarkValue(trc, site->trapClosure, "trap closure");
+    }
+}
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -354,16 +354,39 @@ class ScriptOpcodeCounts
     }
 
     // Boolean conversion, for 'if (counters) ...'
     operator void*() const {
         return counts;
     }
 };
 
+class DebugScript
+{
+    friend struct ::JSScript;
+
+    /*
+     * When non-zero, compile script in single-step mode. The top bit is set and
+     * cleared by setStepMode, as used by JSD. The lower bits are a count,
+     * adjusted by changeStepModeCount, used by the Debugger object. Only
+     * when the bit is clear and the count is zero may we compile the script
+     * without single-step support.
+     */
+    uint32          stepMode;
+
+    /* Number of breakpoint sites at opcodes in the script. */
+    uint32          numSites;
+
+    /*
+     * Array with all breakpoints installed at opcodes in the script, indexed
+     * by the offset of the opcode into the script.
+     */
+    BreakpointSite  *breakpoints[1];
+};
+
 } /* namespace js */
 
 static const uint32 JS_SCRIPT_COOKIE = 0xc00cee;
 
 struct JSScript : public js::gc::Cell {
     /*
      * Two successively less primitive ways to make a new JSScript.  The first
      * does *not* call a non-null cx->runtime->newScriptHook -- only the second,
@@ -413,25 +436,16 @@ struct JSScript : public js::gc::Cell {
                                        regexps  */
     uint8           trynotesOffset; /* offset to the array of try notes */
     uint8           globalsOffset;  /* offset to the array of global slots */
     uint8           constOffset;    /* offset to the array of constants */
 
     uint16          nTypeSets;      /* number of type sets used in this script for
                                        dynamic type monitoring */
 
-    /*
-     * When non-zero, compile script in single-step mode. The top bit is set and
-     * cleared by setStepMode, as used by JSD. The lower bits are a count,
-     * adjusted by changeStepModeCount, used by the Debugger object. Only
-     * when the bit is clear and the count is zero may we compile the script
-     * without single-step support.
-     */
-    uint32          stepMode;
-
     uint32          lineno;     /* base line number of script */
 
     uint32          mainOffset; /* offset of main entry point from code, after
                                    predef'ing prolog */
     bool            noScriptRval:1; /* no need for result value of last
                                        expression statement */
     bool            savedCallerFun:1; /* can call getCallerFunction() */
     bool            hasSharps:1;      /* script uses sharp variables */
@@ -508,16 +522,17 @@ struct JSScript : public js::gc::Cell {
     JSScript        *&evalHashLink() { return *globalObject.unsafeGetUnioned(); }
 
     uint32          *closedSlots; /* vector of closed slots; args first, then vars. */
 
     /* Execution and profiling information for JIT code in the script. */
     js::ScriptOpcodeCounts pcCounters;
 
   private:
+    js::DebugScript *debug;
     JSFunction      *function_;
   public:
 
     /*
      * Original compiled function for the script, if it has a function.
      * NULL for global and eval scripts.
      */
     JSFunction *function() const { return function_; }
@@ -627,17 +642,17 @@ struct JSScript : public js::gc::Cell {
 
     /* Size of the JITScript and all sections.  (This method is implemented in MethodJIT.h.) */
     JS_FRIEND_API(size_t) jitDataSize(JSMallocSizeOfFun mallocSizeOf);
 
 #endif
 
     /* Counter accessors. */
     js::OpcodeCounts getCounts(jsbytecode *pc) {
-        JS_ASSERT(unsigned(pc - code) < length);
+        JS_ASSERT(size_t(pc - code) < length);
         return pcCounters.counts[pc - code];
     }
 
     bool initCounts(JSContext *cx);
     void destroyCounts(JSContext *cx);
 
     jsbytecode *main() {
         return code + mainOffset;
@@ -741,37 +756,58 @@ struct JSScript : public js::gc::Cell {
      * Attempt to recompile with or without single-stepping support, as directed
      * by stepModeEnabled().
      */
     bool recompileForStepMode(JSContext *cx);
 
     /* Attempt to change this->stepMode to |newValue|. */
     bool tryNewStepMode(JSContext *cx, uint32 newValue);
 
+    bool ensureHasDebug(JSContext *cx);
+
   public:
+    bool hasBreakpointsAt(jsbytecode *pc) { return !!getBreakpointSite(pc); }
+    bool hasAnyBreakpointsOrStepMode() { return !!debug; }
+
+    js::BreakpointSite *getBreakpointSite(jsbytecode *pc)
+    {
+        JS_ASSERT(size_t(pc - code) < length);
+        return debug ? debug->breakpoints[pc - code] : NULL;
+    }
+
+    js::BreakpointSite *getOrCreateBreakpointSite(JSContext *cx, jsbytecode *pc,
+                                                  js::GlobalObject *scriptGlobal);
+
+    void destroyBreakpointSite(JSRuntime *rt, jsbytecode *pc);
+
+    void clearBreakpointsIn(JSContext *cx, js::Debugger *dbg, JSObject *handler);
+    void clearTraps(JSContext *cx);
+
+    void markTrapClosures(JSTracer *trc);
+
     /*
      * Set or clear the single-step flag. If the flag is set or the count
      * (adjusted by changeStepModeCount) is non-zero, then the script is in
      * single-step mode. (JSD uses an on/off-style interface; Debugger uses a
      * count-style interface.)
      */
     bool setStepModeFlag(JSContext *cx, bool step);
 
     /*
      * Increment or decrement the single-step count. If the count is non-zero or
      * the flag (set by setStepModeFlag) is set, then the script is in
      * single-step mode. (JSD uses an on/off-style interface; Debugger uses a
      * count-style interface.)
      */
     bool changeStepModeCount(JSContext *cx, int delta);
 
-    bool stepModeEnabled() { return !!stepMode; }
+    bool stepModeEnabled() { return debug && !!debug->stepMode; }
 
 #ifdef DEBUG
-    uint32 stepModeCount() { return stepMode & stepCountMask; }
+    uint32 stepModeCount() { return debug ? (debug->stepMode & stepCountMask) : 0; }
 #endif
 
     void finalize(JSContext *cx, bool background);
 
     static inline void writeBarrierPre(JSScript *script);
     static inline void writeBarrierPost(JSScript *script, void *addr);
 };
 
@@ -884,25 +920,16 @@ enum LineOption {
     NOT_CALLED_FROM_JSOP_EVAL
 };
 
 inline const char *
 CurrentScriptFileAndLine(JSContext *cx, uintN *linenop, LineOption = NOT_CALLED_FROM_JSOP_EVAL);
 
 }
 
-static JS_INLINE JSOp
-js_GetOpcode(JSContext *cx, JSScript *script, jsbytecode *pc)
-{
-    JSOp op = (JSOp) *pc;
-    if (op == JSOP_TRAP)
-        op = JS_GetTrapOpcode(cx, script, pc);
-    return op;
-}
-
 extern JSScript *
 js_CloneScript(JSContext *cx, JSScript *script);
 
 /*
  * NB: after a successful JSXDR_DECODE, js_XDRScript callers must do any
  * required subsequent set-up of owning function or script object and then call
  * js_CallNewScriptHook.
  */
--- a/js/src/jsscriptinlines.h
+++ b/js/src/jsscriptinlines.h
@@ -114,17 +114,17 @@ Bindings::extensibleParents()
 
 extern const char *
 CurrentScriptFileAndLineSlow(JSContext *cx, uintN *linenop);
 
 inline const char *
 CurrentScriptFileAndLine(JSContext *cx, uintN *linenop, LineOption opt)
 {
     if (opt == CALLED_FROM_JSOP_EVAL) {
-        JS_ASSERT(js_GetOpcode(cx, cx->fp()->script(), cx->regs().pc) == JSOP_EVAL);
+        JS_ASSERT(JSOp(*cx->regs().pc) == JSOP_EVAL);
         JS_ASSERT(*(cx->regs().pc + JSOP_EVAL_LENGTH) == JSOP_LINENO);
         *linenop = GET_UINT16(cx->regs().pc + JSOP_EVAL_LENGTH);
         return cx->fp()->script()->filename;
     }
 
     return CurrentScriptFileAndLineSlow(cx, linenop);
 }
 
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -2156,18 +2156,18 @@ js::str_replace(JSContext *cx, uintN arg
                 Value table = UndefinedValue();
                 if (JSOp(*pc) == JSOP_GETFCSLOT) {
                     table = fun->getFlatClosureUpvar(GET_UINT16(pc));
                     pc += JSOP_GETFCSLOT_LENGTH;
                 }
 
                 if (table.isObject() &&
                     JSOp(*pc) == JSOP_GETARG && GET_SLOTNO(pc) == 0 &&
-                    JSOp(*(pc + JSOP_GETARG_LENGTH)) == JSOP_GETELEM &&
-                    JSOp(*(pc + JSOP_GETARG_LENGTH + JSOP_GETELEM_LENGTH)) == JSOP_RETURN) {
+                    JSOp(pc[JSOP_GETARG_LENGTH]) == JSOP_GETELEM &&
+                    JSOp(pc[JSOP_GETARG_LENGTH + JSOP_GETELEM_LENGTH]) == JSOP_RETURN) {
                     Class *clasp = table.toObject().getClass();
                     if (clasp->isNative() &&
                         !clasp->ops.lookupProperty &&
                         !clasp->ops.getProperty) {
                         rdata.elembase = &table.toObject();
                     }
                 }
             }
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -237,17 +237,17 @@ mjit::Compiler::scanInlineCalls(uint32 i
         script->isActiveEval) {
         return Compile_Okay;
     }
 
     uint32 nextOffset = 0;
     while (nextOffset < script->length) {
         uint32 offset = nextOffset;
         jsbytecode *pc = script->code + offset;
-        nextOffset = offset + analyze::GetBytecodeLength(pc);
+        nextOffset = offset + GetBytecodeLength(pc);
 
         Bytecode *code = analysis->maybeCode(pc);
         if (!code)
             continue;
 
         /* :XXX: Not yet inlining 'new' calls. */
         if (JSOp(*pc) != JSOP_CALL)
             continue;
@@ -953,22 +953,18 @@ mjit::Compiler::finishThisUp(JITScript *
     stubcc.masm.executableCopy(result + masm.size());
 
     JSC::LinkBuffer fullCode(result, codeSize, JSC::METHOD_CODE);
     JSC::LinkBuffer stubCode(result + masm.size(), stubcc.size(), JSC::METHOD_CODE);
 
     size_t nNmapLive = loopEntries.length();
     for (size_t i = 0; i < script->length; i++) {
         Bytecode *opinfo = analysis->maybeCode(i);
-        if (opinfo && opinfo->safePoint) {
-            /* loopEntries cover any safe points which are at loop heads. */
-            JSOp op = js_GetOpcode(cx, script, script->code + i);
-            if (!cx->typeInferenceEnabled() || op != JSOP_LOOPHEAD)
-                nNmapLive++;
-        }
+        if (opinfo && opinfo->safePoint)
+            nNmapLive++;
     }
 
     /* Please keep in sync with JITScript::scriptDataSize! */
     size_t dataSize = sizeof(JITScript) +
                       sizeof(NativeMapEntry) * nNmapLive +
                       sizeof(InlineFrame) * inlineFrames.length() +
                       sizeof(CallSite) * callSites.length() +
 #if defined JS_MONOIC
@@ -1019,19 +1015,16 @@ mjit::Compiler::finishThisUp(JITScript *
     NativeMapEntry *jitNmap = (NativeMapEntry *)cursor;
     jit->nNmapPairs = nNmapLive;
     cursor += sizeof(NativeMapEntry) * jit->nNmapPairs;
     size_t ix = 0;
     if (jit->nNmapPairs > 0) {
         for (size_t i = 0; i < script->length; i++) {
             Bytecode *opinfo = analysis->maybeCode(i);
             if (opinfo && opinfo->safePoint) {
-                JSOp op = js_GetOpcode(cx, script, script->code + i);
-                if (cx->typeInferenceEnabled() && op == JSOP_LOOPHEAD)
-                    continue;
                 Label L = jumpMap[i];
                 JS_ASSERT(L.isSet());
                 jitNmap[ix].bcOff = i;
                 jitNmap[ix].ncode = (uint8 *)(result + masm.distanceOf(L));
                 ix++;
             }
         }
         for (size_t i = 0; i < loopEntries.length(); i++) {
@@ -1506,34 +1499,30 @@ FixDouble(Value &val)
 {
     if (val.isInt32())
         val.setDouble((double)val.toInt32());
 }
 
 CompileStatus
 mjit::Compiler::generateMethod()
 {
-    mjit::AutoScriptRetrapper trapper(cx, script);
     SrcNoteLineScanner scanner(script->notes(), script->lineno);
 
     /* For join points, whether there was fallthrough from the previous opcode. */
     bool fallthrough = true;
 
     /* Last bytecode processed. */
     jsbytecode *lastPC = NULL;
 
     for (;;) {
         JSOp op = JSOp(*PC);
         int trap = stubs::JSTRAP_NONE;
-        if (op == JSOP_TRAP) {
-            if (!trapper.untrap(PC))
-                return Compile_Error;
-            op = JSOp(*PC);
+
+        if (script->hasBreakpointsAt(PC))
             trap |= stubs::JSTRAP_TRAP;
-        }
 
         Bytecode *opinfo = analysis->maybeCode(PC);
 
         if (!opinfo) {
             if (op == JSOP_STOP)
                 break;
             if (js_CodeSpec[op].length != -1)
                 PC += js_CodeSpec[op].length;
@@ -1732,17 +1721,17 @@ mjit::Compiler::generateMethod()
 
             /*
              * Watch for gotos which are entering a 'for' or 'while' loop.
              * These jump to the loop condition test and are immediately
              * followed by the head of the loop.
              */
             jsbytecode *next = PC + js_CodeSpec[op].length;
             if (cx->typeInferenceEnabled() && analysis->maybeCode(next) &&
-                js_GetOpcode(cx, script, next) == JSOP_LOOPHEAD) {
+                JSOp(*next) == JSOP_LOOPHEAD) {
                 frame.syncAndForgetEverything();
                 Jump j = masm.jump();
                 if (!startLoop(next, j, target))
                     return Compile_Error;
             } else {
                 if (!frame.syncForBranch(target, Uses(0)))
                     return Compile_Error;
                 Jump j = masm.jump();
@@ -2840,17 +2829,17 @@ mjit::Compiler::generateMethod()
 #endif
             return Compile_Abort;
         }
 
     /**********************
      *  END COMPILER OPS  *
      **********************/
 
-        if (cx->typeInferenceEnabled() && PC == lastPC + analyze::GetBytecodeLength(lastPC)) {
+        if (cx->typeInferenceEnabled() && PC == lastPC + GetBytecodeLength(lastPC)) {
             /*
              * Inform the frame of the type sets for values just pushed. Skip
              * this if we did any opcode fusions, we don't keep track of the
              * associated type sets in such cases.
              */
             unsigned nuses = GetUseCount(script, lastPC - script->code);
             unsigned ndefs = GetDefCount(script, lastPC - script->code);
             for (unsigned i = 0; i < ndefs; i++) {
@@ -2864,17 +2853,17 @@ mjit::Compiler::generateMethod()
 
         if (script->pcCounters) {
             size_t length = masm.size() - masm.distanceOf(codeStart);
             bool typesUpdated = false;
 
             /* Update information about the type of value pushed by arithmetic ops. */
             if ((js_CodeSpec[op].format & JOF_ARITH) && !arithUpdated) {
                 FrameEntry *pushed = NULL;
-                if (PC == lastPC + analyze::GetBytecodeLength(lastPC))
+                if (PC == lastPC + GetBytecodeLength(lastPC))
                     pushed = frame.peek(-1);
                 updateArithCounters(lastPC, pushed, arithFirstUseType, arithSecondUseType);
                 typesUpdated = true;
             }
 
             /* Update information about the result type of access operations. */
             if (OpcodeCounts::accessOp(op) &&
                 op != JSOP_SETPROP && op != JSOP_SETMETHOD && op != JSOP_SETELEM) {
@@ -3408,17 +3397,17 @@ mjit::Compiler::emitReturn(FrameEntry *f
 
         /*
          * Simple tests to see if we are at the end of the script and will
          * fallthrough after the script body finishes, thus won't need to jump.
          */
         bool endOfScript =
             (JSOp(*PC) == JSOP_STOP) ||
             (JSOp(*PC) == JSOP_RETURN &&
-             (JSOp(*(PC + JSOP_RETURN_LENGTH)) == JSOP_STOP &&
+             (JSOp(PC[JSOP_RETURN_LENGTH]) == JSOP_STOP &&
               !analysis->maybeCode(PC + JSOP_RETURN_LENGTH)));
         if (!endOfScript)
             a->returnJumps->append(masm.jump());
 
         if (a->returnSet)
             frame.freeReg(a->returnRegister);
         return;
     }
--- a/js/src/methodjit/FrameState.cpp
+++ b/js/src/methodjit/FrameState.cpp
@@ -575,17 +575,17 @@ RegisterAllocation *
 FrameState::computeAllocation(jsbytecode *target)
 {
     JS_ASSERT(cx->typeInferenceEnabled());
     RegisterAllocation *alloc = cx->typeLifoAlloc().new_<RegisterAllocation>(false);
     if (!alloc)
         return NULL;
 
     if (a->analysis->getCode(target).exceptionEntry || a->analysis->getCode(target).switchTarget ||
-        JSOp(*target) == JSOP_TRAP) {
+        a->script->hasBreakpointsAt(target)) {
         /* State must be synced at exception and switch targets, and at traps. */
 #ifdef DEBUG
         if (IsJaegerSpewChannelActive(JSpew_Regalloc)) {
             JaegerSpew(JSpew_Regalloc, "allocation at %u:", unsigned(target - a->script->code));
             dumpAllocation(alloc);
         }
 #endif
         return alloc;
--- a/js/src/methodjit/InvokeHelpers.cpp
+++ b/js/src/methodjit/InvokeHelpers.cpp
@@ -113,17 +113,17 @@ top:
 
             jsbytecode *pc = script->main() + tn->start + tn->length;
             cx->regs().pc = pc;
             JSBool ok = UnwindScope(cx, tn->stackDepth, JS_TRUE);
             JS_ASSERT(cx->regs().sp == fp->base() + tn->stackDepth);
 
             switch (tn->kind) {
                 case JSTRY_CATCH:
-                  JS_ASSERT(js_GetOpcode(cx, fp->script(), pc) == JSOP_ENTERBLOCK);
+                  JS_ASSERT(JSOp(*pc) == JSOP_ENTERBLOCK);
 
 #if JS_HAS_GENERATORS
                   /* Catch cannot intercept the closing of a generator. */
                   if (JS_UNLIKELY(cx->getPendingException().isMagic(JS_GENERATOR_CLOSING)))
                       break;
 #endif
 
                   /*
@@ -149,17 +149,17 @@ top:
                   /*
                    * This is similar to JSOP_ENDITER in the interpreter loop,
                    * except the code now uses the stack slot normally used by
                    * JSOP_NEXTITER, namely regs.sp[-1] before the regs.sp -= 2
                    * adjustment and regs.sp[1] after, to save and restore the
                    * pending exception.
                    */
                   Value v = cx->getPendingException();
-                  JS_ASSERT(js_GetOpcode(cx, fp->script(), pc) == JSOP_ENDITER);
+                  JS_ASSERT(JSOp(*pc) == JSOP_ENDITER);
                   cx->clearPendingException();
                   ok = !!js_CloseIterator(cx, &cx->regs().sp[-1].toObject());
                   cx->regs().sp -= 1;
                   if (!ok)
                       goto top;
                   cx->setPendingException(v);
                 }
             }
@@ -174,17 +174,17 @@ top:
  */
 static void
 InlineReturn(VMFrame &f)
 {
     JS_ASSERT(f.fp() != f.entryfp);
     JS_ASSERT(!IsActiveWithOrBlock(f.cx, f.fp()->scopeChain(), 0));
     f.cx->stack.popInlineFrame(f.regs);
 
-    DebugOnly<JSOp> op = js_GetOpcode(f.cx, f.fp()->script(), f.regs.pc);
+    DebugOnly<JSOp> op = JSOp(*f.regs.pc);
     JS_ASSERT(op == JSOP_CALL ||
               op == JSOP_NEW ||
               op == JSOP_EVAL ||
               op == JSOP_FUNCALL ||
               op == JSOP_FUNAPPLY);
     f.regs.pc += JSOP_CALL_LENGTH;
 }
 
@@ -623,22 +623,22 @@ js_InternalThrow(VMFrame &f)
     cx->regs().sp = fp->base() + script->analysis()->getCode(pc).stackDepth;
 
     /*
      * Interpret the ENTERBLOCK and EXCEPTION opcodes, so that we don't go
      * back into the interpreter with a pending exception. This will cause
      * it to immediately rethrow.
      */
     if (cx->isExceptionPending()) {
-        JS_ASSERT(js_GetOpcode(cx, script, pc) == JSOP_ENTERBLOCK);
+        JS_ASSERT(JSOp(*pc) == JSOP_ENTERBLOCK);
         JSObject *obj = script->getObject(GET_SLOTNO(pc));
         Value *vp = cx->regs().sp + OBJ_BLOCK_COUNT(cx, obj);
         SetValueRangeToUndefined(cx->regs().sp, vp);
         cx->regs().sp = vp;
-        JS_ASSERT(js_GetOpcode(cx, script, pc + JSOP_ENTERBLOCK_LENGTH) == JSOP_EXCEPTION);
+        JS_ASSERT(JSOp(pc[JSOP_ENTERBLOCK_LENGTH]) == JSOP_EXCEPTION);
         cx->regs().sp[0] = cx->getPendingException();
         cx->clearPendingException();
         cx->regs().sp++;
         cx->regs().pc = pc + JSOP_ENTERBLOCK_LENGTH + JSOP_EXCEPTION_LENGTH;
     }
 
     *f.oldregs = f.regs;
 
@@ -710,17 +710,17 @@ static const char *OpcodeNames[] = {
 static void
 FinishVarIncOp(VMFrame &f, RejoinState rejoin, Value ov, Value nv, Value *vp)
 {
     /* Finish an increment operation on a LOCAL or ARG. These do not involve property accesses. */
     JS_ASSERT(rejoin == REJOIN_POS || rejoin == REJOIN_BINARY);
 
     JSContext *cx = f.cx;
 
-    JSOp op = js_GetOpcode(cx, f.script(), f.pc());
+    JSOp op = JSOp(*f.pc());
     const JSCodeSpec *cs = &js_CodeSpec[op];
 
     unsigned i = GET_SLOTNO(f.pc());
     Value *var = (JOF_TYPE(cs->format) == JOF_LOCAL) ? f.fp()->slots() + i : &f.fp()->formalArg(i);
 
     if (rejoin == REJOIN_POS) {
         double d = ov.toNumber();
         double N = (cs->format & JOF_INC) ? 1 : -1;
@@ -747,17 +747,16 @@ js_InternalInterpret(void *returnData, v
         rejoin = (RejoinState) (jsrejoin >> 1);
     }
 
     JSContext *cx = f.cx;
     StackFrame *fp = f.regs.fp();
     JSScript *script = fp->script();
 
     jsbytecode *pc = f.regs.pc;
-    analyze::UntrapOpcode untrap(cx, script, pc);
 
     JSOp op = JSOp(*pc);
     const JSCodeSpec *cs = &js_CodeSpec[op];
 
     if (!script->ensureRanAnalysis(cx, NULL)) {
         js_ReportOutOfMemory(cx);
         return js_InternalThrow(f);
     }
@@ -768,17 +767,17 @@ js_InternalInterpret(void *returnData, v
     /*
      * f.regs.sp is not normally maintained by stubs (except for call prologues
      * where it indicates the new frame), so is not expected to be coherent
      * here. Update it to its value at the start of the opcode.
      */
     Value *oldsp = f.regs.sp;
     f.regs.sp = fp->base() + analysis->getCode(pc).stackDepth;
 
-    jsbytecode *nextpc = pc + analyze::GetBytecodeLength(pc);
+    jsbytecode *nextpc = pc + GetBytecodeLength(pc);
     Value *nextsp = NULL;
     if (nextpc != script->code + script->length && analysis->maybeCode(nextpc))
         nextsp = fp->base() + analysis->getCode(nextpc).stackDepth;
 
     JS_ASSERT(&cx->regs() == &f.regs);
 
 #ifdef JS_METHODJIT_SPEW
     JaegerSpew(JSpew_Recompile, "interpreter rejoin (file \"%s\") (line \"%d\") (op %s) (opline \"%d\")\n",
@@ -793,17 +792,16 @@ js_InternalInterpret(void *returnData, v
         /*
          * We may reenter the interpreter while finishing the INC/DEC operation
          * on a local or arg (property INC/DEC operations will rejoin into the
          * decomposed version of the op.
          */
         JS_ASSERT(cs->format & (JOF_LOCAL | JOF_QARG));
 
         nextDepth = analysis->getCode(nextpc).stackDepth;
-        untrap.retrap();
         enter.leave();
 
         if (rejoin != REJOIN_BINARY || !analysis->incrementInitialValueObserved(pc)) {
             /* Stack layout is 'V', 'N' or 'N+1' (only if the N is not needed) */
             FinishVarIncOp(f, rejoin, nextsp[-1], nextsp[-1], &nextsp[-1]);
         } else {
             /* Stack layout is 'N N+1' */
             FinishVarIncOp(f, rejoin, nextsp[-1], nextsp[0], &nextsp[-1]);
@@ -837,19 +835,19 @@ js_InternalInterpret(void *returnData, v
         break;
 
       case REJOIN_RESUME:
         break;
 
       case REJOIN_TRAP:
         /*
          * Make sure when resuming in the interpreter we do not execute the
-         * trap again. Watch out for the case where the TRAP removed itself.
+         * trap again. Watch out for the case where the trap removed itself.
          */
-        if (untrap.trap)
+        if (script->hasBreakpointsAt(pc))
             skipTrap = true;
         break;
 
       case REJOIN_FALLTHROUGH:
         f.regs.pc = nextpc;
         break;
 
       case REJOIN_NATIVE:
@@ -1005,17 +1003,16 @@ js_InternalInterpret(void *returnData, v
                 nextsp[-1] = nextsp[0];
             }
         }
         break;
 
       case REJOIN_CALL_SPLAT: {
         /* Leave analysis early and do the Invoke which SplatApplyArgs prepared. */
         nextDepth = analysis->getCode(nextpc).stackDepth;
-        untrap.retrap();
         enter.leave();
         f.regs.sp = nextsp + 2 + f.u.call.dynamicArgc;
         if (!InvokeKernel(cx, CallArgsFromSp(f.u.call.dynamicArgc, f.regs.sp)))
             return js_InternalThrow(f);
         nextsp[-1] = nextsp[0];
         f.regs.pc = nextpc;
         break;
       }
@@ -1096,33 +1093,32 @@ js_InternalInterpret(void *returnData, v
 
       case REJOIN_BRANCH: {
         /*
          * This must be an opcode fused with IFNE/IFEQ. Unfused IFNE/IFEQ are
          * implemented in terms of ValueToBoolean, which is infallible and
          * cannot trigger recompilation.
          */
         bool takeBranch = false;
-        analyze::UntrapOpcode untrap(cx, script, nextpc);
         switch (JSOp(*nextpc)) {
           case JSOP_IFNE:
           case JSOP_IFNEX:
             takeBranch = returnReg != NULL;
             break;
           case JSOP_IFEQ:
           case JSOP_IFEQX:
             takeBranch = returnReg == NULL;
             break;
           default:
             JS_NOT_REACHED("Bad branch op");
         }
         if (takeBranch)
             f.regs.pc = nextpc + analyze::GetJumpOffset(nextpc, nextpc);
         else
-            f.regs.pc = nextpc + analyze::GetBytecodeLength(nextpc);
+            f.regs.pc = nextpc + GetBytecodeLength(nextpc);
         break;
       }
 
       default:
         JS_NOT_REACHED("Missing rejoin");
     }
 
     if (nextDepth == uint32(-1))
--- a/js/src/methodjit/LoopState.cpp
+++ b/js/src/methodjit/LoopState.cpp
@@ -1783,17 +1783,17 @@ LoopState::analyzeLoopBody(unsigned fram
 
     /* Analyze the entire script for frames inlined in the loop body. */
     unsigned start = (frame == CrossScriptSSA::OUTER_FRAME) ? lifetime->head + JSOP_LOOPHEAD_LENGTH : 0;
     unsigned end = (frame == CrossScriptSSA::OUTER_FRAME) ? lifetime->backedge : script->length;
 
     unsigned offset = start;
     while (offset < end) {
         jsbytecode *pc = script->code + offset;
-        uint32 successorOffset = offset + analyze::GetBytecodeLength(pc);
+        uint32 successorOffset = offset + GetBytecodeLength(pc);
 
         analyze::Bytecode *opinfo = analysis->maybeCode(offset);
         if (!opinfo) {
             offset = successorOffset;
             continue;
         }
 
         JSOp op = JSOp(*pc);
--- a/js/src/methodjit/PolyIC.cpp
+++ b/js/src/methodjit/PolyIC.cpp
@@ -1735,17 +1735,17 @@ class ScopeNameCompiler : public PICStub
     {
         JSObject *obj = getprop.obj;
         JSObject *holder = getprop.holder;
         const JSProperty *prop = getprop.prop;
 
         if (!prop) {
             /* Kludge to allow (typeof foo == "undefined") tests. */
             if (kind == ic::PICInfo::NAME) {
-                JSOp op2 = js_GetOpcode(cx, f.script(), f.pc() + JSOP_NAME_LENGTH);
+                JSOp op2 = JSOp(f.pc()[JSOP_NAME_LENGTH]);
                 if (op2 == JSOP_TYPEOF) {
                     vp->setUndefined();
                     return true;
                 }
             }
             ReportAtomNotDefined(cx, atom);
             return false;
         }
--- a/js/src/methodjit/Retcon.cpp
+++ b/js/src/methodjit/Retcon.cpp
@@ -55,34 +55,16 @@
 #include "MethodJIT-inl.h"
 
 using namespace js;
 using namespace js::mjit;
 
 namespace js {
 namespace mjit {
 
-AutoScriptRetrapper::~AutoScriptRetrapper()
-{
-    while (!traps.empty()) {
-        jsbytecode *pc = traps.back();
-        traps.popBack();
-        *pc = JSOP_TRAP;
-    }
-}
-
-bool
-AutoScriptRetrapper::untrap(jsbytecode *pc)
-{
-    if (!traps.append(pc))
-        return false;
-    *pc = JS_GetTrapOpcode(traps.allocPolicy().context(), script, pc);
-    return true;
-}
-
 static inline JSRejoinState ScriptedRejoin(uint32 pcOffset)
 {
     return REJOIN_SCRIPTED | (pcOffset << 1);
 }
 
 static inline JSRejoinState StubRejoin(RejoinState rejoin)
 {
     return rejoin << 1;
--- a/js/src/methodjit/Retcon.h
+++ b/js/src/methodjit/Retcon.h
@@ -50,36 +50,16 @@
 #include "jsscript.h"
 #include "MethodJIT.h"
 #include "Compiler.h"
 
 namespace js {
 namespace mjit {
 
 /*
- * A problem often arises where, for one reason or another, a piece of code
- * wants to touch the script->code, but isn't expecting JSOP_TRAP. This allows
- * one to temporarily remove JSOP_TRAPs from the instruction stream (without
- * copying) and automatically re-add them on scope exit.
- */
-class AutoScriptRetrapper
-{
-  public:
-    AutoScriptRetrapper(JSContext *cx, JSScript *script1) :
-        script(script1), traps(cx) {};
-    ~AutoScriptRetrapper();
-
-    bool untrap(jsbytecode *pc);
-
-  private:
-    JSScript *script;
-    Vector<jsbytecode*> traps;
-};
-
-/*
  * This class is responsible for sanely destroying a JITed script while frames
  * for it are still on the stack, removing all references in the world to it
  * and patching up those existing frames to go into the interpreter. If you
  * ever change the code associated with a JSScript, or otherwise would cause
  * existing JITed code to be incorrect, you /must/ use this to invalidate the
  * JITed code, fixing up the stack in the process.
  */
 class Recompiler {
--- a/js/src/methodjit/StubCalls.cpp
+++ b/js/src/methodjit/StubCalls.cpp
@@ -295,17 +295,17 @@ NameOp(VMFrame &f, JSObject *obj, bool c
     } else {
         id = ATOM_TO_JSID(atom);
         JSProperty *prop;
         bool global = (js_CodeSpec[*f.pc()].format & JOF_GNAME);
         if (!js_FindPropertyHelper(cx, id, true, global, &obj, &obj2, &prop))
             return NULL;
         if (!prop) {
             /* Kludge to allow (typeof foo == "undefined") tests. */
-            JSOp op2 = js_GetOpcode(cx, f.script(), f.pc() + JSOP_NAME_LENGTH);
+            JSOp op2 = JSOp(f.pc()[JSOP_NAME_LENGTH]);
             if (op2 == JSOP_TYPEOF) {
                 f.regs.sp++;
                 f.regs.sp[-1].setUndefined();
                 return obj;
             }
             ReportAtomNotDefined(cx, atom);
             return NULL;
         }
@@ -1462,17 +1462,17 @@ static bool JS_ALWAYS_INLINE
 InlineGetProp(VMFrame &f)
 {
     JSContext *cx = f.cx;
     FrameRegs &regs = f.regs;
 
     Value *vp = &f.regs.sp[-1];
 
     if (vp->isMagic(JS_LAZY_ARGUMENTS)) {
-        JS_ASSERT(js_GetOpcode(cx, f.script(), f.pc()) == JSOP_LENGTH);
+        JS_ASSERT(JSOp(*f.pc()) == JSOP_LENGTH);
         regs.sp[-1] = Int32Value(regs.fp()->numActualArgs());
         return true;
     }
 
     JSObject *obj = ValueToObject(f.cx, vp);
     if (!obj)
         return false;
 
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -1807,17 +1807,17 @@ UpdateSwitchTableBounds(JSContext *cx, J
                         uintN *start, uintN *end)
 {
     jsbytecode *pc;
     JSOp op;
     ptrdiff_t jmplen;
     jsint low, high, n;
 
     pc = script->code + offset;
-    op = js_GetOpcode(cx, script, pc);
+    op = JSOp(*pc);
     switch (op) {
       case JSOP_TABLESWITCHX:
         jmplen = JUMPX_OFFSET_LEN;
         goto jump_table;
       case JSOP_TABLESWITCH:
         jmplen = JUMP_OFFSET_LEN;
       jump_table:
         pc += jmplen;
@@ -1866,17 +1866,17 @@ SrcNotes(JSContext *cx, JSScript *script
         offset += delta;
         SrcNoteType type = (SrcNoteType) SN_TYPE(sn);
         const char *name = js_SrcNoteSpec[type].name;
         if (type == SRC_LABEL) {
             /* Check if the source note is for a switch case. */
             if (switchTableStart <= offset && offset < switchTableEnd) {
                 name = "case";
             } else {
-                JSOp op = js_GetOpcode(cx, script, script->code + offset);
+                JSOp op = JSOp(script->code[offset]);
                 JS_ASSERT(op == JSOP_LABEL || op == JSOP_LABELX);
             }
         }
         Sprint(sp, "%3u: %4u %5u [%4u] %-8s", uintN(sn - notes), lineno, offset, delta, name);
         switch (type) {
           case SRC_SETLINE:
             lineno = js_GetSrcNoteOffset(sn, 0);
             Sprint(sp, " lineno %u", lineno);
@@ -1925,17 +1925,17 @@ SrcNotes(JSContext *cx, JSScript *script
             JSString *str = JS_DecompileFunction(cx, fun, JS_DONT_PRETTY_PRINT);
             JSAutoByteString bytes;
             if (!str || !bytes.encode(cx, str))
                 ReportException(cx);
             Sprint(sp, " function %u (%s)", index, !!bytes ? bytes.ptr() : "N/A");
             break;
           }
           case SRC_SWITCH: {
-            JSOp op = js_GetOpcode(cx, script, script->code + offset);
+            JSOp op = JSOp(script->code[offset]);
             if (op == JSOP_GOTO || op == JSOP_GOTOX)
                 break;
             Sprint(sp, " length %u", uintN(js_GetSrcNoteOffset(sn, 0)));
             uintN caseOff = (uintN) js_GetSrcNoteOffset(sn, 1);
             if (caseOff)
                 Sprint(sp, " first case offset %u", caseOff);
             UpdateSwitchTableBounds(cx, script, offset,
                                     &switchTableStart, &switchTableEnd);
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -120,20 +120,20 @@ ReportObjectRequired(JSContext *cx)
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_NONNULL_OBJECT);
     return false;
 }
 
 
 /*** Breakpoints *********************************************************************************/
 
 BreakpointSite::BreakpointSite(JSScript *script, jsbytecode *pc)
-  : script(script), pc(pc), realOpcode(JSOp(*pc)), scriptGlobal(NULL), enabledCount(0),
+  : script(script), pc(pc), scriptGlobal(NULL), enabledCount(0),
     trapHandler(NULL), trapClosure(UndefinedValue())
 {
-    JS_ASSERT(realOpcode != JSOP_TRAP);
+    JS_ASSERT(!script->hasBreakpointsAt(pc));
     JS_INIT_CLIST(&breakpoints);
 }
 
 /*
  * Precondition: script is live, meaning either it is a non-held script that is
  * on the stack or a held script that hasn't been GC'd.
  */
 static GlobalObject *
@@ -171,85 +171,68 @@ BreakpointSite::recompile(JSContext *cx,
 #endif
     return true;
 }
 
 bool
 BreakpointSite::inc(JSContext *cx)
 {
     if (enabledCount == 0 && !trapHandler) {
-        JS_ASSERT(*pc == realOpcode);
-        *pc = JSOP_TRAP;
-        if (!recompile(cx, false)) {
-            *pc = realOpcode;
+        if (!recompile(cx, false))
             return false;
-        }
     }
     enabledCount++;
     return true;
 }
 
 void
 BreakpointSite::dec(JSContext *cx)
 {
     JS_ASSERT(enabledCount > 0);
-    JS_ASSERT(*pc == JSOP_TRAP);
     enabledCount--;
-    if (enabledCount == 0 && !trapHandler) {
-        *pc = realOpcode;
+    if (enabledCount == 0 && !trapHandler)
         recompile(cx, false);  /* ignore failure */
-    }
 }
 
 bool
 BreakpointSite::setTrap(JSContext *cx, JSTrapHandler handler, const Value &closure)
 {
     if (enabledCount == 0) {
-        *pc = JSOP_TRAP;
-        if (!recompile(cx, true)) {
-            *pc = realOpcode;
+        if (!recompile(cx, true))
             return false;
-        }
     }
     trapHandler = handler;
     trapClosure = closure;
     return true;
 }
 
 void
-BreakpointSite::clearTrap(JSContext *cx, BreakpointSiteMap::Enum *e,
-                          JSTrapHandler *handlerp, Value *closurep)
+BreakpointSite::clearTrap(JSContext *cx, JSTrapHandler *handlerp, Value *closurep)
 {
     if (handlerp)
         *handlerp = trapHandler;
     if (closurep)
         *closurep = trapClosure;
 
     trapHandler = NULL;
     trapClosure = UndefinedValue();
     if (enabledCount == 0) {
-        *pc = realOpcode;
         if (!cx->runtime->gcRunning) {
             /* If the GC is running then the script is being destroyed. */
             recompile(cx, true);  /* ignore failure */
         }
-        destroyIfEmpty(cx->runtime, e);
+        destroyIfEmpty(cx->runtime);
     }
 }
 
 void
-BreakpointSite::destroyIfEmpty(JSRuntime *rt, BreakpointSiteMap::Enum *e)
+BreakpointSite::destroyIfEmpty(JSRuntime *rt)
 {
-    if (JS_CLIST_IS_EMPTY(&breakpoints) && !trapHandler) {
-        if (e)
-            e->removeFront();
-        else
-            script->compartment()->breakpointSites.remove(pc);
-        rt->delete_(this);
-    }
+    if (JS_CLIST_IS_EMPTY(&breakpoints) && !trapHandler)
+        script->destroyBreakpointSite(rt, pc);
 }
 
 Breakpoint *
 BreakpointSite::firstBreakpoint() const
 {
     if (JS_CLIST_IS_EMPTY(&breakpoints))
         return NULL;
     return Breakpoint::fromSiteLinks(JS_NEXT_LINK(&breakpoints));
@@ -279,24 +262,24 @@ Breakpoint::fromDebuggerLinks(JSCList *l
 
 Breakpoint *
 Breakpoint::fromSiteLinks(JSCList *links)
 {
     return (Breakpoint *) ((unsigned char *) links - offsetof(Breakpoint, siteLinks));
 }
 
 void
-Breakpoint::destroy(JSContext *cx, BreakpointSiteMap::Enum *e)
+Breakpoint::destroy(JSContext *cx)
 {
     if (debugger->enabled)
         site->dec(cx);
     JS_REMOVE_LINK(&debuggerLinks);
     JS_REMOVE_LINK(&siteLinks);
     JSRuntime *rt = cx->runtime;
-    site->destroyIfEmpty(rt, e);
+    site->destroyIfEmpty(rt);
     rt->delete_(this);
 }
 
 Breakpoint *
 Breakpoint::nextInDebugger()
 {
     JSCList *link = JS_NEXT_LINK(&debuggerLinks);
     return (link == &debugger->breakpoints) ? NULL : fromDebuggerLinks(link);
@@ -477,17 +460,17 @@ Debugger::slowPathOnLeaveFrame(JSContext
     }
 
     /*
      * If this is an eval frame, then from the debugger's perspective the
      * script is about to be destroyed. Remove any breakpoints in it.
      */
     if (fp->isEvalFrame()) {
         JSScript *script = fp->script();
-        script->compartment()->clearBreakpointsIn(cx, NULL, script, NULL);
+        script->clearBreakpointsIn(cx, NULL, NULL);
     }
 }
 
 bool
 Debugger::wrapDebuggeeValue(JSContext *cx, Value *vp)
 {
     assertSameCompartment(cx, object.get());
 
@@ -856,20 +839,21 @@ Debugger::slowPathOnNewScript(JSContext 
         }
     }
 }
 
 JSTrapStatus
 Debugger::onTrap(JSContext *cx, Value *vp)
 {
     StackFrame *fp = cx->fp();
+    JSScript *script = fp->script();
     GlobalObject *scriptGlobal = fp->scopeChain().getGlobal();
     jsbytecode *pc = cx->regs().pc;
-    BreakpointSite *site = cx->compartment->getBreakpointSite(pc);
-    JSOp op = site->realOpcode;
+    BreakpointSite *site = script->getBreakpointSite(pc);
+    JSOp op = JSOp(*pc);
 
     /* Build list of breakpoint handlers. */
     Vector<Breakpoint *> triggered(cx);
     for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = bp->nextInSite()) {
         if (!triggered.append(bp))
             return JSTRAP_ERROR;
     }
 
@@ -891,17 +875,17 @@ Debugger::onTrap(JSContext *cx, Value *v
                 return dbg->handleUncaughtException(ac, vp, false);
             Value rv;
             bool ok = CallMethodIfPresent(cx, bp->handler, "hit", 1, argv, &rv);
             JSTrapStatus st = dbg->parseResumptionValue(ac, ok, rv, vp, true);
             if (st != JSTRAP_CONTINUE)
                 return st;
 
             /* Calling JS code invalidates site. Reload it. */
-            site = cx->compartment->getBreakpointSite(pc);
+            site = script->getBreakpointSite(pc);
         }
     }
 
     if (site && site->trapHandler) {
         JSTrapStatus st = site->trapHandler(cx, fp->script(), pc, vp, site->trapClosure);
         if (st != JSTRAP_CONTINUE)
             return st;
     }
@@ -1099,20 +1083,16 @@ Debugger::markAllIteratively(GCMarker *t
      * convoluted since the easiest way to find them is via their debuggees.
      */
     JSContext *cx = trc->context;
     JSRuntime *rt = cx->runtime;
     JSCompartment *comp = rt->gcCurrentCompartment;
     for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); c++) {
         JSCompartment *dc = *c;
 
-        /* If dc is being collected, mark jsdbgapi.h trap closures in it. */
-        if (!comp || dc == comp)
-            markedAny = markedAny | dc->markTrapClosuresIteratively(trc);
-
         /*
          * If this is a single-compartment GC, no compartment can debug itself, so skip
          * |comp|. If it's a global GC, then search every compartment.
          */
         if (comp && dc == comp)
             continue;
 
         const GlobalObjectSet &debuggees = dc->getDebuggees();
@@ -1566,17 +1546,17 @@ Debugger::getNewestFrame(JSContext *cx, 
     return true;
 }
 
 JSBool
 Debugger::clearAllBreakpoints(JSContext *cx, uintN argc, Value *vp)
 {
     THIS_DEBUGGER(cx, argc, vp, "clearAllBreakpoints", args, dbg);
     for (GlobalObjectSet::Range r = dbg->debuggees.all(); !r.empty(); r.popFront())
-        r.front()->compartment()->clearBreakpointsIn(cx, dbg, NULL, NULL);
+        r.front()->compartment()->clearBreakpointsIn(cx, dbg, NULL);
     return true;
 }
 
 JSBool
 Debugger::construct(JSContext *cx, uintN argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
@@ -1983,18 +1963,18 @@ DebuggerScript_getOffsetLine(JSContext *
 class BytecodeRangeWithLineNumbers : private BytecodeRange
 {
   public:
     using BytecodeRange::empty;
     using BytecodeRange::frontPC;
     using BytecodeRange::frontOpcode;
     using BytecodeRange::frontOffset;
 
-    BytecodeRangeWithLineNumbers(JSContext *cx, JSScript *script)
-      : BytecodeRange(cx, script), lineno(script->lineno), sn(script->notes()), snpc(script->code)
+    BytecodeRangeWithLineNumbers(JSScript *script)
+      : BytecodeRange(script), lineno(script->lineno), sn(script->notes()), snpc(script->code)
     {
         if (!SN_IS_TERMINATOR(sn))
             snpc += SN_DELTA(sn);
         updateLine();
     }
 
     void popFront() {
         BytecodeRange::popFront();
@@ -2071,17 +2051,17 @@ class FlowGraphSummary : public Vector<s
             return false;
         FlowGraphSummary &self = *this;
         self[0] = MultipleEdges;
         for (size_t i = 1; i < script->length; i++)
             self[i] = NoEdges;
 
         size_t prevLine = script->lineno;
         JSOp prevOp = JSOP_NOP;
-        for (BytecodeRangeWithLineNumbers r(cx, script); !r.empty(); r.popFront()) {
+        for (BytecodeRangeWithLineNumbers r(script); !r.empty(); r.popFront()) {
             size_t lineno = r.frontLineNumber();
             JSOp op = r.frontOpcode();
 
             if (FlowsIntoNext(prevOp))
                 addEdge(prevLine, r.frontOffset());
 
             if (js_CodeSpec[op].type() == JOF_JUMP) {
                 addEdge(lineno, r.frontOffset() + GET_JUMP_OFFSET(r.frontPC()));
@@ -2140,17 +2120,17 @@ DebuggerScript_getAllOffsets(JSContext *
     FlowGraphSummary flowData(cx);
     if (!flowData.populate(cx, script))
         return false;
 
     /* Second pass: build the result array. */
     JSObject *result = NewDenseEmptyArray(cx);
     if (!result)
         return false;
-    for (BytecodeRangeWithLineNumbers r(cx, script); !r.empty(); r.popFront()) {
+    for (BytecodeRangeWithLineNumbers r(script); !r.empty(); r.popFront()) {
         size_t offset = r.frontOffset();
         size_t lineno = r.frontLineNumber();
 
         /* Make a note, if the current instruction is an entry point for the current line. */
         if (flowData[offset] != NoEdges && flowData[offset] != lineno) {
             /* Get the offsets array for this line. */
             JSObject *offsets;
             Value offsetsv;
@@ -2212,17 +2192,17 @@ DebuggerScript_getLineOffsets(JSContext 
     FlowGraphSummary flowData(cx);
     if (!flowData.populate(cx, script))
         return false;
 
     /* Second pass: build the result array. */
     JSObject *result = NewDenseEmptyArray(cx);
     if (!result)
         return false;
-    for (BytecodeRangeWithLineNumbers r(cx, script); !r.empty(); r.popFront()) {
+    for (BytecodeRangeWithLineNumbers r(script); !r.empty(); r.popFront()) {
         size_t offset = r.frontOffset();
 
         /* If the op at offset is an entry point, append offset to result. */
         if (r.frontLineNumber() == lineno &&
             flowData[offset] != NoEdges &&
             flowData[offset] != lineno)
         {
             if (!js_NewbornArrayPush(cx, result, NumberValue(offset)))
@@ -2250,29 +2230,28 @@ DebuggerScript_setBreakpoint(JSContext *
     size_t offset;
     if (!ScriptOffset(cx, script, args[0], &offset))
         return false;
 
     JSObject *handler = NonNullObject(cx, args[1]);
     if (!handler)
         return false;
 
-    JSCompartment *comp = script->compartment();
     jsbytecode *pc = script->code + offset;
-    BreakpointSite *site = comp->getOrCreateBreakpointSite(cx, script, pc, scriptGlobal);
+    BreakpointSite *site = script->getOrCreateBreakpointSite(cx, pc, scriptGlobal);
     if (!site)
         return false;
     if (site->inc(cx)) {
         if (cx->runtime->new_<Breakpoint>(dbg, site, handler)) {
             args.rval().setUndefined();
             return true;
         }
         site->dec(cx);
     }
-    site->destroyIfEmpty(cx->runtime, NULL);
+    site->destroyIfEmpty(cx->runtime);
     return false;
 }
 
 static JSBool
 DebuggerScript_getBreakpoints(JSContext *cx, uintN argc, Value *vp)
 {
     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getBreakpoints", args, obj, script);
     Debugger *dbg = Debugger::fromChildJSObject(obj);
@@ -2285,20 +2264,20 @@ DebuggerScript_getBreakpoints(JSContext 
         pc = script->code + offset;
     } else {
         pc = NULL;
     }
 
     JSObject *arr = NewDenseEmptyArray(cx);
     if (!arr)
         return false;
-    JSCompartment *comp = script->compartment();
-    for (BreakpointSiteMap::Range r = comp->breakpointSites.all(); !r.empty(); r.popFront()) {
-        BreakpointSite *site = r.front().value;
-        if (site->script == script && (!pc || site->pc == pc)) {
+
+    for (unsigned i = 0; i < script->length; i++) {
+        BreakpointSite *site = script->getBreakpointSite(script->code + i);
+        if (site && (!pc || site->pc == pc)) {
             for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = bp->nextInSite()) {
                 if (bp->debugger == dbg &&
                     !js_NewbornArrayPush(cx, arr, ObjectValue(*bp->getHandler())))
                 {
                     return false;
                 }
             }
         }
@@ -2313,27 +2292,27 @@ DebuggerScript_clearBreakpoint(JSContext
     REQUIRE_ARGC("Debugger.Script.clearBreakpoint", 1);
     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "clearBreakpoint", args, obj, script);
     Debugger *dbg = Debugger::fromChildJSObject(obj);
 
     JSObject *handler = NonNullObject(cx, args[0]);
     if (!handler)
         return false;
 
-    script->compartment()->clearBreakpointsIn(cx, dbg, script, handler);
+    script->clearBreakpointsIn(cx, dbg, handler);
     args.rval().setUndefined();
     return true;
 }
 
 static JSBool
 DebuggerScript_clearAllBreakpoints(JSContext *cx, uintN argc, Value *vp)
 {
     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "clearAllBreakpoints", args, obj, script);
     Debugger *dbg = Debugger::fromChildJSObject(obj);
-    script->compartment()->clearBreakpointsIn(cx, dbg, script, NULL);
+    script->clearBreakpointsIn(cx, dbg, NULL);
     args.rval().setUndefined();
     return true;
 }
 
 static JSBool
 DebuggerScript_construct(JSContext *cx, uintN argc, Value *vp)
 {
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NO_CONSTRUCTOR, "Debugger.Script");
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -340,22 +340,22 @@ class Debugger {
     /* Prohibit copying. */
     Debugger(const Debugger &);
     Debugger & operator=(const Debugger &);
 };
 
 class BreakpointSite {
     friend class Breakpoint;
     friend struct ::JSCompartment;
+    friend struct ::JSScript;
     friend class Debugger;
 
   public:
     JSScript * const script;
     jsbytecode * const pc;
-    const JSOp realOpcode;
 
   private:
     /*
      * The holder object for script, if known, else NULL.  This is NULL for
      * cached eval scripts and for JSD1 traps. It is always non-null for JSD2
      * breakpoints in held scripts.
      */
     GlobalObject *scriptGlobal;
@@ -372,19 +372,18 @@ class BreakpointSite {
     Breakpoint *firstBreakpoint() const;
     bool hasBreakpoint(Breakpoint *bp);
     bool hasTrap() const { return !!trapHandler; }
     GlobalObject *getScriptGlobal() const { return scriptGlobal; }
 
     bool inc(JSContext *cx);
     void dec(JSContext *cx);
     bool setTrap(JSContext *cx, JSTrapHandler handler, const Value &closure);
-    void clearTrap(JSContext *cx, BreakpointSiteMap::Enum *e = NULL,
-                   JSTrapHandler *handlerp = NULL, Value *closurep = NULL);
-    void destroyIfEmpty(JSRuntime *rt, BreakpointSiteMap::Enum *e);
+    void clearTrap(JSContext *cx, JSTrapHandler *handlerp = NULL, Value *closurep = NULL);
+    void destroyIfEmpty(JSRuntime *rt);
 };
 
 /*
  * Each Breakpoint is a member of two linked lists: its debugger's list and its
  * site's list.
  *
  * GC rules:
  *   - script is live, breakpoint exists, and debugger is enabled
@@ -411,17 +410,17 @@ class Breakpoint {
     js::HeapPtrObject handler;
     JSCList debuggerLinks;
     JSCList siteLinks;
 
   public:
     static Breakpoint *fromDebuggerLinks(JSCList *links);
     static Breakpoint *fromSiteLinks(JSCList *links);
     Breakpoint(Debugger *debugger, BreakpointSite *site, JSObject *handler);
-    void destroy(JSContext *cx, BreakpointSiteMap::Enum *e = NULL);
+    void destroy(JSContext *cx);
     Breakpoint *nextInDebugger();
     Breakpoint *nextInSite();
     const HeapPtrObject &getHandler() const { return handler; }
 };
 
 Debugger *
 Debugger::fromLinks(JSCList *links)
 {
@@ -481,17 +480,20 @@ Debugger::onEnterFrame(JSContext *cx, Va
     if (cx->compartment->getDebuggees().empty())
         return JSTRAP_CONTINUE;
     return slowPathOnEnterFrame(cx, vp);
 }
 
 void
 Debugger::onLeaveFrame(JSContext *cx)
 {
-    if (!cx->compartment->getDebuggees().empty() || !cx->compartment->breakpointSites.empty())
+    /* Traps must be cleared from eval frames, see slowPathOnLeaveFrame. */
+    bool evalTraps = cx->fp()->isEvalFrame() &&
+                     cx->fp()->script()->hasAnyBreakpointsOrStepMode();
+    if (!cx->compartment->getDebuggees().empty() || evalTraps)
         slowPathOnLeaveFrame(cx);
 }
 
 JSTrapStatus
 Debugger::onDebuggerStatement(JSContext *cx, Value *vp)
 {
     return cx->compartment->getDebuggees().empty()
            ? JSTRAP_CONTINUE
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -1037,17 +1037,17 @@ StackIter::settleOnNewState()
              * Function.prototype.call will however appear, hence the debugger
              * can, by inspecting 'args.thisv', give some useful information.
              *
              * For Function.prototype.apply, the situation is even worse: since
              * a dynamic number of arguments have been pushed onto the stack
              * (see SplatApplyArgs), there is no efficient way to know how to
              * find the callee. Thus, calls to apply are lost completely.
              */
-            JSOp op = js_GetOpcode(cx_, fp_->script(), pc_);
+            JSOp op = JSOp(*pc_);
             if (op == JSOP_CALL || op == JSOP_FUNCALL) {
                 uintN argc = GET_ARGC(pc_);
                 DebugOnly<uintN> spoff = sp_ - fp_->base();
                 JS_ASSERT_IF(cx_->stackIterAssertionEnabled,
                              spoff == js_ReconstructStackDepth(cx_, fp_->script(), pc_));
                 Value *vp = sp_ - (2 + argc);
 
                 CrashIfInvalidSlot(fp_, vp);