From e2853b8a8fc05a7526d933a62828c26747b4580a Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 25 Mar 2018 13:32:14 +0200 Subject: Bug 1322069 - Add TryEmitter Issue #74 --- js/src/frontend/BytecodeEmitter.cpp | 510 ++++++++++++++++++++++++------------ 1 file changed, 341 insertions(+), 169 deletions(-) (limited to 'js') diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 76f1f75b5..b1b0055e2 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -1563,6 +1563,318 @@ BytecodeEmitter::TDZCheckCache::noteTDZCheck(BytecodeEmitter* bce, JSAtom* name, return true; } +class MOZ_STACK_CLASS TryEmitter +{ + public: + enum Kind { + TryCatch, + TryCatchFinally, + TryFinally + }; + enum ShouldUseRetVal { + UseRetVal, + DontUseRetVal + }; + enum ShouldUseControl { + UseControl, + DontUseControl, + }; + + private: + BytecodeEmitter* bce_; + Kind kind_; + ShouldUseRetVal retValKind_; + + // Track jumps-over-catches and gosubs-to-finally for later fixup. + // + // When a finally block is active, non-local jumps (including + // jumps-over-catches) result in a GOSUB being written into the bytecode + // stream and fixed-up later. + // + // If ShouldUseControl is DontUseControl, all that handling is skipped. + // DontUseControl is used by yield*, that matches to the following: + // * has only one catch block + // * has no catch guard + // * has JSOP_GOTO at the end of catch-block + // * has no non-local-jump + // * doesn't use finally block for normal completion of try-block and + // catch-block + Maybe controlInfo_; + + int depth_; + unsigned noteIndex_; + ptrdiff_t tryStart_; + JumpList catchAndFinallyJump_; + JumpTarget tryEnd_; + JumpTarget finallyStart_; + + enum State { + Start, + Try, + TryEnd, + Catch, + CatchEnd, + Finally, + FinallyEnd, + End + }; + State state_; + + bool hasCatch() const { + return kind_ == TryCatch || kind_ == TryCatchFinally; + } + bool hasFinally() const { + return kind_ == TryCatchFinally || kind_ == TryFinally; + } + + public: + TryEmitter(BytecodeEmitter* bce, Kind kind, ShouldUseRetVal retValKind = UseRetVal, + ShouldUseControl controlKind = UseControl) + : bce_(bce), + kind_(kind), + retValKind_(retValKind), + depth_(0), + noteIndex_(0), + tryStart_(0), + state_(Start) + { + if (controlKind == UseControl) + controlInfo_.emplace(bce_, hasFinally() ? StatementKind::Finally : StatementKind::Try); + finallyStart_.offset = 0; + } + + bool emitJumpOverCatchAndFinally() { + if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_)) + return false; + return true; + } + + bool emitTry() { + MOZ_ASSERT(state_ == Start); + + // Since an exception can be thrown at any place inside the try block, + // we need to restore the stack and the scope chain before we transfer + // the control to the exception handler. + // + // For that we store in a try note associated with the catch or + // finally block the stack depth upon the try entry. The interpreter + // uses this depth to properly unwind the stack and the scope chain. + depth_ = bce_->stackDepth; + + // Record the try location, then emit the try block. + if (!bce_->newSrcNote(SRC_TRY, ¬eIndex_)) + return false; + if (!bce_->emit1(JSOP_TRY)) + return false; + tryStart_ = bce_->offset(); + + state_ = Try; + return true; + } + + private: + bool emitTryEnd() { + MOZ_ASSERT(state_ == Try); + MOZ_ASSERT(depth_ == bce_->stackDepth); + + // GOSUB to finally, if present. + if (hasFinally() && controlInfo_) { + if (!bce_->emitJump(JSOP_GOSUB, &controlInfo_->gosubs)) + return false; + } + + // Source note points to the jump at the end of the try block. + if (!bce_->setSrcNoteOffset(noteIndex_, 0, bce_->offset() - tryStart_ + JSOP_TRY_LENGTH)) + return false; + + // Emit jump over catch and/or finally. + if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_)) + return false; + + if (!bce_->emitJumpTarget(&tryEnd_)) + return false; + + return true; + } + + public: + bool emitCatch() { + if (state_ == Try) { + if (!emitTryEnd()) + return false; + } else { + MOZ_ASSERT(state_ == Catch); + if (!emitCatchEnd(true)) + return false; + } + + MOZ_ASSERT(bce_->stackDepth == depth_); + + if (retValKind_ == UseRetVal) { + // Clear the frame's return value that might have been set by the + // try block: + // + // eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1 + if (!bce_->emit1(JSOP_UNDEFINED)) + return false; + if (!bce_->emit1(JSOP_SETRVAL)) + return false; + } + + state_ = Catch; + return true; + } + + private: + bool emitCatchEnd(bool hasNext) { + MOZ_ASSERT(state_ == Catch); + + if (!controlInfo_) + return true; + + // gosub , if required. + if (hasFinally()) { + if (!bce_->emitJump(JSOP_GOSUB, &controlInfo_->gosubs)) + return false; + MOZ_ASSERT(bce_->stackDepth == depth_); + } + + // Jump over the remaining catch blocks. This will get fixed + // up to jump to after catch/finally. + if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_)) + return false; + + // If this catch block had a guard clause, patch the guard jump to + // come here. + if (controlInfo_->guardJump.offset != -1) { + if (!bce_->emitJumpTargetAndPatch(controlInfo_->guardJump)) + return false; + controlInfo_->guardJump.offset = -1; + + // If this catch block is the last one, rethrow, delegating + // execution of any finally block to the exception handler. + if (!hasNext) { + if (!bce_->emit1(JSOP_EXCEPTION)) + return false; + if (!bce_->emit1(JSOP_THROW)) + return false; + } + } + + return true; + } + + public: + bool emitFinally(Maybe finallyPos = Nothing()) { + MOZ_ASSERT(hasFinally()); + + if (state_ == Try) { + if (!emitTryEnd()) + return false; + } else { + MOZ_ASSERT(state_ == Catch); + if (!emitCatchEnd(false)) + return false; + } + + MOZ_ASSERT(bce_->stackDepth == depth_); + + if (!bce_->emitJumpTarget(&finallyStart_)) + return false; + + if (controlInfo_) { + // Fix up the gosubs that might have been emitted before non-local + // jumps to the finally code. + bce_->patchJumpsToTarget(controlInfo_->gosubs, finallyStart_); + + // Indicate that we're emitting a subroutine body. + controlInfo_->setEmittingSubroutine(); + } + if (finallyPos) { + if (!bce_->updateSourceCoordNotes(finallyPos.value())) + return false; + } + if (!bce_->emit1(JSOP_FINALLY)) + return false; + + if (retValKind_ == UseRetVal) { + if (!bce_->emit1(JSOP_GETRVAL)) + return false; + + // Clear the frame's return value to make break/continue return + // correct value even if there's no other statement before them: + // + // eval("x: try { 1 } finally { break x; }"); // undefined, not 1 + if (!bce_->emit1(JSOP_UNDEFINED)) + return false; + if (!bce_->emit1(JSOP_SETRVAL)) + return false; + } + + state_ = Finally; + return true; + } + + private: + bool emitFinallyEnd() { + MOZ_ASSERT(state_ == Finally); + + if (retValKind_ == UseRetVal) { + if (!bce_->emit1(JSOP_SETRVAL)) + return false; + } + + if (!bce_->emit1(JSOP_RETSUB)) + return false; + + bce_->hasTryFinally = true; + return true; + } + + public: + bool emitEnd() { + if (state_ == Catch) { + MOZ_ASSERT(!hasFinally()); + if (!emitCatchEnd(false)) + return false; + } else { + MOZ_ASSERT(state_ == Finally); + MOZ_ASSERT(hasFinally()); + if (!emitFinallyEnd()) + return false; + } + + MOZ_ASSERT(bce_->stackDepth == depth_); + + // ReconstructPCStack needs a NOP here to mark the end of the last + // catch block. + if (!bce_->emit1(JSOP_NOP)) + return false; + + // Fix up the end-of-try/catch jumps to come here. + if (!bce_->emitJumpTargetAndPatch(catchAndFinallyJump_)) + return false; + + // Add the try note last, to let post-order give us the right ordering + // (first to last for a given nesting level, inner to outer by level). + if (hasCatch()) { + if (!bce_->tryNoteList.append(JSTRY_CATCH, depth_, tryStart_, tryEnd_.offset)) + return false; + } + + // If we've got a finally, mark try+catch region with additional + // trynote to catch exceptions (re)thrown from a catch block or + // for the try{}finally{} case. + if (hasFinally()) { + if (!bce_->tryNoteList.append(JSTRY_FINALLY, depth_, tryStart_, finallyStart_.offset)) + return false; + } + + state_ = End; + return true; + } +}; + class MOZ_STACK_CLASS IfThenElseEmitter { BytecodeEmitter* bce_; @@ -6073,57 +6385,28 @@ BytecodeEmitter::emitCatch(ParseNode* pn) MOZ_NEVER_INLINE bool BytecodeEmitter::emitTry(ParseNode* pn) { - // Track jumps-over-catches and gosubs-to-finally for later fixup. - // - // When a finally block is active, non-local jumps (including - // jumps-over-catches) result in a GOSUB being written into the bytecode - // stream and fixed-up later. - // - TryFinallyControl controlInfo(this, pn->pn_kid3 ? StatementKind::Finally : StatementKind::Try); - - // Since an exception can be thrown at any place inside the try block, - // we need to restore the stack and the scope chain before we transfer - // the control to the exception handler. - // - // For that we store in a try note associated with the catch or - // finally block the stack depth upon the try entry. The interpreter - // uses this depth to properly unwind the stack and the scope chain. - // - int depth = stackDepth; - - // Record the try location, then emit the try block. - unsigned noteIndex; - if (!newSrcNote(SRC_TRY, ¬eIndex)) - return false; - if (!emit1(JSOP_TRY)) - return false; - - ptrdiff_t tryStart = offset(); - if (!emitTree(pn->pn_kid1)) - return false; - MOZ_ASSERT(depth == stackDepth); + ParseNode* catchList = pn->pn_kid2; + ParseNode* finallyNode = pn->pn_kid3; - // GOSUB to finally, if present. - if (pn->pn_kid3) { - if (!emitJump(JSOP_GOSUB, &controlInfo.gosubs)) - return false; + TryEmitter::Kind kind; + if (catchList) { + if (finallyNode) + kind = TryEmitter::TryCatchFinally; + else + kind = TryEmitter::TryCatch; + } else { + MOZ_ASSERT(finallyNode); + kind = TryEmitter::TryFinally; } + TryEmitter tryCatch(this, kind); - // Source note points to the jump at the end of the try block. - if (!setSrcNoteOffset(noteIndex, 0, offset() - tryStart + JSOP_TRY_LENGTH)) - return false; - - // Emit jump over catch and/or finally. - JumpList catchJump; - if (!emitJump(JSOP_GOTO, &catchJump)) + if (!tryCatch.emitTry()) return false; - JumpTarget tryEnd; - if (!emitJumpTarget(&tryEnd)) + if (!emitTree(pn->pn_kid1)) return false; // If this try has a catch block, emit it. - ParseNode* catchList = pn->pn_kid2; if (catchList) { MOZ_ASSERT(catchList->isKind(PNK_CATCHLIST)); @@ -6153,110 +6436,26 @@ BytecodeEmitter::emitTry(ParseNode* pn) // capturing exceptions thrown from catch{} blocks. // for (ParseNode* pn3 = catchList->pn_head; pn3; pn3 = pn3->pn_next) { - MOZ_ASSERT(this->stackDepth == depth); - - // Clear the frame's return value that might have been set by the - // try block: - // - // eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1 - if (!emit1(JSOP_UNDEFINED)) - return false; - if (!emit1(JSOP_SETRVAL)) + if (!tryCatch.emitCatch()) return false; // Emit the lexical scope and catch body. MOZ_ASSERT(pn3->isKind(PNK_LEXICALSCOPE)); if (!emitTree(pn3)) return false; - - // gosub , if required. - if (pn->pn_kid3) { - if (!emitJump(JSOP_GOSUB, &controlInfo.gosubs)) - return false; - MOZ_ASSERT(this->stackDepth == depth); - } - - // Jump over the remaining catch blocks. This will get fixed - // up to jump to after catch/finally. - if (!emitJump(JSOP_GOTO, &catchJump)) - return false; - - // If this catch block had a guard clause, patch the guard jump to - // come here. - if (controlInfo.guardJump.offset != -1) { - if (!emitJumpTargetAndPatch(controlInfo.guardJump)) - return false; - controlInfo.guardJump.offset = -1; - - // If this catch block is the last one, rethrow, delegating - // execution of any finally block to the exception handler. - if (!pn3->pn_next) { - if (!emit1(JSOP_EXCEPTION)) - return false; - if (!emit1(JSOP_THROW)) - return false; - } - } } } - MOZ_ASSERT(this->stackDepth == depth); - // Emit the finally handler, if there is one. - JumpTarget finallyStart{ 0 }; - if (pn->pn_kid3) { - if (!emitJumpTarget(&finallyStart)) - return false; - - // Fix up the gosubs that might have been emitted before non-local - // jumps to the finally code. - patchJumpsToTarget(controlInfo.gosubs, finallyStart); - - // Indicate that we're emitting a subroutine body. - controlInfo.setEmittingSubroutine(); - if (!updateSourceCoordNotes(pn->pn_kid3->pn_pos.begin)) - return false; - if (!emit1(JSOP_FINALLY)) - return false; - if (!emit1(JSOP_GETRVAL)) - return false; - - // Clear the frame's return value to make break/continue return - // correct value even if there's no other statement before them: - // - // eval("x: try { 1 } finally { break x; }"); // undefined, not 1 - if (!emit1(JSOP_UNDEFINED)) - return false; - if (!emit1(JSOP_SETRVAL)) + if (finallyNode) { + if (!tryCatch.emitFinally(Some(finallyNode->pn_pos.begin))) return false; - if (!emitTree(pn->pn_kid3)) - return false; - if (!emit1(JSOP_SETRVAL)) - return false; - if (!emit1(JSOP_RETSUB)) + if (!emitTree(finallyNode)) return false; - hasTryFinally = true; - MOZ_ASSERT(this->stackDepth == depth); } - // ReconstructPCStack needs a NOP here to mark the end of the last catch block. - if (!emit1(JSOP_NOP)) - return false; - - // Fix up the end-of-try/catch jumps to come here. - if (!emitJumpTargetAndPatch(catchJump)) - return false; - - // Add the try note last, to let post-order give us the right ordering - // (first to last for a given nesting level, inner to outer by level). - if (catchList && !tryNoteList.append(JSTRY_CATCH, depth, tryStart, tryEnd.offset)) - return false; - - // If we've got a finally, mark try+catch region with additional - // trynote to catch exceptions (re)thrown from a catch block or - // for the try{}finally{} case. - if (pn->pn_kid3 && !tryNoteList.append(JSTRY_FINALLY, depth, tryStart, finallyStart.offset)) + if (!tryCatch.emitEnd()) return false; return true; @@ -8040,17 +8239,15 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) int depth = stackDepth; MOZ_ASSERT(depth >= 2); - JumpList send; - if (!emitJump(JSOP_GOTO, &send)) // goto send + TryEmitter tryCatch(this, TryEmitter::TryCatchFinally, TryEmitter::DontUseRetVal, + TryEmitter::DontUseControl); + if (!tryCatch.emitJumpOverCatchAndFinally()) // ITER RESULT return false; - // Try prologue. // ITER RESULT - unsigned noteIndex; - if (!newSrcNote(SRC_TRY, ¬eIndex)) - return false; JumpTarget tryStart{ offset() }; - if (!emit1(JSOP_TRY)) // tryStart: + if (!tryCatch.emitTry()) // ITER RESULT return false; + MOZ_ASSERT(this->stackDepth == depth); // Load the generator object. @@ -8061,17 +8258,9 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) if (!emitYieldOp(JSOP_YIELD)) // ITER RECEIVED return false; - // Try epilogue. - if (!setSrcNoteOffset(noteIndex, 0, offset() - tryStart.offset)) - return false; - if (!emitJump(JSOP_GOTO, &send)) // goto send - return false; - - JumpTarget tryEnd; - if (!emitJumpTarget(&tryEnd)) // tryEnd: + if (!tryCatch.emitCatch()) // ITER RESULT return false; - // Catch location. stackDepth = uint32_t(depth); // ITER RESULT if (!emit1(JSOP_EXCEPTION)) // ITER RESULT EXCEPTION return false; @@ -8118,36 +8307,19 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) if (!emitJump(JSOP_GOTO, &checkResult)) // goto checkResult return false; - // The finally block, IteratorClose logic. + // IteratorClose logic. + if (!tryCatch.emitFinally()) + return false; - JumpTarget finallyStart{ 0 }; - if (!emitJumpTarget(&finallyStart)) - return false; - if (!emit1(JSOP_FINALLY)) // ITER RESULT FTYPE FVALUE - return false; if (!emitDupAt(3)) // ITER RESULT FTYPE FVALUE ITER return false; if (!emitIteratorClose(Some(tryStart))) // ITER RESULT FTYPE FVALUE return false; - if (!emit1(JSOP_RETSUB)) // ITER RESULT - return false; - - // Catch and finally epilogue. - // This is a peace offering to ReconstructPCStack. See the note in EmitTry. - if (!emit1(JSOP_NOP)) - return false; - size_t tryStartOffset = tryStart.offset + JSOP_TRY_LENGTH; - if (!tryNoteList.append(JSTRY_CATCH, depth, tryStartOffset, tryEnd.offset)) - return false; - if (!tryNoteList.append(JSTRY_FINALLY, depth, tryStartOffset, finallyStart.offset)) + if (!tryCatch.emitEnd()) return false; // After the try-catch-finally block: send the received value to the iterator. - if (!emitJumpTargetAndPatch(send)) // send: - return false; - - // Send location. // result = iter.next(received) // ITER RECEIVED if (!emit1(JSOP_SWAP)) // RECEIVED ITER return false; -- cgit v1.2.3