| // 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/ast/scopes.h" |
| #include "src/codegen/compiler.h" |
| #include "src/codegen/unoptimized-compilation-info.h" |
| #include "src/init/bootstrapper.h" |
| #include "src/init/setup-isolate.h" |
| #include "src/interpreter/bytecode-generator.h" |
| #include "src/interpreter/bytecodes.h" |
| #include "src/logging/counters-inl.h" |
| #include "src/objects/objects-inl.h" |
| #include "src/objects/shared-function-info.h" |
| #include "src/objects/slots.h" |
| #include "src/objects/visitors.h" |
| #include "src/parsing/parse-info.h" |
| #include "src/snapshot/snapshot.h" |
| #include "src/utils/ostreams.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); |
| InterpreterCompilationJob(const InterpreterCompilationJob&) = delete; |
| InterpreterCompilationJob& operator=(const InterpreterCompilationJob&) = |
| delete; |
| |
| protected: |
| Status ExecuteJobImpl() final; |
| Status FinalizeJobImpl(Handle<SharedFunctionInfo> shared_info, |
| Isolate* isolate) final; |
| Status FinalizeJobImpl(Handle<SharedFunctionInfo> shared_info, |
| LocalIsolate* isolate) final; |
| |
| private: |
| BytecodeGenerator* generator() { return &generator_; } |
| template <typename LocalIsolate> |
| void CheckAndPrintBytecodeMismatch(LocalIsolate* isolate, |
| Handle<Script> script, |
| Handle<BytecodeArray> bytecode); |
| |
| template <typename LocalIsolate> |
| Status DoFinalizeJobImpl(Handle<SharedFunctionInfo> shared_info, |
| LocalIsolate* isolate); |
| |
| Zone zone_; |
| UnoptimizedCompilationInfo compilation_info_; |
| BytecodeGenerator generator_; |
| }; |
| |
| 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 = static_cast<int>(bytecode); |
| if (operand_scale != OperandScale::kSingle) { |
| // The table contains uint8_t offsets starting at 0 with |
| // kIllegalBytecodeHandlerEncoding for illegal bytecode/scale combinations. |
| uint8_t offset = kWideBytecodeToBuiltinsMapping[index]; |
| if (offset == kIllegalBytecodeHandlerEncoding) { |
| return Builtins::kIllegalHandler; |
| } else { |
| index = kNumberOfBytecodeHandlers + offset; |
| if (operand_scale == OperandScale::kQuadruple) { |
| index += kNumberOfWideBytecodeHandlers; |
| } |
| } |
| } |
| return Builtins::kFirstBytecodeHandler + index; |
| } |
| |
| } // 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.is_off_heap_trampoline()); |
| DCHECK(handler.kind() == CodeKind::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; |
| } |
| |
| 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_(&zone_, &compilation_info_, parse_info->ast_string_constants(), |
| eager_inner_literals) {} |
| |
| InterpreterCompilationJob::Status InterpreterCompilationJob::ExecuteJobImpl() { |
| RuntimeCallTimerScope runtimeTimerScope( |
| parse_info()->runtime_call_stats(), |
| RuntimeCallCounterId::kCompileIgnition, |
| RuntimeCallStats::kThreadSpecific); |
| // 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; |
| } |
| |
| #ifdef DEBUG |
| template <typename LocalIsolate> |
| void InterpreterCompilationJob::CheckAndPrintBytecodeMismatch( |
| LocalIsolate* isolate, Handle<Script> script, |
| Handle<BytecodeArray> bytecode) { |
| int first_mismatch = generator()->CheckBytecodeMatches(*bytecode); |
| if (first_mismatch >= 0) { |
| parse_info()->ast_value_factory()->Internalize(isolate); |
| DeclarationScope::AllocateScopeInfos(parse_info(), isolate); |
| |
| Handle<BytecodeArray> new_bytecode = |
| generator()->FinalizeBytecode(isolate, script); |
| |
| std::cerr << "Bytecode mismatch"; |
| #ifdef OBJECT_PRINT |
| std::cerr << " found for function: "; |
| MaybeHandle<String> maybe_name = parse_info()->literal()->GetName(isolate); |
| Handle<String> name; |
| if (maybe_name.ToHandle(&name) && name->length() != 0) { |
| name->PrintUC16(std::cerr); |
| } else { |
| std::cerr << "anonymous"; |
| } |
| Object script_name = script->GetNameOrSourceURL(); |
| if (script_name.IsString()) { |
| std::cerr << " "; |
| String::cast(script_name).PrintUC16(std::cerr); |
| std::cerr << ":" << parse_info()->literal()->start_position(); |
| } |
| #endif |
| std::cerr << "\nOriginal bytecode:\n"; |
| bytecode->Disassemble(std::cerr); |
| std::cerr << "\nNew bytecode:\n"; |
| new_bytecode->Disassemble(std::cerr); |
| FATAL("Bytecode mismatch at offset %d\n", first_mismatch); |
| } |
| } |
| #endif |
| |
| 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"); |
| return DoFinalizeJobImpl(shared_info, isolate); |
| } |
| |
| InterpreterCompilationJob::Status InterpreterCompilationJob::FinalizeJobImpl( |
| Handle<SharedFunctionInfo> shared_info, LocalIsolate* isolate) { |
| RuntimeCallTimerScope runtimeTimerScope( |
| parse_info()->runtime_call_stats(), |
| RuntimeCallCounterId::kCompileBackgroundIgnitionFinalization); |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), |
| "V8.CompileIgnitionFinalization"); |
| return DoFinalizeJobImpl(shared_info, isolate); |
| } |
| |
| template <typename LocalIsolate> |
| InterpreterCompilationJob::Status InterpreterCompilationJob::DoFinalizeJobImpl( |
| Handle<SharedFunctionInfo> shared_info, LocalIsolate* isolate) { |
| Handle<BytecodeArray> bytecodes = compilation_info_.bytecode_array(); |
| if (bytecodes.is_null()) { |
| bytecodes = generator()->FinalizeBytecode( |
| isolate, handle(Script::cast(shared_info->script()), isolate)); |
| if (generator()->HasStackOverflow()) { |
| return FAILED; |
| } |
| compilation_info()->SetBytecodeArray(bytecodes); |
| } |
| |
| if (compilation_info()->SourcePositionRecordingMode() == |
| SourcePositionTableBuilder::RecordingMode::RECORD_SOURCE_POSITIONS) { |
| Handle<ByteArray> source_position_table = |
| generator()->FinalizeSourcePositionTable(isolate); |
| bytecodes->set_source_position_table(*source_position_table, kReleaseStore); |
| } |
| |
| if (ShouldPrintBytecode(shared_info)) { |
| StdoutStream os; |
| std::unique_ptr<char[]> name = |
| compilation_info()->literal()->GetDebugName(); |
| os << "[generated bytecode for function: " << name.get() << " (" |
| << shared_info << ")]" << std::endl; |
| bytecodes->Disassemble(os); |
| os << std::flush; |
| } |
| |
| #ifdef DEBUG |
| CheckAndPrintBytecodeMismatch( |
| isolate, handle(Script::cast(shared_info->script()), isolate), bytecodes); |
| #endif |
| |
| return SUCCEEDED; |
| } |
| |
| std::unique_ptr<UnoptimizedCompilationJob> Interpreter::NewCompilationJob( |
| ParseInfo* parse_info, FunctionLiteral* literal, |
| AccountingAllocator* allocator, |
| std::vector<FunctionLiteral*>* eager_inner_literals) { |
| return std::make_unique<InterpreterCompilationJob>( |
| parse_info, literal, allocator, eager_inner_literals); |
| } |
| |
| std::unique_ptr<UnoptimizedCompilationJob> |
| Interpreter::NewSourcePositionCollectionJob( |
| ParseInfo* parse_info, FunctionLiteral* literal, |
| Handle<BytecodeArray> existing_bytecode, AccountingAllocator* allocator) { |
| auto job = std::make_unique<InterpreterCompilationJob>(parse_info, literal, |
| allocator, nullptr); |
| job->compilation_info()->SetBytecodeArray(existing_bytecode); |
| return std::unique_ptr<UnoptimizedCompilationJob>(job.release()); |
| } |
| |
| 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) { |
| if (code.kind() == CodeKind::BYTECODE_HANDLER) { |
| return Builtins::name(code.builtin_index()); |
| } |
| 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()).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()).ToLocalChecked(); |
| |
| CHECK( |
| counters_map->DefineOwnProperty(context, from_name_object, counters_row) |
| .IsJust()); |
| } |
| |
| return counters_map; |
| } |
| |
| } // namespace interpreter |
| } // namespace internal |
| } // namespace v8 |