| // 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/common/globals.h" |
| #include "src/execution/local-isolate.h" |
| #include "src/heap/parked-scope.h" |
| #include "src/init/setup-isolate.h" |
| #include "src/interpreter/bytecode-generator.h" |
| #include "src/interpreter/bytecodes.h" |
| #include "src/logging/runtime-call-stats-scope.h" |
| #include "src/objects/objects-inl.h" |
| #include "src/objects/shared-function-info.h" |
| #include "src/parsing/parse-info.h" |
| #include "src/utils/ostreams.h" |
| |
| namespace v8 { |
| namespace internal { |
| namespace interpreter { |
| |
| class InterpreterCompilationJob final : public UnoptimizedCompilationJob { |
| public: |
| InterpreterCompilationJob(ParseInfo* parse_info, FunctionLiteral* literal, |
| Handle<Script> script, |
| AccountingAllocator* allocator, |
| std::vector<FunctionLiteral*>* eager_inner_literals, |
| LocalIsolate* local_isolate); |
| 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 IsolateT> |
| void CheckAndPrintBytecodeMismatch(IsolateT* isolate, Handle<Script> script, |
| DirectHandle<BytecodeArray> bytecode); |
| |
| template <typename IsolateT> |
| Status DoFinalizeJobImpl(Handle<SharedFunctionInfo> shared_info, |
| IsolateT* isolate); |
| |
| Zone zone_; |
| UnoptimizedCompilationInfo compilation_info_; |
| LocalIsolate* local_isolate_; |
| BytecodeGenerator generator_; |
| }; |
| |
| Interpreter::Interpreter(Isolate* isolate) |
| : isolate_(isolate), |
| interpreter_entry_trampoline_instruction_start_(kNullAddress) { |
| memset(dispatch_table_, 0, sizeof(dispatch_table_)); |
| |
| if (V8_IGNITION_DISPATCH_COUNTING_BOOL) { |
| InitDispatchCounters(); |
| } |
| } |
| |
| void Interpreter::InitDispatchCounters() { |
| 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 { |
| |
| Builtin BuiltinIndexFromBytecode(Bytecode bytecode, |
| OperandScale operand_scale) { |
| int index = static_cast<int>(bytecode); |
| if (operand_scale == OperandScale::kSingle) { |
| if (Bytecodes::IsShortStar(bytecode)) { |
| index = static_cast<int>(Bytecode::kFirstShortStar); |
| } else if (bytecode > Bytecode::kLastShortStar) { |
| // Adjust the index due to repeated handlers. |
| index -= Bytecodes::kShortStarCount - 1; |
| } |
| } else { |
| // 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 Builtin::kIllegalHandler; |
| } else { |
| index = kNumberOfBytecodeHandlers + offset; |
| if (operand_scale == OperandScale::kQuadruple) { |
| index += kNumberOfWideBytecodeHandlers; |
| } |
| } |
| } |
| return Builtins::FromInt(static_cast<int>(Builtin::kFirstBytecodeHandler) + |
| index); |
| } |
| |
| } // namespace |
| |
| Tagged<Code> Interpreter::GetBytecodeHandler(Bytecode bytecode, |
| OperandScale operand_scale) { |
| Builtin builtin = BuiltinIndexFromBytecode(bytecode, operand_scale); |
| return isolate_->builtins()->code(builtin); |
| } |
| |
| void Interpreter::SetBytecodeHandler(Bytecode bytecode, |
| OperandScale operand_scale, |
| Tagged<Code> handler) { |
| DCHECK(!handler->has_instruction_stream()); |
| DCHECK(handler->kind() == CodeKind::BYTECODE_HANDLER); |
| size_t index = GetDispatchTableIndex(bytecode, operand_scale); |
| dispatch_table_[index] = handler->instruction_start(); |
| } |
| |
| // 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 (!v8_flags.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(DirectHandle<SharedFunctionInfo> shared) { |
| if (!v8_flags.print_bytecode) return false; |
| |
| // Checks whether function passed the filter. |
| if (shared->is_toplevel()) { |
| base::Vector<const char> filter = |
| base::CStrVector(v8_flags.print_bytecode_filter); |
| return filter.empty() || (filter.length() == 1 && filter[0] == '*'); |
| } else { |
| return shared->PassesFilter(v8_flags.print_bytecode_filter); |
| } |
| } |
| |
| } // namespace |
| |
| InterpreterCompilationJob::InterpreterCompilationJob( |
| ParseInfo* parse_info, FunctionLiteral* literal, Handle<Script> script, |
| AccountingAllocator* allocator, |
| std::vector<FunctionLiteral*>* eager_inner_literals, |
| LocalIsolate* local_isolate) |
| : UnoptimizedCompilationJob(parse_info->stack_limit(), parse_info, |
| &compilation_info_), |
| zone_(allocator, ZONE_NAME), |
| compilation_info_(&zone_, parse_info, literal), |
| local_isolate_(local_isolate), |
| generator_(local_isolate, &zone_, &compilation_info_, |
| parse_info->ast_string_constants(), eager_inner_literals, |
| script) {} |
| |
| InterpreterCompilationJob::Status InterpreterCompilationJob::ExecuteJobImpl() { |
| RCS_SCOPE(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. |
| { |
| DisallowGarbageCollection no_heap_access; |
| MaybePrintAst(parse_info(), compilation_info()); |
| } |
| |
| local_isolate_->ParkIfOnBackgroundAndExecute( |
| [this]() { generator()->GenerateBytecode(stack_limit()); }); |
| |
| if (generator()->HasStackOverflow()) { |
| return FAILED; |
| } |
| return SUCCEEDED; |
| } |
| |
| #ifdef DEBUG |
| template <typename IsolateT> |
| void InterpreterCompilationJob::CheckAndPrintBytecodeMismatch( |
| IsolateT* isolate, Handle<Script> script, |
| DirectHandle<BytecodeArray> bytecode) { |
| int first_mismatch = generator()->CheckBytecodeMatches(*bytecode); |
| if (first_mismatch >= 0) { |
| parse_info()->ast_value_factory()->Internalize(isolate); |
| DeclarationScope::AllocateScopeInfos(parse_info(), script, isolate); |
| |
| DirectHandle<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"; |
| } |
| Tagged<Object> script_name = script->GetNameOrSourceURL(); |
| if (IsString(script_name)) { |
| std::cerr << " "; |
| Cast<String>(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) { |
| RCS_SCOPE(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) { |
| RCS_SCOPE(isolate, RuntimeCallCounterId::kCompileIgnitionFinalization, |
| RuntimeCallStats::kThreadSpecific); |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), |
| "V8.CompileIgnitionFinalization"); |
| return DoFinalizeJobImpl(shared_info, isolate); |
| } |
| |
| template <typename IsolateT> |
| InterpreterCompilationJob::Status InterpreterCompilationJob::DoFinalizeJobImpl( |
| Handle<SharedFunctionInfo> shared_info, IsolateT* isolate) { |
| Handle<BytecodeArray> bytecodes = compilation_info_.bytecode_array(); |
| if (bytecodes.is_null()) { |
| bytecodes = generator()->FinalizeBytecode( |
| isolate, handle(Cast<Script>(shared_info->script()), isolate)); |
| if (generator()->HasStackOverflow()) { |
| return FAILED; |
| } |
| compilation_info()->SetBytecodeArray(bytecodes); |
| } |
| |
| if (compilation_info()->SourcePositionRecordingMode() == |
| SourcePositionTableBuilder::RecordingMode::RECORD_SOURCE_POSITIONS) { |
| DirectHandle<TrustedByteArray> 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; |
| os << "Bytecode length: " << bytecodes->length() << std::endl; |
| bytecodes->Disassemble(os); |
| os << std::flush; |
| } |
| |
| #ifdef DEBUG |
| if (parse_info()->literal()->shared_function_info().is_null()) { |
| parse_info()->literal()->set_shared_function_info(shared_info); |
| } |
| CheckAndPrintBytecodeMismatch( |
| isolate, handle(Cast<Script>(shared_info->script()), isolate), bytecodes); |
| #endif |
| |
| return SUCCEEDED; |
| } |
| |
| std::unique_ptr<UnoptimizedCompilationJob> Interpreter::NewCompilationJob( |
| ParseInfo* parse_info, FunctionLiteral* literal, Handle<Script> script, |
| AccountingAllocator* allocator, |
| std::vector<FunctionLiteral*>* eager_inner_literals, |
| LocalIsolate* local_isolate) { |
| return std::make_unique<InterpreterCompilationJob>( |
| parse_info, literal, script, allocator, eager_inner_literals, |
| local_isolate); |
| } |
| |
| std::unique_ptr<UnoptimizedCompilationJob> |
| Interpreter::NewSourcePositionCollectionJob( |
| ParseInfo* parse_info, FunctionLiteral* literal, |
| Handle<BytecodeArray> existing_bytecode, AccountingAllocator* allocator, |
| LocalIsolate* local_isolate) { |
| auto job = std::make_unique<InterpreterCompilationJob>( |
| parse_info, literal, Handle<Script>(), allocator, nullptr, local_isolate); |
| job->compilation_info()->SetBytecodeArray(existing_bytecode); |
| return job; |
| } |
| |
| 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. |
| DirectHandle<Code> code = BUILTIN_CODE(isolate_, InterpreterEntryTrampoline); |
| DCHECK(builtins->is_initialized()); |
| DCHECK(!code->has_instruction_stream()); |
| interpreter_entry_trampoline_instruction_start_ = code->instruction_start(); |
| |
| // Initialize the dispatch table. |
| ForEachBytecode([=, this](Bytecode bytecode, OperandScale operand_scale) { |
| Builtin builtin = BuiltinIndexFromBytecode(bytecode, operand_scale); |
| Tagged<Code> handler = builtins->code(builtin); |
| if (Bytecodes::BytecodeHasHandler(bytecode, operand_scale)) { |
| #ifdef DEBUG |
| std::string builtin_name(Builtins::name(builtin)); |
| std::string expected_name = |
| (Bytecodes::IsShortStar(bytecode) |
| ? "ShortStar" |
| : Bytecodes::ToString(bytecode, operand_scale, "")) + |
| "Handler"; |
| DCHECK_EQ(expected_name, builtin_name); |
| #endif |
| } |
| |
| SetBytecodeHandler(bytecode, operand_scale, handler); |
| }); |
| DCHECK(IsDispatchTableInitialized()); |
| } |
| |
| bool Interpreter::IsDispatchTableInitialized() const { |
| return dispatch_table_[0] != kNullAddress; |
| } |
| |
| uintptr_t Interpreter::GetDispatchCounter(Bytecode from, Bytecode to) const { |
| int from_index = Bytecodes::ToByte(from); |
| int to_index = Bytecodes::ToByte(to); |
| CHECK_WITH_MSG(bytecode_dispatch_counters_table_ != nullptr, |
| "Dispatch counters require building with " |
| "v8_enable_ignition_dispatch_counting"); |
| return bytecode_dispatch_counters_table_[from_index * kNumberOfBytecodes + |
| to_index]; |
| } |
| |
| Handle<JSObject> Interpreter::GetDispatchCountersObject() { |
| Handle<JSObject> counters_map = |
| isolate_->factory()->NewJSObjectWithNullProto(); |
| |
| // 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); |
| Handle<JSObject> counters_row = |
| isolate_->factory()->NewJSObjectWithNullProto(); |
| |
| 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) { |
| Handle<Object> value = isolate_->factory()->NewNumberFromSize(counter); |
| JSObject::AddProperty(isolate_, counters_row, |
| Bytecodes::ToString(to_bytecode), value, NONE); |
| } |
| } |
| |
| JSObject::AddProperty(isolate_, counters_map, |
| Bytecodes::ToString(from_bytecode), counters_row, |
| NONE); |
| } |
| |
| return counters_map; |
| } |
| |
| } // namespace interpreter |
| } // namespace internal |
| } // namespace v8 |