diff options
Diffstat (limited to 'js/src/frontend/ForOfEmitter.cpp')
-rw-r--r-- | js/src/frontend/ForOfEmitter.cpp | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/js/src/frontend/ForOfEmitter.cpp b/js/src/frontend/ForOfEmitter.cpp new file mode 100644 index 0000000000..0fd0cfbe36 --- /dev/null +++ b/js/src/frontend/ForOfEmitter.cpp @@ -0,0 +1,224 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "frontend/ForOfEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/EmitterScope.h" +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex +#include "vm/Opcodes.h" +#include "vm/StencilEnums.h" // TryNoteKind + +using namespace js; +using namespace js::frontend; + +using mozilla::Nothing; + +ForOfEmitter::ForOfEmitter(BytecodeEmitter* bce, + const EmitterScope* headLexicalEmitterScope, + SelfHostedIter selfHostedIter, IteratorKind iterKind) + : bce_(bce), + selfHostedIter_(selfHostedIter), + iterKind_(iterKind), + headLexicalEmitterScope_(headLexicalEmitterScope) {} + +bool ForOfEmitter::emitIterated() { + MOZ_ASSERT(state_ == State::Start); + + // Evaluate the expression being iterated. The forHeadExpr should use a + // distinct TDZCheckCache to evaluate since (abstractly) it runs in its + // own LexicalEnvironment. + tdzCacheForIteratedValue_.emplace(bce_); + +#ifdef DEBUG + state_ = State::Iterated; +#endif + return true; +} + +bool ForOfEmitter::emitInitialize(uint32_t forPos) { + MOZ_ASSERT(state_ == State::Iterated); + + tdzCacheForIteratedValue_.reset(); + + // [stack] # if AllowContentWithNext + // [stack] NEXT ITER + // [stack] # elif AllowContentWith + // [stack] ITERABLE ITERFN SYNC_ITERFN? + // [stack] # else + // [stack] ITERABLE + + if (iterKind_ == IteratorKind::Async) { + if (!bce_->emitAsyncIterator(selfHostedIter_)) { + // [stack] NEXT ITER + return false; + } + } else { + if (!bce_->emitIterator(selfHostedIter_)) { + // [stack] NEXT ITER + return false; + } + } + + // For-of loops have the iterator next method and the iterator itself on the + // stack. + + int32_t iterDepth = bce_->bytecodeSection().stackDepth(); + loopInfo_.emplace(bce_, iterDepth, selfHostedIter_, iterKind_); + + if (!loopInfo_->emitLoopHead(bce_, Nothing())) { + // [stack] NEXT ITER + return false; + } + + // If the loop had an escaping lexical declaration, replace the current + // environment with an dead zoned one to implement TDZ semantics. + if (headLexicalEmitterScope_) { + // The environment chain only includes an environment for the for-of + // loop head *if* a scope binding is captured, thereby requiring + // recreation each iteration. If a lexical scope exists for the head, + // it must be the innermost one. If that scope has closed-over + // bindings inducing an environment, recreate the current environment. + MOZ_ASSERT(headLexicalEmitterScope_ == bce_->innermostEmitterScope()); + MOZ_ASSERT(headLexicalEmitterScope_->scope(bce_).kind() == + ScopeKind::Lexical); + + if (headLexicalEmitterScope_->hasEnvironment()) { + if (!bce_->emitInternedScopeOp(headLexicalEmitterScope_->index(), + JSOp::RecreateLexicalEnv)) { + // [stack] NEXT ITER + return false; + } + } + + // For uncaptured bindings, put them back in TDZ. + if (!headLexicalEmitterScope_->deadZoneFrameSlots(bce_)) { + return false; + } + } + +#ifdef DEBUG + loopDepth_ = bce_->bytecodeSection().stackDepth(); +#endif + + // Make sure this code is attributed to the "for". + if (!bce_->updateSourceCoordNotes(forPos)) { + return false; + } + + if (!bce_->emit1(JSOp::Dup2)) { + // [stack] NEXT ITER NEXT ITER + return false; + } + + if (!bce_->emitIteratorNext(mozilla::Some(forPos), iterKind_, + selfHostedIter_)) { + // [stack] NEXT ITER RESULT + return false; + } + + if (!bce_->emit1(JSOp::Dup)) { + // [stack] NEXT ITER RESULT RESULT + return false; + } + if (!bce_->emitAtomOp(JSOp::GetProp, + TaggedParserAtomIndex::WellKnown::done())) { + // [stack] NEXT ITER RESULT DONE + return false; + } + + // if (done) break; + MOZ_ASSERT(bce_->innermostNestableControl == loopInfo_.ptr(), + "must be at the top-level of the loop"); + if (!bce_->emitJump(JSOp::JumpIfTrue, &loopInfo_->breaks)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Emit code to assign result.value to the iteration variable. + // + // Note that ES 13.7.5.13, step 5.c says getting result.value does not + // call IteratorClose, so start TryNoteKind::ForOfIterClose after the GetProp. + if (!bce_->emitAtomOp(JSOp::GetProp, + TaggedParserAtomIndex::WellKnown::value())) { + // [stack] NEXT ITER VALUE + return false; + } + + if (!loopInfo_->emitBeginCodeNeedingIteratorClose(bce_)) { + return false; + } + +#ifdef DEBUG + state_ = State::Initialize; +#endif + return true; +} + +bool ForOfEmitter::emitBody() { + MOZ_ASSERT(state_ == State::Initialize); + + MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == loopDepth_ + 1, + "the stack must be balanced around the initializing " + "operation"); + +#ifdef DEBUG + state_ = State::Body; +#endif + return true; +} + +bool ForOfEmitter::emitEnd(uint32_t iteratedPos) { + MOZ_ASSERT(state_ == State::Body); + + MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == loopDepth_ + 1, + "the stack must be balanced around the for-of body"); + + if (!loopInfo_->emitEndCodeNeedingIteratorClose(bce_)) { + // [stack] NEXT ITER VALUE + return false; + } + + if (!loopInfo_->emitContinueTarget(bce_)) { + // [stack] NEXT ITER VALUE + return false; + } + + // We use the iterated value's position to attribute the backedge, + // which corresponds to the iteration protocol. + // This is a bit misleading for 2nd and later iterations and might need + // some fix (bug 1482003). + if (!bce_->updateSourceCoordNotes(iteratedPos)) { + return false; + } + + if (!bce_->emit1(JSOp::Pop)) { + // [stack] NEXT ITER + return false; + } + + if (!loopInfo_->emitLoopEnd(bce_, JSOp::Goto, TryNoteKind::ForOf)) { + // [stack] NEXT ITER + return false; + } + + // All jumps/breaks to this point still have an extra value on the stack. + MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == loopDepth_); + bce_->bytecodeSection().setStackDepth(bce_->bytecodeSection().stackDepth() + + 1); + + if (!bce_->emitPopN(3)) { + // [stack] + return false; + } + + loopInfo_.reset(); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} |