v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
rewriter.cc
Go to the documentation of this file.
1// Copyright 2012 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
6
7#include <optional>
8
9#include "src/ast/ast.h"
10#include "src/ast/scopes.h"
14#include "src/parsing/parser.h"
16
17// Use this macro when `replacement_` or other data produced by Visit() is used
18// in a non-trivial way (needs to be valid) after calling Visit().
19#define VISIT_AND_RETURN_IF_STACK_OVERFLOW(param) \
20 Visit(param); \
21 if (CheckStackOverflow()) return;
22
23namespace v8::internal {
24
25class Processor final : public AstVisitor<Processor> {
26 public:
27 Processor(uintptr_t stack_limit, DeclarationScope* closure_scope,
28 Variable* result, AstValueFactory* ast_value_factory, Zone* zone)
29 : result_(result),
30 replacement_(nullptr),
31 zone_(zone),
33 factory_(ast_value_factory, zone),
34 result_assigned_(false),
35 is_set_(false),
36 breakable_(false) {
38 InitializeAstVisitor(stack_limit);
39 }
40
41 void Process(ZonePtrList<Statement>* statements);
42 bool result_assigned() const { return result_assigned_; }
43
44 Zone* zone() { return zone_; }
47
48 // Returns ".result = value"
50 result_assigned_ = true;
52 return factory()->NewAssignment(Token::kAssign, result_proxy, value,
54 }
55
56 // Inserts '.result = undefined' in front of the given statement.
58
59 private:
61
62 // When visiting a node, we "return" a replacement for that node in
63 // [replacement_]. In many cases this will just be the original node.
65
67 public:
68 explicit BreakableScope(Processor* processor, bool breakable = true)
69 : processor_(processor), previous_(processor->breakable_) {
70 processor->breakable_ = processor->breakable_ || breakable;
71 }
72
73 ~BreakableScope() { processor_->breakable_ = previous_; }
74
75 private:
78 };
79
83
84 // Node visitors.
85#define DEF_VISIT(type) void Visit##type(type* node);
87#undef DEF_VISIT
88
89 void VisitIterationStatement(IterationStatement* stmt);
90
92
93 // We are not tracking result usage via the result_'s use
94 // counts (we leave the accurate computation to the
95 // usage analyzer). Instead we simple remember if
96 // there was ever an assignment to result_.
98
99 // To avoid storing to .result all the time, we eliminate some of
100 // the stores by keeping track of whether or not we're sure .result
101 // will be overwritten anyway. This is a bit more tricky than what I
102 // was hoping for.
104
106};
107
108
111 Expression* assignment = SetResult(undef);
112 Block* b = factory()->NewBlock(2, false);
113 b->statements()->Add(
114 factory()->NewExpressionStatement(assignment, kNoSourcePosition), zone());
115 b->statements()->Add(s, zone());
116 return b;
117}
118
120 // If we're in a breakable scope (named block, iteration, or switch), we walk
121 // all statements. The last value producing statement before the break needs
122 // to assign to .result. If we're not in a breakable scope, only the last
123 // value producing statement in the block assigns to .result, so we can stop
124 // early.
125 for (int i = statements->length() - 1; i >= 0 && (breakable_ || !is_set_);
126 --i) {
127 Visit(statements->at(i));
128 statements->Set(i, replacement_);
129 }
130}
131
132
133void Processor::VisitBlock(Block* node) {
134 // An initializer block is the rewritten form of a variable declaration
135 // with initialization expressions. The initializer block contains the
136 // list of assignments corresponding to the initialization expressions.
137 // While unclear from the spec (ECMA-262, 3rd., 12.2), the value of
138 // a variable declaration with initialization expression is 'undefined'
139 // with some JS VMs: For instance, using smjs, print(eval('var x = 7'))
140 // returns 'undefined'. To obtain the same behavior with v8, we need
141 // to prevent rewriting in that case.
142 if (!node->ignore_completion_value()) {
143 BreakableScope scope(this, node->is_breakable());
144 Process(node->statements());
145 }
147}
148
149
150void Processor::VisitExpressionStatement(ExpressionStatement* node) {
151 // Rewrite : <x>; -> .result = <x>;
152 if (!is_set_) {
153 node->set_expression(SetResult(node->expression()));
154 is_set_ = true;
155 }
157}
158
159
160void Processor::VisitIfStatement(IfStatement* node) {
161 // Rewrite both branches.
162 bool set_after = is_set_;
163
164 Visit(node->then_statement());
165 node->set_then_statement(replacement_);
166 bool set_in_then = is_set_;
167
168 is_set_ = set_after;
169 Visit(node->else_statement());
170 node->set_else_statement(replacement_);
171
172 replacement_ = set_in_then && is_set_ ? node : AssignUndefinedBefore(node);
173 is_set_ = true;
174}
175
176
178 // The statement may have to produce a value, so always assign undefined
179 // before.
180 // TODO(verwaest): Omit it if we know that there's no break/continue leaving
181 // it early.
183 BreakableScope scope(this);
184
185 Visit(node->body());
186 node->set_body(replacement_);
187
189 is_set_ = true;
190}
191
192
193void Processor::VisitDoWhileStatement(DoWhileStatement* node) {
195}
196
197
198void Processor::VisitWhileStatement(WhileStatement* node) {
200}
201
202
203void Processor::VisitForStatement(ForStatement* node) {
205}
206
207
208void Processor::VisitForInStatement(ForInStatement* node) {
210}
211
212
213void Processor::VisitForOfStatement(ForOfStatement* node) {
215}
216
217
218void Processor::VisitTryCatchStatement(TryCatchStatement* node) {
219 // Rewrite both try and catch block.
220 bool set_after = is_set_;
221
222 VISIT_AND_RETURN_IF_STACK_OVERFLOW(node->try_block());
223 node->set_try_block(static_cast<Block*>(replacement_));
224 bool set_in_try = is_set_;
225
226 is_set_ = set_after;
227 VISIT_AND_RETURN_IF_STACK_OVERFLOW(node->catch_block());
228 node->set_catch_block(static_cast<Block*>(replacement_));
229
230 replacement_ = is_set_ && set_in_try ? node : AssignUndefinedBefore(node);
231 is_set_ = true;
232}
233
234
235void Processor::VisitTryFinallyStatement(TryFinallyStatement* node) {
236 // Only rewrite finally if it could contain 'break' or 'continue'. Always
237 // rewrite try.
238 if (breakable_) {
239 // Only set result before a 'break' or 'continue'.
240 is_set_ = true;
241 VISIT_AND_RETURN_IF_STACK_OVERFLOW(node->finally_block());
242 node->set_finally_block(replacement_->AsBlock());
244 if (is_set_) {
245 // Save .result value at the beginning of the finally block and restore it
246 // at the end again: ".backup = .result; ...; .result = .backup" This is
247 // necessary because the finally block does not normally contribute to the
248 // completion value.
250 factory()->ast_value_factory()->dot_result_string());
251 Expression* backup_proxy = factory()->NewVariableProxy(backup);
252 Expression* result_proxy = factory()->NewVariableProxy(result_);
253 Expression* save = factory()->NewAssignment(
254 Token::kAssign, backup_proxy, result_proxy, kNoSourcePosition);
255 Expression* restore = factory()->NewAssignment(
256 Token::kAssign, result_proxy, backup_proxy, kNoSourcePosition);
257 node->finally_block()->statements()->InsertAt(
258 0, factory()->NewExpressionStatement(save, kNoSourcePosition),
259 zone());
260 node->finally_block()->statements()->Add(
261 factory()->NewExpressionStatement(restore, kNoSourcePosition),
262 zone());
263 } else {
264 // If is_set_ is false, it means the finally block has a 'break' or a
265 // 'continue' and was not preceded by a statement that assigned to
266 // .result. Try-finally statements return the abrupt completions from the
267 // finally block, meaning this case should get an undefined.
268 //
269 // Since the finally block will definitely result in an abrupt completion,
270 // there's no need to save and restore the .result.
271 Expression* undef = factory()->NewUndefinedLiteral(kNoSourcePosition);
272 Expression* assignment = SetResult(undef);
273 node->finally_block()->statements()->InsertAt(
274 0, factory()->NewExpressionStatement(assignment, kNoSourcePosition),
275 zone());
276 }
277 // We can't tell whether the finally-block is guaranteed to set .result, so
278 // reset is_set_ before visiting the try-block.
279 is_set_ = false;
280 }
281 VISIT_AND_RETURN_IF_STACK_OVERFLOW(node->try_block());
282 node->set_try_block(replacement_->AsBlock());
283
285 is_set_ = true;
286}
287
288
289void Processor::VisitSwitchStatement(SwitchStatement* node) {
290 // The statement may have to produce a value, so always assign undefined
291 // before.
292 // TODO(verwaest): Omit it if we know that there's no break/continue leaving
293 // it early.
295 BreakableScope scope(this);
296 // Rewrite statements in all case clauses.
297 ZonePtrList<CaseClause>* clauses = node->cases();
298 for (int i = clauses->length() - 1; i >= 0; --i) {
299 CaseClause* clause = clauses->at(i);
300 Process(clause->statements());
301 }
302
304 is_set_ = true;
305}
306
307
308void Processor::VisitContinueStatement(ContinueStatement* node) {
309 is_set_ = false;
311}
312
313
314void Processor::VisitBreakStatement(BreakStatement* node) {
315 is_set_ = false;
317}
318
319
320void Processor::VisitWithStatement(WithStatement* node) {
321 Visit(node->statement());
322 node->set_statement(replacement_);
323
325 is_set_ = true;
326}
327
328
329void Processor::VisitSloppyBlockFunctionStatement(
330 SloppyBlockFunctionStatement* node) {
331 Visit(node->statement());
332 node->set_statement(replacement_);
334}
335
336
337void Processor::VisitEmptyStatement(EmptyStatement* node) {
339}
340
341
342void Processor::VisitReturnStatement(ReturnStatement* node) {
343 is_set_ = true;
345}
346
347
348void Processor::VisitDebuggerStatement(DebuggerStatement* node) {
350}
351
352void Processor::VisitInitializeClassMembersStatement(
353 InitializeClassMembersStatement* node) {
355}
356
357void Processor::VisitInitializeClassStaticElementsStatement(
358 InitializeClassStaticElementsStatement* node) {
360}
361
362void Processor::VisitAutoAccessorGetterBody(AutoAccessorGetterBody* node) {
364}
365
366void Processor::VisitAutoAccessorSetterBody(AutoAccessorSetterBody* node) {
368}
369
370// Expressions are never visited.
371#define DEF_VISIT(type) \
372 void Processor::Visit##type(type* expr) { UNREACHABLE(); }
374#undef DEF_VISIT
375
376
377// Declarations are never visited.
378#define DEF_VISIT(type) \
379 void Processor::Visit##type(type* expr) { UNREACHABLE(); }
381#undef DEF_VISIT
382
383
384// Assumes code has been parsed. Mutates the AST, so the AST should not
385// continue to be used in the case of failure.
386bool Rewriter::Rewrite(ParseInfo* info, bool* out_has_stack_overflow) {
387 RCS_SCOPE(info->runtime_call_stats(),
388 RuntimeCallCounterId::kCompileRewriteReturnResult,
389 RuntimeCallStats::kThreadSpecific);
390
391 FunctionLiteral* function = info->literal();
392 DCHECK_NOT_NULL(function);
393 Scope* scope = function->scope();
394 DCHECK_NOT_NULL(scope);
395 DCHECK_EQ(scope, scope->GetClosureScope());
396
397 if (scope->is_repl_mode_scope() ||
398 !(scope->is_script_scope() || scope->is_eval_scope())) {
399 return true;
400 }
401
402 ZonePtrList<Statement>* body = function->body();
403 return RewriteBody(info, scope, body, out_has_stack_overflow).has_value();
404}
405
406std::optional<VariableProxy*> Rewriter::RewriteBody(
407 ParseInfo* info, Scope* scope, ZonePtrList<Statement>* body,
408 bool* out_has_stack_overflow) {
410 DisallowHandleAllocation no_handles;
412
413 if (!body->is_empty()) {
415 info->ast_value_factory()->dot_result_string());
416 Processor processor(info->stack_limit(), scope->AsDeclarationScope(),
417 result, info->ast_value_factory(), info->zone());
418 processor.Process(body);
419
420 if (processor.result_assigned()) {
422 VariableProxy* result_value =
423 processor.factory()->NewVariableProxy(result, pos);
424 if (!info->flags().is_repl_mode()) {
425 Statement* result_statement;
426 result_statement =
427 processor.factory()->NewReturnStatement(result_value, pos);
428 body->Add(result_statement, info->zone());
429 }
430 return result_value;
431 }
432
433 if (processor.HasStackOverflow()) {
434 *out_has_stack_overflow = true;
435 return std::nullopt;
436 }
437 }
438 return nullptr;
439}
440
441#undef VISIT_AND_RETURN_IF_STACK_OVERFLOW
442
443} // namespace v8::internal
#define AST_NODE_LIST(V)
Definition ast.h:121
#define EXPRESSION_NODE_LIST(V)
Definition ast.h:84
#define DECLARATION_NODE_LIST(V)
Definition ast.h:45
SourcePosition pos
Assignment * NewAssignment(Token::Value op, Expression *target, Expression *value, int pos)
Definition ast.h:3416
Literal * NewUndefinedLiteral(int pos)
Definition ast.h:3254
VariableProxy * NewVariableProxy(Variable *var, int start_position=kNoSourcePosition)
Definition ast.h:3299
Block * NewBlock(int capacity, bool ignore_completion_value)
Definition ast.h:3053
void Visit(AstNode *node)
Definition ast.h:2936
ZonePtrList< Statement > * statements()
Definition ast.h:323
BreakableScope(Processor *processor, bool breakable=true)
Definition rewriter.cc:68
Processor(uintptr_t stack_limit, DeclarationScope *closure_scope, Variable *result, AstValueFactory *ast_value_factory, Zone *zone)
Definition rewriter.cc:27
Statement * AssignUndefinedBefore(Statement *s)
Definition rewriter.cc:109
Statement * replacement_
Definition rewriter.cc:64
void Process(ZonePtrList< Statement > *statements)
Definition rewriter.cc:119
DeclarationScope * closure_scope()
Definition rewriter.cc:45
void VisitIterationStatement(IterationStatement *stmt)
Definition rewriter.cc:177
DeclarationScope * closure_scope_
Definition rewriter.cc:81
Expression * SetResult(Expression *value)
Definition rewriter.cc:49
AstNodeFactory * factory()
Definition rewriter.cc:46
AstNodeFactory factory_
Definition rewriter.cc:82
bool result_assigned() const
Definition rewriter.cc:42
static V8_EXPORT_PRIVATE bool Rewrite(ParseInfo *info, bool *out_has_stack_overflow)
Definition rewriter.cc:386
static std::optional< VariableProxy * > RewriteBody(ParseInfo *info, Scope *scope, ZonePtrList< Statement > *body, bool *out_has_stack_overflow)
Definition rewriter.cc:406
bool is_repl_mode_scope() const
Definition scopes.h:610
DeclarationScope * AsDeclarationScope()
bool is_script_scope() const
Definition scopes.h:364
bool is_eval_scope() const
Definition scopes.h:361
DeclarationScope * GetClosureScope()
Definition scopes.cc:1480
Zone * zone() const
Definition scopes.h:179
Variable * NewTemporary(const AstRawString *name)
Definition scopes.cc:1250
void Set(int index, const T &element)
V8_INLINE int length() const
Definition zone-list.h:101
T & at(int i) const
Definition zone-list.h:88
V8_INLINE bool is_empty() const
Definition zone-list.h:100
void Add(const T &element, Zone *zone)
Node * node
ZoneVector< RpoNumber > & result
ProcessorImpl * processor_
Definition mul-fft.cc:474
SnapshotTable< OpIndex, VariableData >::Key Variable
Definition operations.h:82
constexpr int kNoSourcePosition
Definition globals.h:850
ZoneList< T * > ZonePtrList
#define DEF_VISIT(type)
Definition rewriter.cc:85
#define VISIT_AND_RETURN_IF_STACK_OVERFLOW(param)
Definition rewriter.cc:19
#define RCS_SCOPE(...)
#define DCHECK_NOT_NULL(val)
Definition logging.h:492
#define CHECK_NOT_NULL(val)
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_EQ(v1, v2)
Definition logging.h:485
#define V8_NODISCARD
Definition v8config.h:693