blob: 8b5d18f5cb9fcaa481fe6c6dd8406d8c09074f59 [file] [log] [blame]
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/interpreter/bytecode-generator.h"
#include <map>
#include <unordered_map>
#include <unordered_set>
#include "include/v8-extension.h"
#include "src/api/api-inl.h"
#include "src/ast/ast-source-ranges.h"
#include "src/ast/ast.h"
#include "src/ast/scopes.h"
#include "src/builtins/builtins-constructor.h"
#include "src/codegen/compiler.h"
#include "src/codegen/unoptimized-compilation-info.h"
#include "src/common/globals.h"
#include "src/compiler-dispatcher/lazy-compile-dispatcher.h"
#include "src/heap/parked-scope.h"
#include "src/interpreter/bytecode-array-builder.h"
#include "src/interpreter/bytecode-flags-and-tokens.h"
#include "src/interpreter/bytecode-jump-table.h"
#include "src/interpreter/bytecode-label.h"
#include "src/interpreter/bytecode-register-allocator.h"
#include "src/interpreter/bytecode-register-optimizer.h"
#include "src/interpreter/bytecode-register.h"
#include "src/interpreter/control-flow-builders.h"
#include "src/logging/local-logger.h"
#include "src/logging/log.h"
#include "src/numbers/conversions.h"
#include "src/objects/debug-objects.h"
#include "src/objects/objects.h"
#include "src/objects/smi.h"
#include "src/objects/template-objects.h"
#include "src/parsing/parse-info.h"
#include "src/parsing/token.h"
#include "src/utils/ostreams.h"
namespace v8 {
namespace internal {
namespace interpreter {
// Scoped class tracking context objects created by the visitor. Represents
// mutations of the context chain within the function body, allowing pushing and
// popping of the current {context_register} during visitation.
class V8_NODISCARD BytecodeGenerator::ContextScope {
public:
ContextScope(BytecodeGenerator* generator, Scope* scope,
Register outer_context_reg = Register())
: generator_(generator),
scope_(scope),
outer_(generator_->execution_context()),
register_(Register::current_context()),
depth_(0) {
DCHECK(scope->NeedsContext() || outer_ == nullptr);
if (outer_) {
depth_ = outer_->depth_ + 1;
// Push the outer context into a new context register.
if (!outer_context_reg.is_valid()) {
outer_context_reg = generator_->register_allocator()->NewRegister();
}
outer_->set_register(outer_context_reg);
generator_->builder()->PushContext(outer_context_reg);
}
generator_->set_execution_context(this);
}
~ContextScope() {
if (outer_) {
DCHECK_EQ(register_.index(), Register::current_context().index());
generator_->builder()->PopContext(outer_->reg());
outer_->set_register(register_);
}
generator_->set_execution_context(outer_);
}
ContextScope(const ContextScope&) = delete;
ContextScope& operator=(const ContextScope&) = delete;
// Returns the depth of the given |scope| for the current execution context.
int ContextChainDepth(Scope* scope) {
return scope_->ContextChainLength(scope);
}
// Returns the execution context at |depth| in the current context chain if it
// is a function local execution context, otherwise returns nullptr.
ContextScope* Previous(int depth) {
if (depth > depth_) {
return nullptr;
}
ContextScope* previous = this;
for (int i = depth; i > 0; --i) {
previous = previous->outer_;
}
return previous;
}
Register reg() const { return register_; }
private:
const BytecodeArrayBuilder* builder() const { return generator_->builder(); }
void set_register(Register reg) { register_ = reg; }
BytecodeGenerator* generator_;
Scope* scope_;
ContextScope* outer_;
Register register_;
int depth_;
};
// Scoped class for tracking control statements entered by the
// visitor.
class V8_NODISCARD BytecodeGenerator::ControlScope {
public:
explicit ControlScope(BytecodeGenerator* generator)
: generator_(generator),
outer_(generator->execution_control()),
context_(generator->execution_context()) {
generator_->set_execution_control(this);
}
~ControlScope() { generator_->set_execution_control(outer()); }
ControlScope(const ControlScope&) = delete;
ControlScope& operator=(const ControlScope&) = delete;
void Break(Statement* stmt) {
PerformCommand(CMD_BREAK, stmt, kNoSourcePosition);
}
void Continue(Statement* stmt) {
PerformCommand(CMD_CONTINUE, stmt, kNoSourcePosition);
}
void ReturnAccumulator(int source_position) {
PerformCommand(CMD_RETURN, nullptr, source_position);
}
void AsyncReturnAccumulator(int source_position) {
PerformCommand(CMD_ASYNC_RETURN, nullptr, source_position);
}
class DeferredCommands;
protected:
enum Command {
CMD_BREAK,
CMD_CONTINUE,
CMD_RETURN,
CMD_ASYNC_RETURN,
CMD_RETHROW
};
static constexpr bool CommandUsesAccumulator(Command command) {
return command != CMD_BREAK && command != CMD_CONTINUE;
}
void PerformCommand(Command command, Statement* statement,
int source_position);
virtual bool Execute(Command command, Statement* statement,
int source_position) = 0;
// Helper to pop the context chain to a depth expected by this control scope.
// Note that it is the responsibility of each individual {Execute} method to
// trigger this when commands are handled and control-flow continues locally.
void PopContextToExpectedDepth();
BytecodeGenerator* generator() const { return generator_; }
ControlScope* outer() const { return outer_; }
ContextScope* context() const { return context_; }
private:
BytecodeGenerator* generator_;
ControlScope* outer_;
ContextScope* context_;
};
// Helper class for a try-finally control scope. It can record intercepted
// control-flow commands that cause entry into a finally-block, and re-apply
// them after again leaving that block. Special tokens are used to identify
// paths going through the finally-block to dispatch after leaving the block.
class V8_NODISCARD BytecodeGenerator::ControlScope::DeferredCommands final {
public:
DeferredCommands(BytecodeGenerator* generator, Register token_register,
Register result_register, Register message_register)
: generator_(generator),
deferred_(generator->zone()),
token_register_(token_register),
result_register_(result_register),
message_register_(message_register),
return_token_(-1),
async_return_token_(-1) {
// There's always a rethrow path.
// TODO(leszeks): We could decouple deferred_ index and token to allow us
// to still push this lazily.
static_assert(
static_cast<int>(TryFinallyContinuationToken::kRethrowToken) == 0);
deferred_.push_back(
{CMD_RETHROW, nullptr,
static_cast<int>(TryFinallyContinuationToken::kRethrowToken)});
}
// One recorded control-flow command.
struct Entry {
Command command; // The command type being applied on this path.
Statement* statement; // The target statement for the command or {nullptr}.
int token; // A token identifying this particular path.
};
// Records a control-flow command while entering the finally-block. This also
// generates a new dispatch token that identifies one particular path. This
// expects the result to be in the accumulator.
void RecordCommand(Command command, Statement* statement) {
int token = GetTokenForCommand(command, statement);
DCHECK_LT(token, deferred_.size());
DCHECK_EQ(deferred_[token].command, command);
DCHECK_EQ(deferred_[token].statement, statement);
DCHECK_EQ(deferred_[token].token, token);
if (CommandUsesAccumulator(command)) {
builder()->StoreAccumulatorInRegister(result_register_);
}
builder()->LoadLiteral(Smi::FromInt(token));
builder()->StoreAccumulatorInRegister(token_register_);
if (!CommandUsesAccumulator(command)) {
// If we're not saving the accumulator in the result register, shove a
// harmless value there instead so that it is still considered "killed" in
// the liveness analysis. Normally we would LdaUndefined first, but the
// Smi token value is just as good, and by reusing it we save a bytecode.
builder()->StoreAccumulatorInRegister(result_register_);
}
if (command == CMD_RETHROW) {
// Clear message object as we enter the catch block. It will be restored
// if we rethrow.
builder()->LoadTheHole().SetPendingMessage().StoreAccumulatorInRegister(
message_register_);
}
}
// Records the dispatch token to be used to identify the re-throw path when
// the finally-block has been entered through the exception handler. This
// expects the exception to be in the accumulator.
void RecordHandlerReThrowPath() {
// The accumulator contains the exception object.
RecordCommand(CMD_RETHROW, nullptr);
}
// Records the dispatch token to be used to identify the implicit fall-through
// path at the end of a try-block into the corresponding finally-block.
void RecordFallThroughPath() {
builder()->LoadLiteral(Smi::FromInt(
static_cast<int>(TryFinallyContinuationToken::kFallthroughToken)));
builder()->StoreAccumulatorInRegister(token_register_);
// Since we're not saving the accumulator in the result register, shove a
// harmless value there instead so that it is still considered "killed" in
// the liveness analysis. Normally we would LdaUndefined first, but the Smi
// token value is just as good, and by reusing it we save a bytecode.
builder()->StoreAccumulatorInRegister(result_register_);
}
// Applies all recorded control-flow commands after the finally-block again.
// This generates a dynamic dispatch on the token from the entry point.
void ApplyDeferredCommands() {
if (deferred_.empty()) return;
BytecodeLabel fall_through;
if (deferred_.size() == 1) {
// For a single entry, just jump to the fallthrough if we don't match the
// entry token.
const Entry& entry = deferred_[0];
builder()
->LoadLiteral(Smi::FromInt(entry.token))
.CompareReference(token_register_)
.JumpIfFalse(ToBooleanMode::kAlreadyBoolean, &fall_through);
if (entry.command == CMD_RETHROW) {
// Pending message object is restored on exit.
builder()
->LoadAccumulatorWithRegister(message_register_)
.SetPendingMessage();
}
if (CommandUsesAccumulator(entry.command)) {
builder()->LoadAccumulatorWithRegister(result_register_);
}
execution_control()->PerformCommand(entry.command, entry.statement,
kNoSourcePosition);
} else {
// For multiple entries, build a jump table and switch on the token,
// jumping to the fallthrough if none of them match.
BytecodeJumpTable* jump_table =
builder()->AllocateJumpTable(static_cast<int>(deferred_.size()), 0);
builder()
->LoadAccumulatorWithRegister(token_register_)
.SwitchOnSmiNoFeedback(jump_table)
.Jump(&fall_through);
for (const Entry& entry : deferred_) {
builder()->Bind(jump_table, entry.token);
if (entry.command == CMD_RETHROW) {
// Pending message object is restored on exit.
builder()
->LoadAccumulatorWithRegister(message_register_)
.SetPendingMessage();
}
if (CommandUsesAccumulator(entry.command)) {
builder()->LoadAccumulatorWithRegister(result_register_);
}
execution_control()->PerformCommand(entry.command, entry.statement,
kNoSourcePosition);
}
}
builder()->Bind(&fall_through);
}
BytecodeArrayBuilder* builder() { return generator_->builder(); }
ControlScope* execution_control() { return generator_->execution_control(); }
private:
int GetTokenForCommand(Command command, Statement* statement) {
switch (command) {
case CMD_RETURN:
return GetReturnToken();
case CMD_ASYNC_RETURN:
return GetAsyncReturnToken();
case CMD_RETHROW:
return static_cast<int>(TryFinallyContinuationToken::kRethrowToken);
default:
// TODO(leszeks): We could also search for entries with the same
// command and statement.
return GetNewTokenForCommand(command, statement);
}
}
int GetReturnToken() {
if (return_token_ == -1) {
return_token_ = GetNewTokenForCommand(CMD_RETURN, nullptr);
}
return return_token_;
}
int GetAsyncReturnToken() {
if (async_return_token_ == -1) {
async_return_token_ = GetNewTokenForCommand(CMD_ASYNC_RETURN, nullptr);
}
return async_return_token_;
}
int GetNewTokenForCommand(Command command, Statement* statement) {
int token = static_cast<int>(deferred_.size());
deferred_.push_back({command, statement, token});
return token;
}
BytecodeGenerator* generator_;
ZoneVector<Entry> deferred_;
Register token_register_;
Register result_register_;
Register message_register_;
// Tokens for commands that don't need a statement.
int return_token_;
int async_return_token_;
};
// Scoped class for dealing with control flow reaching the function level.
class BytecodeGenerator::ControlScopeForTopLevel final
: public BytecodeGenerator::ControlScope {
public:
explicit ControlScopeForTopLevel(BytecodeGenerator* generator)
: ControlScope(generator) {}
protected:
bool Execute(Command command, Statement* statement,
int source_position) override {
switch (command) {
case CMD_BREAK: // We should never see break/continue in top-level.
case CMD_CONTINUE:
UNREACHABLE();
case CMD_RETURN:
// No need to pop contexts, execution leaves the method body.
generator()->BuildReturn(source_position);
return true;
case CMD_ASYNC_RETURN:
// No need to pop contexts, execution leaves the method body.
generator()->BuildAsyncReturn(source_position);
return true;
case CMD_RETHROW:
// No need to pop contexts, execution leaves the method body.
generator()->BuildReThrow();
return true;
}
return false;
}
};
// Scoped class for enabling break inside blocks and switch blocks.
class BytecodeGenerator::ControlScopeForBreakable final
: public BytecodeGenerator::ControlScope {
public:
ControlScopeForBreakable(BytecodeGenerator* generator,
BreakableStatement* statement,
BreakableControlFlowBuilder* control_builder)
: ControlScope(generator),
statement_(statement),
control_builder_(control_builder) {}
protected:
bool Execute(Command command, Statement* statement,
int source_position) override {
if (statement != statement_) return false;
switch (command) {
case CMD_BREAK:
PopContextToExpectedDepth();
control_builder_->Break();
return true;
case CMD_CONTINUE:
case CMD_RETURN:
case CMD_ASYNC_RETURN:
case CMD_RETHROW:
break;
}
return false;
}
private:
Statement* statement_;
BreakableControlFlowBuilder* control_builder_;
};
// Scoped class for enabling 'break' and 'continue' in iteration
// constructs, e.g. do...while, while..., for...
class BytecodeGenerator::ControlScopeForIteration final
: public BytecodeGenerator::ControlScope {
public:
ControlScopeForIteration(BytecodeGenerator* generator,
IterationStatement* statement,
LoopBuilder* loop_builder)
: ControlScope(generator),
statement_(statement),
loop_builder_(loop_builder) {}
protected:
bool Execute(Command command, Statement* statement,
int source_position) override {
if (statement != statement_) return false;
switch (command) {
case CMD_BREAK:
PopContextToExpectedDepth();
loop_builder_->Break();
return true;
case CMD_CONTINUE:
PopContextToExpectedDepth();
loop_builder_->Continue();
return true;
case CMD_RETURN:
case CMD_ASYNC_RETURN:
case CMD_RETHROW:
break;
}
return false;
}
private:
Statement* statement_;
LoopBuilder* loop_builder_;
};
// Scoped class for enabling 'throw' in try-catch constructs.
class BytecodeGenerator::ControlScopeForTryCatch final
: public BytecodeGenerator::ControlScope {
public:
ControlScopeForTryCatch(BytecodeGenerator* generator,
TryCatchBuilder* try_catch_builder)
: ControlScope(generator) {}
protected:
bool Execute(Command command, Statement* statement,
int source_position) override {
switch (command) {
case CMD_BREAK:
case CMD_CONTINUE:
case CMD_RETURN:
case CMD_ASYNC_RETURN:
break;
case CMD_RETHROW:
// No need to pop contexts, execution re-enters the method body via the
// stack unwinding mechanism which itself restores contexts correctly.
generator()->BuildReThrow();
return true;
}
return false;
}
};
// Scoped class for enabling control flow through try-finally constructs.
class BytecodeGenerator::ControlScopeForTryFinally final
: public BytecodeGenerator::ControlScope {
public:
ControlScopeForTryFinally(BytecodeGenerator* generator,
TryFinallyBuilder* try_finally_builder,
DeferredCommands* commands)
: ControlScope(generator),
try_finally_builder_(try_finally_builder),
commands_(commands) {}
protected:
bool Execute(Command command, Statement* statement,
int source_position) override {
switch (command) {
case CMD_BREAK:
case CMD_CONTINUE:
case CMD_RETURN:
case CMD_ASYNC_RETURN:
case CMD_RETHROW:
PopContextToExpectedDepth();
// We don't record source_position here since we don't generate return
// bytecode right here and will generate it later as part of finally
// block. Each return bytecode generated in finally block will get own
// return source position from corresponded return statement or we'll
// use end of function if no return statement is presented.
commands_->RecordCommand(command, statement);
try_finally_builder_->LeaveTry();
return true;
}
return false;
}
private:
TryFinallyBuilder* try_finally_builder_;
DeferredCommands* commands_;
};
// Scoped class for collecting 'return' statments in a derived constructor.
// Derived constructors can only return undefined or objects, and this check
// must occur right before return (e.g., after `finally` blocks execute).
class BytecodeGenerator::ControlScopeForDerivedConstructor final
: public BytecodeGenerator::ControlScope {
public:
ControlScopeForDerivedConstructor(BytecodeGenerator* generator,
Register result_register,
BytecodeLabels* check_return_value_labels)
: ControlScope(generator),
result_register_(result_register),
check_return_value_labels_(check_return_value_labels) {}
protected:
bool Execute(Command command, Statement* statement,
int source_position) override {
// Constructors are never async.
DCHECK_NE(CMD_ASYNC_RETURN, command);
if (command == CMD_RETURN) {
PopContextToExpectedDepth();
generator()->builder()->SetStatementPosition(source_position);
generator()->builder()->StoreAccumulatorInRegister(result_register_);
generator()->builder()->Jump(check_return_value_labels_->New());
return true;
}
return false;
}
private:
Register result_register_;
BytecodeLabels* check_return_value_labels_;
};
// Allocate and fetch the coverage indices tracking NaryLogical Expressions.
class BytecodeGenerator::NaryCodeCoverageSlots {
public:
NaryCodeCoverageSlots(BytecodeGenerator* generator, NaryOperation* expr)
: generator_(generator) {
if (generator_->block_coverage_builder_ == nullptr) return;
for (size_t i = 0; i < expr->subsequent_length(); i++) {
coverage_slots_.push_back(
generator_->AllocateNaryBlockCoverageSlotIfEnabled(expr, i));
}
}
int GetSlotFor(size_t subsequent_expr_index) const {
if (generator_->block_coverage_builder_ == nullptr) {
return BlockCoverageBuilder::kNoCoverageArraySlot;
}
DCHECK(coverage_slots_.size() > subsequent_expr_index);
return coverage_slots_[subsequent_expr_index];
}
private:
BytecodeGenerator* generator_;
std::vector<int> coverage_slots_;
};
void BytecodeGenerator::ControlScope::PerformCommand(Command command,
Statement* statement,
int source_position) {
ControlScope* current = this;
do {
if (current->Execute(command, statement, source_position)) {
return;
}
current = current->outer();
} while (current != nullptr);
UNREACHABLE();
}
void BytecodeGenerator::ControlScope::PopContextToExpectedDepth() {
// Pop context to the expected depth. Note that this can in fact pop multiple
// contexts at once because the {PopContext} bytecode takes a saved register.
if (generator()->execution_context() != context()) {
generator()->builder()->PopContext(context()->reg());
}
}
class V8_NODISCARD BytecodeGenerator::RegisterAllocationScope final {
public:
explicit RegisterAllocationScope(BytecodeGenerator* generator)
: generator_(generator),
outer_next_register_index_(
generator->register_allocator()->next_register_index()) {}
~RegisterAllocationScope() {
generator_->register_allocator()->ReleaseRegisters(
outer_next_register_index_);
}
RegisterAllocationScope(const RegisterAllocationScope&) = delete;
RegisterAllocationScope& operator=(const RegisterAllocationScope&) = delete;
BytecodeGenerator* generator() const { return generator_; }
private:
BytecodeGenerator* generator_;
int outer_next_register_index_;
};
class V8_NODISCARD BytecodeGenerator::AccumulatorPreservingScope final {
public:
explicit AccumulatorPreservingScope(BytecodeGenerator* generator,
AccumulatorPreservingMode mode)
: generator_(generator) {
if (mode == AccumulatorPreservingMode::kPreserve) {
saved_accumulator_register_ =
generator_->register_allocator()->NewRegister();
generator_->builder()->StoreAccumulatorInRegister(
saved_accumulator_register_);
}
}
~AccumulatorPreservingScope() {
if (saved_accumulator_register_.is_valid()) {
generator_->builder()->LoadAccumulatorWithRegister(
saved_accumulator_register_);
}
}
AccumulatorPreservingScope(const AccumulatorPreservingScope&) = delete;
AccumulatorPreservingScope& operator=(const AccumulatorPreservingScope&) =
delete;
private:
BytecodeGenerator* generator_;
Register saved_accumulator_register_;
};
// Scoped base class for determining how the result of an expression will be
// used.
class V8_NODISCARD BytecodeGenerator::ExpressionResultScope {
public:
ExpressionResultScope(BytecodeGenerator* generator, Expression::Context kind)
: outer_(generator->execution_result()),
allocator_(generator),
kind_(kind),
type_hint_(TypeHint::kUnknown) {
generator->set_execution_result(this);
}
~ExpressionResultScope() {
allocator_.generator()->set_execution_result(outer_);
}
ExpressionResultScope(const ExpressionResultScope&) = delete;
ExpressionResultScope& operator=(const ExpressionResultScope&) = delete;
bool IsEffect() const { return kind_ == Expression::kEffect; }
bool IsValue() const { return kind_ == Expression::kValue; }
bool IsTest() const { return kind_ == Expression::kTest; }
TestResultScope* AsTest() {
DCHECK(IsTest());
return reinterpret_cast<TestResultScope*>(this);
}
// Specify expression always returns a Boolean result value.
void SetResultIsBoolean() {
DCHECK_EQ(type_hint_, TypeHint::kUnknown);
type_hint_ = TypeHint::kBoolean;
}
void SetResultIsString() {
DCHECK_EQ(type_hint_, TypeHint::kUnknown);
type_hint_ = TypeHint::kString;
}
void SetResultIsInternalizedString() {
DCHECK_EQ(type_hint_, TypeHint::kUnknown);
type_hint_ = TypeHint::kInternalizedString;
}
TypeHint type_hint() const { return type_hint_; }
private:
ExpressionResultScope* outer_;
RegisterAllocationScope allocator_;
Expression::Context kind_;
TypeHint type_hint_;
};
// Scoped class used when the result of the current expression is not
// expected to produce a result.
class BytecodeGenerator::EffectResultScope final
: public ExpressionResultScope {
public:
explicit EffectResultScope(BytecodeGenerator* generator)
: ExpressionResultScope(generator, Expression::kEffect) {}
};
// Scoped class used when the result of the current expression to be
// evaluated should go into the interpreter's accumulator.
class V8_NODISCARD BytecodeGenerator::ValueResultScope final
: public ExpressionResultScope {
public:
explicit ValueResultScope(BytecodeGenerator* generator)
: ExpressionResultScope(generator, Expression::kValue) {}
};
// Scoped class used when the result of the current expression to be
// evaluated is only tested with jumps to two branches.
class V8_NODISCARD BytecodeGenerator::TestResultScope final
: public ExpressionResultScope {
public:
TestResultScope(BytecodeGenerator* generator, BytecodeLabels* then_labels,
BytecodeLabels* else_labels, TestFallthrough fallthrough)
: ExpressionResultScope(generator, Expression::kTest),
result_consumed_by_test_(false),
fallthrough_(fallthrough),
then_labels_(then_labels),
else_labels_(else_labels) {}
TestResultScope(const TestResultScope&) = delete;
TestResultScope& operator=(const TestResultScope&) = delete;
// Used when code special cases for TestResultScope and consumes any
// possible value by testing and jumping to a then/else label.
void SetResultConsumedByTest() { result_consumed_by_test_ = true; }
bool result_consumed_by_test() { return result_consumed_by_test_; }
// Inverts the control flow of the operation, swapping the then and else
// labels and the fallthrough.
void InvertControlFlow() {
std::swap(then_labels_, else_labels_);
fallthrough_ = inverted_fallthrough();
}
BytecodeLabel* NewThenLabel() { return then_labels_->New(); }
BytecodeLabel* NewElseLabel() { return else_labels_->New(); }
BytecodeLabels* then_labels() const { return then_labels_; }
BytecodeLabels* else_labels() const { return else_labels_; }
void set_then_labels(BytecodeLabels* then_labels) {
then_labels_ = then_labels;
}
void set_else_labels(BytecodeLabels* else_labels) {
else_labels_ = else_labels;
}
TestFallthrough fallthrough() const { return fallthrough_; }
TestFallthrough inverted_fallthrough() const {
switch (fallthrough_) {
case TestFallthrough::kThen:
return TestFallthrough::kElse;
case TestFallthrough::kElse:
return TestFallthrough::kThen;
default:
return TestFallthrough::kNone;
}
}
void set_fallthrough(TestFallthrough fallthrough) {
fallthrough_ = fallthrough;
}
private:
bool result_consumed_by_test_;
TestFallthrough fallthrough_;
BytecodeLabels* then_labels_;
BytecodeLabels* else_labels_;
};
// Used to build a list of toplevel declaration data.
class BytecodeGenerator::TopLevelDeclarationsBuilder final : public ZoneObject {
public:
template <typename IsolateT>
Handle<FixedArray> AllocateDeclarations(UnoptimizedCompilationInfo* info,
BytecodeGenerator* generator,
Handle<Script> script,
IsolateT* isolate) {
DCHECK(has_constant_pool_entry_);
Handle<FixedArray> data =
isolate->factory()->NewFixedArray(entry_slots_, AllocationType::kOld);
int array_index = 0;
if (info->scope()->is_module_scope()) {
for (Declaration* decl : *info->scope()->declarations()) {
Variable* var = decl->var();
if (!var->is_used()) continue;
if (var->location() != VariableLocation::MODULE) continue;
#ifdef DEBUG
int start = array_index;
#endif
if (decl->IsFunctionDeclaration()) {
FunctionLiteral* f = static_cast<FunctionDeclaration*>(decl)->fun();
Handle<SharedFunctionInfo> sfi(
Compiler::GetSharedFunctionInfo(f, script, isolate));
// Return a null handle if any initial values can't be created. Caller
// will set stack overflow.
if (sfi.is_null()) return Handle<FixedArray>();
data->set(array_index++, *sfi);
int literal_index = generator->GetCachedCreateClosureSlot(f);
data->set(array_index++, Smi::FromInt(literal_index));
DCHECK(var->IsExport());
data->set(array_index++, Smi::FromInt(var->index()));
DCHECK_EQ(start + kModuleFunctionDeclarationSize, array_index);
} else if (var->IsExport() && var->binding_needs_init()) {
data->set(array_index++, Smi::FromInt(var->index()));
DCHECK_EQ(start + kModuleVariableDeclarationSize, array_index);
}
}
} else {
for (Declaration* decl : *info->scope()->declarations()) {
Variable* var = decl->var();
if (!var->is_used()) continue;
if (var->location() != VariableLocation::UNALLOCATED) continue;
#ifdef DEBUG
int start = array_index;
#endif
if (decl->IsVariableDeclaration()) {
data->set(array_index++, *var->raw_name()->string());
DCHECK_EQ(start + kGlobalVariableDeclarationSize, array_index);
} else {
FunctionLiteral* f = static_cast<FunctionDeclaration*>(decl)->fun();
Handle<SharedFunctionInfo> sfi(
Compiler::GetSharedFunctionInfo(f, script, isolate));
// Return a null handle if any initial values can't be created. Caller
// will set stack overflow.
if (sfi.is_null()) return Handle<FixedArray>();
data->set(array_index++, *sfi);
int literal_index = generator->GetCachedCreateClosureSlot(f);
data->set(array_index++, Smi::FromInt(literal_index));
DCHECK_EQ(start + kGlobalFunctionDeclarationSize, array_index);
}
}
}
DCHECK_EQ(array_index, data->length());
return data;
}
size_t constant_pool_entry() {
DCHECK(has_constant_pool_entry_);
return constant_pool_entry_;
}
void set_constant_pool_entry(size_t constant_pool_entry) {
DCHECK(has_top_level_declaration());
DCHECK(!has_constant_pool_entry_);
constant_pool_entry_ = constant_pool_entry;
has_constant_pool_entry_ = true;
}
void record_global_variable_declaration() {
entry_slots_ += kGlobalVariableDeclarationSize;
}
void record_global_function_declaration() {
entry_slots_ += kGlobalFunctionDeclarationSize;
}
void record_module_variable_declaration() {
entry_slots_ += kModuleVariableDeclarationSize;
}
void record_module_function_declaration() {
entry_slots_ += kModuleFunctionDeclarationSize;
}
bool has_top_level_declaration() { return entry_slots_ > 0; }
bool processed() { return processed_; }
void mark_processed() { processed_ = true; }
private:
const int kGlobalVariableDeclarationSize = 1;
const int kGlobalFunctionDeclarationSize = 2;
const int kModuleVariableDeclarationSize = 1;
const int kModuleFunctionDeclarationSize = 3;
size_t constant_pool_entry_ = 0;
int entry_slots_ = 0;
bool has_constant_pool_entry_ = false;
bool processed_ = false;
};
class V8_NODISCARD BytecodeGenerator::CurrentScope final {
public:
CurrentScope(BytecodeGenerator* generator, Scope* scope)
: generator_(generator), outer_scope_(generator->current_scope()) {
if (scope != nullptr) {
DCHECK_EQ(outer_scope_, scope->outer_scope());
generator_->set_current_scope(scope);
}
}
~CurrentScope() {
if (outer_scope_ != generator_->current_scope()) {
generator_->set_current_scope(outer_scope_);
}
}
CurrentScope(const CurrentScope&) = delete;
CurrentScope& operator=(const CurrentScope&) = delete;
private:
BytecodeGenerator* generator_;
Scope* outer_scope_;
};
class V8_NODISCARD BytecodeGenerator::MultipleEntryBlockContextScope {
public:
MultipleEntryBlockContextScope(BytecodeGenerator* generator, Scope* scope)
: generator_(generator), scope_(scope), is_in_scope_(false) {
if (scope) {
inner_context_ = generator->register_allocator()->NewRegister();
outer_context_ = generator->register_allocator()->NewRegister();
generator->BuildNewLocalBlockContext(scope_);
generator->builder()->StoreAccumulatorInRegister(inner_context_);
}
}
void SetEnteredIf(bool condition) {
RegisterAllocationScope register_scope(generator_);
if (condition && scope_ != nullptr && !is_in_scope_) {
EnterScope();
} else if (!condition && is_in_scope_) {
ExitScope();
}
}
~MultipleEntryBlockContextScope() { DCHECK(!is_in_scope_); }
MultipleEntryBlockContextScope(const MultipleEntryBlockContextScope&) =
delete;
MultipleEntryBlockContextScope& operator=(
const MultipleEntryBlockContextScope&) = delete;
private:
void EnterScope() {
DCHECK(inner_context_.is_valid());
DCHECK(outer_context_.is_valid());
DCHECK(!is_in_scope_);
generator_->builder()->LoadAccumulatorWithRegister(inner_context_);
current_scope_.emplace(generator_, scope_);
context_scope_.emplace(generator_, scope_, outer_context_);
is_in_scope_ = true;
}
void ExitScope() {
DCHECK(inner_context_.is_valid());
DCHECK(outer_context_.is_valid());
DCHECK(is_in_scope_);
context_scope_ = base::nullopt;
current_scope_ = base::nullopt;
is_in_scope_ = false;
}
BytecodeGenerator* generator_;
Scope* scope_;
Register inner_context_;
Register outer_context_;
bool is_in_scope_;
base::Optional<CurrentScope> current_scope_;
base::Optional<ContextScope> context_scope_;
};
class BytecodeGenerator::FeedbackSlotCache : public ZoneObject {
public:
enum class SlotKind {
kStoreGlobalSloppy,
kStoreGlobalStrict,
kSetNamedStrict,
kSetNamedSloppy,
kLoadProperty,
kLoadSuperProperty,
kLoadGlobalNotInsideTypeof,
kLoadGlobalInsideTypeof,
kClosureFeedbackCell
};
explicit FeedbackSlotCache(Zone* zone) : map_(zone) {}
void Put(SlotKind slot_kind, Variable* variable, int slot_index) {
PutImpl(slot_kind, 0, variable, slot_index);
}
void Put(SlotKind slot_kind, AstNode* node, int slot_index) {
PutImpl(slot_kind, 0, node, slot_index);
}
void Put(SlotKind slot_kind, int variable_index, const AstRawString* name,
int slot_index) {
PutImpl(slot_kind, variable_index, name, slot_index);
}
void Put(SlotKind slot_kind, const AstRawString* name, int slot_index) {
PutImpl(slot_kind, 0, name, slot_index);
}
int Get(SlotKind slot_kind, Variable* variable) const {
return GetImpl(slot_kind, 0, variable);
}
int Get(SlotKind slot_kind, AstNode* node) const {
return GetImpl(slot_kind, 0, node);
}
int Get(SlotKind slot_kind, int variable_index,
const AstRawString* name) const {
return GetImpl(slot_kind, variable_index, name);
}
int Get(SlotKind slot_kind, const AstRawString* name) const {
return GetImpl(slot_kind, 0, name);
}
private:
using Key = std::tuple<SlotKind, int, const void*>;
void PutImpl(SlotKind slot_kind, int index, const void* node,
int slot_index) {
Key key = std::make_tuple(slot_kind, index, node);
auto entry = std::make_pair(key, slot_index);
map_.insert(entry);
}
int GetImpl(SlotKind slot_kind, int index, const void* node) const {
Key key = std::make_tuple(slot_kind, index, node);
auto iter = map_.find(key);
if (iter != map_.end()) {
return iter->second;
}
return -1;
}
ZoneMap<Key, int> map_;
};
// Scoped class to help elide hole checks within a conditionally executed basic
// block. Each conditionally executed basic block must have a scope to emit
// hole checks correctly.
//
// The duration of the scope must correspond to a basic block. Numbered
// Variables (see Variable::HoleCheckBitmap) are remembered in the bitmap when
// the first hole check is emitted. Subsequent hole checks are elided.
//
// On scope exit, the hole check state at construction time is restored.
class V8_NODISCARD BytecodeGenerator::HoleCheckElisionScope {
public:
explicit HoleCheckElisionScope(BytecodeGenerator* bytecode_generator)
: HoleCheckElisionScope(&bytecode_generator->hole_check_bitmap_) {}
~HoleCheckElisionScope() { *bitmap_ = prev_bitmap_value_; }
protected:
explicit HoleCheckElisionScope(Variable::HoleCheckBitmap* bitmap)
: bitmap_(bitmap), prev_bitmap_value_(*bitmap) {}
Variable::HoleCheckBitmap* bitmap_;
Variable::HoleCheckBitmap prev_bitmap_value_;
};
// Scoped class to help elide hole checks within control flow that branch and
// merge.
//
// Each such control flow construct (e.g., if-else, ternary expressions) must
// have a scope to emit hole checks correctly. Additionally, each branch must
// have a Branch.
//
// The Merge or MergeIf method must be called to merge variables that have been
// hole-checked along every branch are marked as no longer needing a hole check.
//
// Example:
//
// HoleCheckElisionMergeScope merge_elider(this);
// {
// HoleCheckElisionMergeScope::Branch branch_elider(merge_elider);
// Visit(then_branch);
// }
// {
// HoleCheckElisionMergeScope::Branch branch_elider(merge_elider);
// Visit(else_branch);
// }
// merge_elider.Merge();
//
// Conversely, it is incorrect to use this class for control flow constructs
// that do not merge (e.g., if without else). HoleCheckElisionScope should be
// used for those cases.
class V8_NODISCARD BytecodeGenerator::HoleCheckElisionMergeScope final {
public:
explicit HoleCheckElisionMergeScope(BytecodeGenerator* bytecode_generator)
: bitmap_(&bytecode_generator->hole_check_bitmap_) {}
~HoleCheckElisionMergeScope() {
// Did you forget to call Merge or MergeIf?
DCHECK(merge_called_);
}
void Merge() {
DCHECK_NE(UINT64_MAX, merge_value_);
*bitmap_ = merge_value_;
#ifdef DEBUG
merge_called_ = true;
#endif
}
void MergeIf(bool cond) {
if (cond) Merge();
#ifdef DEBUG
merge_called_ = true;
#endif
}
class V8_NODISCARD Branch final : public HoleCheckElisionScope {
public:
explicit Branch(HoleCheckElisionMergeScope& merge_into)
: HoleCheckElisionScope(merge_into.bitmap_),
merge_into_bitmap_(&merge_into.merge_value_) {}
~Branch() { *merge_into_bitmap_ &= *bitmap_; }
private:
Variable::HoleCheckBitmap* merge_into_bitmap_;
};
private:
Variable::HoleCheckBitmap* bitmap_;
Variable::HoleCheckBitmap merge_value_ = UINT64_MAX;
#ifdef DEBUG
bool merge_called_ = false;
#endif
};
class BytecodeGenerator::IteratorRecord final {
public:
IteratorRecord(Register object_register, Register next_register,
IteratorType type = IteratorType::kNormal)
: type_(type), object_(object_register), next_(next_register) {
DCHECK(object_.is_valid() && next_.is_valid());
}
inline IteratorType type() const { return type_; }
inline Register object() const { return object_; }
inline Register next() const { return next_; }
private:
IteratorType type_;
Register object_;
Register next_;
};
class V8_NODISCARD BytecodeGenerator::OptionalChainNullLabelScope final {
public:
explicit OptionalChainNullLabelScope(BytecodeGenerator* bytecode_generator)
: bytecode_generator_(bytecode_generator),
labels_(bytecode_generator->zone()) {
prev_ = bytecode_generator_->optional_chaining_null_labels_;
bytecode_generator_->optional_chaining_null_labels_ = &labels_;
}
~OptionalChainNullLabelScope() {
bytecode_generator_->optional_chaining_null_labels_ = prev_;
}
BytecodeLabels* labels() { return &labels_; }
private:
BytecodeGenerator* bytecode_generator_;
BytecodeLabels labels_;
BytecodeLabels* prev_;
};
// LoopScope delimits the scope of {loop}, from its header to its final jump.
// It should be constructed iff a (conceptual) back edge should be produced. In
// the case of creating a LoopBuilder but never emitting the loop, it is valid
// to skip the creation of LoopScope.
class V8_NODISCARD BytecodeGenerator::LoopScope final {
public:
explicit LoopScope(BytecodeGenerator* bytecode_generator, LoopBuilder* loop)
: bytecode_generator_(bytecode_generator),
parent_loop_scope_(bytecode_generator_->current_loop_scope()),
loop_builder_(loop) {
loop_builder_->LoopHeader();
bytecode_generator_->set_current_loop_scope(this);
bytecode_generator_->loop_depth_++;
}
~LoopScope() {
bytecode_generator_->loop_depth_--;
bytecode_generator_->set_current_loop_scope(parent_loop_scope_);
DCHECK_GE(bytecode_generator_->loop_depth_, 0);
loop_builder_->JumpToHeader(
bytecode_generator_->loop_depth_,
parent_loop_scope_ ? parent_loop_scope_->loop_builder_ : nullptr);
}
private:
BytecodeGenerator* const bytecode_generator_;
LoopScope* const parent_loop_scope_;
LoopBuilder* const loop_builder_;
};
class V8_NODISCARD BytecodeGenerator::ForInScope final {
public:
explicit ForInScope(BytecodeGenerator* bytecode_generator,
ForInStatement* stmt, Register enum_index,
Register cache_type)
: bytecode_generator_(bytecode_generator),
parent_for_in_scope_(bytecode_generator_->current_for_in_scope()),
each_var_(nullptr),
enum_index_(enum_index),
cache_type_(cache_type) {
if (v8_flags.enable_enumerated_keyed_access_bytecode) {
Expression* each = stmt->each();
if (each->IsVariableProxy()) {
Variable* each_var = each->AsVariableProxy()->var();
if (each_var->IsStackLocal()) {
each_var_ = each_var;
bytecode_generator_->SetVariableInRegister(
each_var_,
bytecode_generator_->builder()->Local(each_var_->index()));
}
}
bytecode_generator_->set_current_for_in_scope(this);
}
}
~ForInScope() {
if (v8_flags.enable_enumerated_keyed_access_bytecode) {
bytecode_generator_->set_current_for_in_scope(parent_for_in_scope_);
}
}
// Get corresponding {ForInScope} for a given {each} variable.
ForInScope* GetForInScope(Variable* each) {
DCHECK(v8_flags.enable_enumerated_keyed_access_bytecode);
ForInScope* scope = this;
do {
if (each == scope->each_var_) break;
scope = scope->parent_for_in_scope_;
} while (scope != nullptr);
return scope;
}
Register enum_index() { return enum_index_; }
Register cache_type() { return cache_type_; }
private:
BytecodeGenerator* const bytecode_generator_;
ForInScope* const parent_for_in_scope_;
Variable* each_var_;
Register enum_index_;
Register cache_type_;
};
class V8_NODISCARD BytecodeGenerator::DisposablesStackScope final {
public:
explicit DisposablesStackScope(BytecodeGenerator* bytecode_generator)
: bytecode_generator_(bytecode_generator),
prev_disposables_stack_(
bytecode_generator_->current_disposables_stack()) {
bytecode_generator_->current_disposables_stack_ =
bytecode_generator->register_allocator()->NewRegister();
bytecode_generator->builder()->CallRuntime(
Runtime::kInitializeDisposableStack);
bytecode_generator->builder()->StoreAccumulatorInRegister(
bytecode_generator_->current_disposables_stack_);
}
~DisposablesStackScope() {
bytecode_generator_->set_current_disposables_stack(prev_disposables_stack_);
}
private:
BytecodeGenerator* const bytecode_generator_;
Register prev_disposables_stack_;
};
namespace {
template <typename PropertyT>
struct Accessors : public ZoneObject {
Accessors() : getter(nullptr), setter(nullptr) {}
PropertyT* getter;
PropertyT* setter;
};
// A map from property names to getter/setter pairs allocated in the zone that
// also provides a way of accessing the pairs in the order they were first
// added so that the generated bytecode is always the same.
template <typename PropertyT>
class AccessorTable
: public base::TemplateHashMap<Literal, Accessors<PropertyT>,
bool (*)(void*, void*),
ZoneAllocationPolicy> {
public:
explicit AccessorTable(Zone* zone)
: base::TemplateHashMap<Literal, Accessors<PropertyT>,
bool (*)(void*, void*), ZoneAllocationPolicy>(
Literal::Match, ZoneAllocationPolicy(zone)),
zone_(zone) {}
Accessors<PropertyT>* LookupOrInsert(Literal* key) {
auto it = this->find(key, true);
if (it->second == nullptr) {
it->second = zone_->New<Accessors<PropertyT>>();
ordered_accessors_.push_back({key, it->second});
}
return it->second;
}
const std::vector<std::pair<Literal*, Accessors<PropertyT>*>>&
ordered_accessors() {
return ordered_accessors_;
}
private:
std::vector<std::pair<Literal*, Accessors<PropertyT>*>> ordered_accessors_;
Zone* zone_;
};
} // namespace
#ifdef DEBUG
static bool IsInEagerLiterals(
FunctionLiteral* literal,
const std::vector<FunctionLiteral*>& eager_literals) {
for (FunctionLiteral* eager_literal : eager_literals) {
if (literal == eager_literal) return true;
}
return false;
}
#endif // DEBUG
BytecodeGenerator::BytecodeGenerator(
LocalIsolate* local_isolate, Zone* compile_zone,
UnoptimizedCompilationInfo* info,
const AstStringConstants* ast_string_constants,
std::vector<FunctionLiteral*>* eager_inner_literals, Handle<Script> script)
: local_isolate_(local_isolate),
zone_(compile_zone),
builder_(zone(), info->num_parameters_including_this(),
info->scope()->num_stack_slots(), info->feedback_vector_spec(),
info->SourcePositionRecordingMode()),
info_(info),
ast_string_constants_(ast_string_constants),
closure_scope_(info->scope()),
current_scope_(info->scope()),
eager_inner_literals_(eager_inner_literals),
script_(script),
feedback_slot_cache_(zone()->New<FeedbackSlotCache>(zone())),
top_level_builder_(zone()->New<TopLevelDeclarationsBuilder>()),
block_coverage_builder_(nullptr),
function_literals_(0, zone()),
native_function_literals_(0, zone()),
object_literals_(0, zone()),
array_literals_(0, zone()),
class_literals_(0, zone()),
template_objects_(0, zone()),
vars_in_hole_check_bitmap_(0, zone()),
execution_control_(nullptr),
execution_context_(nullptr),
execution_result_(nullptr),
incoming_new_target_or_generator_(),
current_disposables_stack_(),
optional_chaining_null_labels_(nullptr),
dummy_feedback_slot_(feedback_spec(), FeedbackSlotKind::kCompareOp),
generator_jump_table_(nullptr),
suspend_count_(0),
loop_depth_(0),
hole_check_bitmap_(0),
current_loop_scope_(nullptr),
current_for_in_scope_(nullptr),
catch_prediction_(HandlerTable::UNCAUGHT) {
DCHECK_EQ(closure_scope(), closure_scope()->GetClosureScope());
if (info->has_source_range_map()) {
block_coverage_builder_ = zone()->New<BlockCoverageBuilder>(
zone(), builder(), info->source_range_map());
}
}
namespace {
template <typename Isolate>
struct NullContextScopeHelper;
template <>
struct NullContextScopeHelper<Isolate> {
using Type = NullContextScope;
};
template <>
struct NullContextScopeHelper<LocalIsolate> {
class V8_NODISCARD DummyNullContextScope {
public:
explicit DummyNullContextScope(LocalIsolate*) {}
};
using Type = DummyNullContextScope;
};
template <typename Isolate>
using NullContextScopeFor = typename NullContextScopeHelper<Isolate>::Type;
} // namespace
template <typename IsolateT>
Handle<BytecodeArray> BytecodeGenerator::FinalizeBytecode(
IsolateT* isolate, Handle<Script> script) {
DCHECK_EQ(ThreadId::Current(), isolate->thread_id());
#ifdef DEBUG
// Unoptimized compilation should be context-independent. Verify that we don't
// access the native context by nulling it out during finalization.
NullContextScopeFor<IsolateT> null_context_scope(isolate);
#endif
AllocateDeferredConstants(isolate, script);
if (block_coverage_builder_) {
Handle<CoverageInfo> coverage_info =
isolate->factory()->NewCoverageInfo(block_coverage_builder_->slots());
info()->set_coverage_info(coverage_info);
if (v8_flags.trace_block_coverage) {
StdoutStream os;
coverage_info->CoverageInfoPrint(os, info()->literal()->GetDebugName());
}
}
if (HasStackOverflow()) return Handle<BytecodeArray>();
Handle<BytecodeArray> bytecode_array = builder()->ToBytecodeArray(isolate);
if (incoming_new_target_or_generator_.is_valid()) {
bytecode_array->set_incoming_new_target_or_generator_register(
incoming_new_target_or_generator_);
}
return bytecode_array;
}
template Handle<BytecodeArray> BytecodeGenerator::FinalizeBytecode(
Isolate* isolate, Handle<Script> script);
template Handle<BytecodeArray> BytecodeGenerator::FinalizeBytecode(
LocalIsolate* isolate, Handle<Script> script);
template <typename IsolateT>
Handle<TrustedByteArray> BytecodeGenerator::FinalizeSourcePositionTable(
IsolateT* isolate) {
DCHECK_EQ(ThreadId::Current(), isolate->thread_id());
#ifdef DEBUG
// Unoptimized compilation should be context-independent. Verify that we don't
// access the native context by nulling it out during finalization.
NullContextScopeFor<IsolateT> null_context_scope(isolate);
#endif
Handle<TrustedByteArray> source_position_table =
builder()->ToSourcePositionTable(isolate);
LOG_CODE_EVENT(isolate,
CodeLinePosInfoRecordEvent(
info_->bytecode_array()->GetFirstBytecodeAddress(),
*source_position_table, JitCodeEvent::BYTE_CODE));
return source_position_table;
}
template Handle<TrustedByteArray>
BytecodeGenerator::FinalizeSourcePositionTable(Isolate* isolate);
template Handle<TrustedByteArray>
BytecodeGenerator::FinalizeSourcePositionTable(LocalIsolate* isolate);
#ifdef DEBUG
int BytecodeGenerator::CheckBytecodeMatches(Tagged<BytecodeArray> bytecode) {
return builder()->CheckBytecodeMatches(bytecode);
}
#endif
template <typename IsolateT>
void BytecodeGenerator::AllocateDeferredConstants(IsolateT* isolate,
Handle<Script> script) {
if (top_level_builder()->has_top_level_declaration()) {
// Build global declaration pair array.
Handle<FixedArray> declarations = top_level_builder()->AllocateDeclarations(
info(), this, script, isolate);
if (declarations.is_null()) return SetStackOverflow();
builder()->SetDeferredConstantPoolEntry(
top_level_builder()->constant_pool_entry(), declarations);
}
// Find or build shared function infos.
for (std::pair<FunctionLiteral*, size_t> literal : function_literals_) {
FunctionLiteral* expr = literal.first;
Handle<SharedFunctionInfo> shared_info =
Compiler::GetSharedFunctionInfo(expr, script, isolate);
if (shared_info.is_null()) return SetStackOverflow();
builder()->SetDeferredConstantPoolEntry(literal.second, shared_info);
}
// Find or build shared function infos for the native function templates.
for (std::pair<NativeFunctionLiteral*, size_t> literal :
native_function_literals_) {
// This should only happen for main-thread compilations.
DCHECK((std::is_same<Isolate, v8::internal::Isolate>::value));
NativeFunctionLiteral* expr = literal.first;
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
// Compute the function template for the native function.
v8::Local<v8::FunctionTemplate> info =
expr->extension()->GetNativeFunctionTemplate(
v8_isolate, Utils::ToLocal(expr->name()));
DCHECK(!info.IsEmpty());
Handle<SharedFunctionInfo> shared_info =
FunctionTemplateInfo::GetOrCreateSharedFunctionInfo(
isolate, Utils::OpenHandle(*info), expr->name());
DCHECK(!shared_info.is_null());
builder()->SetDeferredConstantPoolEntry(literal.second, shared_info);
}
// Build object literal constant properties
for (std::pair<ObjectLiteralBoilerplateBuilder*, size_t> literal :
object_literals_) {
ObjectLiteralBoilerplateBuilder* object_literal_builder = literal.first;
if (object_literal_builder->properties_count() > 0) {
// If constant properties is an empty fixed array, we've already added it
// to the constant pool when visiting the object literal.
Handle<ObjectBoilerplateDescription> constant_properties =
object_literal_builder->GetOrBuildBoilerplateDescription(isolate);
builder()->SetDeferredConstantPoolEntry(literal.second,
constant_properties);
}
}
// Build array literal constant elements
for (std::pair<ArrayLiteralBoilerplateBuilder*, size_t> literal :
array_literals_) {
ArrayLiteralBoilerplateBuilder* array_literal_builder = literal.first;
Handle<ArrayBoilerplateDescription> constant_elements =
array_literal_builder->GetOrBuildBoilerplateDescription(isolate);
builder()->SetDeferredConstantPoolEntry(literal.second, constant_elements);
}
// Build class literal boilerplates.
for (std::pair<ClassLiteral*, size_t> literal : class_literals_) {
ClassLiteral* class_literal = literal.first;
Handle<ClassBoilerplate> class_boilerplate =
ClassBoilerplate::New(isolate, class_literal, AllocationType::kOld);
builder()->SetDeferredConstantPoolEntry(literal.second, class_boilerplate);
}
// Build template literals.
for (std::pair<GetTemplateObject*, size_t> literal : template_objects_) {
GetTemplateObject* get_template_object = literal.first;
Handle<TemplateObjectDescription> description =
get_template_object->GetOrBuildDescription(isolate);
builder()->SetDeferredConstantPoolEntry(literal.second, description);
}
}
template void BytecodeGenerator::AllocateDeferredConstants(
Isolate* isolate, Handle<Script> script);
template void BytecodeGenerator::AllocateDeferredConstants(
LocalIsolate* isolate, Handle<Script> script);
namespace {
bool NeedsContextInitialization(DeclarationScope* scope) {
return scope->NeedsContext() && !scope->is_script_scope() &&
!scope->is_module_scope();
}
} // namespace
void BytecodeGenerator::GenerateBytecode(uintptr_t stack_limit) {
InitializeAstVisitor(stack_limit);
if (v8_flags.stress_lazy_compilation && local_isolate_->is_main_thread()) {
// Trigger stack overflow with 1/stress_lazy_compilation probability.
// Do this only for the main thread compilations because querying random
// numbers from background threads will make the random values dependent
// on the thread scheduling and thus non-deterministic.
stack_overflow_ = local_isolate_->fuzzer_rng()->NextInt(
v8_flags.stress_lazy_compilation) == 0;
}
// Initialize the incoming context.
ContextScope incoming_context(this, closure_scope());
// Initialize control scope.
ControlScopeForTopLevel control(this);
RegisterAllocationScope register_scope(this);
AllocateTopLevelRegisters();
builder()->EmitFunctionStartSourcePosition(
info()->literal()->start_position());
if (info()->literal()->CanSuspend()) {
BuildGeneratorPrologue();
}
if (NeedsContextInitialization(closure_scope())) {
// Push a new inner context scope for the function.
BuildNewLocalActivationContext();
ContextScope local_function_context(this, closure_scope());
BuildLocalActivationContextInitialization();
GenerateBytecodeBody();
} else {
GenerateBytecodeBody();
}
// Reset variables with hole check bitmap indices for subsequent compilations
// in the same parsing zone.
for (Variable* var : vars_in_hole_check_bitmap_) {
var->ResetHoleCheckBitmapIndex();
}
// Check that we are not falling off the end.
DCHECK(builder()->RemainderOfBlockIsDead());
}
void BytecodeGenerator::GenerateBytecodeBody() {
FunctionLiteral* literal = info()->literal();
if (literal->kind() == FunctionKind::kDerivedConstructor) {
// Per spec, derived constructors can only return undefined or an object;
// other primitives trigger an exception in ConstructStub.
//
// Since the receiver is popped by the callee, derived constructors return
// <this> if the original return value was undefined.
//
// Also per spec, this return value check is done after all user code (e.g.,
// finally blocks) are executed. For example, the following code does not
// throw.
//
// class C extends class {} {
// constructor() {
// try { throw 42; }
// catch(e) { return; }
// finally { super(); }
// }
// }
// new C();
//
// This check is implemented by jumping to the check instead of emitting a
// return bytecode in-place inside derived constructors.
//
// Note that default derived constructors do not need this check as they
// just forward a super call.
BytecodeLabels check_return_value(zone());
Register result = register_allocator()->NewRegister();
ControlScopeForDerivedConstructor control(this, result,
&check_return_value);
{
HoleCheckElisionScope elider(this);
GenerateBytecodeBodyWithoutImplicitFinalReturn();
}
if (check_return_value.empty()) {
if (!builder()->RemainderOfBlockIsDead()) {
BuildThisVariableLoad();
BuildReturn(literal->return_position());
}
} else {
BytecodeLabels return_this(zone());
if (!builder()->RemainderOfBlockIsDead()) {
builder()->Jump(return_this.New());
}
check_return_value.Bind(builder());
builder()->LoadAccumulatorWithRegister(result);
builder()->JumpIfUndefined(return_this.New());
BuildReturn(literal->return_position());
{
return_this.Bind(builder());
BuildThisVariableLoad();
BuildReturn(literal->return_position());
}
}
} else {
GenerateBytecodeBodyWithoutImplicitFinalReturn();
// Emit an implicit return instruction in case control flow can fall off the
// end of the function without an explicit return being present on all
// paths.
if (!builder()->RemainderOfBlockIsDead()) {
builder()->LoadUndefined();
BuildReturn(literal->return_position());
}
}
}
void BytecodeGenerator::GenerateBytecodeBodyWithoutImplicitFinalReturn() {
if (v8_flags.js_explicit_resource_management && closure_scope() != nullptr &&
closure_scope()->has_using_declaration()) {
BuildDisposeScope(
[&]() { GenerateBytecodeBodyWithoutImplicitFinalReturnOrDispose(); });
} else {
GenerateBytecodeBodyWithoutImplicitFinalReturnOrDispose();
}
}
void BytecodeGenerator::
GenerateBytecodeBodyWithoutImplicitFinalReturnOrDispose() {
// Build the arguments object if it is used.
VisitArgumentsObject(closure_scope()->arguments());
// Build rest arguments array if it is used.
Variable* rest_parameter = closure_scope()->rest_parameter();
VisitRestArgumentsArray(rest_parameter);
// Build assignment to the function name or {.this_function}
// variables if used.
VisitThisFunctionVariable(closure_scope()->function_var());
VisitThisFunctionVariable(closure_scope()->this_function_var());
// Build assignment to {new.target} variable if it is used.
VisitNewTargetVariable(closure_scope()->new_target_var());
// Create a generator object if necessary and initialize the
// {.generator_object} variable.
FunctionLiteral* literal = info()->literal();
if (IsResumableFunction(literal->kind())) {
BuildGeneratorObjectVariableInitialization();
}
// Emit tracing call if requested to do so.
if (v8_flags.trace) builder()->CallRuntime(Runtime::kTraceEnter);
// Increment the function-scope block coverage counter.
BuildIncrementBlockCoverageCounterIfEnabled(literal, SourceRangeKind::kBody);
// Visit declarations within the function scope.
if (closure_scope()->is_script_scope()) {
VisitGlobalDeclarations(closure_scope()->declarations());
} else if (closure_scope()->is_module_scope()) {
VisitModuleDeclarations(closure_scope()->declarations());
} else {
VisitDeclarations(closure_scope()->declarations());
}
// Emit initializing assignments for module namespace imports (if any).
VisitModuleNamespaceImports();
// The derived constructor case is handled in VisitCallSuper.
if (IsBaseConstructor(function_kind())) {
if (literal->class_scope_has_private_brand()) {
ClassScope* scope = info()->scope()->outer_scope()->AsClassScope();
DCHECK_NOT_NULL(scope->brand());
BuildPrivateBrandInitialization(builder()->Receiver(), scope->brand());
}
if (literal->requires_instance_members_initializer()) {
BuildInstanceMemberInitialization(Register::function_closure(),
builder()->Receiver());
}
}
// Visit statements in the function body.
VisitStatements(literal->body());
}
void BytecodeGenerator::AllocateTopLevelRegisters() {
if (IsResumableFunction(info()->literal()->kind())) {
// Either directly use generator_object_var or allocate a new register for
// the incoming generator object.
Variable* generator_object_var = closure_scope()->generator_object_var();
if (generator_object_var->location() == VariableLocation::LOCAL) {
incoming_new_target_or_generator_ =
GetRegisterForLocalVariable(generator_object_var);
} else {
incoming_new_target_or_generator_ = register_allocator()->NewRegister();
}
} else if (closure_scope()->new_target_var()) {
// Either directly use new_target_var or allocate a new register for
// the incoming new target object.
Variable* new_target_var = closure_scope()->new_target_var();
if (new_target_var->location() == VariableLocation::LOCAL) {
incoming_new_target_or_generator_ =
GetRegisterForLocalVariable(new_target_var);
} else {
incoming_new_target_or_generator_ = register_allocator()->NewRegister();
}
}
}
void BytecodeGenerator::BuildGeneratorPrologue() {
DCHECK_GT(info()->literal()->suspend_count(), 0);
DCHECK(generator_object().is_valid());
generator_jump_table_ =
builder()->AllocateJumpTable(info()->literal()->suspend_count(), 0);
// If the generator is not undefined, this is a resume, so perform state
// dispatch.
builder()->SwitchOnGeneratorState(generator_object(), generator_jump_table_);
// Otherwise, fall-through to the ordinary function prologue, after which we
// will run into the generator object creation and other extra code inserted
// by the parser.
}
void BytecodeGenerator::VisitBlock(Block* stmt) {
// Visit declarations and statements.
CurrentScope current_scope(this, stmt->scope());
if (stmt->scope() != nullptr && stmt->scope()->NeedsContext()) {
BuildNewLocalBlockContext(stmt->scope());
ContextScope scope(this, stmt->scope());
VisitBlockMaybeDispose(stmt);
} else {
VisitBlockMaybeDispose(stmt);
}
}
void BytecodeGenerator::VisitBlockMaybeDispose(Block* stmt) {
if (v8_flags.js_explicit_resource_management && stmt->scope() != nullptr &&
stmt->scope()->has_using_declaration()) {
BuildDisposeScope([&]() { VisitBlockDeclarationsAndStatements(stmt); });
} else {
VisitBlockDeclarationsAndStatements(stmt);
}
}
void BytecodeGenerator::VisitBlockDeclarationsAndStatements(Block* stmt) {
BlockBuilder block_builder(builder(), block_coverage_builder_, stmt);
ControlScopeForBreakable execution_control(this, stmt, &block_builder);
if (stmt->scope() != nullptr) {
VisitDeclarations(stmt->scope()->declarations());
}
if (V8_UNLIKELY(stmt->is_breakable())) {
// Loathsome labeled blocks can be the target of break statements, which
// causes unconditional blocks to act conditionally, and therefore to
// require their own elision scope.
//
// lbl: {
// if (cond) break lbl;
// x;
// }
// x; <-- Cannot elide TDZ check
HoleCheckElisionScope elider(this);
VisitStatements(stmt->statements());
} else {
VisitStatements(stmt->statements());
}
}
void BytecodeGenerator::VisitVariableDeclaration(VariableDeclaration* decl) {
Variable* variable = decl->var();
// Unused variables don't need to be visited.
if (!variable->is_used()) return;
switch (variable->location()) {
case VariableLocation::UNALLOCATED:
case VariableLocation::MODULE:
UNREACHABLE();
case VariableLocation::LOCAL:
if (variable->binding_needs_init()) {
Register destination(builder()->Local(variable->index()));
builder()->LoadTheHole().StoreAccumulatorInRegister(destination);
}
break;
case VariableLocation::PARAMETER:
if (variable->binding_needs_init()) {
Register destination(builder()->Parameter(variable->index()));
builder()->LoadTheHole().StoreAccumulatorInRegister(destination);
}
break;
case VariableLocation::REPL_GLOBAL:
// REPL let's are stored in script contexts. They get initialized
// with the hole the same way as normal context allocated variables.
case VariableLocation::CONTEXT:
if (variable->binding_needs_init()) {
DCHECK_EQ(0, execution_context()->ContextChainDepth(variable->scope()));
builder()->LoadTheHole().StoreContextSlot(execution_context()->reg(),
variable, 0);
}
break;
case VariableLocation::LOOKUP: {
DCHECK_EQ(VariableMode::kDynamic, variable->mode());
DCHECK(!variable->binding_needs_init());
Register name = register_allocator()->NewRegister();
builder()
->LoadLiteral(variable->raw_name())
.StoreAccumulatorInRegister(name)
.CallRuntime(Runtime::kDeclareEvalVar, name);
break;
}
}
}
void BytecodeGenerator::VisitFunctionDeclaration(FunctionDeclaration* decl) {
Variable* variable = decl->var();
DCHECK(variable->mode() == VariableMode::kLet ||
variable->mode() == VariableMode::kVar ||
variable->mode() == VariableMode::kDynamic);
// Unused variables don't need to be visited.
if (!variable->is_used()) return;
switch (variable->location()) {
case VariableLocation::UNALLOCATED:
case VariableLocation::MODULE:
UNREACHABLE();
case VariableLocation::PARAMETER:
case VariableLocation::LOCAL: {
VisitFunctionLiteral(decl->fun());
BuildVariableAssignment(variable, Token::kInit, HoleCheckMode::kElided);
break;
}
case VariableLocation::REPL_GLOBAL:
case VariableLocation::CONTEXT: {
DCHECK_EQ(0, execution_context()->ContextChainDepth(variable->scope()));
VisitFunctionLiteral(decl->fun());
builder()->StoreContextSlot(execution_context()->reg(), variable, 0);
break;
}
case VariableLocation::LOOKUP: {
RegisterList args = register_allocator()->NewRegisterList(2);
builder()
->LoadLiteral(variable->raw_name())
.StoreAccumulatorInRegister(args[0]);
VisitFunctionLiteral(decl->fun());
builder()->StoreAccumulatorInRegister(args[1]).CallRuntime(
Runtime::kDeclareEvalFunction, args);
break;
}
}
DCHECK_IMPLIES(
eager_inner_literals_ != nullptr && decl->fun()->ShouldEagerCompile(),
IsInEagerLiterals(decl->fun(), *eager_inner_literals_));
}
void BytecodeGenerator::VisitModuleNamespaceImports() {
if (!closure_scope()->is_module_scope()) return;
RegisterAllocationScope register_scope(this);
Register module_request = register_allocator()->NewRegister();
SourceTextModuleDescriptor* descriptor =
closure_scope()->AsModuleScope()->module();
for (auto entry : descriptor->namespace_imports()) {
builder()
->LoadLiteral(Smi::FromInt(entry->module_request))
.StoreAccumulatorInRegister(module_request)
.CallRuntime(Runtime::kGetModuleNamespace, module_request);
Variable* var = closure_scope()->LookupInModule(entry->local_name);
BuildVariableAssignment(var, Token::kInit, HoleCheckMode::kElided);
}
}
void BytecodeGenerator::BuildDeclareCall(Runtime::FunctionId id) {
if (!top_level_builder()->has_top_level_declaration()) return;
DCHECK(!top_level_builder()->processed());
top_level_builder()->set_constant_pool_entry(
builder()->AllocateDeferredConstantPoolEntry());
// Emit code to declare globals.
RegisterList args = register_allocator()->NewRegisterList(2);
builder()
->LoadConstantPoolEntry(top_level_builder()->constant_pool_entry())
.StoreAccumulatorInRegister(args[0])
.MoveRegister(Register::function_closure(), args[1])
.CallRuntime(id, args);
top_level_builder()->mark_processed();
}
void BytecodeGenerator::VisitModuleDeclarations(Declaration::List* decls) {
RegisterAllocationScope register_scope(this);
for (Declaration* decl : *decls) {
Variable* var = decl->var();
if (!var->is_used()) continue;
if (var->location() == VariableLocation::MODULE) {
if (decl->IsFunctionDeclaration()) {
DCHECK(var->IsExport());
FunctionDeclaration* f = static_cast<FunctionDeclaration*>(decl);
AddToEagerLiteralsIfEager(f->fun());
top_level_builder()->record_module_function_declaration();
} else if (var->IsExport() && var->binding_needs_init()) {
DCHECK(decl->IsVariableDeclaration());
top_level_builder()->record_module_variable_declaration();
}
} else {
RegisterAllocationScope inner_register_scope(this);
Visit(decl);
}
}
BuildDeclareCall(Runtime::kDeclareModuleExports);
}
void BytecodeGenerator::VisitGlobalDeclarations(Declaration::List* decls) {
RegisterAllocationScope register_scope(this);
for (Declaration* decl : *decls) {
Variable* var = decl->var();
DCHECK(var->is_used());
if (var->location() == VariableLocation::UNALLOCATED) {
// var or function.
if (decl->IsFunctionDeclaration()) {
top_level_builder()->record_global_function_declaration();
FunctionDeclaration* f = static_cast<FunctionDeclaration*>(decl);
AddToEagerLiteralsIfEager(f->fun());
} else {
top_level_builder()->record_global_variable_declaration();
}
} else {
// let or const. Handled in NewScriptContext.
DCHECK(decl->IsVariableDeclaration());
DCHECK(IsLexicalVariableMode(var->mode()));
}
}
BuildDeclareCall(Runtime::kDeclareGlobals);
}
void BytecodeGenerator::VisitDeclarations(Declaration::List* declarations) {
for (Declaration* decl : *declarations) {
RegisterAllocationScope register_scope(this);
Visit(decl);
}
}
void BytecodeGenerator::VisitStatements(
const ZonePtrList<Statement>* statements) {
for (int i = 0; i < statements->length(); i++) {
// Allocate an outer register allocations scope for the statement.
RegisterAllocationScope allocation_scope(this);
Statement* stmt = statements->at(i);
Visit(stmt);
if (builder()->RemainderOfBlockIsDead()) break;
}
}
void BytecodeGenerator::VisitExpressionStatement(ExpressionStatement* stmt) {
builder()->SetStatementPosition(stmt);
VisitForEffect(stmt->expression());
}
void BytecodeGenerator::VisitEmptyStatement(EmptyStatement* stmt) {}
void BytecodeGenerator::VisitIfStatement(IfStatement* stmt) {
ConditionalControlFlowBuilder conditional_builder(
builder(), block_coverage_builder_, stmt);
builder()->SetStatementPosition(stmt);
if (stmt->condition()->ToBooleanIsTrue()) {
// Generate then block unconditionally as always true.
conditional_builder.Then();
Visit(stmt->then_statement());
} else if (stmt->condition()->ToBooleanIsFalse()) {
// Generate else block unconditionally if it exists.
if (stmt->HasElseStatement()) {
conditional_builder.Else();
Visit(stmt->else_statement());
}
} else {
// TODO(oth): If then statement is BreakStatement or
// ContinueStatement we can reduce number of generated
// jump/jump_ifs here. See BasicLoops test.
VisitForTest(stmt->condition(), conditional_builder.then_labels(),
conditional_builder.else_labels(), TestFallthrough::kThen);
HoleCheckElisionMergeScope merge_elider(this);
{
HoleCheckElisionMergeScope::Branch branch(merge_elider);
conditional_builder.Then();
Visit(stmt->then_statement());
}
{
HoleCheckElisionMergeScope::Branch branch(merge_elider);
if (stmt->HasElseStatement()) {
conditional_builder.JumpToEnd();
conditional_builder.Else();
Visit(stmt->else_statement());
}
}
merge_elider.Merge();
}
}
void BytecodeGenerator::VisitSloppyBlockFunctionStatement(
SloppyBlockFunctionStatement* stmt) {
Visit(stmt->statement());
}
void BytecodeGenerator::VisitContinueStatement(ContinueStatement* stmt) {
AllocateBlockCoverageSlotIfEnabled(stmt, SourceRangeKind::kContinuation);
builder()->SetStatementPosition(stmt);
execution_control()->Continue(stmt->target());
}
void BytecodeGenerator::VisitBreakStatement(BreakStatement* stmt) {
AllocateBlockCoverageSlotIfEnabled(stmt, SourceRangeKind::kContinuation);
builder()->SetStatementPosition(stmt);
execution_control()->Break(stmt->target());
}
void BytecodeGenerator::VisitReturnStatement(ReturnStatement* stmt) {
AllocateBlockCoverageSlotIfEnabled(stmt, SourceRangeKind::kContinuation);
builder()->SetStatementPosition(stmt);
VisitForAccumulatorValue(stmt->expression());
int return_position = stmt->end_position();
if (return_position == ReturnStatement::kFunctionLiteralReturnPosition) {
return_position = info()->literal()->return_position();
}
if (stmt->is_async_return()) {
execution_control()->AsyncReturnAccumulator(return_position);
} else {
execution_control()->ReturnAccumulator(return_position);
}
}
void BytecodeGenerator::VisitWithStatement(WithStatement* stmt) {
builder()->SetStatementPosition(stmt);
VisitForAccumulatorValue(stmt->expression());
BuildNewLocalWithContext(stmt->scope());
VisitInScope(stmt->statement(), stmt->scope());
}
namespace {
bool IsSmiLiteralSwitchCaseValue(Expression* expr) {
if (expr->IsSmiLiteral() ||
(expr->IsLiteral() && expr->AsLiteral()->IsNumber() &&
expr->AsLiteral()->AsNumber() == 0.0)) {
return true;
#ifdef DEBUG
} else if (expr->IsLiteral() && expr->AsLiteral()->IsNumber()) {
DCHECK(!IsSmiDouble(expr->AsLiteral()->AsNumber()));
#endif
}
return false;
}
// Precondition: we called IsSmiLiteral to check this.
inline int ReduceToSmiSwitchCaseValue(Expression* expr) {
if (V8_LIKELY(expr->IsSmiLiteral())) {
return expr->AsLiteral()->AsSmiLiteral().value();
} else {
// Only the zero case is possible otherwise.
DCHECK(expr->IsLiteral() && expr->AsLiteral()->IsNumber() &&
expr->AsLiteral()->AsNumber() == -0.0);
return 0;
}
}
// Is the range of Smi's small enough relative to number of cases?
inline bool IsSpreadAcceptable(int spread, int ncases) {
return spread < v8_flags.switch_table_spread_threshold * ncases;
}
struct SwitchInfo {
static const int kDefaultNotFound = -1;
std::map<int, CaseClause*> covered_cases;
int default_case;
SwitchInfo() { default_case = kDefaultNotFound; }
bool DefaultExists() { return default_case != kDefaultNotFound; }
bool CaseExists(int j) {
return covered_cases.find(j) != covered_cases.end();
}
bool CaseExists(Expression* expr) {
return IsSmiLiteralSwitchCaseValue(expr)
? CaseExists(ReduceToSmiSwitchCaseValue(expr))
: false;
}
CaseClause* GetClause(int j) { return covered_cases[j]; }
bool IsDuplicate(CaseClause* clause) {
return IsSmiLiteralSwitchCaseValue(clause->label()) &&
CaseExists(clause->label()) &&
clause != GetClause(ReduceToSmiSwitchCaseValue(clause->label()));
}
int MinCase() {
return covered_cases.empty() ? INT_MAX : covered_cases.begin()->first;
}
int MaxCase() {
return covered_cases.empty() ? INT_MIN : covered_cases.rbegin()->first;
}
void Print() {
std::cout << "Covered_cases: " << '\n';
for (auto iter = covered_cases.begin(); iter != covered_cases.end();
++iter) {
std::cout << iter->first << "->" << iter->second << '\n';
}
std::cout << "Default_case: " << default_case << '\n';
}
};
// Checks whether we should use a jump table to implement a switch operation.
bool IsSwitchOptimizable(SwitchStatement* stmt, SwitchInfo* info) {
ZonePtrList<CaseClause>* cases = stmt->cases();
for (int i = 0; i < cases->length(); ++i) {
CaseClause* clause = cases->at(i);
if (clause->is_default()) {
continue;
} else if (!(clause->label()->IsLiteral())) {
// Don't consider Smi cases after a non-literal, because we
// need to evaluate the non-literal.
break;
} else if (IsSmiLiteralSwitchCaseValue(clause->label())) {
int value = ReduceToSmiSwitchCaseValue(clause->label());
info->covered_cases.insert({value, clause});
}
}
// GCC also jump-table optimizes switch statements with 6 cases or more.
if (static_cast<int>(info->covered_cases.size()) >=
v8_flags.switch_table_min_cases) {
// Due to case spread will be used as the size of jump-table,
// we need to check if it doesn't overflow by casting its
// min and max bounds to int64_t, and calculate if the difference is less
// than or equal to INT_MAX.
int64_t min = static_cast<int64_t>(info->MinCase());
int64_t max = static_cast<int64_t>(info->MaxCase());
int64_t spread = max - min + 1;
DCHECK_GT(spread, 0);
// Check if casted spread is acceptable and doesn't overflow.
if (spread <= INT_MAX &&
IsSpreadAcceptable(static_cast<int>(spread), cases->length())) {
return true;
}
}
// Invariant- covered_cases has all cases and only cases that will go in the
// jump table.
info->covered_cases.clear();
return false;
}
} // namespace
// This adds a jump table optimization for switch statements with Smi cases.
// If there are 5+ non-duplicate Smi clauses, and they are sufficiently compact,
// we generate a jump table. In the fall-through path, we put the compare-jumps
// for the non-Smi cases.
// e.g.
//
// switch(x){
// case -0: out = 10;
// case 1: out = 11; break;
// case 0: out = 12; break;
// case 2: out = 13;
// case 3: out = 14; break;
// case 0.5: out = 15; break;
// case 4: out = 16;
// case y: out = 17;
// case 5: out = 18;
// default: out = 19; break;
// }
// becomes this pseudo-bytecode:
// lda x
// star r1
// test_type number
// jump_if_false @fallthrough
// ldar r1
// test_greater_than_or_equal_to smi_min
// jump_if_false @fallthrough
// ldar r1
// test_less_than_or_equal_to smi_max
// jump_if_false @fallthrough
// ldar r1
// bitwise_or 0
// star r2
// test_strict_equal r1
// jump_if_false @fallthrough
// ldar r2
// switch_on_smi {1: @case_1, 2: @case_2, 3: @case_3, 4: @case_4}
// @fallthrough:
// jump_if_strict_equal -0.0 @case_minus_0.0
// jump_if_strict_equal 0.5 @case_0.5
// jump_if_strict_equal y @case_y
// jump_if_strict_equal 5 @case_5
// jump @default
// @case_minus_0.0:
// <out = 10>
// @case_1
// <out = 11, break>
// @case_0:
// <out = 12, break>
// @case_2:
// <out = 13>
// @case_3:
// <out = 14, break>
// @case_0.5:
// <out = 15, break>
// @case_4:
// <out = 16>
// @case_y:
// <out = 17>
// @case_5:
// <out = 18>
// @default:
// <out = 19, break>
void BytecodeGenerator::VisitSwitchStatement(SwitchStatement* stmt) {
// We need this scope because we visit for register values. We have to
// maintain a execution result scope where registers can be allocated.
ZonePtrList<CaseClause>* clauses = stmt->cases();
SwitchInfo info;
BytecodeJumpTable* jump_table = nullptr;
bool use_jump_table = IsSwitchOptimizable(stmt, &info);
// N_comp_cases is number of cases we will generate comparison jumps for.
// Note we ignore duplicate cases, since they are very unlikely.
int n_comp_cases = clauses->length();
if (use_jump_table) {
n_comp_cases -= static_cast<int>(info.covered_cases.size());
jump_table = builder()->AllocateJumpTable(
info.MaxCase() - info.MinCase() + 1, info.MinCase());
}
// Are we still using any if-else bytecodes to evaluate the switch?
bool use_jumps = n_comp_cases != 0;
SwitchBuilder switch_builder(builder(), block_coverage_builder_, stmt,
n_comp_cases, jump_table);
ControlScopeForBreakable scope(this, stmt, &switch_builder);
builder()->SetStatementPosition(stmt);
VisitForAccumulatorValue(stmt->tag());
if (use_jump_table) {
// Release temps so that they can be reused in clauses.
RegisterAllocationScope allocation_scope(this);
// This also fills empty slots in jump table.
Register r2 = register_allocator()->NewRegister();
Register r1 = register_allocator()->NewRegister();
builder()->StoreAccumulatorInRegister(r1);
builder()->CompareTypeOf(TestTypeOfFlags::LiteralFlag::kNumber);
switch_builder.JumpToFallThroughIfFalse();
builder()->LoadAccumulatorWithRegister(r1);
// TODO(leszeks): Note these are duplicated range checks with the
// SwitchOnSmi handler for the most part.
builder()->LoadLiteral(Smi::kMinValue);
builder()->StoreAccumulatorInRegister(r2);
builder()->CompareOperation(
Token::kGreaterThanEq, r1,
feedback_index(feedback_spec()->AddCompareICSlot()));
switch_builder.JumpToFallThroughIfFalse();
builder()->LoadAccumulatorWithRegister(r1);
builder()->LoadLiteral(Smi::kMaxValue);
builder()->StoreAccumulatorInRegister(r2);
builder()->CompareOperation(
Token::kLessThanEq, r1,
feedback_index(feedback_spec()->AddCompareICSlot()));
switch_builder.JumpToFallThroughIfFalse();
builder()->LoadAccumulatorWithRegister(r1);
builder()->BinaryOperationSmiLiteral(
Token::kBitOr, Smi::FromInt(0),
feedback_index(feedback_spec()->AddBinaryOpICSlot()));
builder()->StoreAccumulatorInRegister(r2);
builder()->CompareOperation(
Token::kEqStrict, r1,
feedback_index(feedback_spec()->AddCompareICSlot()));
switch_builder.JumpToFallThroughIfFalse();
builder()->LoadAccumulatorWithRegister(r2);
switch_builder.EmitJumpTableIfExists(info.MinCase(), info.MaxCase(),
info.covered_cases);
if (use_jumps) {
builder()->LoadAccumulatorWithRegister(r1);
}
}
int case_compare_ctr = 0;
#ifdef DEBUG
std::unordered_map<int, int> case_ctr_checker;
#endif
if (use_jumps) {
Register tag_holder = register_allocator()->NewRegister();
FeedbackSlot slot = clauses->length() > 0
? feedback_spec()->AddCompareICSlot()
: FeedbackSlot::Invalid();
builder()->StoreAccumulatorInRegister(tag_holder);
{
// The comparisons linearly dominate, so no need to open a new elision
// scope for each one.
base::Optional<HoleCheckElisionScope> elider;
bool first_jump_emitted = false;
for (int i = 0; i < clauses->length(); ++i) {
CaseClause* clause = clauses->at(i);
if (clause->is_default()) {
info.default_case = i;
} else if (!info.CaseExists(clause->label())) {
// The first non-default label is
// unconditionally executed, so we only need to emplace it before
// visiting the second non-default label.
if (first_jump_emitted) elider.emplace(this);
// Perform label comparison as if via '===' with tag.
VisitForAccumulatorValue(clause->label());
builder()->CompareOperation(Token::kEqStrict, tag_holder,
feedback_index(slot));
#ifdef DEBUG
case_ctr_checker[i] = case_compare_ctr;
#endif
switch_builder.JumpToCaseIfTrue(ToBooleanMode::kAlreadyBoolean,
case_compare_ctr++);
first_jump_emitted = true;
}
}
}
register_allocator()->ReleaseRegister(tag_holder);
}
// For fall-throughs after comparisons (or out-of-range/non-Smi's for jump
// tables).
if (info.DefaultExists()) {
switch_builder.JumpToDefault();
} else {
switch_builder.Break();
}
// It is only correct to merge hole check states if there is a default clause,
// as otherwise it's unknown if the switch is exhaustive.
HoleCheckElisionMergeScope merge_elider(this);
case_compare_ctr = 0;
for (int i = 0; i < clauses->length(); ++i) {
CaseClause* clause = clauses->at(i);
if (i != info.default_case) {
if (!info.IsDuplicate(clause)) {
bool use_table = use_jump_table && info.CaseExists(clause->label());
if (!use_table) {
// Guarantee that we should generate compare/jump if no table.
#ifdef DEBUG
DCHECK(case_ctr_checker[i] == case_compare_ctr);
#endif
switch_builder.BindCaseTargetForCompareJump(case_compare_ctr++,
clause);
} else {
// Use jump table if this is not a duplicate label.
switch_builder.BindCaseTargetForJumpTable(
ReduceToSmiSwitchCaseValue(clause->label()), clause);
}
}
} else {
switch_builder.BindDefault(clause);
}
// Regardless, generate code (in case of fall throughs).
HoleCheckElisionMergeScope::Branch branch_elider(merge_elider);
VisitStatements(clause->statements());
}
merge_elider.MergeIf(info.DefaultExists());
}
template <typename TryBodyFunc, typename CatchBodyFunc>
void BytecodeGenerator::BuildTryCatch(
TryBodyFunc try_body_func, CatchBodyFunc catch_body_func,
HandlerTable::CatchPrediction catch_prediction,
TryCatchStatement* stmt_for_coverage) {
if (builder()->RemainderOfBlockIsDead()) return;
TryCatchBuilder try_control_builder(
builder(),
stmt_for_coverage == nullptr ? nullptr : block_coverage_builder_,
stmt_for_coverage, catch_prediction);
// Preserve the context in a dedicated register, so that it can be restored
// when the handler is entered by the stack-unwinding machinery.
// TODO(ignition): Be smarter about register allocation.
Register context = register_allocator()->NewRegister();
builder()->MoveRegister(Register::current_context(), context);
// Evaluate the try-block inside a control scope. This simulates a handler
// that is intercepting 'throw' control commands.
try_control_builder.BeginTry(context);
HoleCheckElisionMergeScope merge_elider(this);
{
ControlScopeForTryCatch scope(this, &try_control_builder);
// The try-block itself, even though unconditionally executed, can throw
// basically at any point, and so must be treated as conditional from the
// perspective of the hole check elision analysis.
//
// try { x } catch (e) { }
// use(x); <-- Still requires a TDZ check
//
// However, if both the try-block and the catch-block emit a hole check,
// subsequent TDZ checks can be elided.
//
// try { x; } catch (e) { x; }
// use(x); <-- TDZ check can be elided
HoleCheckElisionMergeScope::Branch branch_elider(merge_elider);
try_body_func();
}
try_control_builder.EndTry();
{
HoleCheckElisionMergeScope::Branch branch_elider(merge_elider);
catch_body_func(context);
}
merge_elider.Merge();
try_control_builder.EndCatch();
}
template <typename TryBodyFunc, typename FinallyBodyFunc>
void BytecodeGenerator::BuildTryFinally(
TryBodyFunc try_body_func, FinallyBodyFunc finally_body_func,
HandlerTable::CatchPrediction catch_prediction,
TryFinallyStatement* stmt_for_coverage) {
if (builder()->RemainderOfBlockIsDead()) return;
// We can't know whether the finally block will override ("catch") an
// exception thrown in the try block, so we just adopt the outer prediction.
TryFinallyBuilder try_control_builder(
builder(),
stmt_for_coverage == nullptr ? nullptr : block_coverage_builder_,
stmt_for_coverage, catch_prediction);
// We keep a record of all paths that enter the finally-block to be able to
// dispatch to the correct continuation point after the statements in the
// finally-block have been evaluated.
//
// The try-finally construct can enter the finally-block in three ways:
// 1. By exiting the try-block normally, falling through at the end.
// 2. By exiting the try-block with a function-local control flow transfer
// (i.e. through break/continue/return statements).
// 3. By exiting the try-block with a thrown exception.
//
// The result register semantics depend on how the block was entered:
// - ReturnStatement: It represents the return value being returned.
// - ThrowStatement: It represents the exception being thrown.
// - BreakStatement/ContinueStatement: Undefined and not used.
// - Falling through into finally-block: Undefined and not used.
Register token = register_allocator()->NewRegister();
Register result = register_allocator()->NewRegister();
Register message = register_allocator()->NewRegister();
builder()->LoadTheHole().StoreAccumulatorInRegister(message);
ControlScope::DeferredCommands commands(this, token, result, message);
// Preserve the context in a dedicated register, so that it can be restored
// when the handler is entered by the stack-unwinding machinery.
// TODO(ignition): Be smarter about register allocation.
Register context = register_allocator()->NewRegister();
builder()->MoveRegister(Register::current_context(), context);
// Evaluate the try-block inside a control scope. This simulates a handler
// that is intercepting all control commands.
try_control_builder.BeginTry(context);
{
ControlScopeForTryFinally scope(this, &try_control_builder, &commands);
// The try-block itself, even though unconditionally executed, can throw
// basically at any point, and so must be treated as conditional from the
// perspective of the hole check elision analysis.
HoleCheckElisionScope elider(this);
try_body_func();
}
try_control_builder.EndTry();
// Record fall-through and exception cases.
commands.RecordFallThroughPath();
try_control_builder.LeaveTry();
try_control_builder.BeginHandler();
commands.RecordHandlerReThrowPath();
try_control_builder.BeginFinally();
// Evaluate the finally-block.
finally_body_func(token, result);
try_control_builder.EndFinally();
// Dynamic dispatch after the finally-block.
commands.ApplyDeferredCommands();
}
template <typename WrappedFunc>
void BytecodeGenerator::BuildDisposeScope(WrappedFunc wrapped_func) {
RegisterAllocationScope allocation_scope(this);
DisposablesStackScope disposables_stack_scope(this);
BuildTryFinally(
// Try block
[&]() { wrapped_func(); },
// Finally block
[&](Register body_continuation_token, Register body_continuation_result) {
RegisterList args = register_allocator()->NewRegisterList(3);
builder()
->MoveRegister(current_disposables_stack_, args[0])
.MoveRegister(body_continuation_token, args[1])
.MoveRegister(body_continuation_result, args[2]);
builder()->CallRuntime(Runtime::kDisposeDisposableStack, args);
},
catch_prediction());
}
void BytecodeGenerator::VisitIterationBody(IterationStatement* stmt,
LoopBuilder* loop_builder) {
loop_builder->LoopBody();
ControlScopeForIteration execution_control(this, stmt, loop_builder);
Visit(stmt->body());
loop_builder->BindContinueTarget();
}
void BytecodeGenerator::VisitIterationBodyInHoleCheckElisionScope(
IterationStatement* stmt, LoopBuilder* loop_builder) {
HoleCheckElisionScope elider(this);
VisitIterationBody(stmt, loop_builder);
}
void BytecodeGenerator::VisitDoWhileStatement(DoWhileStatement* stmt) {
LoopBuilder loop_builder(builder(), block_coverage_builder_, stmt,
feedback_spec());
if (stmt->cond()->ToBooleanIsFalse()) {
// Since we know that the condition is false, we don't create a loop.
// Therefore, we don't create a LoopScope (and thus we don't create a header
// and a JumpToHeader). However, we still need to iterate once through the
// body.
VisitIterationBodyInHoleCheckElisionScope(stmt, &loop_builder);
} else if (stmt->cond()->ToBooleanIsTrue()) {
LoopScope loop_scope(this, &loop_builder);
VisitIterationBodyInHoleCheckElisionScope(stmt, &loop_builder);
} else {
LoopScope loop_scope(this, &loop_builder);
VisitIterationBodyInHoleCheckElisionScope(stmt, &loop_builder);
builder()->SetExpressionAsStatementPosition(stmt->cond());
BytecodeLabels loop_backbranch(zone());
if (!loop_builder.break_labels()->empty()) {
// The test may be conditionally executed if there was a break statement
// inside the loop body, and therefore requires its own elision scope.
HoleCheckElisionScope elider(this);
VisitForTest(stmt->cond(), &loop_backbranch, loop_builder.break_labels(),
TestFallthrough::kThen);
} else {
VisitForTest(stmt->cond(), &loop_backbranch, loop_builder.break_labels(),
TestFallthrough::kThen);
}
loop_backbranch.Bind(builder());
}
}
void BytecodeGenerator::VisitWhileStatement(WhileStatement* stmt) {
LoopBuilder loop_builder(builder(), block_coverage_builder_, stmt,
feedback_spec());
if (stmt->cond()->ToBooleanIsFalse()) {
// If the condition is false there is no need to generate the loop.
return;
}
LoopScope loop_scope(this, &loop_builder);
if (!stmt->cond()->ToBooleanIsTrue()) {
builder()->SetExpressionAsStatementPosition(stmt->cond());
BytecodeLabels loop_body(zone());
VisitForTest(stmt->cond(), &loop_body, loop_builder.break_labels(),
TestFallthrough::kThen);
loop_body.Bind(builder());
}
VisitIterationBodyInHoleCheckElisionScope(stmt, &loop_builder);
}
void BytecodeGenerator::VisitForStatement(ForStatement* stmt) {
if (stmt->init() != nullptr) {
Visit(stmt->init());
}
LoopBuilder loop_builder(builder(), block_coverage_builder_, stmt,
feedback_spec());
if (stmt->cond() && stmt->cond()->ToBooleanIsFalse()) {
// If the condition is known to be false there is no need to generate
// body, next or condition blocks. Init block should be generated.
return;
}
LoopScope loop_scope(this, &loop_builder);
if (stmt->cond() && !stmt->cond()->ToBooleanIsTrue()) {
builder()->SetExpressionAsStatementPosition(stmt->cond());
BytecodeLabels loop_body(zone());
VisitForTest(stmt->cond(), &loop_body, loop_builder.break_labels(),
TestFallthrough::kThen);
loop_body.Bind(builder());
}
// C-style for loops' textual order differs from dominator order.
//
// for (INIT; TEST; NEXT) BODY
// REST
//
// has the dominator order of
//
// INIT dominates TEST dominates BODY dominates NEXT
// and
// INIT dominates TEST dominates REST
//
// INIT and TEST are always evaluated and so do not have their own
// HoleCheckElisionScope. BODY, like all iteration bodies, can contain control
// flow like breaks or continues, has its own HoleCheckElisionScope. NEXT is
// therefore conditionally evaluated and also so has its own
// HoleCheckElisionScope.
HoleCheckElisionScope elider(this);
VisitIterationBody(stmt, &loop_builder);
if (stmt->next() != nullptr) {
builder()->SetStatementPosition(stmt->next());
Visit(stmt->next());
}
}
void BytecodeGenerator::VisitForInStatement(ForInStatement* stmt) {
if (stmt->subject()->IsNullLiteral() ||
stmt->subject()->IsUndefinedLiteral()) {
// ForIn generates lots of code, skip if it wouldn't produce any effects.
return;
}
BytecodeLabel subject_undefined_label;
FeedbackSlot slot = feedback_spec()->AddForInSlot();
// Prepare the state for executing ForIn.
builder()->SetExpressionAsStatementPosition(stmt->subject());
VisitForAccumulatorValue(stmt->subject());
builder()->JumpIfUndefinedOrNull(&subject_undefined_label);
Register receiver = register_allocator()->NewRegister();
builder()->ToObject(receiver);
// Used as kRegTriple and kRegPair in ForInPrepare and ForInNext.
RegisterList triple = register_allocator()->NewRegisterList(3);
Register cache_length = triple[2];
builder()->ForInEnumerate(receiver);
builder()->ForInPrepare(triple, feedback_index(slot));
// Set up loop counter
Register index = register_allocator()->NewRegister();
builder()->LoadLiteral(Smi::zero());
builder()->StoreAccumulatorInRegister(index);
// The loop
{
LoopBuilder loop_builder(builder(), block_coverage_builder_, stmt,
feedback_spec());
LoopScope loop_scope(this, &loop_builder);
HoleCheckElisionScope elider(this);
builder()->SetExpressionAsStatementPosition(stmt->each());
loop_builder.BreakIfForInDone(index, cache_length);
builder()->ForInNext(receiver, index, triple.Truncate(2),
feedback_index(slot));
loop_builder.ContinueIfUndefined();
// Assign accumulator value to the 'each' target.
{
EffectResultScope scope(this);
// Make sure to preserve the accumulator across the PrepareAssignmentLhs
// call.
AssignmentLhsData lhs_data = PrepareAssignmentLhs(
stmt->each(), AccumulatorPreservingMode::kPreserve);
builder()->SetExpressionPosition(stmt->each());
BuildAssignment(lhs_data, Token::kAssign, LookupHoistingMode::kNormal);
}
{
Register cache_type = triple[0];
ForInScope scope(this, stmt, index, cache_type);
VisitIterationBody(stmt, &loop_builder);
builder()->ForInStep(index);
}
}
builder()->Bind(&subject_undefined_label);
}
// Desugar a for-of statement into an application of the iteration protocol.
//
// for (EACH of SUBJECT) BODY
//
// becomes
//
// iterator = %GetIterator(SUBJECT)
// try {
//
// loop {
// // Make sure we are considered 'done' if .next(), .done or .value fail.
// done = true
// value = iterator.next()
// if (value.done) break;
// value = value.value
// done = false
//
// EACH = value
// BODY
// }
// done = true
//
// } catch(e) {
// iteration_continuation = RETHROW
// } finally {
// %FinalizeIteration(iterator, done, iteration_continuation)
// }
void BytecodeGenerator::VisitForOfStatement(ForOfStatement* stmt) {
EffectResultScope effect_scope(this);
builder()->SetExpressionAsStatementPosition(stmt->subject());
VisitForAccumulatorValue(stmt->subject());
// Store the iterator in a dedicated register so that it can be closed on
// exit, and the 'done' value in a dedicated register so that it can be
// changed and accessed independently of the iteration result.
IteratorRecord iterator = BuildGetIteratorRecord(stmt->type());
Register done = register_allocator()->NewRegister();
builder()->LoadFalse();
builder()->StoreAccumulatorInRegister(done);
BuildTryFinally(
// Try block.
[&]() {
LoopBuilder loop_builder(builder(), block_coverage_builder_, stmt,
feedback_spec());
LoopScope loop_scope(this, &loop_builder);
// This doesn't need a HoleCheckElisionScope because BuildTryFinally
// already makes one for try blocks.
builder()->LoadTrue().StoreAccumulatorInRegister(done);
{
RegisterAllocationScope allocation_scope(this);
Register next_result = register_allocator()->NewRegister();
// Call the iterator's .next() method. Break from the loop if the
// `done` property is truthy, otherwise load the value from the
// iterator result and append the argument.
builder()->SetExpressionAsStatementPosition(stmt->each());
BuildIteratorNext(iterator, next_result);
builder()->LoadNamedProperty(
next_result, ast_string_constants()->done_string(),
feedback_index(feedback_spec()->AddLoadICSlot()));
loop_builder.BreakIfTrue(ToBooleanMode::kConvertToBoolean);
builder()
// value = value.value
->LoadNamedProperty(
next_result, ast_string_constants()->value_string(),
feedback_index(feedback_spec()->AddLoadICSlot()));
// done = false, before the assignment to each happens, so that done
// is false if the assignment throws.
builder()
->StoreAccumulatorInRegister(next_result)
.LoadFalse()
.StoreAccumulatorInRegister(done);
// Assign to the 'each' target.
AssignmentLhsData lhs_data = PrepareAssignmentLhs(stmt->each());
builder()->LoadAccumulatorWithRegister(next_result);
BuildAssignment(lhs_data, Token::kAssign,
LookupHoistingMode::kNormal);
}
VisitIterationBody(stmt, &loop_builder);
},
// Finally block.
[&](Register iteration_continuation_token,
Register iteration_continuation_result) {
// Finish the iteration in the finally block.
BuildFinalizeIteration(iterator, done, iteration_continuation_token);
},
catch_prediction());
}
void BytecodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) {
// Update catch prediction tracking. The updated catch_prediction value lasts
// until the end of the try_block in the AST node, and does not apply to the
// catch_block.
HandlerTable::CatchPrediction outer_catch_prediction = catch_prediction();
set_catch_prediction(stmt->GetCatchPrediction(outer_catch_prediction));
BuildTryCatch(
// Try body.
[&]() {
Visit(stmt->try_block());
set_catch_prediction(outer_catch_prediction);
},
// Catch body.
[&](Register context) {
if (stmt->scope()) {
// Create a catch scope that binds the exception.
BuildNewLocalCatchContext(stmt->scope());
builder()->StoreAccumulatorInRegister(context);
}
// If requested, clear message object as we enter the catch block.
if (stmt->ShouldClearException(outer_catch_prediction)) {
builder()->LoadTheHole().SetPendingMessage();
}
// Load the catch context into the accumulator.
builder()->LoadAccumulatorWithRegister(context);
// Evaluate the catch-block.
if (stmt->scope()) {
VisitInScope(stmt->catch_block(), stmt->scope());
} else {
VisitBlock(stmt->catch_block());
}
},
catch_prediction(), stmt);
}
void BytecodeGenerator::VisitTryFinallyStatement(TryFinallyStatement* stmt) {
BuildTryFinally(
// Try block.
[&]() { Visit(stmt->try_block()); },
// Finally block.
[&](Register body_continuation_token, Register body_continuation_result) {
Visit(stmt->finally_block());
},
catch_prediction(), stmt);
}
void BytecodeGenerator::VisitDebuggerStatement(DebuggerStatement* stmt) {
builder()->SetStatementPosition(stmt);
builder()->Debugger();
}
void BytecodeGenerator::VisitFunctionLiteral(FunctionLiteral* expr) {
DCHECK_EQ(expr->scope()->outer_scope(), current_scope());
uint8_t flags = CreateClosureFlags::Encode(
expr->pretenure(), closure_scope()->is_function_scope(),
info()->flags().might_always_turbofan());
size_t entry = builder()->AllocateDeferredConstantPoolEntry();
builder()->CreateClosure(entry, GetCachedCreateClosureSlot(expr), flags);
function_literals_.push_back(std::make_pair(expr, entry));
AddToEagerLiteralsIfEager(expr);
}
void BytecodeGenerator::AddToEagerLiteralsIfEager(FunctionLiteral* literal) {
// Only parallel compile when there's a script (not the case for source
// position collection).
if (!script_.is_null() && literal->should_parallel_compile()) {
// If we should normally be eagerly compiling this function, we must be here
// because of post_parallel_compile_tasks_for_eager_toplevel.