Refactor and improve GETELEM IC (bug 602641, r=dmandelin).
[mozilla-central.git] / js / src / methodjit / InvokeHelpers.cpp
blob7adac76021a2b47059763d11e79df0c422caeb90
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=4 sw=4 et tw=99:
4 * ***** BEGIN LICENSE BLOCK *****
5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
15 * License.
17 * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released
18 * May 28, 2008.
20 * The Initial Developer of the Original Code is
21 * Brendan Eich <brendan@mozilla.org>
23 * Contributor(s):
24 * David Anderson <danderson@mozilla.com>
25 * David Mandelin <dmandelin@mozilla.com>
27 * Alternatively, the contents of this file may be used under the terms of
28 * either of the GNU General Public License Version 2 or later (the "GPL"),
29 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
39 * ***** END LICENSE BLOCK ***** */
41 #include "jscntxt.h"
42 #include "jsscope.h"
43 #include "jsobj.h"
44 #include "jslibmath.h"
45 #include "jsiter.h"
46 #include "jsnum.h"
47 #include "jsxml.h"
48 #include "jsstaticcheck.h"
49 #include "jsbool.h"
50 #include "assembler/assembler/MacroAssemblerCodeRef.h"
51 #include "assembler/assembler/CodeLocation.h"
52 #include "assembler/assembler/RepatchBuffer.h"
53 #include "jsiter.h"
54 #include "jstypes.h"
55 #include "methodjit/StubCalls.h"
56 #include "jstracer.h"
57 #include "jspropertycache.h"
58 #include "methodjit/MonoIC.h"
59 #include "jsanalyze.h"
61 #include "jsinterpinlines.h"
62 #include "jspropertycacheinlines.h"
63 #include "jsscopeinlines.h"
64 #include "jsscriptinlines.h"
65 #include "jsstrinlines.h"
66 #include "jsobjinlines.h"
67 #include "jscntxtinlines.h"
68 #include "jsatominlines.h"
69 #include "StubCalls-inl.h"
71 #include "jsautooplen.h"
73 using namespace js;
74 using namespace js::mjit;
75 using namespace JSC;
77 static jsbytecode *
78 FindExceptionHandler(JSContext *cx)
80 JSStackFrame *fp = cx->fp();
81 JSScript *script = fp->script();
83 top:
84 if (cx->throwing && script->trynotesOffset) {
85 // The PC is updated before every stub call, so we can use it here.
86 unsigned offset = cx->regs->pc - script->main;
88 JSTryNoteArray *tnarray = script->trynotes();
89 for (unsigned i = 0; i < tnarray->length; ++i) {
90 JSTryNote *tn = &tnarray->vector[i];
91 JS_ASSERT(offset < script->length);
92 // The following if condition actually tests two separate conditions:
93 // (1) offset - tn->start >= tn->length
94 // means the PC is not in the range of this try note, so we
95 // should continue searching, after considering:
96 // (2) offset - tn->start == tn->length
97 // means the PC is at the first op of the exception handler
98 // for this try note. This happens when an exception is thrown
99 // during recording: the interpreter sets the PC to the handler
100 // and then exits. In this case, we are in fact at the right
101 // exception handler.
103 // Hypothetically, the op we are at might have thrown an
104 // exception, in which case this would not be the right handler.
105 // But the first ops of exception handlers generated by our
106 // bytecode compiler cannot throw, so this is not possible.
107 if (offset - tn->start > tn->length)
108 continue;
109 if (tn->stackDepth > cx->regs->sp - fp->base())
110 continue;
112 jsbytecode *pc = script->main + tn->start + tn->length;
113 JSBool ok = js_UnwindScope(cx, tn->stackDepth, JS_TRUE);
114 JS_ASSERT(cx->regs->sp == fp->base() + tn->stackDepth);
116 switch (tn->kind) {
117 case JSTRY_CATCH:
118 JS_ASSERT(js_GetOpcode(cx, fp->script(), pc) == JSOP_ENTERBLOCK);
120 #if JS_HAS_GENERATORS
121 /* Catch cannot intercept the closing of a generator. */
122 if (JS_UNLIKELY(cx->exception.isMagic(JS_GENERATOR_CLOSING)))
123 break;
124 #endif
127 * Don't clear cx->throwing to save cx->exception from GC
128 * until it is pushed to the stack via [exception] in the
129 * catch block.
131 return pc;
133 case JSTRY_FINALLY:
135 * Push (true, exception) pair for finally to indicate that
136 * [retsub] should rethrow the exception.
138 cx->regs->sp[0].setBoolean(true);
139 cx->regs->sp[1] = cx->exception;
140 cx->regs->sp += 2;
141 cx->throwing = JS_FALSE;
142 return pc;
144 case JSTRY_ITER:
147 * This is similar to JSOP_ENDITER in the interpreter loop,
148 * except the code now uses the stack slot normally used by
149 * JSOP_NEXTITER, namely regs.sp[-1] before the regs.sp -= 2
150 * adjustment and regs.sp[1] after, to save and restore the
151 * pending exception.
153 AutoValueRooter tvr(cx, cx->exception);
154 JS_ASSERT(js_GetOpcode(cx, fp->script(), pc) == JSOP_ENDITER);
155 cx->throwing = JS_FALSE;
156 ok = !!js_CloseIterator(cx, &cx->regs->sp[-1].toObject());
157 cx->regs->sp -= 1;
158 if (!ok)
159 goto top;
160 cx->throwing = JS_TRUE;
161 cx->exception = tvr.value();
167 return NULL;
171 * Clean up a frame and return. popFrame indicates whether to additionally pop
172 * the frame and store the return value on the caller's stack. The frame will
173 * normally be popped by the caller on return from a call into JIT code,
174 * so must be popped here when that caller code will not execute. This can be
175 * either because of a call into an un-JITable script, or because the call is
176 * throwing an exception.
178 static void
179 InlineReturn(VMFrame &f)
181 JSContext *cx = f.cx;
182 JSStackFrame *fp = f.regs.fp;
184 JS_ASSERT(f.fp() != f.entryFp);
186 JS_ASSERT(!js_IsActiveWithOrBlock(cx, &fp->scopeChain(), 0));
188 Value *newsp = fp->actualArgs() - 1;
189 newsp[-1] = fp->returnValue();
190 cx->stack().popInlineFrame(cx, fp->prev(), newsp);
193 void JS_FASTCALL
194 stubs::SlowCall(VMFrame &f, uint32 argc)
196 Value *vp = f.regs.sp - (argc + 2);
198 if (!Invoke(f.cx, InvokeArgsAlreadyOnTheStack(vp, argc), 0))
199 THROW();
202 void JS_FASTCALL
203 stubs::SlowNew(VMFrame &f, uint32 argc)
205 JSContext *cx = f.cx;
206 Value *vp = f.regs.sp - (argc + 2);
208 if (!InvokeConstructor(cx, InvokeArgsAlreadyOnTheStack(vp, argc)))
209 THROW();
213 * This function must only be called after the early prologue, since it depends
214 * on fp->exec.fun.
216 static inline void
217 RemovePartialFrame(JSContext *cx, JSStackFrame *fp)
219 JSStackFrame *prev = fp->prev();
220 Value *newsp = (Value *)fp;
221 cx->stack().popInlineFrame(cx, prev, newsp);
225 * HitStackQuota is called after the early prologue pushing the new frame would
226 * overflow f.stackLimit.
228 void JS_FASTCALL
229 stubs::HitStackQuota(VMFrame &f)
231 /* Include space to push another frame. */
232 uintN nvals = f.fp()->script()->nslots + VALUES_PER_STACK_FRAME;
233 JS_ASSERT(f.regs.sp == f.fp()->base());
234 if (f.cx->stack().bumpCommitAndLimit(f.entryFp, f.regs.sp, nvals, &f.stackLimit))
235 return;
237 /* Remove the current partially-constructed frame before throwing. */
238 RemovePartialFrame(f.cx, f.fp());
239 js_ReportOverRecursed(f.cx);
240 THROW();
244 * This function must only be called after the early prologue, since it depends
245 * on fp->exec.fun.
247 void * JS_FASTCALL
248 stubs::FixupArity(VMFrame &f, uint32 nactual)
250 JSContext *cx = f.cx;
251 JSStackFrame *oldfp = f.fp();
253 JS_ASSERT(nactual != oldfp->numFormalArgs());
256 * Grossssss! *move* the stack frame. If this ends up being perf-critical,
257 * we can figure out how to spot-optimize it. Be careful to touch only the
258 * members that have been initialized by initCallFrameCallerHalf and the
259 * early prologue.
261 uint32 flags = oldfp->isConstructingFlag();
262 JSFunction *fun = oldfp->fun();
263 void *ncode = oldfp->nativeReturnAddress();
265 /* Pop the inline frame. */
266 f.fp() = oldfp->prev();
267 f.regs.sp = (Value*) oldfp;
269 /* Reserve enough space for a callee frame. */
270 JSStackFrame *newfp = cx->stack().getInlineFrameWithinLimit(cx, (Value*) oldfp, nactual,
271 fun, fun->script(), &flags,
272 f.entryFp, &f.stackLimit);
273 if (!newfp)
274 THROWV(NULL);
276 /* Reset the part of the stack frame set by the caller. */
277 newfp->initCallFrameCallerHalf(cx, nactual, flags);
279 /* Reset the part of the stack frame set by the prologue up to now. */
280 newfp->initCallFrameEarlyPrologue(fun, ncode);
282 /* The caller takes care of assigning fp to regs. */
283 return newfp;
286 void * JS_FASTCALL
287 stubs::CompileFunction(VMFrame &f, uint32 nactual)
290 * We have a partially constructed frame. That's not really good enough to
291 * compile though because we could throw, so get a full, adjusted frame.
293 JSContext *cx = f.cx;
294 JSStackFrame *fp = f.fp();
297 * Since we can only use members set by initCallFrameCallerHalf,
298 * we must carefully extract the callee from the nactual.
300 JSObject &callee = fp->formalArgsEnd()[-(int(nactual) + 2)].toObject();
301 JSFunction *fun = callee.getFunctionPrivate();
302 JSScript *script = fun->script();
305 * FixupArity/RemovePartialFrame expect to be called after the early
306 * prologue. Pass the existing value for ncode, it has already been set
307 * by the jit code calling into this stub.
309 fp->initCallFrameEarlyPrologue(fun, fp->nativeReturnAddress());
311 /* Empty script does nothing. */
312 bool callingNew = fp->isConstructing();
313 if (script->isEmpty()) {
314 RemovePartialFrame(cx, fp);
315 Value *vp = f.regs.sp - (nactual + 2);
316 if (callingNew)
317 vp[0] = vp[1];
318 else
319 vp[0].setUndefined();
320 return NULL;
323 if (nactual != fp->numFormalArgs()) {
324 fp = (JSStackFrame *)FixupArity(f, nactual);
325 if (!fp)
326 return NULL;
329 /* Finish frame initialization. */
330 fp->initCallFrameLatePrologue();
332 /* These would have been initialized by the prologue. */
333 f.regs.fp = fp;
334 f.regs.sp = fp->base();
335 f.regs.pc = script->code;
337 if (fun->isHeavyweight() && !js_GetCallObject(cx, fp))
338 THROWV(NULL);
340 CompileStatus status = CanMethodJIT(cx, script, fp);
341 if (status == Compile_Okay)
342 return script->getJIT(callingNew)->invokeEntry;
344 /* Function did not compile... interpret it. */
345 JSBool ok = Interpret(cx, fp);
346 InlineReturn(f);
348 if (!ok)
349 THROWV(NULL);
351 return NULL;
354 static inline bool
355 UncachedInlineCall(VMFrame &f, uint32 flags, void **pret, uint32 argc)
357 JSContext *cx = f.cx;
358 Value *vp = f.regs.sp - (argc + 2);
359 JSObject &callee = vp->toObject();
360 JSFunction *newfun = callee.getFunctionPrivate();
361 JSScript *newscript = newfun->script();
363 /* Get pointer to new frame/slots, prepare arguments. */
364 StackSpace &stack = cx->stack();
365 JSStackFrame *newfp = stack.getInlineFrameWithinLimit(cx, f.regs.sp, argc,
366 newfun, newscript, &flags,
367 f.entryFp, &f.stackLimit);
368 if (JS_UNLIKELY(!newfp))
369 return false;
370 JS_ASSERT_IF(!vp[1].isPrimitive() && !(flags & JSFRAME_CONSTRUCTING),
371 IsSaneThisObject(vp[1].toObject()));
373 /* Initialize frame, locals. */
374 newfp->initCallFrame(cx, callee, newfun, argc, flags);
375 SetValueRangeToUndefined(newfp->slots(), newscript->nfixed);
377 /* Officially push the frame. */
378 stack.pushInlineFrame(cx, newscript, newfp, &f.regs);
379 JS_ASSERT(newfp == f.regs.fp);
381 /* Scope with a call object parented by callee's parent. */
382 if (newfun->isHeavyweight() && !js_GetCallObject(cx, newfp))
383 return false;
385 /* Try to compile if not already compiled. */
386 if (newscript->getJITStatus(newfp->isConstructing()) == JITScript_None) {
387 if (mjit::TryCompile(cx, newfp) == Compile_Error) {
388 /* A runtime exception was thrown, get out. */
389 InlineReturn(f);
390 return false;
394 /* If newscript was successfully compiled, run it. */
395 if (JITScript *jit = newscript->getJIT(newfp->isConstructing())) {
396 *pret = jit->invokeEntry;
397 return true;
400 /* Otherwise, run newscript in the interpreter. */
401 bool ok = !!Interpret(cx, cx->fp());
402 InlineReturn(f);
404 *pret = NULL;
405 return ok;
408 void * JS_FASTCALL
409 stubs::UncachedNew(VMFrame &f, uint32 argc)
411 UncachedCallResult ucr;
412 UncachedNewHelper(f, argc, &ucr);
413 return ucr.codeAddr;
416 void
417 stubs::UncachedNewHelper(VMFrame &f, uint32 argc, UncachedCallResult *ucr)
419 ucr->init();
421 JSContext *cx = f.cx;
422 Value *vp = f.regs.sp - (argc + 2);
424 /* Try to do a fast inline call before the general Invoke path. */
425 if (IsFunctionObject(*vp, &ucr->fun) && ucr->fun->isInterpreted() &&
426 !ucr->fun->script()->isEmpty())
428 ucr->callee = &vp->toObject();
429 if (!UncachedInlineCall(f, JSFRAME_CONSTRUCTING, &ucr->codeAddr, argc))
430 THROW();
431 } else {
432 if (!InvokeConstructor(cx, InvokeArgsAlreadyOnTheStack(vp, argc)))
433 THROW();
437 void * JS_FASTCALL
438 stubs::UncachedCall(VMFrame &f, uint32 argc)
440 UncachedCallResult ucr;
441 UncachedCallHelper(f, argc, &ucr);
442 return ucr.codeAddr;
445 void
446 stubs::UncachedCallHelper(VMFrame &f, uint32 argc, UncachedCallResult *ucr)
448 ucr->init();
450 JSContext *cx = f.cx;
451 Value *vp = f.regs.sp - (argc + 2);
453 if (IsFunctionObject(*vp, &ucr->callee)) {
454 ucr->callee = &vp->toObject();
455 ucr->fun = GET_FUNCTION_PRIVATE(cx, ucr->callee);
457 if (ucr->fun->isInterpreted()) {
458 if (ucr->fun->u.i.script->isEmpty()) {
459 vp->setUndefined();
460 f.regs.sp = vp + 1;
461 return;
464 if (!UncachedInlineCall(f, 0, &ucr->codeAddr, argc))
465 THROW();
466 return;
469 if (ucr->fun->isNative()) {
470 if (!CallJSNative(cx, ucr->fun->u.n.native, argc, vp))
471 THROW();
472 return;
476 if (!Invoke(f.cx, InvokeArgsAlreadyOnTheStack(vp, argc), 0))
477 THROW();
479 return;
482 void JS_FASTCALL
483 stubs::PutCallObject(VMFrame &f)
485 JS_ASSERT(f.fp()->hasCallObj());
486 js_PutCallObject(f.cx, f.fp());
489 void JS_FASTCALL
490 stubs::PutActivationObjects(VMFrame &f)
492 JS_ASSERT(f.fp()->hasCallObj() || f.fp()->hasArgsObj());
493 js::PutActivationObjects(f.cx, f.fp());
496 extern "C" void *
497 js_InternalThrow(VMFrame &f)
499 JSContext *cx = f.cx;
501 // Make sure sp is up to date.
502 JS_ASSERT(cx->regs == &f.regs);
504 // Call the throw hook if necessary
505 JSThrowHook handler = f.cx->debugHooks->throwHook;
506 if (handler) {
507 Value rval;
508 switch (handler(cx, cx->fp()->script(), cx->regs->pc, Jsvalify(&rval),
509 cx->debugHooks->throwHookData)) {
510 case JSTRAP_ERROR:
511 cx->throwing = JS_FALSE;
512 return NULL;
514 case JSTRAP_RETURN:
515 cx->throwing = JS_FALSE;
516 cx->fp()->setReturnValue(rval);
517 return JS_FUNC_TO_DATA_PTR(void *,
518 JS_METHODJIT_DATA(cx).trampolines.forceReturn);
520 case JSTRAP_THROW:
521 cx->exception = rval;
522 break;
524 default:
525 break;
529 jsbytecode *pc = NULL;
530 for (;;) {
531 pc = FindExceptionHandler(cx);
532 if (pc)
533 break;
535 // If on the 'topmost' frame (where topmost means the first frame
536 // called into through js_Interpret). In this case, we still unwind,
537 // but we shouldn't return from a JS function, because we're not in a
538 // JS function.
539 bool lastFrame = (f.entryFp == f.fp());
540 js_UnwindScope(cx, 0, cx->throwing);
542 // For consistency with Interpret(), always run the script epilogue.
543 // This simplifies interactions with RunTracer(), since it can assume
544 // no matter how a function exited (error or not), that the epilogue
545 // does not need to be run.
546 ScriptEpilogue(f.cx, f.fp(), false);
548 if (lastFrame)
549 break;
551 JS_ASSERT(f.regs.sp == cx->regs->sp);
552 InlineReturn(f);
555 JS_ASSERT(f.regs.sp == cx->regs->sp);
557 if (!pc)
558 return NULL;
560 JSStackFrame *fp = cx->fp();
561 JSScript *script = fp->script();
562 return script->nativeCodeForPC(fp->isConstructing(), pc);
565 void JS_FASTCALL
566 stubs::GetCallObject(VMFrame &f)
568 JS_ASSERT(f.fp()->fun()->isHeavyweight());
569 if (!js_GetCallObject(f.cx, f.fp()))
570 THROW();
573 void JS_FASTCALL
574 stubs::CreateThis(VMFrame &f, JSObject *proto)
576 JSContext *cx = f.cx;
577 JSStackFrame *fp = f.fp();
578 JSObject *callee = &fp->callee();
579 JSObject *obj = js_CreateThisForFunctionWithProto(cx, callee, proto);
580 if (!obj)
581 THROW();
582 fp->formalArgs()[-1].setObject(*obj);
585 void JS_FASTCALL
586 stubs::EnterScript(VMFrame &f)
588 JSStackFrame *fp = f.fp();
589 JSContext *cx = f.cx;
590 JSInterpreterHook hook = cx->debugHooks->callHook;
591 if (JS_UNLIKELY(hook != NULL) && !fp->isExecuteFrame()) {
592 fp->setHookData(hook(cx, fp, JS_TRUE, 0, cx->debugHooks->callHookData));
595 Probes::enterJSFun(cx, fp->maybeFun());
598 void JS_FASTCALL
599 stubs::LeaveScript(VMFrame &f)
601 JSStackFrame *fp = f.fp();
602 JSContext *cx = f.cx;
603 Probes::exitJSFun(cx, fp->maybeFun());
604 JSInterpreterHook hook = cx->debugHooks->callHook;
606 if (hook && fp->hasHookData() && !fp->isExecuteFrame()) {
607 JSBool ok = JS_TRUE;
608 hook(cx, fp, JS_FALSE, &ok, fp->hookData());
609 if (!ok)
610 THROW();
614 #ifdef JS_TRACER
617 * Called when an error is in progress and the topmost frame could not handle
618 * it. This will unwind to a given frame, or find and align to an exception
619 * handler in the process.
621 static inline bool
622 HandleErrorInExcessFrame(VMFrame &f, JSStackFrame *stopFp, bool searchedTopmostFrame = true)
624 JSContext *cx = f.cx;
627 * Callers of this called either Interpret() or JaegerShot(), which would
628 * have searched for exception handlers already. If we see stopFp, just
629 * return false. Otherwise, pop the frame, since it's guaranteed useless.
631 * Note that this also guarantees ScriptEpilogue() has been called.
633 JSStackFrame *fp = cx->fp();
634 if (searchedTopmostFrame) {
635 if (fp == stopFp)
636 return false;
638 InlineReturn(f);
641 /* Remove the bottom frame. */
642 bool returnOK = false;
643 for (;;) {
644 fp = cx->fp();
646 /* Clear imacros. */
647 if (fp->hasImacropc()) {
648 cx->regs->pc = fp->imacropc();
649 fp->clearImacropc();
651 JS_ASSERT(!fp->hasImacropc());
653 /* If there's an exception and a handler, set the pc and leave. */
654 if (cx->throwing) {
655 jsbytecode *pc = FindExceptionHandler(cx);
656 if (pc) {
657 cx->regs->pc = pc;
658 returnOK = true;
659 break;
663 /* Don't unwind if this was the entry frame. */
664 if (fp == stopFp)
665 break;
667 /* Unwind and return. */
668 returnOK &= bool(js_UnwindScope(cx, 0, returnOK || cx->throwing));
669 returnOK = ScriptEpilogue(cx, fp, returnOK);
670 InlineReturn(f);
673 JS_ASSERT(&f.regs == cx->regs);
674 JS_ASSERT_IF(!returnOK, cx->fp() == stopFp);
676 return returnOK;
679 /* Returns whether the current PC has method JIT'd code. */
680 static inline void *
681 AtSafePoint(JSContext *cx)
683 JSStackFrame *fp = cx->fp();
684 if (fp->hasImacropc())
685 return false;
687 JSScript *script = fp->script();
688 return script->maybeNativeCodeForPC(fp->isConstructing(), cx->regs->pc);
692 * Interprets until either a safe point is reached that has method JIT'd
693 * code, or the current frame tries to return.
695 static inline JSBool
696 PartialInterpret(VMFrame &f)
698 JSContext *cx = f.cx;
699 JSStackFrame *fp = cx->fp();
701 #ifdef DEBUG
702 JSScript *script = fp->script();
703 JS_ASSERT(!fp->finishedInInterpreter());
704 JS_ASSERT(fp->hasImacropc() ||
705 !script->maybeNativeCodeForPC(fp->isConstructing(), cx->regs->pc));
706 #endif
708 JSBool ok = JS_TRUE;
709 ok = Interpret(cx, fp, 0, JSINTERP_SAFEPOINT);
711 return ok;
714 JS_STATIC_ASSERT(JSOP_NOP == 0);
716 /* Returns whether the current PC would return, popping the frame. */
717 static inline JSOp
718 FrameIsFinished(JSContext *cx)
720 JSOp op = JSOp(*cx->regs->pc);
721 return (op == JSOP_RETURN ||
722 op == JSOP_RETRVAL ||
723 op == JSOP_STOP)
724 ? op
725 : JSOP_NOP;
729 /* Simulate an inline_return by advancing the pc. */
730 static inline void
731 AdvanceReturnPC(JSContext *cx)
733 JS_ASSERT(*cx->regs->pc == JSOP_CALL ||
734 *cx->regs->pc == JSOP_NEW ||
735 *cx->regs->pc == JSOP_EVAL ||
736 *cx->regs->pc == JSOP_APPLY);
737 cx->regs->pc += JSOP_CALL_LENGTH;
742 * Given a frame that is about to return, make sure its return value and
743 * activation objects are fixed up. Then, pop the frame and advance the
744 * current PC. Note that while we could enter the JIT at this point, the
745 * logic would still be necessary for the interpreter, so it's easier
746 * (and faster) to finish frames in C++ even if at a safe point here.
748 static bool
749 HandleFinishedFrame(VMFrame &f, JSStackFrame *entryFrame)
751 JSContext *cx = f.cx;
753 JS_ASSERT(FrameIsFinished(cx));
756 * This is the most difficult and complicated piece of the tracer
757 * integration, and historically has been very buggy. The problem is that
758 * although this frame has to be popped (see RemoveExcessFrames), it may
759 * be at a JSOP_RETURN opcode, and it might not have ever been executed.
760 * That is, fp->rval may not be set to the top of the stack, and if it
761 * has, the stack has already been decremented. Note that fp->rval is not
762 * the only problem: the epilogue may never have been executed.
764 * Here are the edge cases and whether the frame has been exited cleanly:
765 * 1. No: A trace exited directly before a RETURN op, and the
766 * interpreter never ran.
767 * 2. Yes: The interpreter exited cleanly.
768 * 3. No: The interpreter exited on a safe point. LEAVE_ON_SAFE_POINT
769 * is not used in between JSOP_RETURN and advancing the PC,
770 * therefore, it cannot have been run if at a safe point.
771 * 4. No: Somewhere in the RunTracer call tree, we removed a frame,
772 * and we returned to a JSOP_RETURN opcode. Note carefully
773 * that in this situation, FrameIsFinished() returns true!
774 * 5. Yes: The function exited in the method JIT. However, in this
775 * case, we'll never enter HandleFinishedFrame(): we always
776 * immediately pop JIT'd frames.
778 * Since the only scenario where this fixup is NOT needed is a normal exit
779 * from the interpreter, we can cleanly check for this scenario by checking
780 * a bit it sets in the frame.
782 bool returnOK = true;
783 if (!cx->fp()->finishedInInterpreter()) {
784 if (JSOp(*cx->regs->pc) == JSOP_RETURN)
785 cx->fp()->setReturnValue(f.regs.sp[-1]);
787 returnOK = ScriptEpilogue(cx, cx->fp(), true);
790 JS_ASSERT_IF(cx->fp()->isFunctionFrame() &&
791 !cx->fp()->isEvalFrame(),
792 !cx->fp()->hasCallObj());
794 if (cx->fp() != entryFrame) {
795 InlineReturn(f);
796 AdvanceReturnPC(cx);
799 return returnOK;
803 * Given a frame newer than the entry frame, try to finish it. If it's at a
804 * return position, pop the frame. If it's at a safe point, execute it in
805 * Jaeger code. Otherwise, try to interpret until a safe point.
807 * While this function is guaranteed to make progress, it may not actually
808 * finish or pop the current frame. It can either:
809 * 1) Finalize a finished frame, or
810 * 2) Finish and finalize the frame in the Method JIT, or
811 * 3) Interpret, which can:
812 * a) Propagate an error, or
813 * b) Finish the frame, but not finalize it, or
814 * c) Abruptly leave at any point in the frame, or in a newer frame
815 * pushed by a call, that has method JIT'd code.
817 static bool
818 EvaluateExcessFrame(VMFrame &f, JSStackFrame *entryFrame)
820 JSContext *cx = f.cx;
821 JSStackFrame *fp = cx->fp();
824 * A "finished" frame is when the interpreter rested on a STOP,
825 * RETURN, RETRVAL, etc. We check for finished frames BEFORE looking
826 * for a safe point. If the frame was finished, we could have already
827 * called ScriptEpilogue(), and entering the JIT could call it twice.
829 if (!fp->hasImacropc() && FrameIsFinished(cx))
830 return HandleFinishedFrame(f, entryFrame);
832 if (void *ncode = AtSafePoint(cx)) {
833 if (!JaegerShotAtSafePoint(cx, ncode))
834 return false;
835 InlineReturn(f);
836 AdvanceReturnPC(cx);
837 return true;
840 return PartialInterpret(f);
844 * Evaluate frames newer than the entry frame until all are gone. This will
845 * always leave f.regs.fp == entryFrame.
847 static bool
848 FinishExcessFrames(VMFrame &f, JSStackFrame *entryFrame)
850 JSContext *cx = f.cx;
852 while (cx->fp() != entryFrame || entryFrame->hasImacropc()) {
853 if (!EvaluateExcessFrame(f, entryFrame)) {
854 if (!HandleErrorInExcessFrame(f, entryFrame))
855 return false;
859 return true;
862 #if JS_MONOIC
863 static void
864 UpdateTraceHintSingle(JSC::CodeLocationJump jump, JSC::CodeLocationLabel target)
867 * Hack: The value that will be patched is before the executable address,
868 * so to get protection right, just unprotect the general region around
869 * the jump.
871 uint8 *addr = (uint8 *)(jump.executableAddress());
872 JSC::RepatchBuffer repatch(addr - 64, 128);
873 repatch.relink(jump, target);
875 JaegerSpew(JSpew_PICs, "relinking trace hint %p to %p\n",
876 jump.executableAddress(), target.executableAddress());
879 static void
880 DisableTraceHint(VMFrame &f, ic::TraceICInfo &tic)
882 UpdateTraceHintSingle(tic.traceHint, tic.jumpTarget);
884 if (tic.hasSlowTraceHint)
885 UpdateTraceHintSingle(tic.slowTraceHint, tic.jumpTarget);
888 static void
889 EnableTraceHintAt(JSScript *script, js::mjit::JITScript *jit, jsbytecode *pc, uint16_t index)
891 JS_ASSERT(index < jit->nTraceICs);
892 ic::TraceICInfo &tic = jit->traceICs[index];
894 JS_ASSERT(tic.jumpTargetPC == pc);
896 JaegerSpew(JSpew_PICs, "Enabling trace IC %u in script %p\n", index, script);
898 UpdateTraceHintSingle(tic.traceHint, tic.stubEntry);
900 if (tic.hasSlowTraceHint)
901 UpdateTraceHintSingle(tic.slowTraceHint, tic.stubEntry);
903 #endif
905 void
906 js::mjit::EnableTraceHint(JSScript *script, jsbytecode *pc, uint16_t index)
908 #if JS_MONOIC
909 if (script->jitNormal)
910 EnableTraceHintAt(script, script->jitNormal, pc, index);
912 if (script->jitCtor)
913 EnableTraceHintAt(script, script->jitCtor, pc, index);
914 #endif
917 #if JS_MONOIC
918 void *
919 RunTracer(VMFrame &f, ic::TraceICInfo &tic)
920 #else
921 void *
922 RunTracer(VMFrame &f)
923 #endif
925 JSContext *cx = f.cx;
926 JSStackFrame *entryFrame = f.fp();
927 TracePointAction tpa;
929 /* :TODO: nuke PIC? */
930 if (!cx->traceJitEnabled)
931 return NULL;
934 * Force initialization of the entry frame's scope chain and return value,
935 * if necessary. The tracer can query the scope chain without needing to
936 * check the HAS_SCOPECHAIN flag, and the frame is guaranteed to have the
937 * correct return value stored if we trace/interpret through to the end
938 * of the frame.
940 entryFrame->scopeChain();
941 entryFrame->returnValue();
943 bool blacklist;
944 uintN inlineCallCount = 0;
945 void **traceData;
946 uintN *traceEpoch;
947 #if JS_MONOIC
948 traceData = &tic.traceData;
949 traceEpoch = &tic.traceEpoch;
950 #else
951 traceData = NULL;
952 traceEpoch = NULL;
953 #endif
954 tpa = MonitorTracePoint(f.cx, inlineCallCount, &blacklist, traceData, traceEpoch);
955 JS_ASSERT(!TRACE_RECORDER(cx));
957 #if JS_MONOIC
958 if (blacklist)
959 DisableTraceHint(f, tic);
960 #endif
962 // Even though ExecuteTree() bypasses the interpreter, it should propagate
963 // error failures correctly.
964 JS_ASSERT_IF(cx->throwing, tpa == TPA_Error);
966 f.fp() = cx->fp();
967 JS_ASSERT(f.fp() == cx->fp());
968 switch (tpa) {
969 case TPA_Nothing:
970 return NULL;
972 case TPA_Error:
973 if (!HandleErrorInExcessFrame(f, entryFrame, f.fp()->finishedInInterpreter()))
974 THROWV(NULL);
975 JS_ASSERT(!cx->fp()->hasImacropc());
976 break;
978 case TPA_RanStuff:
979 case TPA_Recorded:
980 break;
984 * The tracer could have dropped us off on any frame at any position.
985 * Well, it could not have removed frames (recursion is disabled).
987 * Frames after the entryFrame cannot be entered via JaegerShotAtSafePoint()
988 * unless each is at a safe point. We can JaegerShotAtSafePoint these
989 * frames individually, but we must unwind to the entryFrame.
991 * Note carefully that JaegerShotAtSafePoint can resume methods at
992 * arbitrary safe points whereas JaegerShot cannot.
994 * If we land on entryFrame without a safe point in sight, we'll end up
995 * at the RETURN op. This is an edge case with two paths:
997 * 1) The entryFrame is the last inline frame. If it fell on a RETURN,
998 * move the return value down.
999 * 2) The entryFrame is NOT the last inline frame. Pop the frame.
1001 * In both cases, we hijack the stub to return to InjectJaegerReturn. This
1002 * moves |oldFp->rval| into the scripted return registers.
1005 restart:
1006 /* Step 1. Finish frames created after the entry frame. */
1007 if (!FinishExcessFrames(f, entryFrame))
1008 THROWV(NULL);
1010 /* IMacros are guaranteed to have been removed by now. */
1011 JS_ASSERT(f.fp() == entryFrame);
1012 JS_ASSERT(!entryFrame->hasImacropc());
1014 /* Step 2. If entryFrame is done, use a special path to return to EnterMethodJIT(). */
1015 if (FrameIsFinished(cx)) {
1016 if (!HandleFinishedFrame(f, entryFrame))
1017 THROWV(NULL);
1019 void *retPtr = JS_FUNC_TO_DATA_PTR(void *, InjectJaegerReturn);
1020 *f.returnAddressLocation() = retPtr;
1021 return NULL;
1024 /* Step 3. If entryFrame is at a safe point, just leave. */
1025 if (void *ncode = AtSafePoint(cx))
1026 return ncode;
1028 /* Step 4. Do a partial interp, then restart the whole process. */
1029 if (!PartialInterpret(f)) {
1030 if (!HandleErrorInExcessFrame(f, entryFrame))
1031 THROWV(NULL);
1034 goto restart;
1037 #endif /* JS_TRACER */
1039 #if defined JS_TRACER
1040 # if defined JS_MONOIC
1041 void *JS_FASTCALL
1042 stubs::InvokeTracer(VMFrame &f, ic::TraceICInfo *tic)
1044 return RunTracer(f, *tic);
1047 # else
1049 void *JS_FASTCALL
1050 stubs::InvokeTracer(VMFrame &f)
1052 return RunTracer(f);
1054 # endif /* JS_MONOIC */
1055 #endif /* JS_TRACER */