// 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/interpreter.h" #include <fstream> #include <memory> #include "builtins-generated/bytecodes-builtins-list.h" #include "src/ast/prettyprinter.h" #include "src/bootstrapper.h" #include "src/compiler.h" #include "src/counters-inl.h" #include "src/interpreter/bytecode-generator.h" #include "src/interpreter/bytecodes.h" #include "src/log.h" #include "src/objects-inl.h" #include "src/objects/shared-function-info.h" #include "src/objects/slots.h" #include "src/parsing/parse-info.h" #include "src/setup-isolate.h" #include "src/snapshot/snapshot.h" #include "src/unoptimized-compilation-info.h" #include "src/visitors.h" namespace v8 { namespace internal { namespace interpreter { class InterpreterCompilationJob final : public UnoptimizedCompilationJob { public: InterpreterCompilationJob( ParseInfo* parse_info, FunctionLiteral* literal, AccountingAllocator* allocator, std::vector<FunctionLiteral*>* eager_inner_literals); protected: Status ExecuteJobImpl() final; Status FinalizeJobImpl(Handle<SharedFunctionInfo> shared_info, Isolate* isolate) final; private: BytecodeGenerator* generator() { return &generator_; } Zone zone_; UnoptimizedCompilationInfo compilation_info_; BytecodeGenerator generator_; DISALLOW_COPY_AND_ASSIGN(InterpreterCompilationJob); }; Interpreter::Interpreter(Isolate* isolate) : isolate_(isolate), interpreter_entry_trampoline_instruction_start_(kNullAddress) { memset(dispatch_table_, 0, sizeof(dispatch_table_)); if (FLAG_trace_ignition_dispatches) { static const int kBytecodeCount = static_cast<int>(Bytecode::kLast) + 1; bytecode_dispatch_counters_table_.reset( new uintptr_t[kBytecodeCount * kBytecodeCount]); memset(bytecode_dispatch_counters_table_.get(), 0, sizeof(uintptr_t) * kBytecodeCount * kBytecodeCount); } } namespace { int BuiltinIndexFromBytecode(Bytecode bytecode, OperandScale operand_scale) { int index = BytecodeOperands::OperandScaleAsIndex(operand_scale) * kNumberOfBytecodeHandlers + static_cast<int>(bytecode); int offset = kBytecodeToBuiltinsMapping[index]; return offset >= 0 ? Builtins::kFirstBytecodeHandler + offset : Builtins::kIllegalHandler; } } // namespace Code Interpreter::GetBytecodeHandler(Bytecode bytecode, OperandScale operand_scale) { int builtin_index = BuiltinIndexFromBytecode(bytecode, operand_scale); Builtins* builtins = isolate_->builtins(); return builtins->builtin(builtin_index); } void Interpreter::SetBytecodeHandler(Bytecode bytecode, OperandScale operand_scale, Code handler) { DCHECK(handler->kind() == Code::BYTECODE_HANDLER); size_t index = GetDispatchTableIndex(bytecode, operand_scale); dispatch_table_[index] = handler->InstructionStart(); } // static size_t Interpreter::GetDispatchTableIndex(Bytecode bytecode, OperandScale operand_scale) { static const size_t kEntriesPerOperandScale = 1u << kBitsPerByte; size_t index = static_cast<size_t>(bytecode); return index + BytecodeOperands::OperandScaleAsIndex(operand_scale) * kEntriesPerOperandScale; } void Interpreter::IterateDispatchTable(RootVisitor* v) { if (FLAG_embedded_builtins && !isolate_->serializer_enabled() && isolate_->embedded_blob() != nullptr) { // If builtins are embedded (and we're not generating a snapshot), then // every bytecode handler will be off-heap, so there's no point iterating // over them. #ifdef DEBUG for (int i = 0; i < kDispatchTableSize; i++) { Address code_entry = dispatch_table_[i]; CHECK(code_entry == kNullAddress || InstructionStream::PcIsOffHeap(isolate_, code_entry)); } #endif // ENABLE_SLOW_DCHECKS return; } for (int i = 0; i < kDispatchTableSize; i++) { Address code_entry = dispatch_table_[i]; // Skip over off-heap bytecode handlers since they will never move. if (InstructionStream::PcIsOffHeap(isolate_, code_entry)) continue; // TODO(jkummerow): Would it hurt to simply do: // if (code_entry == kNullAddress) continue; Code code; if (code_entry != kNullAddress) { code = Code::GetCodeFromTargetAddress(code_entry); } Code old_code = code; v->VisitRootPointer(Root::kDispatchTable, nullptr, ObjectSlot(&code)); if (code != old_code) { dispatch_table_[i] = code->entry(); } } } int Interpreter::InterruptBudget() { return FLAG_interrupt_budget; } namespace { void MaybePrintAst(ParseInfo* parse_info, UnoptimizedCompilationInfo* compilation_info) { if (!FLAG_print_ast) return; StdoutStream os; std::unique_ptr<char[]> name = compilation_info->literal()->GetDebugName(); os << "[generating bytecode for function: " << name.get() << "]" << std::endl; #ifdef DEBUG os << "--- AST ---" << std::endl << AstPrinter(parse_info->stack_limit()) .PrintProgram(compilation_info->literal()) << std::endl; #endif // DEBUG } bool ShouldPrintBytecode(Handle<SharedFunctionInfo> shared) { if (!FLAG_print_bytecode) return false; // Checks whether function passed the filter. if (shared->is_toplevel()) { Vector<const char> filter = CStrVector(FLAG_print_bytecode_filter); return (filter.length() == 0) || (filter.length() == 1 && filter[0] == '*'); } else { return shared->PassesFilter(FLAG_print_bytecode_filter); } } } // namespace InterpreterCompilationJob::InterpreterCompilationJob( ParseInfo* parse_info, FunctionLiteral* literal, AccountingAllocator* allocator, std::vector<FunctionLiteral*>* eager_inner_literals) : UnoptimizedCompilationJob(parse_info->stack_limit(), parse_info, &compilation_info_), zone_(allocator, ZONE_NAME), compilation_info_(&zone_, parse_info, literal), generator_(&compilation_info_, parse_info->ast_string_constants(), eager_inner_literals) {} InterpreterCompilationJob::Status InterpreterCompilationJob::ExecuteJobImpl() { RuntimeCallTimerScope runtimeTimerScope( parse_info()->runtime_call_stats(), parse_info()->on_background_thread() ? RuntimeCallCounterId::kCompileBackgroundIgnition : RuntimeCallCounterId::kCompileIgnition); // TODO(lpy): add support for background compilation RCS trace. TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), "V8.CompileIgnition"); // Print AST if flag is enabled. Note, if compiling on a background thread // then ASTs from different functions may be intersperse when printed. MaybePrintAst(parse_info(), compilation_info()); generator()->GenerateBytecode(stack_limit()); if (generator()->HasStackOverflow()) { return FAILED; } return SUCCEEDED; } InterpreterCompilationJob::Status InterpreterCompilationJob::FinalizeJobImpl( Handle<SharedFunctionInfo> shared_info, Isolate* isolate) { RuntimeCallTimerScope runtimeTimerScope( parse_info()->runtime_call_stats(), RuntimeCallCounterId::kCompileIgnitionFinalization); TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), "V8.CompileIgnitionFinalization"); Handle<BytecodeArray> bytecodes = generator()->FinalizeBytecode(isolate, parse_info()->script()); if (generator()->HasStackOverflow()) { return FAILED; } if (ShouldPrintBytecode(shared_info)) { StdoutStream os; std::unique_ptr<char[]> name = compilation_info()->literal()->GetDebugName(); os << "[generated bytecode for function: " << name.get() << "]" << std::endl; bytecodes->Disassemble(os); os << std::flush; } compilation_info()->SetBytecodeArray(bytecodes); return SUCCEEDED; } UnoptimizedCompilationJob* Interpreter::NewCompilationJob( ParseInfo* parse_info, FunctionLiteral* literal, AccountingAllocator* allocator, std::vector<FunctionLiteral*>* eager_inner_literals) { return new InterpreterCompilationJob(parse_info, literal, allocator, eager_inner_literals); } void Interpreter::ForEachBytecode( const std::function<void(Bytecode, OperandScale)>& f) { constexpr OperandScale kOperandScales[] = { #define VALUE(Name, _) OperandScale::k##Name, OPERAND_SCALE_LIST(VALUE) #undef VALUE }; for (OperandScale operand_scale : kOperandScales) { for (int i = 0; i < Bytecodes::kBytecodeCount; i++) { f(Bytecodes::FromByte(i), operand_scale); } } } void Interpreter::Initialize() { Builtins* builtins = isolate_->builtins(); // Set the interpreter entry trampoline entry point now that builtins are // initialized. Handle<Code> code = BUILTIN_CODE(isolate_, InterpreterEntryTrampoline); DCHECK(builtins->is_initialized()); DCHECK(code->is_off_heap_trampoline() || isolate_->heap()->IsImmovable(*code)); interpreter_entry_trampoline_instruction_start_ = code->InstructionStart(); // Initialize the dispatch table. Code illegal = builtins->builtin(Builtins::kIllegalHandler); int builtin_id = Builtins::kFirstBytecodeHandler; ForEachBytecode([=, &builtin_id](Bytecode bytecode, OperandScale operand_scale) { Code handler = illegal; if (Bytecodes::BytecodeHasHandler(bytecode, operand_scale)) { #ifdef DEBUG std::string builtin_name(Builtins::name(builtin_id)); std::string expected_name = Bytecodes::ToString(bytecode, operand_scale, "") + "Handler"; DCHECK_EQ(expected_name, builtin_name); #endif handler = builtins->builtin(builtin_id++); } SetBytecodeHandler(bytecode, operand_scale, handler); }); DCHECK(builtin_id == Builtins::builtin_count); DCHECK(IsDispatchTableInitialized()); } bool Interpreter::IsDispatchTableInitialized() const { return dispatch_table_[0] != kNullAddress; } const char* Interpreter::LookupNameOfBytecodeHandler(const Code code) { #ifdef ENABLE_DISASSEMBLER #define RETURN_NAME(Name, ...) \ if (dispatch_table_[Bytecodes::ToByte(Bytecode::k##Name)] == \ code->entry()) { \ return #Name; \ } BYTECODE_LIST(RETURN_NAME) #undef RETURN_NAME #endif // ENABLE_DISASSEMBLER return nullptr; } uintptr_t Interpreter::GetDispatchCounter(Bytecode from, Bytecode to) const { int from_index = Bytecodes::ToByte(from); int to_index = Bytecodes::ToByte(to); return bytecode_dispatch_counters_table_[from_index * kNumberOfBytecodes + to_index]; } Local<v8::Object> Interpreter::GetDispatchCountersObject() { v8::Isolate* isolate = reinterpret_cast<v8::Isolate*>(isolate_); Local<v8::Context> context = isolate->GetCurrentContext(); Local<v8::Object> counters_map = v8::Object::New(isolate); // Output is a JSON-encoded object of objects. // // The keys on the top level object are source bytecodes, // and corresponding value are objects. Keys on these last are the // destinations of the dispatch and the value associated is a counter for // the correspondent source-destination dispatch chain. // // Only non-zero counters are written to file, but an entry in the top-level // object is always present, even if the value is empty because all counters // for that source are zero. for (int from_index = 0; from_index < kNumberOfBytecodes; ++from_index) { Bytecode from_bytecode = Bytecodes::FromByte(from_index); Local<v8::Object> counters_row = v8::Object::New(isolate); for (int to_index = 0; to_index < kNumberOfBytecodes; ++to_index) { Bytecode to_bytecode = Bytecodes::FromByte(to_index); uintptr_t counter = GetDispatchCounter(from_bytecode, to_bytecode); if (counter > 0) { std::string to_name = Bytecodes::ToString(to_bytecode); Local<v8::String> to_name_object = v8::String::NewFromUtf8(isolate, to_name.c_str(), NewStringType::kNormal) .ToLocalChecked(); Local<v8::Number> counter_object = v8::Number::New(isolate, counter); CHECK(counters_row ->DefineOwnProperty(context, to_name_object, counter_object) .IsJust()); } } std::string from_name = Bytecodes::ToString(from_bytecode); Local<v8::String> from_name_object = v8::String::NewFromUtf8(isolate, from_name.c_str(), NewStringType::kNormal) .ToLocalChecked(); CHECK( counters_map->DefineOwnProperty(context, from_name_object, counters_row) .IsJust()); } return counters_map; } } // namespace interpreter } // namespace internal } // namespace v8