// 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 "src/api-inl.h" #include "src/ast/ast-source-ranges.h" #include "src/ast/scopes.h" #include "src/builtins/builtins-constructor.h" #include "src/code-stubs.h" #include "src/compiler.h" #include "src/interpreter/bytecode-flags.h" #include "src/interpreter/bytecode-jump-table.h" #include "src/interpreter/bytecode-label.h" #include "src/interpreter/bytecode-register-allocator.h" #include "src/interpreter/control-flow-builders.h" #include "src/objects-inl.h" #include "src/objects/debug-objects.h" #include "src/objects/literal-objects-inl.h" #include "src/parsing/parse-info.h" #include "src/parsing/token.h" #include "src/unoptimized-compilation-info.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 BytecodeGenerator::ContextScope BASE_EMBEDDED { public: ContextScope(BytecodeGenerator* generator, Scope* scope) : 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. Register 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_); } // 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. The pattern derives AstGraphBuilder::ControlScope. class BytecodeGenerator::ControlScope BASE_EMBEDDED { public: explicit ControlScope(BytecodeGenerator* generator) : generator_(generator), outer_(generator->execution_control()), context_(generator->execution_context()) { generator_->set_execution_control(this); } virtual ~ControlScope() { generator_->set_execution_control(outer()); } void Break(Statement* stmt) { PerformCommand(CMD_BREAK, stmt, kNoSourcePosition); } void Continue(Statement* stmt) { PerformCommand(CMD_CONTINUE, stmt, kNoSourcePosition); } void ReturnAccumulator(int source_position = kNoSourcePosition) { PerformCommand(CMD_RETURN, nullptr, source_position); } void AsyncReturnAccumulator(int source_position = kNoSourcePosition) { 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_; DISALLOW_COPY_AND_ASSIGN(ControlScope); }; // 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 BytecodeGenerator::ControlScope::DeferredCommands final { public: DeferredCommands(BytecodeGenerator* generator, Register token_register, Register result_register) : generator_(generator), deferred_(generator->zone()), token_register_(token_register), result_register_(result_register), return_token_(-1), async_return_token_(-1), rethrow_token_(-1) {} // 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_); } } // 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(-1)); 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_.size() == 0) 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 (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(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 (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 GetRethrowToken(); 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 GetRethrowToken() { if (rethrow_token_ == -1) { rethrow_token_ = GetNewTokenForCommand(CMD_RETHROW, nullptr); } return rethrow_token_; } int GetNewTokenForCommand(Command command, Statement* statement) { int token = static_cast(deferred_.size()); deferred_.push_back({command, statement, token}); return token; } BytecodeGenerator* generator_; ZoneVector deferred_; Register token_register_; Register result_register_; // Tokens for commands that don't need a statement. int return_token_; int async_return_token_; int rethrow_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 { control_builder_->set_needs_continuation_counter(); 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) { generator->loop_depth_++; } ~ControlScopeForIteration() { generator()->loop_depth_--; } 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_; }; // 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 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 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_); } private: BytecodeGenerator* generator_; int outer_next_register_index_; DISALLOW_COPY_AND_ASSIGN(RegisterAllocationScope); }; // Scoped base class for determining how the result of an expression will be // used. class BytecodeGenerator::ExpressionResultScope { public: ExpressionResultScope(BytecodeGenerator* generator, Expression::Context kind) : generator_(generator), outer_(generator->execution_result()), allocator_(generator), kind_(kind), type_hint_(TypeHint::kAny) { generator_->set_execution_result(this); } virtual ~ExpressionResultScope() { generator_->set_execution_result(outer_); } 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(this); } // Specify expression always returns a Boolean result value. void SetResultIsBoolean() { DCHECK_EQ(type_hint_, TypeHint::kAny); type_hint_ = TypeHint::kBoolean; } void SetResultIsString() { DCHECK_EQ(type_hint_, TypeHint::kAny); type_hint_ = TypeHint::kString; } TypeHint type_hint() const { return type_hint_; } private: BytecodeGenerator* generator_; ExpressionResultScope* outer_; RegisterAllocationScope allocator_; Expression::Context kind_; TypeHint type_hint_; DISALLOW_COPY_AND_ASSIGN(ExpressionResultScope); }; // 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 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 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) {} // 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_; DISALLOW_COPY_AND_ASSIGN(TestResultScope); }; // Used to build a list of global declaration initial value pairs. class BytecodeGenerator::GlobalDeclarationsBuilder final : public ZoneObject { public: explicit GlobalDeclarationsBuilder(Zone* zone) : declarations_(0, zone), constant_pool_entry_(0), has_constant_pool_entry_(false) {} void AddFunctionDeclaration(const AstRawString* name, FeedbackSlot slot, FeedbackSlot literal_slot, FunctionLiteral* func) { DCHECK(!slot.IsInvalid()); declarations_.push_back(Declaration(name, slot, literal_slot, func)); } void AddUndefinedDeclaration(const AstRawString* name, FeedbackSlot slot) { DCHECK(!slot.IsInvalid()); declarations_.push_back(Declaration(name, slot, nullptr)); } Handle AllocateDeclarations(UnoptimizedCompilationInfo* info, Handle