summaryrefslogtreecommitdiffstats
path: root/js/src/frontend/ForOfEmitter.cpp
blob: 2bef507a99077003d55952650390cddad810aed4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
/* -*- 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,
                                  bool isIteratorMethodOnStack) {
  MOZ_ASSERT(state_ == State::Iterated);

  tdzCacheForIteratedValue_.reset();

  //                [stack] # if isIteratorMethodOnStack
  //                [stack] ITERABLE ITERFN SYNC_ITERFN?
  //                [stack] # else isIteratorMethodOnStack
  //                [stack] ITERABLE

  if (iterKind_ == IteratorKind::Async) {
    if (!bce_->emitAsyncIterator(selfHostedIter_, isIteratorMethodOnStack)) {
      //            [stack] NEXT ITER
      return false;
    }
  } else {
    if (!bce_->emitIterator(selfHostedIter_, isIteratorMethodOnStack)) {
      //            [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;
}